contacts_subscriptions-1.x-dev/src/Entity/Subscription.php

src/Entity/Subscription.php
<?php

namespace Drupal\contacts_subscriptions\Entity;

use Drupal\commerce_price\Price;
use Drupal\commerce_product\Entity\ProductInterface;
use Drupal\commerce_product\Entity\ProductVariationInterface;
use Drupal\contacts_subscriptions\Event\SubscriptionPostSaveEvent;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\RevisionableContentEntityBase;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface;
use Drupal\user\EntityOwnerTrait;

/**
 * Defines the subscription entity class.
 *
 * @ContentEntityType(
 *   id = "contacts_subscription",
 *   label = @Translation("Subscription"),
 *   label_collection = @Translation("Subscriptions"),
 *   label_singular = @Translation("subscription"),
 *   label_plural = @Translation("subscriptions"),
 *   label_count = @PluralTranslation(
 *     singular = "@count subscription",
 *     plural = "@count subscriptions",
 *   ),
 *   bundle_label = @Translation("Subscription type"),
 *   handlers = {
 *     "storage" = "Drupal\contacts_subscriptions\Entity\SqlSubscriptionStorage",
 *     "view_builder" = "Drupal\contacts_subscriptions\Entity\SubscriptionViewBuilder",
 *     "list_builder" = "Drupal\contacts_subscriptions\Entity\SubscriptionListBuilder",
 *     "views_data" = "Drupal\views\EntityViewsData",
 *     "form" = {
 *       "add" = "Drupal\contacts_subscriptions\Form\SubscriptionForm",
 *       "edit" = "Drupal\contacts_subscriptions\Form\SubscriptionForm",
 *       "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm"
 *     },
 *     "views_data" = "Drupal\contacts_subscriptions\Entity\SubscriptionViewsData",
 *     "route_provider" = {
 *       "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
 *     }
 *   },
 *   base_table = "contacts_subscription",
 *   revision_table = "contacts_subscription_revision",
 *   show_revision_ui = TRUE,
 *   admin_permission = "administer subscriptions types",
 *   entity_keys = {
 *     "id" = "id",
 *     "revision" = "revision_id",
 *     "bundle" = "bundle",
 *     "label" = "id",
 *     "uuid" = "uuid",
 *     "owner" = "uid"
 *   },
 *   revision_metadata_keys = {
 *     "revision_log_message" = "revision_log",
 *     "revision_created" = "revision_created",
 *     "revision_user" = "revision_uid"
 *   },
 *   links = {
 *     "add-form" = "/admin/commerce/subscriptions/add/{contacts_subscription_type}",
 *     "add-page" = "/admin/commerce/subscriptions/add",
 *     "canonical" = "/admin/commerce/subscriptions/{contacts_subscription}",
 *     "edit-form" = "/admin/commerce/subscriptions/{contacts_subscription}/edit",
 *     "delete-form" = "/admin/commerce/subscriptions/{contacts_subscription}/delete",
 *     "collection" = "/admin/commerce/subscriptions"
 *   },
 *   bundle_entity_type = "contacts_subscription_type",
 *   field_ui_base_route = "entity.contacts_subscription_type.edit_form"
 * )
 */
class Subscription extends RevisionableContentEntityBase implements SubscriptionInterface {

  use EntityChangedTrait;
  use EntityOwnerTrait;

  /**
   * The statuses that are active.
   */
  const STATUSES_ACTIVE = [
    'active',
    'cancelled_pending',
    'expired_payment_pending',
    'needs_payment',
  ];

  /**
   * The statuses that will automatically renew.
   */
  const STATUSES_RENEWABLE = [
    'active',
  ];

  /**
   * {@inheritdoc}
   */
  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    $fields = parent::baseFieldDefinitions($entity_type);
    $fields += self::ownerBaseFieldDefinitions($entity_type);

    $fields['changed'] = BaseFieldDefinition::create('changed')
      ->setLabel(t('Changed'))
      ->setDescription(t('The time that the subscriptions was last edited.'));

    $fields['status'] = BaseFieldDefinition::create('state')
      ->setRevisionable(TRUE)
      ->setRequired(TRUE)
      ->setLabel('Status')
      ->setDefaultValue('none')
      ->setInitialValue('none')
      ->setSetting('workflow', 'contacts_subscription')
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayConfigurable('form', TRUE);

    $fields['renewal'] = BaseFieldDefinition::create('datetime')
      ->setRevisionable(TRUE)
      ->setRequired(FALSE)
      ->setLabel('Renewal date')
      ->setDescription('The date of the next membership renewal invoice.<br/><strong>Changing the renewal date can result in over or under payment at the next renewal.</strong>')
      ->setSetting('datetime_type', DateTimeItem::DATETIME_TYPE_DATE)
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayConfigurable('form', TRUE);

    $fields['product'] = BaseFieldDefinition::create('entity_reference')
      ->setRevisionable(TRUE)
      ->setRequired(FALSE)
      ->setLabel('Product')
      ->setDescription('The product used for the membership.')
      ->setSetting('target_type', 'commerce_product')
      ->setSetting('handler_settings', ['target_bundles' => ['subscription' => 'subscription']])
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayConfigurable('form', TRUE);

    $fields['price_override'] = BaseFieldDefinition::create('commerce_price')
      ->setName('price_override')
      ->setRevisionable(TRUE)
      ->setRequired(FALSE)
      ->setLabel('Price override')
      ->setDescription('Price to be applied for membership renewals.')
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayConfigurable('form', TRUE);

    $fields['price_override_date'] = BaseFieldDefinition::create('datetime')
      ->setRevisionable(TRUE)
      ->setRequired(FALSE)
      ->setLabel('Price override expiry')
      ->setDescription('The date until which the discount will apply (inclusive). If not set, the price override will be perpetual.')
      ->setSetting('datetime_type', DateTimeItem::DATETIME_TYPE_DATE)
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayConfigurable('form', TRUE);

    $fields['renewal_product'] = BaseFieldDefinition::create('entity_reference')
      ->setRevisionable(TRUE)
      ->setRequired(FALSE)
      ->setLabel('Renewal product')
      ->setDescription('The renewal product used for the membership.')
      ->setSetting('target_type', 'commerce_product')
      ->setSetting('handler_settings', ['target_bundles' => ['subscription' => 'subscription']])
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayConfigurable('form', TRUE);

    $fields['uid']->setDisplayConfigurable('form', TRUE)
      ->setRequired(TRUE)
      ->setSetting('handler', 'search_api')
      ->setSetting('handler_settings', [
        'index' => 'contacts_index',
      ]);

    return $fields;
  }

  /**
   * {@inheritDoc}
   */
  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
    parent::postSave($storage, $update);

    $event = new SubscriptionPostSaveEvent($this, $update);
    $event_dispatcher = \Drupal::service('event_dispatcher');
    $event_dispatcher->dispatch($event, SubscriptionPostSaveEvent::NAME);
  }

  /**
   * {@inheritdoc}
   */
  public function getStatusId(): string {
    return $this->getStatusItem('status')->value;
  }

  /**
   * {@inheritdoc}
   */
  public function getStatusLabel(): ?string {
    return $this->getStatusItem('status')->getLabel();
  }

  /**
   * {@inheritdoc}
   */
  public function getStatusItem(): StateItemInterface {
    $items = $this->get('status');
    if ($items->isEmpty()) {
      $items->applyDefaultValue();
    }
    return $items->first();
  }

  /**
   * {@inheritdoc}
   */
  public function isActive(): bool {
    return in_array($this->getStatusId(), self::STATUSES_ACTIVE);
  }

  /**
   * {@inheritdoc}
   */
  public function getExpiryDate(): ?DrupalDateTime {
    if (!$this->isActive()) {
      return NULL;
    }

    $date = (clone $this->get('renewal')->date);

    if ($grace = $this->bundle->entity->getGracePeriod()) {
      $date->add(new \DateInterval('P' . $grace . 'D'));
    }

    $date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT);

    return $date;
  }

  /**
   * {@inheritDoc}
   */
  public function willAutoRenew(): bool {
    $return = FALSE;

    if (in_array($this->getStatusId(), self::STATUSES_RENEWABLE)) {
      $return = $this->willRenew();
    }

    return $return;
  }

  /**
   * {@inheritdoc}
   */
  public function willRenew(): bool {
    $return = FALSE;

    if (!$this->get('renewal')->isEmpty()) {
      $now = new DrupalDateTime('now');
      $renewal = (clone $this->get('renewal')->date);

      if ($this->bundle->entity->getRenewBeforeExpiry()) {
        $days = $this->bundle->entity->getDaysBeforeExpiry();
        if ($days === NULL) {
          $return = TRUE;
        }
        else {

          // Remove the number of days that a user can perform an early
          // renewal at from the renewal date to bnring the renewal forward
          // that number of days.
          $renewal->sub(new \DateInterval('P' . $days . 'D'));
        }
      }
      else {

        // Add a day to the current time to ensure that the subscription
        // won't renew on the day it is due to expire, only after.
        $now->add(new \DateInterval('P1D'));
      }

      if (!$return) {
        $now = $now->format('Y-m-d');
        $renewal = $renewal->format('Y-m-d');

        // If 'now' is after the renewal date, the renewal date is in the past
        // and the user can renew.
        $return = ($now >= $renewal);
      }
    }

    return $return;
  }

  /**
   * {@inheritdoc}
   */
  public function getRenewalDate(bool $check_status = TRUE): ?DrupalDateTime {
    if ($check_status && !$this->willRenew()) {
      return NULL;
    }

    if ($this->get('renewal')->isEmpty()) {
      return NULL;
    }

    return clone $this->get('renewal')->date;
  }

  /**
   * {@inheritdoc}
   */
  public function needsPaymentDetails(): bool {
    return $this->isActive() && in_array($this->getStatusId(), [
      'needs_payment',
      'expired_payment_pending',
    ]);
  }

  /**
   * {@inheritdoc}
   */
  public function isCancelPending(): bool {
    return $this->isActive() && $this->getStatusId() === 'cancelled_pending';
  }

  /**
   * {@inheritdoc}
   */
  public function getProduct(bool $check_status = TRUE, bool $renewal_product = FALSE): ?ProductInterface {
    if ($check_status && !$this->isActive()) {
      return NULL;
    }

    if ($renewal_product) {
      if ($product = $this->get('renewal_product')->entity) {
        return $product;
      }
    }

    return $this->get('product')->entity;
  }

  /**
   * {@inheritdoc}
   */
  public function getProductId(bool $check_status = TRUE, bool $renewal_product = FALSE): ?int {
    if ($check_status && !$this->isActive()) {
      return NULL;
    }

    if ($renewal_product) {
      if ($product = $this->get('renewal_product')->target_id) {
        return (int) $product;
      }
    }

    return (int) $this->get('product')->target_id ?: NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getCsrfValue(ProductVariationInterface $variation): string {
    $expiry = $this->getRenewalDate();
    $parts = [
      'uid' => $this->getOwnerId(),
      'current_status' => $this->getStatusId(),
      'current_product' => $this->getProductId(),
      'renewal' => $expiry ? $expiry->getTimestamp() : '',
      'target_variation' => $variation->id(),
    ];
    return implode(':', $parts);
  }

  /**
   * {@inheritdoc}
   */
  public function getOverriddenPrice(): ?Price {
    // Nothing to do if there is no discount.
    if ($this->get('price_override')->isEmpty()) {
      return NULL;
    }

    // Check the expiry has not passed.
    if ($this->hasOverrideExpired() === TRUE) {
      return NULL;
    }

    return $this->get('price_override')->first()->toPrice();
  }

  /**
   * {@inheritdoc}
   */
  public function hasOverrideExpired(?DrupalDateTime $date = NULL): ?bool {
    if ($this->get('price_override')->isEmpty()) {
      return NULL;
    }

    $override_date = $this->get('price_override_date');
    if ($override_date->isEmpty()) {
      return TRUE;
    }

    if ($date === NULL) {
      $date = DrupalDateTime::createFromTimestamp(\Drupal::time()->getCurrentTime());
    }
    $date->setDefaultDateTime();

    /** @var \Drupal\datetime\Plugin\Field\FieldType\DateTimeItem $override_date_item */
    $override_date_item = $override_date->first();
    /** @var \Drupal\Core\Datetime\DrupalDateTime $expiry */
    $expiry = $override_date_item->date;
    return $date->getTimestamp() > $expiry->getTimestamp();
  }

  /**
   * {@inheritdoc}
   */
  public function canChangeProduct() : bool {
    if ($this->get('renewal_product')->target_id) {
      return FALSE;
    }
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function isChanging() : bool {
    if ($renewal_product = $this->get('renewal_product')->target_id) {
      if ($renewal_product != $this->get('product')->target_id) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public static function sort(SubscriptionInterface $a, SubscriptionInterface $b): int {
    // Sort by is renewable, then is active, then by closest expiry.
    return $b->willRenew() <=> $a->willRenew() ?:
      $b->isActive() <=> $a->isActive() ?:
        // Closest expiry is the lower number.
        self::getSortTimestamp($b) <=> self::getSortTimestamp($a);
  }

  /**
   * Wrapper to get a timestamp without triggering errors.
   *
   * @param \Drupal\contacts_subscriptions\Entity\SubscriptionInterface $subscription
   *   The subscription.
   *
   * @return int
   *   The sortable timestamp.
   */
  public static function getSortTimestamp(SubscriptionInterface $subscription): int {
    $date = $subscription->get('renewal')->date;
    return $date ? $date->getTimestamp() : 0;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc