View source
<?php
declare (strict_types=1);
namespace Drupal\date_recur;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeEventSubscriberTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeListenerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Field\FieldStorageDefinitionEventSubscriberTrait;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionListenerInterface;
use Drupal\Core\TypedData\TypedDataManagerInterface;
use Drupal\date_recur\Event\DateRecurEvents;
use Drupal\date_recur\Event\DateRecurValueEvent;
use Drupal\date_recur\Plugin\Field\FieldType\DateRecurItem;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class DateRecurOccurrences implements EventSubscriberInterface, EntityTypeListenerInterface, FieldStorageDefinitionListenerInterface {
use EntityTypeEventSubscriberTrait;
use FieldStorageDefinitionEventSubscriberTrait;
public const IS_DATE_RECUR = 'is_date_recur';
protected $database;
protected $entityTypeManager;
protected $entityFieldManager;
protected $typedDataManager;
public function __construct(Connection $database, EntityFieldManagerInterface $entityFieldManager, TypedDataManagerInterface $typedDataManager, EntityTypeManagerInterface $entityTypeManager) {
$this->database = $database;
$this->entityTypeManager = $entityTypeManager;
$this->entityFieldManager = $entityFieldManager;
$this->typedDataManager = $typedDataManager;
}
public function onSave(DateRecurValueEvent $event) : void {
$list = $event
->getField();
$fieldDefinition = $list
->getFieldDefinition();
$tableName = static::getOccurrenceCacheStorageTableName($fieldDefinition
->getFieldStorageDefinition());
$isInsert = $event
->isInsert();
if (!$isInsert) {
$entityId = $list
->getEntity()
->id();
$this->database
->delete($tableName)
->condition('entity_id', (string) $entityId)
->execute();
}
foreach ($list as $item) {
$this
->saveItem($item, $tableName);
}
}
protected function saveItem(DateRecurItem $item, string $tableName) : void {
$fieldDelta = $item
->getName();
assert(is_int($fieldDelta));
$fieldName = $item
->getFieldDefinition()
->getName();
$entity = $item
->getEntity();
$fields = [
'entity_id',
'field_delta',
'delta',
$fieldName . '_value',
$fieldName . '_end_value',
];
$baseRow = [
'entity_id' => $entity
->id(),
'field_delta' => $fieldDelta,
];
if ($entity
->getEntityType()
->isRevisionable() && $entity instanceof RevisionableInterface) {
$fields[] = 'revision_id';
$baseRow['revision_id'] = $entity
->getRevisionId();
}
$occurrences = $this
->getOccurrencesForCacheStorage($item);
$rows = array_map(function (DateRange $occurrence, $delta) use ($baseRow, $fieldName, $item) : array {
$row = $baseRow;
$row['delta'] = $delta;
$row[$fieldName . '_value'] = $this
->massageDateValueForStorage($occurrence
->getStart(), $item);
$row[$fieldName . '_end_value'] = $this
->massageDateValueForStorage($occurrence
->getEnd(), $item);
return $row;
}, $occurrences, array_keys($occurrences));
$insert = $this->database
->insert($tableName)
->fields($fields);
foreach ($rows as $row) {
$insert
->values($row);
}
$insert
->execute();
}
public function onEntityDelete(DateRecurValueEvent $event) : void {
$list = $event
->getField();
$fieldDefinition = $list
->getFieldDefinition();
$tableName = static::getOccurrenceCacheStorageTableName($fieldDefinition
->getFieldStorageDefinition());
$delete = $this->database
->delete($tableName);
$entityId = $list
->getEntity()
->id();
$delete
->condition('entity_id', (string) $entityId);
$delete
->execute();
}
public function onEntityRevisionDelete(DateRecurValueEvent $event) : void {
$list = $event
->getField();
$entity = $list
->getEntity();
$fieldDefinition = $list
->getFieldDefinition();
$tableName = static::getOccurrenceCacheStorageTableName($fieldDefinition
->getFieldStorageDefinition());
$delete = $this->database
->delete($tableName);
$entityId = $list
->getEntity()
->id();
$delete
->condition('entity_id', (string) $entityId);
if ($entity
->getEntityType()
->isRevisionable() && $entity instanceof RevisionableInterface) {
$delete
->condition('revision_id', $entity
->getRevisionId());
}
$delete
->execute();
}
public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $fieldStorageConfig) : void {
if ($this
->isDateRecur($fieldStorageConfig)) {
$this
->fieldStorageCreate($fieldStorageConfig);
}
}
public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $fieldStorageConfig) : void {
if ($this
->isDateRecur($fieldStorageConfig)) {
$this
->fieldStorageDelete($fieldStorageConfig);
}
}
public function onEntityTypeCreate(EntityTypeInterface $entity_type) : void {
if (!$entity_type instanceof ContentEntityTypeInterface) {
return;
}
foreach ($this
->getBaseFieldStorages($entity_type) as $baseFieldStorage) {
$this
->fieldStorageCreate($baseFieldStorage);
}
}
public function onEntityTypeDelete(EntityTypeInterface $entity_type) : void {
if (!$entity_type instanceof ContentEntityTypeInterface) {
return;
}
foreach ($this
->getBaseFieldStorages($entity_type) as $baseFieldStorage) {
$this
->fieldStorageDelete($baseFieldStorage);
}
}
protected function fieldStorageCreate(FieldStorageDefinitionInterface $fieldDefinition) : void {
$this
->createOccurrenceTable($fieldDefinition);
}
protected function fieldStorageDelete(FieldStorageDefinitionInterface $fieldDefinition) : void {
$tableName = static::getOccurrenceCacheStorageTableName($fieldDefinition);
$this->database
->schema()
->dropTable($tableName);
}
protected function getOccurrencesForCacheStorage(DateRecurItem $item) : array {
$until = NULL;
if ($item
->getHelper()
->isInfinite()) {
$until = (new \DateTime('now'))
->add(new \DateInterval($item
->getFieldDefinition()
->getSetting('precreate')));
}
return $item
->getHelper()
->getOccurrences(NULL, $until);
}
protected function createOccurrenceTable(FieldStorageDefinitionInterface $fieldDefinition) : void {
$entityTypeId = $fieldDefinition
->getTargetEntityTypeId();
$entityType = $this->entityTypeManager
->getDefinition($entityTypeId);
$fieldName = $fieldDefinition
->getName();
$entityFieldDefinitions = $this->entityFieldManager
->getFieldStorageDefinitions($entityTypeId);
$idDefinition = $entityFieldDefinitions[$entityType
->getKey('id')];
if ($idDefinition
->getType() === 'integer') {
$fields['entity_id'] = [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'description' => 'The entity id this data is attached to',
];
}
else {
$fields['entity_id'] = [
'type' => 'varchar_ascii',
'length' => 128,
'not null' => TRUE,
'description' => 'The entity id this data is attached to',
];
}
if ($entityType
->isRevisionable()) {
$revisionDefinition = $entityFieldDefinitions[$entityType
->getKey('revision')];
if ($revisionDefinition
->getType() === 'integer') {
$fields['revision_id'] = [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'description' => 'The entity revision id this data is attached to',
];
}
else {
$fields['revision_id'] = [
'type' => 'varchar_ascii',
'length' => 128,
'not null' => TRUE,
'description' => 'The entity revision id this data is attached to',
];
}
}
$fields['field_delta'] = [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'description' => 'The sequence number for this data item, used for multi-value fields',
];
$fields['delta'] = [
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'description' => 'The sequence number in generated occurrences for the RRULE',
];
$fieldSchema = $fieldDefinition
->getSchema();
$fields[$fieldName . '_value'] = $fieldSchema['columns']['value'];
$fields[$fieldName . '_end_value'] = $fieldSchema['columns']['end_value'];
$schema = [
'description' => sprintf('Occurrences cache for %s.%s', $fieldDefinition
->getTargetEntityTypeId(), $fieldName),
'fields' => $fields,
'indexes' => [
'value' => [
'entity_id',
$fieldName . '_value',
],
],
];
$tableName = DateRecurOccurrences::getOccurrenceCacheStorageTableName($fieldDefinition);
$this->database
->schema()
->createTable($tableName, $schema);
}
protected function massageDateValueForStorage(\DateTimeInterface $date, DateRecurItem $item) : string {
$date
->setTimezone(new \DateTimeZone(DateRecurItem::STORAGE_TIMEZONE));
$storageFormat = $item
->getDateStorageFormat();
if ($storageFormat == DateRecurItem::DATE_STORAGE_FORMAT) {
$date
->setTime(12, 0, 0);
}
return $date
->format($storageFormat);
}
protected function isDateRecur(FieldStorageDefinitionInterface $fieldDefinition) : bool {
$typeDefinition = $this->typedDataManager
->getDefinition('field_item:' . $fieldDefinition
->getType());
return isset($typeDefinition[DateRecurOccurrences::IS_DATE_RECUR]);
}
protected function getBaseFieldStorages(ContentEntityTypeInterface $entityType) : array {
$baseFields = $this->entityFieldManager
->getBaseFieldDefinitions($entityType
->id());
$baseFields = array_filter($baseFields, function (FieldDefinitionInterface $fieldDefinition) : bool {
return $this
->isDateRecur($fieldDefinition
->getFieldStorageDefinition());
});
return array_map(function (FieldDefinitionInterface $baseField) : FieldStorageDefinitionInterface {
return $baseField
->getFieldStorageDefinition();
}, $baseFields);
}
public static function getOccurrenceCacheStorageTableName(FieldStorageDefinitionInterface $fieldDefinition) : string {
return sprintf('date_recur__%s__%s', $fieldDefinition
->getTargetEntityTypeId(), $fieldDefinition
->getName());
}
public static function getSubscribedEvents() : array {
return [
DateRecurEvents::FIELD_VALUE_SAVE => [
'onSave',
],
DateRecurEvents::FIELD_ENTITY_DELETE => [
'onEntityDelete',
],
DateRecurEvents::FIELD_REVISION_DELETE => [
'onEntityRevisionDelete',
],
] + static::getEntityTypeEvents() + static::getFieldStorageDefinitionEvents();
}
}