commerce_license-8.x-2.x-dev/src/Entity/License.php
src/Entity/License.php
<?php
namespace Drupal\commerce_license\Entity;
use Drupal\commerce\EntityOwnerTrait;
use Drupal\commerce\Interval;
use Drupal\commerce_license\Plugin\Commerce\LicensePeriod\LicensePeriodInterface;
use Drupal\commerce_license\Plugin\Commerce\LicenseType\LicenseTypeInterface;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_product\Entity\ProductVariationType;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Defines the License entity.
*
* @ingroup commerce_license
*
* @ContentEntityType(
* id = "commerce_license",
* label = @Translation("License"),
* label_collection = @Translation("Licenses"),
* label_singular = @Translation("license"),
* label_plural = @Translation("licenses"),
* label_count = @PluralTranslation(
* singular = "@count license",
* plural = "@count licenses",
* ),
* bundle_label = @Translation("License type"),
* bundle_plugin_type = "commerce_license_type",
* handlers = {
* "access" = "\Drupal\entity\UncacheableEntityAccessControlHandler",
* "event" = "Drupal\commerce_license\Event\LicenseEvent",
* "permission_provider" = "\Drupal\commerce_license\LicensePermissionProvider",
* "list_builder" = "Drupal\commerce_license\LicenseListBuilder",
* "storage" = "Drupal\commerce_license\LicenseStorage",
* "form" = {
* "default" = "Drupal\commerce_license\Form\LicenseForm",
* "add" = "Drupal\commerce_license\Form\LicenseForm",
* "checkout" = "Drupal\commerce_license\Form\LicenseCheckoutForm",
* "edit" = "Drupal\commerce_license\Form\LicenseForm",
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
* },
* "views_data" = "Drupal\commerce_license\LicenseViewsData",
* "route_provider" = {
* "html" = "Drupal\commerce_license\LicenseRouteProvider",
* "delete-multiple" = "Drupal\entity\Routing\DeleteMultipleRouteProvider",
* },
* },
* base_table = "commerce_license",
* admin_permission = "administer commerce_license",
* fieldable = TRUE,
* entity_keys = {
* "id" = "license_id",
* "bundle" = "type",
* "uuid" = "uuid",
* "uid" = "uid",
* "owner" = "uid",
* },
* links = {
* "canonical" = "/admin/commerce/licenses/{commerce_license}",
* "add-page" = "/admin/commerce/licenses/add",
* "add-form" = "/admin/commerce/licenses/add/{type}",
* "edit-form" = "/admin/commerce/licenses/{commerce_license}/edit",
* "delete-form" = "/admin/commerce/licenses/{commerce_license}/delete",
* "delete-multiple-form" = "/admin/commerce/licenses/delete",
* "collection" = "/admin/commerce/licenses",
* "state-transition-form" = "/admin/commerce/licenses/{commerce_license}/{field_name}/{transition_id}"
* },
* field_ui_base_route = "entity.commerce_license.field_ui_fields",
* )
*/
class License extends ContentEntityBase implements LicenseInterface {
use EntityChangedTrait;
use EntityOwnerTrait;
use StringTranslationTrait;
/**
* The renewal window start time.
*
* Calculated in the case of a renewable license.
*
* @var int|null
*/
protected $renewalWindowStartTime;
/**
* {@inheritdoc}
*/
public function label() {
// Get the label for the license from the plugin.
return new FormattableMarkup('@title #@id', [
'@title' => $this->getTypePlugin()->buildLabel($this),
'@id' => $this->id(),
]);
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);
// Act when the license state changes, or the license is new.
// (Note that $this->original is not set on new entities.)
if ((isset($this->original) && $this->getState()->getId() != $this->original->getState()->getId()) || !isset($this->original)) {
// If the state is being changed to 'active', set the granted and
// expiration timestamps, and notify the license type plugin. We act on
// preSave() rather than postSave() so that the license plugin can set
// values on the license. HOWEVER, this means that if something acts in
// hook_entity_presave() to prevent saving, by throwing an exception, the
// license entity will be unsaved, but the license plugin will have
// granted the license, leaving it in an incorrect state.
// @todo override doPreSave() in LicenseStorage to catch exceptions and
// revert the grant if the save is cancelled.
if ($this->getState()->getId() === 'active') {
// The state is moved to 'active', or the license was created active:
// the license activates.
$this->getTypePlugin()->grantLicense($this);
// Set timestamps.
$activation_time = \Drupal::service('datetime.time')->getRequestTime();
if (empty($this->getGrantedTime())) {
// The license has not previously been granted, and is therefore being
// activated for the first time. Set the 'granted' timestamp.
$this->setGrantedTime($activation_time);
}
else {
if (isset($this->original) && $this->original->getState()->getId() !== 'renewal_cancelled') {
// The license has previously been granted, and is therefore being
// re-activated after a lapse. Set the 'renewed' timestamp.
$this->setRenewedTime($activation_time);
}
}
// Renewal completed.
if (isset($this->original) && $this->original->getState()->getId() === 'renewal_in_progress') {
$expires_time = $this->getExpiresTime();
if ($expires_time < $activation_time) {
$expires_time = $activation_time;
}
$this->setExpiresTime(
$this->calculateExpirationTime($expires_time)
);
}
// Set the expiry time on a new license, but allow licenses to be
// created with a set expiry, such as in the case of a migration.
if (!$this->getExpiresTime()) {
$this->setExpiresTime($this->calculateExpirationTime($activation_time));
}
}
// The state is being moved away from 'active'.
if (isset($this->original) && $this->original->getState()->getId() === 'active' && $this->getState()->getId() !== 'renewal_in_progress') {
// The license is revoked.
$this->getTypePlugin()->revokeLicense($this);
}
}
}
/**
* {@inheritdoc}
*/
public function getTypePlugin() {
/** @var \Drupal\commerce_license\LicenseTypeManager $license_type_manager */
$license_type_manager = \Drupal::service('plugin.manager.commerce_license_type');
return $license_type_manager->createInstance($this->bundle());
}
/**
* {@inheritdoc}
*/
public function getExpirationPluginType() {
return $this->get('expiration_type')->target_plugin_id;
}
/**
* {@inheritdoc}
*/
public function getExpirationPlugin() {
return $this->get('expiration_type')->first()->getTargetInstance();
}
/**
* {@inheritdoc}
*/
public function setValuesFromPlugin(LicenseTypeInterface $license_plugin) {
$license_plugin->setConfigurationValuesOnLicense($this);
}
/**
* {@inheritdoc}
*/
public function getExpiresTime() {
return $this->get('expires')->value;
}
/**
* {@inheritdoc}
*/
public function setExpiresTime(int $timestamp) {
$this->set('expires', $timestamp);
return $this;
}
/**
* {@inheritdoc}
*/
public function getGrantedTime() {
return $this->get('granted')->value;
}
/**
* {@inheritdoc}
*/
public function setGrantedTime(int $timestamp) {
$this->set('granted', $timestamp);
return $this;
}
/**
* {@inheritdoc}
*/
public function getRenewedTime() {
return $this->get('renewed')->value;
}
/**
* {@inheritdoc}
*/
public function setRenewedTime(int $timestamp) {
$this->set('renewed', $timestamp);
return $this;
}
/**
* {@inheritdoc}
*/
public function getCreatedTime() {
return $this->get('created')->value;
}
/**
* {@inheritdoc}
*/
public function setCreatedTime(int $timestamp) {
$this->set('created', $timestamp);
return $this;
}
/**
* {@inheritdoc}
*/
public function getRenewalWindowStartTime() {
return $this->renewalWindowStartTime;
}
/**
* Calculate the expiration time for this license from a start time.
*
* @param int $start
* The timestamp to calculate the duration from.
*
* @return int
* The expiry timestamp, or the value
* \Drupal\commerce_license\Plugin\Commerce\LicensePeriod\LicensePeriodInterface::UNLIMITED
* if the license does not expire.
*
* @throws \Exception
*/
protected function calculateExpirationTime(int $start): int {
/** @var \Drupal\commerce_license\Plugin\Commerce\LicensePeriod\LicensePeriodInterface $expiration_type_plugin */
$expiration_type_plugin = $this->getExpirationPlugin();
// The recurring period plugin needs DateTimeImmutable objects in order
// to handle timezones properly. So we convert the timestamp to a datetime
// using an appropriate timezone for the user, and then convert the
// expiration back into a UTC timestamp.
$start_date = (new \DateTimeImmutable('@' . $start))
->setTimezone(new \DateTimeZone(commerce_license_get_user_timezone($this->getOwner())));
$expiration_date = $expiration_type_plugin->calculateEnd($start_date);
// The returned date is either \DateTimeImmutable or
// \Drupal\commerce_license\Plugin\Commerce\LicensePeriod\LicensePeriodInterface::UNLIMITED.
if (is_object($expiration_date)) {
return $expiration_date->format('U');
}
return $expiration_date;
}
/**
* {@inheritdoc}
*/
public function getState() {
return $this->get('state')->first();
}
/**
* {@inheritdoc}
*/
public function getPurchasedEntity() {
return $this->get('product_variation')->entity;
}
/**
* {@inheritdoc}
*/
public static function getWorkflowId(LicenseInterface $license) {
return $license->getTypePlugin()->getWorkflowId();
}
/**
* {@inheritdoc}
*/
public function getOriginatingOrder() {
return $this->get('originating_order')->entity;
}
/**
* {@inheritdoc}
*/
public function setOriginatingOrder(OrderInterface $originating_order) {
$this->set('originating_order', $originating_order);
return $this;
}
/**
* {@inheritdoc}
*/
public function getOriginatingOrderId() {
return $this->get('originating_order')->target_id;
}
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields += static::ownerBaseFieldDefinitions($entity_type);
$fields['type']->setDisplayOptions('view', [
'label' => 'inline',
'type' => 'string',
'weight' => 0,
]);
$fields['uid']
->setLabel(t('Owner'))
->setDescription(t('The user ID of the license owner.'))
->setSetting('handler', 'default')
->setDisplayOptions('form', [
'type' => 'entity_reference_autocomplete',
'weight' => 5,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view', [
'label' => 'inline',
'type' => 'entity_reference_label',
'weight' => 2,
'settings' => [
'link' => TRUE,
],
])
->setDisplayConfigurable('view', TRUE);
$fields['state'] = BaseFieldDefinition::create('state')
->setLabel(t('State'))
->setDescription(t('The license state.'))
->setRequired(TRUE)
->setSetting('max_length', 255)
->setDisplayOptions('form', [
'region' => 'hidden',
])
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view', [
'label' => 'hidden',
'type' => 'state_transition_form',
'settings' => [
'require_confirmation' => TRUE,
'use_modal' => TRUE,
],
'weight' => 10,
])
->setDisplayConfigurable('view', TRUE)
->setSetting('workflow_callback', [static::class, 'getWorkflowId']);
$fields['product_variation'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Licensed product variation'))
->setDescription(t('The licensed product variation.'))
->setRequired(TRUE)
->setSetting('target_type', 'commerce_product_variation')
->setSetting('handler', 'commerce_license')
->setDisplayOptions('form', [
'type' => 'entity_reference_autocomplete',
'weight' => -1,
'settings' => [
'match_operator' => 'CONTAINS',
'size' => '60',
'placeholder' => '',
],
])
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view', [
'label' => 'inline',
'type' => 'entity_reference_label',
'weight' => 1,
'settings' => [
'link' => TRUE,
],
])
->setDisplayConfigurable('view', TRUE);
$fields['expiration_type'] = BaseFieldDefinition::create('commerce_plugin_item:commerce_license_period')
->setLabel(t('Expiration type'))
->setDescription(t("The configuration for calculating the license's expiry."))
->setCardinality(1)
->setRequired(TRUE)
->setDisplayOptions('form', [
'type' => 'commerce_plugin_select',
'weight' => 21,
])
->setDisplayOptions('view', [
'label' => 'inline',
'type' => 'commerce_plugin_item_default',
'weight' => 25,
])
->setDisplayConfigurable('view', TRUE);
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Created'))
->setDescription(t('The time that the license was created.'))
->setDisplayOptions('view', [
'label' => 'inline',
'type' => 'timestamp',
// Start date-type weights at 20, to leave plenty of space for
// license type plugin fields to go before them.
'weight' => 20,
'settings' => [
'date_format' => 'medium',
],
])
->setDisplayConfigurable('view', TRUE);
$fields['granted'] = BaseFieldDefinition::create('timestamp')
->setLabel(t('Granted'))
->setDescription(t('The time that the license was first granted or activated.'))
->setDisplayOptions('view', [
'label' => 'inline',
'type' => 'timestamp',
'weight' => 21,
'settings' => [
'date_format' => 'medium',
],
])
->setDisplayConfigurable('view', TRUE);
$fields['renewed'] = BaseFieldDefinition::create('timestamp')
->setLabel(t('Renewed'))
->setDescription(t('The time that the license was most recently renewed.'))
->setDisplayOptions('view', [
'label' => 'inline',
'type' => 'timestamp',
'weight' => 22,
'settings' => [
'date_format' => 'medium',
],
])
->setDisplayConfigurable('view', TRUE);
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
->setDescription(t('The time that the license was last modified.'))
->setDisplayOptions('view', [
'label' => 'inline',
'type' => 'timestamp',
'weight' => 19,
'settings' => [
'date_format' => 'medium',
],
])
->setDisplayConfigurable('view', TRUE);
$fields['expires'] = BaseFieldDefinition::create('timestamp')
->setLabel(t('Expires'))
->setDescription(t('The time that the license will expire, if any.'))
->setDisplayOptions('view', [
'label' => 'inline',
'type' => 'commerce_license_expiration',
'weight' => 26,
'settings' => [
'date_format' => 'medium',
],
])
->setDisplayConfigurable('view', TRUE)
// Default to unlimited.
->setDefaultValue(0);
$fields['originating_order'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Originating order'))
->setDescription(t('The order that originated the license creation.'))
->setSetting('target_type', 'commerce_order')
->setSetting('handler', 'default')
->setDisplayConfigurable('view', TRUE);
return $fields;
}
/**
* {@inheritdoc}
*/
public function canRenew() {
if (!in_array($this->getState()->getId(), [
'active',
'renewal_in_progress',
], TRUE)) {
return FALSE;
}
$variation = $this->getPurchasedEntity();
$product_variation_type_id = $variation->bundle();
$product_variation_type = ProductVariationType::load($product_variation_type_id);
assert($product_variation_type !== NULL, 'The product variation is NULL.');
$allow_renewal = $product_variation_type->getThirdPartySetting(
'commerce_license',
'allow_renewal',
FALSE
);
if (!$allow_renewal) {
return FALSE;
}
$allow_renewal_window_interval = $product_variation_type->getThirdPartySetting(
'commerce_license',
'interval'
);
$allow_renewal_window_period = $product_variation_type->getThirdPartySetting(
'commerce_license',
'period'
);
$interval_object = new Interval($allow_renewal_window_interval, $allow_renewal_window_period);
$renewal_window_start_time = new DrupalDateTime('@' . $this->getExpiresTime());
if ($renewal_window_start_time->getTimestamp() === LicensePeriodInterface::UNLIMITED) {
return FALSE;
}
$this->renewalWindowStartTime = $interval_object->subtract($renewal_window_start_time)->getTimestamp();
return $this->renewalWindowStartTime < \Drupal::time()->getRequestTime();
}
}
