View source
<?php
namespace Drupal\radioactivity;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\State\StateInterface;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\radioactivity\Event\EnergyBelowCutoffEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class RadioactivityProcessor implements RadioactivityProcessorInterface {
protected $entityTypeManager;
protected $state;
protected $log;
protected $storage;
protected $requestTime;
protected $queue;
protected $eventDispatcher;
public function __construct(EntityTypeManagerInterface $entity_type_manager, StateInterface $state, LoggerChannelFactoryInterface $logger_factory, StorageFactory $storage, TimeInterface $time, QueueFactory $queue, EventDispatcherInterface $event_dispatcher) {
$this->entityTypeManager = $entity_type_manager;
$this->state = $state;
$this->log = $logger_factory
->get(self::LOGGER_CHANNEL);
$this->storage = $storage
->getConfiguredStorage();
$this->requestTime = $time
->getRequestTime();
$this->queue = $queue;
$this->eventDispatcher = $event_dispatcher;
}
public function processDecay() {
$raCount = $this
->processRadioactivityDecay();
$raRefCount = $this
->processRadioactivityReferenceDecay();
if ($raCount === FALSE && $raRefCount === FALSE) {
return 0;
}
$this->state
->set(self::LAST_PROCESSED_STATE_KEY, $this->requestTime);
$raCount = $raCount ?: 0;
$raRefCount = $raRefCount ?: 0;
$resultCount = $raCount + $raRefCount;
$this->log
->notice('Processed @count radioactivity decays.', [
'@count' => $resultCount,
]);
return $resultCount;
}
private function processRadioactivityDecay() {
$resultCount = 0;
$fieldConfigs = $this
->getRadioactivityFieldsConfig('radioactivity');
if (!$fieldConfigs) {
return FALSE;
}
foreach ($fieldConfigs as $fieldConfig) {
$profile = $fieldConfig
->getSetting('profile');
if ($fieldConfig
->hasData() && ($profile === 'linear' || $profile === 'decay') && $this
->hasReachedGranularityThreshold($fieldConfig)) {
$resultCount += $this
->processFieldDecay($fieldConfig, FALSE);
}
}
return $resultCount;
}
private function processRadioactivityReferenceDecay() {
$resultCount = 0;
$fieldConfigs = $this
->getRadioactivityFieldsConfig('radioactivity_reference');
if (!$fieldConfigs) {
return FALSE;
}
foreach ($fieldConfigs as $fieldConfig) {
$profile = $fieldConfig
->getSetting('profile');
if ($fieldConfig
->hasData() && ($profile === 'linear' || $profile === 'decay') && $this
->hasReachedGranularityThreshold($fieldConfig)) {
$resultCount += $this
->processFieldDecay($fieldConfig, TRUE);
}
}
return $resultCount;
}
protected function getRadioactivityFieldsConfig($type) {
$fieldConfigIds = $this->entityTypeManager
->getStorage('field_storage_config')
->getQuery()
->condition('type', $type)
->execute();
if (empty($fieldConfigIds)) {
return [];
}
$configs = $this->entityTypeManager
->getStorage('field_storage_config')
->loadMultiple($fieldConfigIds);
return $configs;
}
private function hasReachedGranularityThreshold(FieldStorageConfigInterface $fieldConfig) {
$granularity = $fieldConfig
->getSetting('granularity');
if ($granularity == 0) {
return TRUE;
}
$lastCronTimestamp = $this->state
->get(self::LAST_PROCESSED_STATE_KEY, 0);
$threshold = $lastCronTimestamp - $lastCronTimestamp % $granularity + $granularity;
return $this->requestTime >= $threshold;
}
private function processFieldDecay(FieldStorageConfigInterface $fieldConfig, $isReference = FALSE) {
$fieldName = $fieldConfig
->get('field_name');
$entityType = $fieldConfig
->getTargetEntityTypeId();
if ($isReference) {
$query = $this->entityTypeManager
->getStorage($entityType)
->getQuery()
->condition($fieldName . '.entity', NULL, 'IS NOT NULL')
->condition($fieldName . '.entity.timestamp', $this->requestTime, ' <= ')
->condition($fieldName . '.entity.energy', 0, '>')
->AccessCheck(FALSE);
}
else {
$query = $this->entityTypeManager
->getStorage($entityType)
->getQuery()
->condition($fieldName . '.timestamp', $this->requestTime, ' <= ')
->condition($fieldName . '.energy', NULL, 'IS NOT NULL')
->condition($fieldName . '.energy', 0, '>')
->AccessCheck(FALSE);
}
$entityIds = $query
->execute();
$chunks = array_chunk($entityIds, self::QUEUE_CHUNK_SIZE, TRUE);
foreach ($chunks as $chunk) {
$queue = $this->queue
->get(self::QUEUE_WORKER_DECAY);
$queue
->createItem([
'field_config' => $fieldConfig,
'entity_ids' => $chunk,
]);
}
return count($entityIds);
}
public function queueProcessDecay(FieldStorageConfigInterface $fieldConfig, array $entityIds) {
$entityType = $fieldConfig
->getTargetEntityTypeId();
$fieldName = $fieldConfig
->get('field_name');
$profile = $fieldConfig
->getSetting('profile');
$halfLife = $fieldConfig
->getSetting('halflife');
$cutoff = $fieldConfig
->getSetting('cutoff');
$entities = $this->entityTypeManager
->getStorage($entityType)
->loadMultiple($entityIds);
foreach ($entities as $entity) {
$languages = $entity
->getTranslationLanguages();
foreach ($languages as $language) {
$entity = $entity
->getTranslation($language
->getId());
if ($entity instanceof EntityPublishedInterface && !$entity
->isPublished()) {
continue;
}
$field = $entity
->get($fieldName);
$isReference = $field
->getFieldDefinition()
->getType() === 'radioactivity_reference';
if ($isReference) {
$radioactivityEntity = $field->entity;
if ($radioactivityEntity) {
$newEnergy = $this
->calculateEnergy($radioactivityEntity
->getEnergy(), $radioactivityEntity
->getTimestamp(), $profile, $halfLife);
if ($this
->energyBelowCutoff($newEnergy, $cutoff)) {
$newEnergy = 0;
$this
->dispatchBelowCutoffEvent($entity);
}
$radioactivityEntity
->setEnergy($newEnergy);
$radioactivityEntity
->setTimestamp($this->requestTime);
$radioactivityEntity
->save();
}
}
else {
$newEnergy = $this
->calculateEnergy($field->energy, $field->timestamp, $profile, $halfLife);
if ($this
->energyBelowCutoff($newEnergy, $cutoff)) {
$newEnergy = 0;
$this
->dispatchBelowCutoffEvent($entity);
}
$entity
->get($fieldName)
->setValue([
'energy' => $newEnergy,
'timestamp' => $this->requestTime,
]);
if ($entity
->getEntityType()
->isRevisionable()) {
$entity
->setNewRevision(FALSE);
}
$entity->radioactivityUpdate = TRUE;
$entity
->save();
}
}
}
}
private function calculateEnergy($energy, $timestamp, $profile, $halfLife) {
$result = $energy;
$elapsed = $timestamp ? $this->requestTime - $timestamp : 0;
switch ($profile) {
case 'linear':
$result = $energy > $elapsed ? $energy - $elapsed : 0;
break;
case 'decay':
$result = $energy * pow(2, -$elapsed / $halfLife);
break;
}
return $result;
}
private function energyBelowCutoff($energy, $cutoff) {
return $energy <= $cutoff;
}
private function dispatchBelowCutoffEvent(ContentEntityInterface $entity) {
$event = new EnergyBelowCutoffEvent($entity);
$this->eventDispatcher
->dispatch($event, 'radioactivity_field_cutoff');
}
public function processIncidents() {
$resultCount = 0;
$incidentsByType = $this->storage
->getIncidentsByType();
$this->storage
->clearIncidents();
foreach ($incidentsByType as $entityType => $incidents) {
$chunks = array_chunk($incidents, self::QUEUE_CHUNK_SIZE, TRUE);
foreach ($chunks as $chunk) {
$queue = $this->queue
->get(self::QUEUE_WORKER_INCIDENTS);
$queue
->createItem([
'entity_type' => $entityType,
'incidents' => $chunk,
]);
}
$resultCount += count($incidents);
}
$this->log
->notice('Processed @count radioactivity incidents.', [
'@count' => $resultCount,
]);
return $resultCount;
}
public function queueProcessIncidents($entityType, array $entityIncidents) {
$entities = $this->entityTypeManager
->getStorage($entityType)
->loadMultiple(array_keys($entityIncidents));
foreach ($entities as $entity) {
$hostEntityUpdated = FALSE;
$referencedEntities = [];
foreach ($entityIncidents[$entity
->id()] as $incident) {
if ($incident
->getTargetId()) {
if (!isset($referencedEntities[$incident
->getTargetId()])) {
$referencedEntities[$incident
->getTargetId()] = $this->entityTypeManager
->getStorage('radioactivity')
->load($incident
->getTargetId());
}
$newEnergy = $referencedEntities[$incident
->getTargetId()]
->getEnergy() + $incident
->getEnergy();
$referencedEntities[$incident
->getTargetId()]
->setEnergy($newEnergy);
}
else {
$entity
->get($incident
->getFieldName())->energy += $incident
->getEnergy();
$hostEntityUpdated = TRUE;
}
}
if ($hostEntityUpdated) {
if ($entity
->getEntityType()
->isRevisionable()) {
$entity
->setNewRevision(FALSE);
}
$entity->radioactivityUpdate = TRUE;
$entity
->save();
}
if ($referencedEntities) {
foreach ($referencedEntities as $referencedEntity) {
$referencedEntity
->save();
}
}
}
}
}