Product.php 13.6 KB
Newer Older
1
2
3
4
<?php

namespace Drupal\commerce_product\Entity;

5
use Drupal\commerce\Entity\CommerceContentEntityBase;
6
use Drupal\commerce\EntityOwnerTrait;
7
8
use Drupal\commerce_product\Event\ProductDefaultVariationEvent;
use Drupal\commerce_product\Event\ProductEvents;
9
use Drupal\commerce_product\Plugin\Field\ComputedDefaultVariation;
10
use Drupal\Core\Cache\Cache;
11
use Drupal\Core\Entity\EntityPublishedTrait;
12
use Drupal\Core\Entity\EntityChangedTrait;
13
use Drupal\Core\Entity\EntityStorageInterface;
14
use Drupal\Core\Entity\EntityTypeInterface;
15
use Drupal\Core\Field\BaseFieldDefinition;
16
17

/**
bojanz's avatar
bojanz committed
18
 * Defines the product entity class.
19
 *
20
21
22
 * @ContentEntityType(
 *   id = "commerce_product",
 *   label = @Translation("Product"),
23
 *   label_collection = @Translation("Products"),
24
25
 *   label_singular = @Translation("product"),
 *   label_plural = @Translation("products"),
26
27
28
29
 *   label_count = @PluralTranslation(
 *     singular = "@count product",
 *     plural = "@count products",
 *   ),
30
 *   bundle_label = @Translation("Product type"),
31
 *   handlers = {
32
33
 *     "event" = "Drupal\commerce_product\Event\ProductEvent",
 *     "storage" = "Drupal\commerce\CommerceContentEntityStorage",
34
 *     "access" = "Drupal\entity\EntityAccessControlHandler",
35
 *     "query_access" = "Drupal\entity\QueryAccess\QueryAccessHandler",
36
 *     "permission_provider" = "Drupal\entity\EntityPermissionProvider",
37
 *     "view_builder" = "Drupal\commerce_product\ProductViewBuilder",
38
 *     "list_builder" = "Drupal\commerce_product\ProductListBuilder",
39
 *     "views_data" = "Drupal\commerce\CommerceEntityViewsData",
40
 *     "form" = {
GoZ's avatar
GoZ committed
41
 *       "default" = "Drupal\commerce_product\Form\ProductForm",
42
43
 *       "add" = "Drupal\commerce_product\Form\ProductForm",
 *       "edit" = "Drupal\commerce_product\Form\ProductForm",
44
 *       "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm"
45
 *     },
46
47
48
 *     "local_task_provider" = {
 *       "default" = "Drupal\entity\Menu\DefaultEntityLocalTaskProvider",
 *     },
bojanz's avatar
bojanz committed
49
 *     "route_provider" = {
50
 *       "default" = "Drupal\entity\Routing\AdminHtmlRouteProvider",
51
 *       "delete-multiple" = "Drupal\entity\Routing\DeleteMultipleRouteProvider",
bojanz's avatar
bojanz committed
52
 *     },
53
 *     "translation" = "Drupal\commerce_product\ProductTranslationHandler"
54
 *   },
55
56
 *   admin_permission = "administer commerce_product",
 *   permission_granularity = "bundle",
57
58
 *   translatable = TRUE,
 *   base_table = "commerce_product",
59
 *   data_table = "commerce_product_field_data",
60
61
 *   entity_keys = {
 *     "id" = "product_id",
62
 *     "bundle" = "type",
63
 *     "label" = "title",
64
 *     "langcode" = "langcode",
65
 *     "uuid" = "uuid",
66
 *     "published" = "status",
67
68
 *     "owner" = "uid",
 *     "uid" = "uid",
69
70
 *   },
 *   links = {
71
 *     "canonical" = "/product/{commerce_product}",
72
 *     "add-page" = "/product/add",
bojanz's avatar
bojanz committed
73
 *     "add-form" = "/product/add/{commerce_product_type}",
74
75
 *     "edit-form" = "/product/{commerce_product}/edit",
 *     "delete-form" = "/product/{commerce_product}/delete",
76
 *     "delete-multiple-form" = "/admin/commerce/products/delete",
77
 *     "collection" = "/admin/commerce/products"
78
 *   },
79
 *   bundle_entity_type = "commerce_product_type",
80
 *   field_ui_base_route = "entity.commerce_product_type.edit_form",
81
82
 * )
 */
83
class Product extends CommerceContentEntityBase implements ProductInterface {
bojanz's avatar
bojanz committed
84

bojanz's avatar
bojanz committed
85
  use EntityChangedTrait;
86
  use EntityOwnerTrait;
87
  use EntityPublishedTrait;
88

89
90
91
92
93
94
95
  /**
   * The default product variation.
   *
   * @var \Drupal\commerce_product\Entity\ProductVariationInterface
   */
  protected $defaultVariation;

96
97
98
99
  /**
   * {@inheritdoc}
   */
  public function getTitle() {
100
    return $this->get('title')->value;
101
  }
102

103
104
105
106
107
108
109
  /**
   * {@inheritdoc}
   */
  public function setTitle($title) {
    $this->set('title', $title);
    return $this;
  }
110

111
112
113
114
  /**
   * {@inheritdoc}
   */
  public function getCreatedTime() {
115
    return $this->get('created')->value;
116
117
118
119
120
121
122
123
124
  }

  /**
   * {@inheritdoc}
   */
  public function setCreatedTime($timestamp) {
    $this->set('created', $timestamp);
    return $this;
  }
125

126
127
128
  /**
   * {@inheritdoc}
   */
129
  public function getStores() {
130
    return $this->getTranslatedReferencedEntities('stores');
131
132
133
134
135
  }

  /**
   * {@inheritdoc}
   */
136
137
  public function setStores(array $stores) {
    $this->set('stores', $stores);
138
139
140
141
142
143
    return $this;
  }

  /**
   * {@inheritdoc}
   */
144
  public function getStoreIds() {
145
146
147
    $store_ids = [];
    foreach ($this->get('stores') as $store_item) {
      $store_ids[] = $store_item->target_id;
148
    }
149
    return $store_ids;
150
151
152
153
154
  }

  /**
   * {@inheritdoc}
   */
155
156
  public function setStoreIds(array $store_ids) {
    $this->set('stores', $store_ids);
157
158
159
    return $this;
  }

160
161
162
163
164
165
166
167
168
169
170
  /**
   * {@inheritdoc}
   */
  public function getVariationIds() {
    $variation_ids = [];
    foreach ($this->get('variations') as $field_item) {
      $variation_ids[] = $field_item->target_id;
    }
    return $variation_ids;
  }

171
172
173
174
  /**
   * {@inheritdoc}
   */
  public function getVariations() {
175
    return $this->getTranslatedReferencedEntities('variations');
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
  }

  /**
   * {@inheritdoc}
   */
  public function setVariations(array $variations) {
    $this->set('variations', $variations);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function hasVariations() {
    return !$this->get('variations')->isEmpty();
  }

  /**
   * {@inheritdoc}
   */
  public function addVariation(ProductVariationInterface $variation) {
    if (!$this->hasVariation($variation)) {
      $this->get('variations')->appendItem($variation);
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function removeVariation(ProductVariationInterface $variation) {
    $index = $this->getVariationIndex($variation);
    if ($index !== FALSE) {
      $this->get('variations')->offsetUnset($index);
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function hasVariation(ProductVariationInterface $variation) {
218
    return in_array($variation->id(), $this->getVariationIds());
219
220
221
222
223
224
225
226
227
228
229
230
  }

  /**
   * Gets the index of the given variation.
   *
   * @param \Drupal\commerce_product\Entity\ProductVariationInterface $variation
   *   The variation.
   *
   * @return int|bool
   *   The index of the given variation, or FALSE if not found.
   */
  protected function getVariationIndex(ProductVariationInterface $variation) {
231
    return array_search($variation->id(), $this->getVariationIds());
232
233
  }

234
235
236
237
  /**
   * {@inheritdoc}
   */
  public function getDefaultVariation() {
238
    if ($this->defaultVariation === NULL) {
239
      $default_variation = NULL;
240
241
242
      foreach ($this->getVariations() as $variation) {
        // Return the first active variation.
        if ($variation->isPublished() && $variation->access('view')) {
243
          $default_variation = $variation;
244
245
          break;
        }
246
      }
247
248
249
      // Allow other modules to set the default variation.
      $event = new ProductDefaultVariationEvent($default_variation, $this);
      $event_dispatcher = \Drupal::service('event_dispatcher');
250
      $event_dispatcher->dispatch($event, ProductEvents::PRODUCT_DEFAULT_VARIATION);
251
      $this->defaultVariation = $event->getDefaultVariation();
252
    }
253
    return $this->defaultVariation;
254
255
  }

256
257
258
259
260
261
262
263
264
  /**
   * {@inheritdoc}
   */
  public function preSave(EntityStorageInterface $storage) {
    parent::preSave($storage);

    foreach (array_keys($this->getTranslationLanguages()) as $langcode) {
      $translation = $this->getTranslation($langcode);

265
266
267
268
      // Explicitly set the owner ID to 0 if the translation owner is anonymous
      // (This will ensure we don't store a broken reference in case the user
      // no longer exists).
      if ($translation->getOwner()->isAnonymous()) {
269
270
271
272
273
        $translation->setOwnerId(0);
      }
    }
  }

274
275
276
277
278
279
280
281
282
  /**
   * {@inheritdoc}
   */
  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
    parent::postSave($storage, $update);

    // Ensure there's a back-reference on each product variation.
    foreach ($this->variations as $item) {
      $variation = $item->entity;
283
      if ($variation && $variation->product_id->isEmpty()) {
284
285
286
287
288
289
        $variation->product_id = $this->id();
        $variation->save();
      }
    }
  }

290
291
292
293
  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
294
    return Cache::mergeContexts(parent::getCacheContexts(), ['url.query_args:v', 'store']);
295
296
  }

297
298
299
300
301
302
303
  /**
   * {@inheritdoc}
   */
  public static function postDelete(EntityStorageInterface $storage, array $entities) {
    // Delete the product variations of a deleted product.
    $variations = [];
    foreach ($entities as $entity) {
304
305
306
      if (empty($entity->variations)) {
        continue;
      }
307
308
309
310
      foreach ($entity->variations as $item) {
        $variations[$item->target_id] = $item->entity;
      }
    }
311
312
    $variation_storage = \Drupal::service('entity_type.manager')->getStorage('commerce_product_variation');
    $variation_storage->delete($variations);
313
314
  }

315
316
317
  /**
   * {@inheritdoc}
   */
318
  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
bojanz's avatar
bojanz committed
319
    $fields = parent::baseFieldDefinitions($entity_type);
320
    $fields += static::ownerBaseFieldDefinitions($entity_type);
321
    $fields += static::publishedBaseFieldDefinitions($entity_type);
322

323
324
325
326
327
328
329
330
331
332
333
334
335
336
    $fields['stores'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Stores'))
      ->setDescription(t('The product stores.'))
      ->setRequired(TRUE)
      ->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED)
      ->setSetting('target_type', 'commerce_store')
      ->setSetting('handler', 'default')
      ->setDisplayOptions('form', [
        'type' => 'commerce_entity_select',
        'weight' => -10,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

337
    $fields['uid']
338
      ->setLabel(t('Author'))
339
      ->setDescription(t('The product author.'))
340
      ->setDisplayConfigurable('view', TRUE)
341
      ->setDisplayOptions('form', [
342
343
        'type' => 'entity_reference_autocomplete',
        'weight' => 5,
344
      ])
345
      ->setDisplayConfigurable('form', TRUE);
346

347
    $fields['title'] = BaseFieldDefinition::create('string')
348
      ->setLabel(t('Title'))
349
      ->setDescription(t('The product title.'))
350
351
      ->setRequired(TRUE)
      ->setTranslatable(TRUE)
352
      ->setSettings([
353
354
        'default_value' => '',
        'max_length' => 255,
355
356
      ])
      ->setDisplayOptions('view', [
357
358
359
        'label' => 'hidden',
        'type' => 'string',
        'weight' => -5,
360
361
      ])
      ->setDisplayOptions('form', [
362
        'type' => 'string_textfield',
363
        'weight' => -5,
364
      ])
365
366
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);
367

368
369
370
371
372
373
374
375
376
377
378
379
380
381
    $fields['variations'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Variations'))
      ->setDescription(t('The product variations.'))
      ->setRequired(TRUE)
      ->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED)
      ->setSetting('target_type', 'commerce_product_variation')
      ->setSetting('handler', 'default')
      ->setDisplayOptions('view', [
        'type' => 'commerce_add_to_cart',
        'weight' => 10,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

382
383
384
385
386
387
388
389
390
391
392
    $fields['default_variation'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Default variation'))
      ->setDescription(t('The default variation.'))
      ->setSetting('target_type', 'commerce_product_variation')
      ->setSetting('handler', 'default')
      ->setComputed(TRUE)
      ->setCardinality(1)
      ->setClass(ComputedDefaultVariation::class)
      ->setDisplayConfigurable('form', FALSE)
      ->setDisplayConfigurable('view', FALSE);

393
394
    $fields['path'] = BaseFieldDefinition::create('path')
      ->setLabel(t('URL alias'))
395
      ->setDescription(t('The product URL alias.'))
396
      ->setTranslatable(TRUE)
bojanz's avatar
bojanz committed
397
      ->setDisplayOptions('form', [
398
399
        'type' => 'path',
        'weight' => 30,
bojanz's avatar
bojanz committed
400
      ])
401
      ->setDisplayConfigurable('form', TRUE)
402
      ->setComputed(TRUE);
403

404
    $fields['status']
405
      ->setLabel(t('Published'))
406
407
408
409
410
411
412
      ->setDisplayOptions('form', [
        'type' => 'boolean_checkbox',
        'settings' => [
          'display_label' => TRUE,
        ],
        'weight' => 90,
      ])
413
      ->setDisplayConfigurable('form', TRUE);
414

415
    $fields['created'] = BaseFieldDefinition::create('created')
416
      ->setLabel(t('Created'))
417
      ->setDescription(t('The time when the product was created.'))
418
      ->setTranslatable(TRUE)
419
      ->setDisplayOptions('form', [
420
421
        'type' => 'datetime_timestamp',
        'weight' => 10,
422
      ])
423
424
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);
425

426
    $fields['changed'] = BaseFieldDefinition::create('changed')
427
      ->setLabel(t('Changed'))
428
      ->setDescription(t('The time when the product was last edited.'))
429
      ->setTranslatable(TRUE);
430
431
432

    return $fields;
  }
bojanz's avatar
bojanz committed
433

434
435
436
437
438
439
440
  /**
   * {@inheritdoc}
   */
  public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
    /** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
    $fields = [];
    $fields['variations'] = clone $base_field_definitions['variations'];
441
    $fields['default_variation'] = clone $base_field_definitions['default_variation'];
442
443
444
445
446
447
448
449
    /** @var \Drupal\commerce_product\Entity\ProductTypeInterface $product_type */
    $product_type = ProductType::load($bundle);
    if ($product_type) {
      $variation_type_id = $product_type->getVariationTypeId();
      // Restrict the variations field to the configured variation type.
      $fields['variations']->setSetting('handler_settings', [
        'target_bundles' => [$variation_type_id => $variation_type_id],
      ]);
450
451
452
      $fields['default_variation']->setSetting('handler_settings', [
        'target_bundles' => [$variation_type_id => $variation_type_id],
      ]);
453
454
455
456
457
    }

    return $fields;
  }

458
}