digital_signage_framework-2.3.x-dev/src/ScheduleManager.php
src/ScheduleManager.php
<?php
namespace Drupal\digital_signage_framework;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\digital_signage_framework\Entity\Schedule;
/**
* Provides schedule services.
*/
class ScheduleManager {
/**
* The schedule generator plugin manager.
*
* @var \Drupal\digital_signage_framework\ScheduleGeneratorPluginManager
*/
protected ScheduleGeneratorPluginManager $generatorPluginManager;
/**
* The platform plugin manager.
*
* @var \Drupal\digital_signage_framework\PlatformPluginManager
*/
protected PlatformPluginManager $platformPluginManager;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManager
*/
protected EntityTypeManager $entityTypeManager;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected ModuleHandlerInterface $moduleHandler;
/**
* Constructs the schedule manager.
*
* @param \Drupal\digital_signage_framework\ScheduleGeneratorPluginManager $generator_plugin_manager
* The schedule generator plugin manager.
* @param \Drupal\digital_signage_framework\PlatformPluginManager $platform_plugin_manager
* The platform plugin manager.
* @param \Drupal\Core\Entity\EntityTypeManager $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(ScheduleGeneratorPluginManager $generator_plugin_manager, PlatformPluginManager $platform_plugin_manager, EntityTypeManager $entity_type_manager, ModuleHandlerInterface $module_handler) {
$this->generatorPluginManager = $generator_plugin_manager;
$this->platformPluginManager = $platform_plugin_manager;
$this->entityTypeManager = $entity_type_manager;
$this->moduleHandler = $module_handler;
}
/**
* Gets devices for schedule updates.
*
* @param int|null $deviceId
* The device ID or NULL, to find the relevant devices.
* @param bool $all
* TRUE, to get all devices or FALSE to get only those that need an update.
*
* @return \Drupal\digital_signage_framework\DeviceInterface[]
* The list of devices.
*/
protected function getDevices(?int $deviceId = NULL, bool $all = FALSE): array {
try {
$deviceManager = $this->entityTypeManager->getStorage('digital_signage_device');
}
catch (InvalidPluginDefinitionException | PluginNotFoundException) {
// Can be ignored.
return [];
}
// @phpstan-ignore-next-line
$query = $deviceManager->getQuery()->accessCheck(FALSE);
if ($deviceId !== NULL) {
$query->condition('id', $deviceId);
}
if (!$all) {
$query->condition('needs_update', 1);
}
$ids = $query
->execute();
$devices = [];
/** @var int[] $ids */
array_walk($ids, static function (&$item) {
$item = (int) $item;
});
foreach ($deviceManager->loadMultiple($ids) as $device) {
if ($device instanceof DeviceInterface) {
$devices[] = $device;
}
}
return $devices;
}
/**
* Create schedule for a device.
*
* @param \Drupal\digital_signage_framework\DeviceInterface $device
* The devices.
* @param \Drupal\digital_signage_framework\ScheduleGeneratorInterface $plugin
* The generator plugin.
* @param bool $store
* Indicates, whether the generated schedule should be stored in database.
* @param bool $force
* Indicates, whether a schedule should be created, even if one already
* exists.
* @param string|null $entityType
* The entity type ID, if only a single slide should be in the schedule,
* NULL otherwise.
* @param string|null $entityId
* The entity ID, if only a single slide should be in the schedule, NULL
* otherwise.
*
* @return \Drupal\digital_signage_framework\ScheduleInterface
* The schedule.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityStorageException
* @throws \JsonException
*/
protected function createSchedule(DeviceInterface $device, ScheduleGeneratorInterface $plugin, bool $store, bool $force, ?string $entityType = NULL, ?string $entityId = NULL): ScheduleInterface {
// Collect the content entities for this schedule.
// @phpstan-ignore-next-line
$query = $this->entityTypeManager
->getStorage('digital_signage_content_setting')
->getQuery()
->accessCheck(FALSE)
->condition('status', 1)
->condition('emergencymode', 0);
if ($entityType === NULL) {
$orQuery = $query->orConditionGroup()
->condition($query->andConditionGroup()
->notExists('devices.target_id')
->notExists('segments.target_id')
)
->condition('devices.target_id', $device->id());
if ($segmentIds = $device->getSegmentIds()) {
$orQuery->condition('segments.target_id', $segmentIds, 'IN');
}
$query->condition($orQuery);
// Ignore entities that have a predecessor.
$query->notExists('predecessor');
// Allow other modules to alter the query for entities.
$this->moduleHandler->alter('digital_signage_schedule_generator_query', $query, $device);
}
else {
$query
->condition('parent_entity__target_type', $entityType)
->condition('parent_entity__target_id', $entityId);
}
$contentSettings = [];
$hashMap = [];
/** @var \Drupal\digital_signage_framework\ContentSettingInterface $item */
foreach ($this->entityTypeManager->getStorage('digital_signage_content_setting')->loadMultiple($query->execute()) as $item) {
/** @var \Drupal\Core\Entity\ContentEntityInterface|null $entity */
$entity = $this->entityTypeManager->getStorage($item->getReverseEntityType())->load($item->getReverseEntityId());
if ($entity instanceof FieldableEntityInterface && (!$entity->hasField('status') || $entity->get('status')->value)) {
$contentSettings[] = $item;
$hash_item = [
'type' => $entity->getEntityTypeId(),
'id' => $entity->id(),
];
if ($entity->hasField('changed')) {
$hash_item['changed'] = $entity->get('changed')->value;
}
elseif ($entity->hasField('created')) {
$hash_item['created'] = $entity->get('created')->value;
}
$hashMap[] = $hash_item;
}
}
// See if we already have a schedule with that.
$scheduleHash = md5(json_encode($hashMap, JSON_THROW_ON_ERROR));
try {
/** @var \Drupal\digital_signage_framework\ScheduleInterface[] $schedules */
$schedules = $this->entityTypeManager
->getStorage('digital_signage_schedule')
->loadByProperties([
'hash' => $scheduleHash,
]);
}
catch (InvalidPluginDefinitionException | PluginNotFoundException) {
}
if ($force || empty($schedules)) {
// We don't have one yet, let's create a new one.
$items = [];
foreach ($plugin->generate($device, $contentSettings) as $sequenceItem) {
$this->addItemAndSuccessors($items, $sequenceItem, $device);
}
/** @var \Drupal\digital_signage_framework\ScheduleInterface $schedule */
$schedule = Schedule::create([
'hash' => $scheduleHash,
'items' => [$items],
]);
if ($store) {
$schedule->save();
}
}
else {
$schedule = reset($schedules);
}
// Store the schedule with the device if necessary.
if ($store) {
if ($force || !$device->getSchedule() || $device->getSchedule()->id() !== $schedule->id()) {
$device->setSchedule($schedule);
$schedule->needsPush(TRUE);
}
$device
->scheduleUpdateCompleted()
->save();
}
return $schedule;
}
/**
* Helper function to add the next item.
*
* @param array $items
* The current item list.
* @param \Drupal\digital_signage_framework\SequenceItem $sequenceItem
* The next item.
* @param DeviceInterface $device
* The device.
* @param int $level
* The nesting level to avoid recursion.
*/
private function addItemAndSuccessors(array &$items, SequenceItem $sequenceItem, DeviceInterface $device, int $level = 0): void {
$items[] = $sequenceItem->toArray();
if ($level > 3) {
// Avoid infinite loop.
return;
}
$level++;
foreach ($sequenceItem->getSuccessors($device) as $successor) {
$this->addItemAndSuccessors($items, $successor, $device, $level);
}
}
/**
* Push a schedule to a device.
*
* @param int|null $deviceId
* The device ID or NULL to push to all devices.
* @param bool $force
* Indicates whether to push to a device even if nothing has changed.
* @param bool $debug
* Indicates whether the device should be set to debug mode.
* @param bool $reload_assets
* Indicates whether the device should reload assets.
* @param bool $reload_content
* Indicates whether the device should reload content.
* @param string|null $entityType
* The entity type ID, if only a single slide should be in the schedule,
* NULL otherwise.
* @param string|null $entityId
* The entity ID, if only a single slide should be in the schedule, NULL
* otherwise.
*/
public function pushSchedules(?int $deviceId = NULL, bool $force = FALSE, bool $debug = FALSE, bool $reload_assets = FALSE, bool $reload_content = FALSE, ?string $entityType = NULL, ?string $entityId = NULL): void {
try {
/** @var \Drupal\digital_signage_framework\ScheduleGeneratorInterface $plugin */
$plugin = $this->generatorPluginManager->createInstance('default');
foreach ($this->getDevices($deviceId, $force) as $device) {
$schedule = $this->createSchedule($device, $plugin, TRUE, $force, $entityType, $entityId);
if ($schedule->needsPush()) {
$this->platformPluginManager->pushSchedule($device, $debug, $reload_assets, $reload_content);
$schedule->needsPush(FALSE);
}
}
}
catch (PluginException | EntityStorageException | \JsonException) {
// @todo Log this exception.
}
}
/**
* Push a configuration to a device.
*
* @param int|null $deviceId
* The device ID or NULL to push to all devices.
* @param bool $debug
* Indicates whether the device should be set to debug mode.
* @param bool $reload_schedule
* Indicates whether the device should reload the schedule.
* @param bool $reload_assets
* Indicates whether the device should reload assets.
* @param bool $reload_content
* Indicates whether the device should reload content.
*/
public function pushConfiguration(?int $deviceId, bool $debug, bool $reload_schedule, bool $reload_assets, bool $reload_content): void {
foreach ($this->getDevices($deviceId, TRUE) as $device) {
try {
$this->platformPluginManager->pushConfiguration($device, $debug, $reload_schedule, $reload_assets, $reload_content);
}
catch (PluginException) {
// @todo Log this exception.
}
}
}
/**
* Gets the latest device schedule.
*
* @param \Drupal\digital_signage_framework\DeviceInterface $device
* The device.
*
* @return \Drupal\digital_signage_framework\ScheduleInterface|null
* The schedule.
*/
public function getSchedule(DeviceInterface $device): ?ScheduleInterface {
try {
/** @var \Drupal\digital_signage_framework\ScheduleGeneratorInterface $plugin */
$plugin = $this->generatorPluginManager->createInstance('default');
return $this->createSchedule($device, $plugin, FALSE, TRUE);
}
catch (PluginException | EntityStorageException | \JsonException) {
// @todo Log this exception.
}
return NULL;
}
}
