Projet

Général

Profil

Paste
Télécharger (14,3 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / commerce / includes / commerce.controller.inc @ b858700c

1
<?php
2

    
3
/**
4
 * @file
5
 * Provides a central controller for Drupal Commerce.
6
 *
7
 * A full fork of Entity API's controller, with support for revisions.
8
 */
9

    
10
class DrupalCommerceEntityController extends DrupalDefaultEntityController implements EntityAPIControllerInterface {
11

    
12
  /**
13
   * Stores our transaction object, necessary for pessimistic locking to work.
14
   */
15
  protected $controllerTransaction = NULL;
16

    
17
  /**
18
   * Stores the ids of locked entities, necessary for knowing when to release a
19
   * lock by committing the transaction.
20
   */
21
  protected $lockedEntities = array();
22

    
23
  /**
24
   * Override of DrupalDefaultEntityController::buildQuery().
25
   *
26
   * Handle pessimistic locking.
27
   */
28
  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
29
    $query = parent::buildQuery($ids, $conditions, $revision_id);
30

    
31
    if (isset($this->entityInfo['locking mode']) && $this->entityInfo['locking mode'] == 'pessimistic') {
32
      // In pessimistic locking mode, we issue the load query with a FOR UPDATE
33
      // clause. This will block all other load queries to the loaded objects
34
      // but requires us to start a transaction.
35
      if (empty($this->controllerTransaction)) {
36
        $this->controllerTransaction = db_transaction();
37
      }
38

    
39
      $query->forUpdate();
40

    
41
      // Store the ids of the entities in the lockedEntities array for later
42
      // tracking, flipped for easier management via unset() below.
43
      if (is_array($ids)) {
44
        $this->lockedEntities += array_flip($ids);
45
      }
46
    }
47

    
48
    return $query;
49
  }
50

    
51
  public function resetCache(array $ids = NULL) {
52
    parent::resetCache($ids);
53

    
54
    // Maintain the list of locked entities, so that the releaseLock() method
55
    // can know when it's time to commit the transaction.
56
    if (!empty($this->lockedEntities)) {
57
      if (isset($ids)) {
58
        foreach ($ids as $id) {
59
          unset($this->lockedEntities[$id]);
60
        }
61
      }
62
      else {
63
        $this->lockedEntities = array();
64
      }
65
    }
66

    
67
    // Try to release the lock, if possible.
68
    $this->releaseLock();
69
  }
70

    
71
  /**
72
   * Checks the list of tracked locked entities, and if it's empty, commits
73
   * the transaction in order to remove the acquired locks.
74
   *
75
   * The transaction is not necessarily committed immediately. Drupal will
76
   * commit it as soon as possible given the state of the transaction stack.
77
   */
78
  protected function releaseLock() {
79
    if (isset($this->entityInfo['locking mode']) && $this->entityInfo['locking mode'] == 'pessimistic') {
80
      if (empty($this->lockedEntities)) {
81
        unset($this->controllerTransaction);
82
      }
83
    }
84
  }
85

    
86
  /**
87
   * (Internal use) Invokes a hook on behalf of the entity.
88
   *
89
   * For hooks that have a respective field API attacher like insert/update/..
90
   * the attacher is called too.
91
   */
92
  public function invoke($hook, $entity) {
93
    if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) {
94
      $function($this->entityType, $entity);
95
    }
96

    
97
    // Invoke the hook.
98
    module_invoke_all($this->entityType . '_' . $hook, $entity);
99
    // Invoke the respective entity level hook.
100
    if ($hook == 'presave' || $hook == 'insert' || $hook == 'update' || $hook == 'delete') {
101
      module_invoke_all('entity_' . $hook, $entity, $this->entityType);
102
    }
103
    // Invoke rules.
104
    if (module_exists('rules')) {
105
      rules_invoke_event($this->entityType . '_' . $hook, $entity);
106
    }
107
  }
108

    
109
  /**
110
   * Delete permanently saved entities.
111
   *
112
   * In case of failures, an exception is thrown.
113
   *
114
   * @param $ids
115
   *   An array of entity IDs.
116
   * @param $transaction
117
   *   An optional transaction object to pass thru. If passed the caller is
118
   *   responsible for rolling back the transaction if something goes wrong.
119
   */
120
  public function delete($ids, DatabaseTransaction $transaction = NULL) {
121
    $entities = $ids ? $this->load($ids) : FALSE;
122
    if (!$entities) {
123
      // Do nothing, in case invalid or no ids have been passed.
124
      return;
125
    }
126

    
127
    if (!isset($transaction)) {
128
      $transaction = db_transaction();
129
      $started_transaction = TRUE;
130
    }
131

    
132
    try {
133
      db_delete($this->entityInfo['base table'])
134
        ->condition($this->idKey, array_keys($entities), 'IN')
135
        ->execute();
136
      if (!empty($this->revisionKey)) {
137
        db_delete($this->entityInfo['revision table'])
138
          ->condition($this->idKey, array_keys($entities), 'IN')
139
          ->execute();
140
      }
141
      // Reset the cache as soon as the changes have been applied.
142
      $this->resetCache($ids);
143

    
144
      foreach ($entities as $id => $entity) {
145
        $this->invoke('delete', $entity);
146
      }
147
      // Ignore slave server temporarily.
148
      db_ignore_slave();
149

    
150
      return TRUE;
151
    }
152
    catch (Exception $e) {
153
      if (!empty($started_transaction)) {
154
        $transaction->rollback();
155
        watchdog_exception($this->entityType, $e);
156
      }
157
      throw $e;
158
    }
159
  }
160

    
161
  /**
162
   * Permanently saves the given entity.
163
   *
164
   * In case of failures, an exception is thrown.
165
   *
166
   * @param $entity
167
   *   The entity to save.
168
   * @param $transaction
169
   *   An optional transaction object to pass thru. If passed the caller is
170
   *   responsible for rolling back the transaction if something goes wrong.
171
   *
172
   * @return
173
   *   SAVED_NEW or SAVED_UPDATED depending on the operation performed.
174
   */
175
  public function save($entity, DatabaseTransaction $transaction = NULL) {
176
    if (!isset($transaction)) {
177
      $transaction = db_transaction();
178
      $started_transaction = TRUE;
179
    }
180

    
181
    try {
182
      // Load the stored entity, if any. If this save was invoked during a
183
      // previous save's insert or update hook, this means the $entity->original
184
      // value already set on the entity will be replaced with the entity as
185
      // saved. This will allow any original entity comparisons in the current
186
      // save process to react to the most recently saved version of the entity.
187
      if (!empty($entity->{$this->idKey})) {
188
        // In order to properly work in case of name changes, load the original
189
        // entity using the id key if it is available.
190
        $entity->original = entity_load_unchanged($this->entityType, $entity->{$this->idKey});
191
      }
192

    
193
      $this->invoke('presave', $entity);
194

    
195
      // When saving a new revision, unset any existing revision ID so as to
196
      // ensure that a new revision will actually be created, then store the old
197
      // revision ID in a separate property for use by hook implementations.
198
      if (!empty($this->revisionKey) && empty($entity->is_new) && !empty($entity->revision) && !empty($entity->{$this->revisionKey})) {
199
        $entity->old_revision_id = $entity->{$this->revisionKey};
200
        unset($entity->{$this->revisionKey});
201
      }
202

    
203
      if (empty($entity->{$this->idKey}) || !empty($entity->is_new)) {
204
        // For new entities, create the row in the base table, then save the
205
        // revision.
206
        $op = 'insert';
207
        $return = drupal_write_record($this->entityInfo['base table'], $entity);
208
        if (!empty($this->revisionKey)) {
209
          drupal_write_record($this->entityInfo['revision table'], $entity);
210
          $update_base_table = TRUE;
211
        }
212
      }
213
      else {
214
        $op = 'update';
215
        $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey);
216

    
217
        if (!empty($this->revisionKey)) {
218
          if (!empty($entity->revision)) {
219
            drupal_write_record($this->entityInfo['revision table'], $entity);
220
            $update_base_table = TRUE;
221
          }
222
          else {
223
            drupal_write_record($this->entityInfo['revision table'], $entity, $this->revisionKey);
224
          }
225
        }
226
      }
227

    
228
      if (!empty($update_base_table)) {
229
        // Go back to the base table and update the pointer to the revision ID.
230
        db_update($this->entityInfo['base table'])
231
        ->fields(array($this->revisionKey => $entity->{$this->revisionKey}))
232
        ->condition($this->idKey, $entity->{$this->idKey})
233
        ->execute();
234
      }
235

    
236
      // Update the static cache so that the next entity_load() will return this
237
      // newly saved entity.
238
      $this->entityCache[$entity->{$this->idKey}] = $entity;
239

    
240
      // Maintain the list of locked entities and release the lock if possible.
241
      unset($this->lockedEntities[$entity->{$this->idKey}]);
242
      $this->releaseLock();
243

    
244
      $this->invoke($op, $entity);
245

    
246
      // Ignore slave server temporarily.
247
      db_ignore_slave();
248

    
249
      // We unset the original version of the entity after the current save as
250
      // it no longer accurately represents the version of the entity saved in
251
      // the database. However, if this save was invoked during a previous
252
      // save's insert or update hook, this means that any hook implementations
253
      // executing after this save will no longer have an original version of
254
      // the entity to compare against. Attempting to compare against the non-
255
      // existent original entity in code or Rules will result in an error.
256
      unset($entity->original);
257
      unset($entity->is_new);
258
      unset($entity->revision);
259

    
260
      return $return;
261
    }
262
    catch (Exception $e) {
263
      if (!empty($started_transaction)) {
264
        $transaction->rollback();
265
        watchdog_exception($this->entityType, $e);
266
      }
267
      throw $e;
268
    }
269
  }
270

    
271
  /**
272
   * Create a new entity.
273
   *
274
   * @param array $values
275
   *   An array of values to set, keyed by property name.
276
   * @return
277
   *   A new instance of the entity type.
278
   */
279
  public function create(array $values = array()) {
280
    // Add is_new property if it is not set.
281
    $values += array('is_new' => TRUE);
282

    
283
    // If there is a class for this entity type, instantiate it now.
284
    if (isset($this->entityInfo['entity class']) && $class = $this->entityInfo['entity class']) {
285
      $entity = new $class($values, $this->entityType);
286
    }
287
    else {
288
      // Otherwise use a good old stdClass.
289
      $entity = (object) $values;
290
    }
291

    
292
    // Allow other modules to alter the created entity.
293
    drupal_alter('commerce_entity_create', $this->entityType, $entity);
294

    
295
    return $entity;
296
  }
297

    
298
  /**
299
   * Implements EntityAPIControllerInterface.
300
   */
301
  public function export($entity, $prefix = '') {
302
    throw new Exception('Not implemented');
303
  }
304

    
305
  /**
306
   * Implements EntityAPIControllerInterface.
307
   */
308
  public function import($export) {
309
    throw new Exception('Not implemented');
310
  }
311

    
312
  /**
313
   * Builds a structured array representing the entity's content.
314
   *
315
   * The content built for the entity will vary depending on the $view_mode
316
   * parameter.
317
   *
318
   * @param $entity
319
   *   An entity object.
320
   * @param $view_mode
321
   *   View mode, e.g. 'full', 'teaser'...
322
   * @param $langcode
323
   *   (optional) A language code to use for rendering. Defaults to the global
324
   *   content language of the current request.
325
   * @return
326
   *   The renderable array.
327
   */
328
  public function buildContent($entity, $view_mode = 'full', $langcode = NULL, $content = array()) {
329
    // Remove previously built content, if exists.
330
    $entity->content = $content;
331
    $langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language;
332

    
333
    // Add in fields.
334
    if (!empty($this->entityInfo['fieldable'])) {
335
      $entity->content += field_attach_view($this->entityType, $entity, $view_mode, $langcode);
336
    }
337

    
338
    // Invoke hook_ENTITY_view() to allow modules to add their additions.
339
    rules_invoke_all($this->entityType . '_view', $entity, $view_mode, $langcode);
340

    
341
    // Invoke the more generic hook_entity_view() to allow the same.
342
    module_invoke_all('entity_view', $entity, $this->entityType, $view_mode, $langcode);
343

    
344
    // Remove the build array information from the entity and return it.
345
    $build = $entity->content;
346
    unset($entity->content);
347

    
348
    return $build;
349
  }
350

    
351
  /**
352
   * Generate an array for rendering the given entities.
353
   *
354
   * @param $entities
355
   *   An array of entities to render.
356
   * @param $view_mode
357
   *   View mode, e.g. 'full', 'teaser'...
358
   * @param $langcode
359
   *   (optional) A language code to use for rendering. Defaults to the global
360
   *   content language of the current request.
361
   * @param $page
362
   *   (optional) If set will control if the entity is rendered: if TRUE
363
   *   the entity will be rendered without its title, so that it can be embeded
364
   *   in another context. If FALSE the entity will be displayed with its title
365
   *   in a mode suitable for lists.
366
   *   If unset, the page mode will be enabled if the current path is the URI
367
   *   of the entity, as returned by entity_uri().
368
   *   This parameter is only supported for entities which controller is a
369
   *   EntityAPIControllerInterface.
370
   * @return
371
   *   The renderable array.
372
   */
373
  public function view($entities, $view_mode = '', $langcode = NULL, $page = NULL) {
374
    // Create a new entities array keyed by entity ID.
375
    $rekeyed_entities = array();
376

    
377
    foreach ($entities as $key => $entity) {
378
      // Use the entity's ID if available and fallback to its existing key value
379
      // if we couldn't determine it.
380
      if (isset($entity->{$this->idKey})) {
381
        $key = $entity->{$this->idKey};
382
      }
383

    
384
      $rekeyed_entities[$key] = $entity;
385
    }
386

    
387
    $entities = $rekeyed_entities;
388

    
389
    // If no view mode is specified, use the first one available..
390
    if (!isset($this->entityInfo['view modes'][$view_mode])) {
391
      reset($this->entityInfo['view modes']);
392
      $view_mode = key($this->entityInfo['view modes']);
393
    }
394

    
395
    if (!empty($this->entityInfo['fieldable'])) {
396
      field_attach_prepare_view($this->entityType, $entities, $view_mode);
397
    }
398

    
399
    entity_prepare_view($this->entityType, $entities);
400
    $langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language;
401
    $view = array();
402

    
403
    // Build the content array for each entity passed in.
404
    foreach ($entities as $key => $entity) {
405
      $build = entity_build_content($this->entityType, $entity, $view_mode, $langcode);
406

    
407
      // Add default properties to the array to ensure the content is passed
408
      // through the theme layer.
409
      $build += array(
410
        '#theme' => 'entity',
411
        '#entity_type' => $this->entityType,
412
        '#entity' => $entity,
413
        '#view_mode' => $view_mode,
414
        '#language' => $langcode,
415
        '#page' => $page,
416
      );
417

    
418
      // Allow modules to modify the structured entity.
419
      drupal_alter(array($this->entityType . '_view', 'entity_view'), $build, $this->entityType);
420
      $view[$this->entityType][$key] = $build;
421
    }
422

    
423
    return $view;
424
  }
425

    
426
}