<?php
/**
* Created by Elements.at New Media Solutions GmbH
*
*/
namespace App\Model\Shop\Event;
use App\Ecommerce\AvailabilitySystem\Event\Availability;
use App\Ecommerce\PriceSystem\Event\PriceSystem;
use App\Model\Shop\CancelableInterface;
use App\Model\Shop\OrderItem;
use App\Model\Shop\TeaserInfoInterface;
use Carbon\Carbon;
use Elements\Bundle\RecurringDatesTypeBundle\Templating\RecurringDatesHelper;
use Elements\Bundle\SkidataTicketingSwebBundle\Model\Constant\CheckoutableType;
use Elements\Bundle\SkidataTicketingSwebBundle\Model\Constant\ValidityUnit;
use Elements\Bundle\SkidataTicketingSwebBundle\Model\SkidataTicketProductInterface;
use Elements\Bundle\SkidataTicketingSwebBundle\Skidata\Sweb\Client\SalesChannel\Enum\PermissionStatusEnum;
use Elements\Bundle\SkidataTicketingSwebBundle\Skidata\Sweb\Client\SalesChannel\Enum\SalesStatusEnum;
use Elements\Bundle\TicketShopFrameworkBundle\Ecommerce\PricingManager\PriceInfo;
use Elements\Bundle\TicketShopFrameworkBundle\Model\DataObject\ProductDateInterface;
use Elements\Bundle\TicketShopFrameworkBundle\Model\Shop\Product\CheckoutableInterface;
use Elements\Bundle\TicketShopFrameworkBundle\Model\Traits\ProductTrait;
use Pimcore\Bundle\EcommerceFrameworkBundle\AvailabilitySystem\AvailabilityInterface;
use Pimcore\Bundle\EcommerceFrameworkBundle\Model\ProductInterface;
use Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\PriceInfoInterface;
use Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\PriceInterface;
use Pimcore\Model\Asset\Image;
use Pimcore\Model\DataObject;
use Pimcore\Model\DataObject\AbstractObject;
use Pimcore\Model\DataObject\ShopEvent;
use Pimcore\Model\DataObject\SkidataConsumerCategory;
use Pimcore\Model\DataObject\SkidataProduct;
/**
* Class EventProduct
*
* @method null|ShopDynamicPrice getOrderedPriceItem()
*/
class EventProduct extends ShopEvent implements
ProductInterface,
CheckoutableInterface,
ProductDateInterface,
TeaserInfoInterface,
CancelableInterface,
SkidataTicketProductInterface
{
use ProductTrait;
const PRODUCT_TYPE = 'event';
public function getPriceSystemName(): ?string
{
return $this->getShopSystemName();
}
public function getAvailabilitySystemName(): ?string
{
return $this->getShopSystemName();
}
protected function getShopSystemName(): ?string
{
return self::PRODUCT_TYPE;
}
public function needsDelivery(): bool
{
return false;
}
public function isBookable(int $quantityScale = 1, bool $useCheckoutable = false): bool
{
/** @var Availability|null $availability */
$availability = $this->getOSAvailabilityInfo($quantityScale);
if ($useCheckoutable) {
return $this->isPublished() && $availability?->isCheckoutable();
} else {
return $this->isPublished() && $availability?->getAvailable();
}
}
public function getOSPrice($quantityScale = 1): ?PriceInterface
{
return $this->getOSPriceInfo($quantityScale)->getPrice();
}
public function getOSIsBookable($quantityScale = 1): bool
{
$price = $this->getOSPrice($quantityScale);
return $this->isBookable() && !empty($price->getGrossAmount()->asNumeric());
}
/** @phpstan-ignore-next-line */
public function getOSPriceInfo($quantityScale = 1, $products = null, Carbon $date = null): ?PriceInfoInterface
{
/** @var PriceSystem $priceSystem */
$priceSystem = $this->getPriceSystemImplementation();
return $priceSystem->getPriceInfo($this, $quantityScale, $products, $date);
}
public function getOSAvailabilityInfo($quantity = null): ?AvailabilityInterface
{
return $this->getAvailabilitySystemImplementation()->getAvailabilityInfo($this, $quantity);
}
public function getProductStartDate(): ?Carbon
{
return $this->getEventStartDate();
}
public function getTeaserPrice(): ?PriceInterface
{
/** @var PriceInfo $priceInfo */
$priceInfo = $this->getOSPriceInfo();
return $priceInfo->getBasePrice();
}
public function getTeaserTopTitle(): ?string
{
if ($category = $this->getProductCategory()) {
return $category->getName();
}
return '';
}
public function getTeaserDescription(): ?string
{
return $this->getShortDescription();
}
public function getTeaserImage(): ?Image
{
return $this->getMainImage();
}
/**
* Events got no validity date on the teaser
*
* @return array<mixed>
*/
public function getTeaserValidity(): array
{
return [];
}
/**
* @return ShopDynamicPrice[]
*/
public function getPriceObjects(?bool $includeFreePrices = false): array
{
$priceObjs = [];
if ($items = $this->getPrices()) {
foreach ($items as $item) {
/** @var ShopDynamicPrice $priceObj */
$priceObj = $item->getObject();
if ($includeFreePrices || $priceObj->getPrice()) {
$priceObjs[] = $priceObj;
}
}
}
return $priceObjs;
}
/**
* @return array<mixed>
*/
public function getPriceObjectGrouped(): array
{
$groupedObjs = [];
if ($items = $this->getPrices()) {
foreach ($items as $item) {
/** @var ShopDynamicPrice $priceObj */
$priceObj = $item->getObject();
if ($priceObj->getPrice()) {
$groupedObjs[$item->getGroupname()][] = $priceObj;
}
}
}
return $groupedObjs;
}
/**
* Get price group containing the given price description
*
* @param ShopDynamicPrice $priceDescription
*
* @return string
*/
public function getPriceGroupForPriceDescription(?ShopDynamicPrice $priceDescription): string
{
if ($priceDescription) {
/** @phpstan-ignore-next-line */
foreach ($this->getReference()->getPrices() as $price) {
if ($price->getObject()->getId() == $priceDescription->getId()) {
return $price->getGroupname();
}
}
}
return '';
}
/**
* @param Carbon|null $startDate
* @param ShopDynamicPrice|null $priceItem
*
* @return EventProduct
*
* @throws \Exception
*/
public function getOrCreateOrderableObject(Carbon $startDate = null, ShopDynamicPrice $priceItem = null): self
{
if ($this->getBookableOnDayOfEvent()) {
$startDate->endOfDay();
}
$key = sprintf('%s-%s-%s',
$this->getKey(),
$startDate->toDateTimeString(),
$priceItem ? $priceItem->getId() : ''
);
$list = new ShopEvent\Listing();
$list->setObjectTypes([AbstractObject::OBJECT_TYPE_VARIANT]);
$list->addConditionParam('o_parentId = ?', $this->getId());
$list->addConditionParam('ifnull(o_key,0) = ?', $key);
$list->addConditionParam('eventStartDate >= ?', $startDate->timestamp);
if ($list->count() == 0) {
// create variant for booking
$orderObject = new self();
$orderObject->setPublished(true);
$orderObject->setType(AbstractObject::OBJECT_TYPE_VARIANT);
$orderObject->setParent($this);
$orderObject->setKey($key);
$orderObject->setEventStartDate($startDate);
$orderObject->setOrderedPriceItem($priceItem);
$orderObject->save();
} else {
/** @var EventProduct $orderObject */
$orderObject = $list->current();
}
return $orderObject;
}
public function isPersonsTimeList(): bool
{
if ($prices = $this->getPrices()) {
$price = array_pop($prices);
return !empty($price->getGroupname());
}
return false;
}
public function isCancelable(OrderItem $orderItem = null): bool
{
return true;
}
public function getFirstValidValidityDate(Carbon $date): ?Carbon
{
$validityDate = null;
$firstValidityDateRange = $this->getFirstValidDateRange($date);
if (isset($firstValidityDateRange['from'])) {
/** @var Carbon $fromDateValidity */
$fromDateValidity = $firstValidityDateRange['from'];
$validityDate = $fromDateValidity->lt($date) ? $date : $fromDateValidity;
}
return $validityDate;
}
/**
* @param Carbon $date
*
* @return array<mixed>
*/
public function getFirstValidDateRange(Carbon $date): array
{
$recurringDatesHelper = new RecurringDatesHelper();
if ($validityDates = $recurringDatesHelper->getCalculatedDates($this, 'getValidityDates')) {
foreach ($validityDates as $validityDate) {
$fromDate = Carbon::parse($validityDate['fromDate']);
$toDate = Carbon::parse($validityDate['toDate']);
if (($validityDate['type'] == 'RecurringDate' || $validityDate['type'] == 'RecurringDateTime')
&& ($date->between($fromDate, $toDate) || $fromDate->gte($date))
) {
return [
'from' => $fromDate,
'to' => $toDate,
];
}
}
}
return [];
}
/**
*
* returns all valid date ranges now and in the future, if no dateformat is given, dats are returned as timestamp
*
* @param Carbon $date
*
* @return array<mixed>
*/
public function getAllValidDateRanges(?Carbon $date = null, string $dateFormat = null): array
{
$ranges = [];
if(!$date) {
$date = Carbon::now()->startOfDay();
}
if ($validDates = $this->getValidityDates()) {
if ($definitions = $validDates->getDefinitionArray()) {
foreach ($definitions as $definition) {
if(!isset($definition['values']['fromDate']) && isset($definition['values']['date'])) {
$fromDate = Carbon::parse($definition['values']['date']);
$toDate = clone $fromDate;
} else {
$fromDate = Carbon::parse($definition['values']['fromDate']);
$toDate = Carbon::parse($definition['values']['toDate']);
}
if (($definition['type'] == 'FixedDate' || $definition['type'] == 'RecurringDate' || $definition['type'] == 'RecurringDateTime')
&& ($date->between($fromDate, $toDate) || $fromDate->gte($date) || $fromDate->getTimestamp()==$date->getTimestamp())
) {
$ranges[] = [
'from' => $fromDate,
'to' => $toDate,
];
}
}
}
}
return $ranges;
}
public function getCheckoutableType(): ?string
{
if ($this->getSkidataProduct()) {
return CheckoutableType::TICKET;
}
return null;
}
public function getSkidataConsumerCategory(): ?SkidataConsumerCategory
{
if ($orderedPrice = $this->getOrderedPriceItem()) {
$consumerCategory = $orderedPrice->getConsumerCategory();
$skidataProduct = $this->getSkidataProduct($orderedPrice);
if ($skidataProduct instanceof SkidataProduct && $consumerCategory instanceof \Elements\Bundle\TicketShopFrameworkBundle\Model\DataObject\TicketConsumerCategory) {
return $consumerCategory->getSkidataConsumerForSkidataProduct($skidataProduct);
}
}
return null;
}
public function getIsDepotTicket(): ?bool
{
return false;
}
public function isSeasonTicket(): ?bool
{
return false;
}
public function getKeycardProduct(): void
{
}
public function getTicketDate(): ?Carbon
{
return $this->getProductStartDate();
}
public function isCancelableInSkidata(OrderItem $orderItem): bool
{
if (($ticket = $this->getSkidataProduct()) && $ticket instanceof SkidataProduct && ($skidataBrick = $orderItem->getSkidataBrick())) {
// copy from TicketProduct.php
$salesStatus = $skidataBrick->getSkiDataSalesStatus();
$cancelableSalesStates = array_merge(SalesStatusEnum::getBookedSalesStates(), SalesStatusEnum::getCanceledSalesStates());
$cancelableSalesStates[] = SalesStatusEnum::Reserved;
if (!in_array($salesStatus, $cancelableSalesStates)) {
return false;
}
$permissionStatus = $skidataBrick->getSkiDataPermissionStatus();
// not cancelable anymore if they are on this states !
if (in_array($permissionStatus, [PermissionStatusEnum::Consumed, PermissionStatusEnum::Blocked])) {
return false;
}
if ($skidataBrick->getSkiDataPermissionConsumed()) {
return false;
}
return true;
}
return false;
}
/**
* Copied from TicketProduct. Method necessary for correct skidata transmission
*/
public function getValidityDate(): ?Carbon
{
return null;
}
/**
* Copied from TicketProduct. Method necessary for correct skidata transmission
*/
public function getValidityFrom(): ?Carbon
{
return null;
}
/**
* Copied from TicketProduct. Method necessary for correct skidata transmission
*/
public function getValidityTo(): ?Carbon
{
return null;
}
/**
* Copied from TicketProduct. Method necessary for correct skidata transmission
*/
public function getReloadValidityFrom(): ?Carbon
{
return null;
}
/**
* Copied from TicketProduct. Method necessary for correct skidata transmission
*/
public function getValidityValue(): float|int|string
{
if ($this->getSkidataProduct() === null) {
return 0;
}
try {
/** @phpstan-ignore-next-line */
return $this->getSkidataProduct()?->getValidityCategory()?->getValidityValue();
} catch (\Throwable) {
return 0;
}
}
/**
* Copied from TicketProduct. Method necessary for correct skidata transmission
*/
public function getValidity(): float|int|string
{
return $this->getValidityValue();
}
/**
* Copied from TicketProduct. Method necessary for correct skidata transmission
*/
public function getIsValidDayBefore(): ?bool
{
return false;
}
/**
* Copied from TicketProduct. Method necessary for correct skidata transmission
*/
public function getValidityValueInDays(): int
{
if ($this->getValidityUnit() === ValidityUnit::DAY) {
return (int)$this->getValidityValue();
}
if ($this->getValidityUnit() === ValidityUnit::POINT) {
return (int)round((int)$this->getValidityValue() / 2);
}
return 1;
}
/**
* Copied from TicketProduct. Method necessary for correct skidata transmission
*/
public function getValidityUnit(): string
{
if ($this->getSkidataProduct() === null) {
return ValidityUnit::DAY; // default
}
try {
return $this->getSkidataProduct()->getValidityCategory()?->getValueUnit();
} catch (\Throwable) {
return ValidityUnit::DAY;
}
}
public function getTicketEndDate(): ?Carbon
{
return $this->getTicketStartDate()?->endOfDay();
}
public function getTicketStartDate(): ?Carbon
{
return $this->getProductStartDate();
}
public function isVoucher(): bool
{
return !$this->getSkidataProduct();
}
public function hasVouchers(): bool
{
// fieldcollection "vouchers" shows how many vouchers the event product has
// if empty and a skidata product is available -> event is a ticket
// if emtpy and no skidata product -> event has one voucher (= event before multiple voucher feature)
return !$this->getSkidataProduct() || $this->getVouchersFieldcollection();
}
public function getVouchersFieldcollection(): ?\Pimcore\Model\DataObject\Fieldcollection
{
$parentProduct = $this->getType() === DataObject::OBJECT_TYPE_VARIANT ? $this->getParent() : $this;
if ($parentProduct instanceof EventProduct) {
return $parentProduct->getVouchers();
}
return null;
}
public function getSkidataProduct(?ShopDynamicPrice $priceItem = null): ?\Pimcore\Model\Element\AbstractElement
{
if ($priceItem && $priceItem->getSkidataProduct()) {
return $priceItem->getSkidataProduct();
}
if ($this->getOrderedPriceItem() && $this->getOrderedPriceItem()->getSkidataProduct()) {
return $this->getOrderedPriceItem()->getSkidataProduct();
}
if ($this->getParent() instanceof EventProduct && $this->getParent()->getSkidataProduct()) {
return $this->getParent()->getSkidataProduct();
}
return parent::getSkidataProduct();
}
public function getAcquisitionType(?ShopDynamicPrice $priceItem = null): ?string
{
if ($priceItem && $priceItem->getAcquisitionType()) {
return $priceItem->getAcquisitionType();
}
if ($this->getOrderedPriceItem() && $this->getOrderedPriceItem()->getAcquisitionType()) {
return $this->getOrderedPriceItem()->getAcquisitionType();
}
return parent::getAcquisitionType();
}
}