Projet

Général

Profil

Paste
Télécharger (13,5 ko) Statistiques
| Branche: | Révision:

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

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. If rules is there, use the rule funtion so that a rules
98
    // event is invoked too.
99
    if (module_exists('rules')) {
100
      rules_invoke_all($this->entityType . '_' . $hook, $entity);
101
    }
102
    else {
103
      module_invoke_all($this->entityType . '_' . $hook, $entity);
104
    }
105
    // Invoke the respective entity level hook.
106
    if ($hook == 'presave' || $hook == 'insert' || $hook == 'update' || $hook == 'delete') {
107
      module_invoke_all('entity_' . $hook, $entity, $this->entityType);
108
    }
109
  }
110

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

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

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

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

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

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

    
183
    try {
184
      // Load the stored entity, if any.
185
      if (!empty($entity->{$this->idKey}) && !isset($entity->original)) {
186
        // In order to properly work in case of name changes, load the original
187
        // entity using the id key if it is available.
188
        $entity->original = entity_load_unchanged($this->entityType, $entity->{$this->idKey});
189
      }
190

    
191
      $this->invoke('presave', $entity);
192

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

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

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

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

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

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

    
242
      $this->invoke($op, $entity);
243

    
244
      // Ignore slave server temporarily.
245
      db_ignore_slave();
246
      unset($entity->is_new);
247
      unset($entity->original);
248
      unset($entity->revision);
249

    
250
      return $return;
251
    }
252
    catch (Exception $e) {
253
      if (!empty($started_transaction)) {
254
        $transaction->rollback();
255
        watchdog_exception($this->entityType, $e);
256
      }
257
      throw $e;
258
    }
259
  }
260

    
261
  /**
262
   * Create a new entity.
263
   *
264
   * @param array $values
265
   *   An array of values to set, keyed by property name.
266
   * @return
267
   *   A new instance of the entity type.
268
   */
269
  public function create(array $values = array()) {
270
    // Add is_new property if it is not set.
271
    $values += array('is_new' => TRUE);
272

    
273
    // If there is a class for this entity type, instantiate it now.
274
    if (isset($this->entityInfo['entity class']) && $class = $this->entityInfo['entity class']) {
275
      $entity = new $class($values, $this->entityType);
276
    }
277
    else {
278
      // Otherwise use a good old stdClass.
279
      $entity = (object) $values;
280
    }
281

    
282
    // Allow other modules to alter the created entity.
283
    drupal_alter('commerce_entity_create', $this->entityType, $entity);
284

    
285
    return $entity;
286
  }
287

    
288
  /**
289
   * Implements EntityAPIControllerInterface.
290
   */
291
  public function export($entity, $prefix = '') {
292
    throw new Exception('Not implemented');
293
  }
294

    
295
  /**
296
   * Implements EntityAPIControllerInterface.
297
   */
298
  public function import($export) {
299
    throw new Exception('Not implemented');
300
  }
301

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

    
323
    // Add in fields.
324
    if (!empty($this->entityInfo['fieldable'])) {
325
      $entity->content += field_attach_view($this->entityType, $entity, $view_mode, $langcode);
326
    }
327

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

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

    
334
    // Remove the build array information from the entity and return it.
335
    $build = $entity->content;
336
    unset($entity->content);
337

    
338
    return $build;
339
  }
340

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

    
367
    foreach ($entities as $key => $entity) {
368
      // Use the entity's ID if available and fallback to its existing key value
369
      // if we couldn't determine it.
370
      if (isset($entity->{$this->idKey})) {
371
        $key = $entity->{$this->idKey};
372
      }
373

    
374
      $rekeyed_entities[$key] = $entity;
375
    }
376

    
377
    $entities = $rekeyed_entities;
378

    
379
    // If no view mode is specified, use the first one available..
380
    if (!isset($this->entityInfo['view modes'][$view_mode])) {
381
      reset($this->entityInfo['view modes']);
382
      $view_mode = key($this->entityInfo['view modes']);
383
    }
384

    
385
    if (!empty($this->entityInfo['fieldable'])) {
386
      field_attach_prepare_view($this->entityType, $entities, $view_mode);
387
    }
388

    
389
    entity_prepare_view($this->entityType, $entities);
390
    $langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language;
391
    $view = array();
392

    
393
    // Build the content array for each entity passed in.
394
    foreach ($entities as $key => $entity) {
395
      $build = entity_build_content($this->entityType, $entity, $view_mode, $langcode);
396

    
397
      // Add default properties to the array to ensure the content is passed
398
      // through the theme layer.
399
      $build += array(
400
        '#theme' => 'entity',
401
        '#entity_type' => $this->entityType,
402
        '#entity' => $entity,
403
        '#view_mode' => $view_mode,
404
        '#language' => $langcode,
405
        '#page' => $page,
406
      );
407

    
408
      // Allow modules to modify the structured entity.
409
      drupal_alter(array($this->entityType . '_view', 'entity_view'), $build, $this->entityType);
410
      $view[$this->entityType][$key] = $build;
411
    }
412

    
413
    return $view;
414
  }
415

    
416
}