Re-add opdavies_talks module
This commit is contained in:
parent
c4f976fcf7
commit
01398280c7
|
@ -21,6 +21,7 @@
|
||||||
"drupal/core-composer-scaffold": "^10.2",
|
"drupal/core-composer-scaffold": "^10.2",
|
||||||
"drupal/core-project-message": "^10.2",
|
"drupal/core-project-message": "^10.2",
|
||||||
"drupal/core-recommended": "^10.2",
|
"drupal/core-recommended": "^10.2",
|
||||||
|
"drupal/hook_event_dispatcher": "^4.0",
|
||||||
"drupal/inline_entity_form": "^3.0@RC",
|
"drupal/inline_entity_form": "^3.0@RC",
|
||||||
"drupal/layout_builder_modal": "^1.2",
|
"drupal/layout_builder_modal": "^1.2",
|
||||||
"drupal/markdown": "^3.0",
|
"drupal/markdown": "^3.0",
|
||||||
|
@ -32,7 +33,9 @@
|
||||||
"drupal/speakerdeck_field": "^2.0",
|
"drupal/speakerdeck_field": "^2.0",
|
||||||
"drupal/video_embed_field": "^2.5",
|
"drupal/video_embed_field": "^2.5",
|
||||||
"drush/drush": "^12.5",
|
"drush/drush": "^12.5",
|
||||||
"league/commonmark": "^1.0"
|
"illuminate/collections": "^11.3",
|
||||||
|
"league/commonmark": "^1.0",
|
||||||
|
"nesbot/carbon": "^3.2"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"drupal/drupal": "*"
|
"drupal/drupal": "*"
|
||||||
|
@ -112,5 +115,8 @@
|
||||||
"SubformState incorrect interface error": "./tools/patches/drupal/markdown/3409277-29.diff"
|
"SubformState incorrect interface error": "./tools/patches/drupal/markdown/3409277-29.diff"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"drupal/core-dev": "^10.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
5933
composer.lock
generated
5933
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -14,6 +14,7 @@ module:
|
||||||
config: 0
|
config: 0
|
||||||
contact: 0
|
contact: 0
|
||||||
contextual: 0
|
contextual: 0
|
||||||
|
core_event_dispatcher: 0
|
||||||
datetime: 0
|
datetime: 0
|
||||||
dblog: 0
|
dblog: 0
|
||||||
dynamic_page_cache: 0
|
dynamic_page_cache: 0
|
||||||
|
@ -25,6 +26,7 @@ module:
|
||||||
filter: 0
|
filter: 0
|
||||||
help: 0
|
help: 0
|
||||||
history: 0
|
history: 0
|
||||||
|
hook_event_dispatcher: 0
|
||||||
image: 0
|
image: 0
|
||||||
inline_entity_form: 0
|
inline_entity_form: 0
|
||||||
layout_builder: 0
|
layout_builder: 0
|
||||||
|
@ -36,6 +38,7 @@ module:
|
||||||
menu_ui: 0
|
menu_ui: 0
|
||||||
mysql: 0
|
mysql: 0
|
||||||
node: 0
|
node: 0
|
||||||
|
opdavies_talks: 0
|
||||||
options: 0
|
options: 0
|
||||||
page_cache: 0
|
page_cache: 0
|
||||||
path: 0
|
path: 0
|
||||||
|
|
5
web/modules/custom/talks/opdavies_talks.info.yml
Normal file
5
web/modules/custom/talks/opdavies_talks.info.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
name: opdavies Talks
|
||||||
|
description: Custom code for talks pages.
|
||||||
|
type: module
|
||||||
|
core_version_requirement: ^10
|
||||||
|
package: Custom
|
22
web/modules/custom/talks/opdavies_talks.install
Normal file
22
web/modules/custom/talks/opdavies_talks.install
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Install, update and uninstall functions for opdavies_talks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Drupal\opdavies_talks\Repository\TalkRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set talk type for all existing talks.
|
||||||
|
*/
|
||||||
|
function opdavies_talks_update_8001(): void {
|
||||||
|
$talkRepository = \Drupal::service(TalkRepository::class);
|
||||||
|
|
||||||
|
foreach ($talkRepository->getAll() as $talk) {
|
||||||
|
$talk->set('field_type', 'talk');
|
||||||
|
$talk->save();
|
||||||
|
}
|
||||||
|
}
|
74
web/modules/custom/talks/opdavies_talks.module
Normal file
74
web/modules/custom/talks/opdavies_talks.module
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Custom code for talks pages.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityTypeInterface;
|
||||||
|
use Drupal\Core\Render\BubbleableMetadata;
|
||||||
|
use Drupal\opdavies_talks\Service\TalkCounter;
|
||||||
|
use Drupal\opdavies_talks\Service\TalkDateUpdater;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_cron().
|
||||||
|
*/
|
||||||
|
function opdavies_talks_cron(): void {
|
||||||
|
$dateUpdater = Drupal::service(TalkDateUpdater::class);
|
||||||
|
$dateUpdater->__invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_views_data_alter().
|
||||||
|
*/
|
||||||
|
function opdavies_talks_views_data_alter(array &$data): void {
|
||||||
|
$data['node__field_event_date']['event_sort'] = [
|
||||||
|
'title' => t('Custom event sort'),
|
||||||
|
'group' => t('Content'),
|
||||||
|
'help' => t('Sort events by past/future, then distance from now.'),
|
||||||
|
'sort' => [
|
||||||
|
'field' => 'field_event_date_value',
|
||||||
|
'id' => 'event_sort',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_token_info().
|
||||||
|
*/
|
||||||
|
function opdavies_talks_token_info(): array {
|
||||||
|
$info = [];
|
||||||
|
|
||||||
|
$info['types']['opdavies_talks'] = [
|
||||||
|
'name' => t('Oliver Davies Talks'),
|
||||||
|
'description' => t('Custom tokens for the Oliver Davies Talks module.'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$info['tokens']['opdavies_talks']['talk_count'] = 'ddd';
|
||||||
|
|
||||||
|
return $info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_tokens().
|
||||||
|
*/
|
||||||
|
function opdavies_talks_tokens(string $type, array $tokens, array $data, array $options, BubbleableMetadata $bubbleableMetadata): array {
|
||||||
|
$replacements = [];
|
||||||
|
|
||||||
|
if ($type == 'opdavies_talks') {
|
||||||
|
/** @var TalkCounter $talkCounter */
|
||||||
|
$talkCounter = Drupal::service(TalkCounter::class);
|
||||||
|
|
||||||
|
foreach ($tokens as $name => $original) {
|
||||||
|
switch ($name) {
|
||||||
|
case 'talk_count':
|
||||||
|
$replacements[$original] = $talkCounter->getCount();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $replacements;
|
||||||
|
}
|
13
web/modules/custom/talks/opdavies_talks.services.yml
Normal file
13
web/modules/custom/talks/opdavies_talks.services.yml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
services:
|
||||||
|
Drupal\opdavies_talks\Repository\TalkRepository:
|
||||||
|
autowire: true
|
||||||
|
|
||||||
|
Drupal\opdavies_talks\Service\TalkCounter:
|
||||||
|
autowire: true
|
||||||
|
|
||||||
|
Drupal\opdavies_talks\Service\TalkDateUpdater:
|
||||||
|
autowire: true
|
||||||
|
|
||||||
|
Drupal\opdavies_talks\EventSubscriber\UpdateTalkNodeBeforeSave:
|
||||||
|
tags:
|
||||||
|
- { name: event_subscriber }
|
24
web/modules/custom/talks/src/Collection/TalkCollection.php
Normal file
24
web/modules/custom/talks/src/Collection/TalkCollection.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\opdavies_talks\Collection;
|
||||||
|
|
||||||
|
use Drupal\node\NodeInterface;
|
||||||
|
use Drupal\opdavies_talks\Entity\Node\Talk;
|
||||||
|
use Drupal\paragraphs\ParagraphInterface;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
final class TalkCollection extends Collection {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the events for the talks in the Collection.
|
||||||
|
*
|
||||||
|
* @return Collection|ParagraphInterface[]
|
||||||
|
*/
|
||||||
|
public function getEvents(): Collection {
|
||||||
|
return $this
|
||||||
|
->flatMap(fn(Talk $talk): Collection => $talk->getEvents());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
98
web/modules/custom/talks/src/Entity/Node/Talk.php
Normal file
98
web/modules/custom/talks/src/Entity/Node/Talk.php
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\opdavies_talks\Entity\Node;
|
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityInterface;
|
||||||
|
use Drupal\Core\Field\FieldItemListInterface;
|
||||||
|
use Drupal\node\Entity\Node;
|
||||||
|
use Drupal\node\NodeInterface;
|
||||||
|
use Drupal\paragraphs\ParagraphInterface;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
final class Talk {
|
||||||
|
|
||||||
|
public const FIELD_EVENTS = 'field_events';
|
||||||
|
public const FIELD_EVENT_DATE = 'field_event_date';
|
||||||
|
|
||||||
|
private NodeInterface $node;
|
||||||
|
|
||||||
|
public function __construct(EntityInterface $node) {
|
||||||
|
$this->node = $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addEvent(ParagraphInterface $event): void {
|
||||||
|
$this->set(
|
||||||
|
self::FIELD_EVENTS,
|
||||||
|
$this->getEvents()->push($event)->toArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the date for the latest event.
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function findLatestEventDate(): ?string {
|
||||||
|
return $this->getEvents()
|
||||||
|
->map(fn(ParagraphInterface $event) => $event->get('field_date')
|
||||||
|
->getString())
|
||||||
|
->max();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $name): FieldItemListInterface {
|
||||||
|
return $this->node->get($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedTime(): int {
|
||||||
|
return (int) $this->node->getCreatedTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEvents(): Collection {
|
||||||
|
return Collection::make($this->get(self::FIELD_EVENTS)
|
||||||
|
->referencedEntities());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNextDate(): ?int {
|
||||||
|
if ($this->get(self::FIELD_EVENT_DATE)->isEmpty()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) $this->get(self::FIELD_EVENT_DATE)->getString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function id(): int {
|
||||||
|
return (int) $this->node->id();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function label(): string {
|
||||||
|
return $this->node->label();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(): void {
|
||||||
|
$this->node->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(string $name, $value): void {
|
||||||
|
$this->node->set($name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedTime(int $timestamp): void {
|
||||||
|
$this->node->setCreatedTime($timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEvents(array $events): void {
|
||||||
|
$this->set(self::FIELD_EVENTS, $events);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNextDate(int $date): void {
|
||||||
|
$this->set(self::FIELD_EVENT_DATE, $date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createFromNode(EntityInterface $node): self {
|
||||||
|
// TODO: ensure that this is a node and a `talk` type.
|
||||||
|
return new self($node);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\opdavies_talks\EventSubscriber;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Drupal\core_event_dispatcher\EntityHookEvents;
|
||||||
|
use Drupal\core_event_dispatcher\Event\Entity\AbstractEntityEvent;
|
||||||
|
use Drupal\opdavies_talks\Entity\Node\Talk;
|
||||||
|
use Drupal\paragraphs\ParagraphInterface;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a talk node before it's saved.
|
||||||
|
*/
|
||||||
|
final class UpdateTalkNodeBeforeSave implements EventSubscriberInterface {
|
||||||
|
|
||||||
|
public static function getSubscribedEvents() {
|
||||||
|
return [
|
||||||
|
EntityHookEvents::ENTITY_PRE_SAVE => 'onEntityPreSave',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onEntityPreSave(AbstractEntityEvent $event): void {
|
||||||
|
if ($event->getEntity()->getEntityTypeId() != 'node') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($event->getEntity()->bundle() != 'talk') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$node = $event->getEntity();
|
||||||
|
$talk = Talk::createFromNode($node);
|
||||||
|
|
||||||
|
$this->reorderEvents($talk);
|
||||||
|
$this->updateCreatedDate($talk);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function reorderEvents(Talk $talk): void {
|
||||||
|
$events = $talk->getEvents();
|
||||||
|
$eventsByDate = $this->sortEventsByDate($events);
|
||||||
|
|
||||||
|
// If the original event IDs don't match the sorted event IDs, update the event field to use the sorted ones.
|
||||||
|
if ($events->map->id() != $eventsByDate->map->id()) {
|
||||||
|
$talk->setEvents($eventsByDate->toArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sortEventsByDate(Collection $events): Collection {
|
||||||
|
return $events
|
||||||
|
->sortBy(fn(ParagraphInterface $event) => $event->get('field_date')
|
||||||
|
->getString())
|
||||||
|
->values();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateCreatedDate(Talk $talk): void {
|
||||||
|
if (!$eventDate = $talk->findLatestEventDate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$talkDate = Carbon::parse($eventDate)->getTimestamp();
|
||||||
|
|
||||||
|
if ($talkDate == $talk->getCreatedTime()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$talk->setCreatedTime($talkDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\opdavies_talks;
|
||||||
|
|
||||||
|
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||||
|
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\Definition;
|
||||||
|
use Symfony\Component\Finder\Finder;
|
||||||
|
|
||||||
|
final class OpdaviesTalksServiceProvider implements ServiceProviderInterface {
|
||||||
|
|
||||||
|
public function register(ContainerBuilder $container): void {
|
||||||
|
foreach (['EventSubscriber', 'Repository', 'Service'] as $directory) {
|
||||||
|
$files = Finder::create()
|
||||||
|
->in(__DIR__ . '/' . $directory)
|
||||||
|
->files()
|
||||||
|
->name('*.php');
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$class = 'Drupal\opdavies_talks\\' . $directory . '\\' .
|
||||||
|
str_replace('/', '\\', substr($file->getRelativePathname(), 0, -4));
|
||||||
|
|
||||||
|
if ($container->hasDefinition($class)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$definition = new Definition($class);
|
||||||
|
$definition->setAutowired(TRUE);
|
||||||
|
if ($directory == 'EventSubscriber') {
|
||||||
|
$definition->addTag('event_subscriber');
|
||||||
|
}
|
||||||
|
$container->setDefinition($class, $definition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
69
web/modules/custom/talks/src/Plugin/views/sort/Event.php
Normal file
69
web/modules/custom/talks/src/Plugin/views/sort/Event.php
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\opdavies_talks\Plugin\views\sort;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Drupal\Component\Datetime\TimeInterface;
|
||||||
|
use Drupal\views\Annotation\ViewsSort;
|
||||||
|
use Drupal\views\Plugin\views\sort\Date;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ViewsSort("event_sort")
|
||||||
|
*/
|
||||||
|
final class Event extends Date {
|
||||||
|
|
||||||
|
private TimeInterface $time;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
array $configuration,
|
||||||
|
string $pluginId,
|
||||||
|
array $pluginDefinition,
|
||||||
|
TimeInterface $time
|
||||||
|
) {
|
||||||
|
parent::__construct($configuration, $pluginId, $pluginDefinition);
|
||||||
|
|
||||||
|
$this->time = $time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create(
|
||||||
|
ContainerInterface $container,
|
||||||
|
array $configuration,
|
||||||
|
$pluginId,
|
||||||
|
$pluginDefinition
|
||||||
|
) {
|
||||||
|
return new static(
|
||||||
|
$configuration,
|
||||||
|
$pluginId,
|
||||||
|
$pluginDefinition,
|
||||||
|
$container->get('datetime.time')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function query(): void {
|
||||||
|
$this->ensureMyTable();
|
||||||
|
|
||||||
|
$currentDate = Carbon::parse('today')->getTimestamp();
|
||||||
|
|
||||||
|
$dateAlias = "$this->tableAlias.$this->realField";
|
||||||
|
|
||||||
|
// Is this event in the past?
|
||||||
|
$this->query->addOrderBy(
|
||||||
|
NULL,
|
||||||
|
sprintf("%d > %s", $currentDate, $dateAlias),
|
||||||
|
$this->options['order'],
|
||||||
|
"in_past"
|
||||||
|
);
|
||||||
|
|
||||||
|
// How far in the past/future is this event?
|
||||||
|
$this->query->addOrderBy(
|
||||||
|
NULL,
|
||||||
|
sprintf('ABS(%s - %d)', $dateAlias, $currentDate),
|
||||||
|
$this->options['order'],
|
||||||
|
"distance_from_now"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
46
web/modules/custom/talks/src/Repository/TalkRepository.php
Normal file
46
web/modules/custom/talks/src/Repository/TalkRepository.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\opdavies_talks\Repository;
|
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityStorageInterface;
|
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||||
|
use Drupal\node\NodeInterface;
|
||||||
|
use Drupal\opdavies_talks\Collection\TalkCollection;
|
||||||
|
use Drupal\opdavies_talks\Entity\Node\Talk;
|
||||||
|
|
||||||
|
final class TalkRepository {
|
||||||
|
|
||||||
|
private EntityStorageInterface $nodeStorage;
|
||||||
|
|
||||||
|
public function __construct(EntityTypeManagerInterface $entityTypeManager) {
|
||||||
|
$this->nodeStorage = $entityTypeManager->getStorage('node');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findAll(): TalkCollection {
|
||||||
|
$talks = $this->nodeStorage->loadByProperties($this->defaultProperties());
|
||||||
|
|
||||||
|
return (new TalkCollection($talks))
|
||||||
|
->map(fn(NodeInterface $node): Talk => Talk::createFromNode($node));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findAllPublished(): TalkCollection {
|
||||||
|
$talks = $this->nodeStorage->loadByProperties(array_merge(
|
||||||
|
$this->defaultProperties(),
|
||||||
|
[
|
||||||
|
'status' => NodeInterface::PUBLISHED,
|
||||||
|
],
|
||||||
|
));
|
||||||
|
|
||||||
|
return (new TalkCollection($talks))
|
||||||
|
->map(fn(NodeInterface $node): Talk => Talk::createFromNode($node));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function defaultProperties(): array {
|
||||||
|
return [
|
||||||
|
'type' => 'talk',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
30
web/modules/custom/talks/src/Service/TalkCounter.php
Normal file
30
web/modules/custom/talks/src/Service/TalkCounter.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\opdavies_talks\Service;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Drupal\opdavies_talks\Repository\TalkRepository;
|
||||||
|
use Drupal\paragraphs\ParagraphInterface;
|
||||||
|
|
||||||
|
final class TalkCounter {
|
||||||
|
|
||||||
|
private TalkRepository $talkRepository;
|
||||||
|
|
||||||
|
public function __construct(TalkRepository $talkRepository) {
|
||||||
|
$this->talkRepository = $talkRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCount(): int {
|
||||||
|
$today = Carbon::today()->format('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
return $this->talkRepository
|
||||||
|
->findAllPublished()
|
||||||
|
->getEvents()
|
||||||
|
->filter(fn(ParagraphInterface $event) => $event->get('field_date')
|
||||||
|
->getString() <= $today)
|
||||||
|
->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
71
web/modules/custom/talks/src/Service/TalkDateUpdater.php
Normal file
71
web/modules/custom/talks/src/Service/TalkDateUpdater.php
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Drupal\opdavies_talks\Service;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Drupal\Component\Datetime\TimeInterface;
|
||||||
|
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
|
||||||
|
use Drupal\opdavies_talks\Entity\Node\Talk;
|
||||||
|
use Drupal\opdavies_talks\Repository\TalkRepository;
|
||||||
|
use Drupal\paragraphs\ParagraphInterface;
|
||||||
|
|
||||||
|
final class TalkDateUpdater {
|
||||||
|
|
||||||
|
private TalkRepository $talkRepository;
|
||||||
|
private TimeInterface $time;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
TalkRepository $talkRepository,
|
||||||
|
TimeInterface $time
|
||||||
|
) {
|
||||||
|
$this->talkRepository = $talkRepository;
|
||||||
|
$this->time = $time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(): void {
|
||||||
|
foreach ($this->talkRepository->findAll() as $talk) {
|
||||||
|
$this->updateNextEventDate($talk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateNextEventDate(Talk $talk): void {
|
||||||
|
if (!$nextDate = $this->findNextEventDate($talk)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nextDateTimestamp = Carbon::parse($nextDate)
|
||||||
|
->getTimestamp();
|
||||||
|
|
||||||
|
if ($nextDateTimestamp == $talk->getNextDate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$talk->setNextDate($nextDateTimestamp);
|
||||||
|
$talk->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findNextEventDate(Talk $talk): ?string {
|
||||||
|
$currentTime = Carbon::today()
|
||||||
|
->format(DateTimeItemInterface::DATE_STORAGE_FORMAT);
|
||||||
|
|
||||||
|
$dates = $talk->getEvents()
|
||||||
|
->map(fn(ParagraphInterface $event) => $event->get('field_date')
|
||||||
|
->getString())
|
||||||
|
->sort();
|
||||||
|
|
||||||
|
if ($dates->isEmpty()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a future date is found, return it.
|
||||||
|
if ($futureDate = $dates->first(fn(string $eventDate) => $eventDate > $currentTime)) {
|
||||||
|
return $futureDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no future date is found, return the last past date.
|
||||||
|
return $dates->last();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
uuid: 5bb25694-3431-4c5e-9dc2-c7c46a91eab5
|
||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- field.storage.node.field_event_date
|
||||||
|
- node.type.talk
|
||||||
|
module:
|
||||||
|
- datetime
|
||||||
|
id: node.talk.field_event_date
|
||||||
|
field_name: field_event_date
|
||||||
|
entity_type: node
|
||||||
|
bundle: talk
|
||||||
|
label: 'Next event date'
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
translatable: false
|
||||||
|
default_value: { }
|
||||||
|
default_value_callback: ''
|
||||||
|
settings: { }
|
||||||
|
field_type: datetime
|
|
@ -0,0 +1,30 @@
|
||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- field.storage.node.field_events
|
||||||
|
- node.type.talk
|
||||||
|
- paragraphs.paragraphs_type.event
|
||||||
|
module:
|
||||||
|
- entity_reference_revisions
|
||||||
|
id: node.talk.field_events
|
||||||
|
field_name: field_events
|
||||||
|
entity_type: node
|
||||||
|
bundle: talk
|
||||||
|
label: Events
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
translatable: false
|
||||||
|
default_value: { }
|
||||||
|
default_value_callback: ''
|
||||||
|
settings:
|
||||||
|
handler: 'default:paragraph'
|
||||||
|
handler_settings:
|
||||||
|
negate: 0
|
||||||
|
target_bundles:
|
||||||
|
event: event
|
||||||
|
target_bundles_drag_drop:
|
||||||
|
event:
|
||||||
|
enabled: true
|
||||||
|
weight: 2
|
||||||
|
field_type: entity_reference_revisions
|
|
@ -0,0 +1,20 @@
|
||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- field.storage.paragraph.field_date
|
||||||
|
- paragraphs.paragraphs_type.event
|
||||||
|
module:
|
||||||
|
- datetime
|
||||||
|
id: paragraph.event.field_date
|
||||||
|
field_name: field_date
|
||||||
|
entity_type: paragraph
|
||||||
|
bundle: event
|
||||||
|
label: Date
|
||||||
|
description: ''
|
||||||
|
required: true
|
||||||
|
translatable: false
|
||||||
|
default_value: { }
|
||||||
|
default_value_callback: ''
|
||||||
|
settings: { }
|
||||||
|
field_type: datetime
|
|
@ -0,0 +1,18 @@
|
||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- field.storage.paragraph.field_name
|
||||||
|
- paragraphs.paragraphs_type.event
|
||||||
|
id: paragraph.event.field_name
|
||||||
|
field_name: field_name
|
||||||
|
entity_type: paragraph
|
||||||
|
bundle: event
|
||||||
|
label: 'Event name'
|
||||||
|
description: ''
|
||||||
|
required: true
|
||||||
|
translatable: false
|
||||||
|
default_value: { }
|
||||||
|
default_value_callback: ''
|
||||||
|
settings: { }
|
||||||
|
field_type: string
|
|
@ -0,0 +1,20 @@
|
||||||
|
uuid: e718e9e3-0765-4cf4-b7e8-cccf41ee3d1a
|
||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
module:
|
||||||
|
- datetime
|
||||||
|
- node
|
||||||
|
id: node.field_event_date
|
||||||
|
field_name: field_event_date
|
||||||
|
entity_type: node
|
||||||
|
type: datetime
|
||||||
|
settings:
|
||||||
|
datetime_type: date
|
||||||
|
module: datetime
|
||||||
|
locked: false
|
||||||
|
cardinality: 1
|
||||||
|
translatable: true
|
||||||
|
indexes: { }
|
||||||
|
persist_with_no_fields: false
|
||||||
|
custom_storage: false
|
|
@ -0,0 +1,20 @@
|
||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
module:
|
||||||
|
- entity_reference_revisions
|
||||||
|
- node
|
||||||
|
- paragraphs
|
||||||
|
id: node.field_events
|
||||||
|
field_name: field_events
|
||||||
|
entity_type: node
|
||||||
|
type: entity_reference_revisions
|
||||||
|
settings:
|
||||||
|
target_type: paragraph
|
||||||
|
module: entity_reference_revisions
|
||||||
|
locked: false
|
||||||
|
cardinality: -1
|
||||||
|
translatable: true
|
||||||
|
indexes: { }
|
||||||
|
persist_with_no_fields: false
|
||||||
|
custom_storage: false
|
|
@ -0,0 +1,19 @@
|
||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
module:
|
||||||
|
- datetime
|
||||||
|
- paragraphs
|
||||||
|
id: paragraph.field_date
|
||||||
|
field_name: field_date
|
||||||
|
entity_type: paragraph
|
||||||
|
type: datetime
|
||||||
|
settings:
|
||||||
|
datetime_type: date
|
||||||
|
module: datetime
|
||||||
|
locked: false
|
||||||
|
cardinality: 1
|
||||||
|
translatable: true
|
||||||
|
indexes: { }
|
||||||
|
persist_with_no_fields: false
|
||||||
|
custom_storage: false
|
|
@ -0,0 +1,20 @@
|
||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
module:
|
||||||
|
- paragraphs
|
||||||
|
id: paragraph.field_name
|
||||||
|
field_name: field_name
|
||||||
|
entity_type: paragraph
|
||||||
|
type: string
|
||||||
|
settings:
|
||||||
|
max_length: 255
|
||||||
|
is_ascii: false
|
||||||
|
case_sensitive: false
|
||||||
|
module: core
|
||||||
|
locked: false
|
||||||
|
cardinality: 1
|
||||||
|
translatable: true
|
||||||
|
indexes: { }
|
||||||
|
persist_with_no_fields: false
|
||||||
|
custom_storage: false
|
|
@ -0,0 +1,10 @@
|
||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies: { }
|
||||||
|
name: Talk
|
||||||
|
type: talk
|
||||||
|
description: ''
|
||||||
|
help: ''
|
||||||
|
new_revision: true
|
||||||
|
preview_mode: 1
|
||||||
|
display_submitted: false
|
|
@ -0,0 +1,9 @@
|
||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies: { }
|
||||||
|
id: event
|
||||||
|
label: Event
|
||||||
|
icon_uuid: null
|
||||||
|
icon_default: null
|
||||||
|
description: ''
|
||||||
|
behavior_plugins: { }
|
|
@ -0,0 +1,196 @@
|
||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- core.entity_view_mode.node.teaser
|
||||||
|
- node.type.talk
|
||||||
|
- system.menu.main
|
||||||
|
module:
|
||||||
|
- node
|
||||||
|
- opdavies_talks
|
||||||
|
- user
|
||||||
|
id: talks
|
||||||
|
label: Talks
|
||||||
|
module: views
|
||||||
|
description: ''
|
||||||
|
tag: ''
|
||||||
|
base_table: node_field_data
|
||||||
|
base_field: nid
|
||||||
|
display:
|
||||||
|
default:
|
||||||
|
display_plugin: default
|
||||||
|
id: default
|
||||||
|
display_title: Master
|
||||||
|
position: 0
|
||||||
|
display_options:
|
||||||
|
access:
|
||||||
|
type: perm
|
||||||
|
options:
|
||||||
|
perm: 'access content'
|
||||||
|
cache:
|
||||||
|
type: tag
|
||||||
|
options: { }
|
||||||
|
query:
|
||||||
|
type: views_query
|
||||||
|
options:
|
||||||
|
disable_sql_rewrite: false
|
||||||
|
distinct: false
|
||||||
|
replica: false
|
||||||
|
query_comment: ''
|
||||||
|
query_tags: { }
|
||||||
|
exposed_form:
|
||||||
|
type: basic
|
||||||
|
options:
|
||||||
|
submit_button: Apply
|
||||||
|
reset_button: false
|
||||||
|
reset_button_label: Reset
|
||||||
|
exposed_sorts_label: 'Sort by'
|
||||||
|
expose_sort_order: true
|
||||||
|
sort_asc_label: Asc
|
||||||
|
sort_desc_label: Desc
|
||||||
|
pager:
|
||||||
|
type: none
|
||||||
|
options:
|
||||||
|
items_per_page: 0
|
||||||
|
offset: 0
|
||||||
|
style:
|
||||||
|
type: html_list
|
||||||
|
options:
|
||||||
|
row_class: ''
|
||||||
|
default_row_class: true
|
||||||
|
uses_fields: false
|
||||||
|
type: ul
|
||||||
|
wrapper_class: ''
|
||||||
|
class: space-y-8
|
||||||
|
row:
|
||||||
|
type: 'entity:node'
|
||||||
|
options:
|
||||||
|
view_mode: teaser
|
||||||
|
fields:
|
||||||
|
title:
|
||||||
|
id: title
|
||||||
|
table: node_field_data
|
||||||
|
field: title
|
||||||
|
entity_type: node
|
||||||
|
entity_field: title
|
||||||
|
label: ''
|
||||||
|
alter:
|
||||||
|
alter_text: false
|
||||||
|
make_link: false
|
||||||
|
absolute: false
|
||||||
|
trim: false
|
||||||
|
word_boundary: false
|
||||||
|
ellipsis: false
|
||||||
|
strip_tags: false
|
||||||
|
html: false
|
||||||
|
hide_empty: false
|
||||||
|
empty_zero: false
|
||||||
|
settings:
|
||||||
|
link_to_entity: true
|
||||||
|
plugin_id: field
|
||||||
|
relationship: none
|
||||||
|
group_type: group
|
||||||
|
admin_label: ''
|
||||||
|
exclude: false
|
||||||
|
element_type: ''
|
||||||
|
element_class: ''
|
||||||
|
element_label_type: ''
|
||||||
|
element_label_class: ''
|
||||||
|
element_label_colon: true
|
||||||
|
element_wrapper_type: ''
|
||||||
|
element_wrapper_class: ''
|
||||||
|
element_default_classes: true
|
||||||
|
empty: ''
|
||||||
|
hide_alter_empty: true
|
||||||
|
click_sort_column: value
|
||||||
|
type: string
|
||||||
|
group_column: value
|
||||||
|
group_columns: { }
|
||||||
|
group_rows: true
|
||||||
|
delta_limit: 0
|
||||||
|
delta_offset: 0
|
||||||
|
delta_reversed: false
|
||||||
|
delta_first_last: false
|
||||||
|
multi_type: separator
|
||||||
|
separator: ', '
|
||||||
|
field_api_classes: false
|
||||||
|
filters:
|
||||||
|
status:
|
||||||
|
value: '1'
|
||||||
|
table: node_field_data
|
||||||
|
field: status
|
||||||
|
plugin_id: boolean
|
||||||
|
entity_type: node
|
||||||
|
entity_field: status
|
||||||
|
id: status
|
||||||
|
expose:
|
||||||
|
operator: ''
|
||||||
|
operator_limit_selection: false
|
||||||
|
operator_list: { }
|
||||||
|
group: 1
|
||||||
|
type:
|
||||||
|
id: type
|
||||||
|
table: node_field_data
|
||||||
|
field: type
|
||||||
|
value:
|
||||||
|
talk: talk
|
||||||
|
entity_type: node
|
||||||
|
entity_field: type
|
||||||
|
plugin_id: bundle
|
||||||
|
expose:
|
||||||
|
operator_limit_selection: false
|
||||||
|
operator_list: { }
|
||||||
|
sorts:
|
||||||
|
event_sort:
|
||||||
|
id: event_sort
|
||||||
|
table: node__field_event_date
|
||||||
|
field: event_sort
|
||||||
|
relationship: none
|
||||||
|
group_type: group
|
||||||
|
admin_label: ''
|
||||||
|
order: ASC
|
||||||
|
exposed: false
|
||||||
|
expose:
|
||||||
|
label: ''
|
||||||
|
granularity: second
|
||||||
|
plugin_id: event_sort
|
||||||
|
title: Talks
|
||||||
|
header: { }
|
||||||
|
footer: { }
|
||||||
|
empty: { }
|
||||||
|
relationships: { }
|
||||||
|
arguments: { }
|
||||||
|
display_extenders: { }
|
||||||
|
cache_metadata:
|
||||||
|
max-age: -1
|
||||||
|
contexts:
|
||||||
|
- 'languages:language_content'
|
||||||
|
- 'languages:language_interface'
|
||||||
|
- 'user.node_grants:view'
|
||||||
|
- user.permissions
|
||||||
|
tags: { }
|
||||||
|
page_1:
|
||||||
|
display_plugin: page
|
||||||
|
id: page_1
|
||||||
|
display_title: Page
|
||||||
|
position: 1
|
||||||
|
display_options:
|
||||||
|
display_extenders: { }
|
||||||
|
path: talks
|
||||||
|
menu:
|
||||||
|
type: normal
|
||||||
|
title: Talks
|
||||||
|
description: ''
|
||||||
|
expanded: false
|
||||||
|
parent: ''
|
||||||
|
weight: -48
|
||||||
|
context: '0'
|
||||||
|
menu_name: main
|
||||||
|
cache_metadata:
|
||||||
|
max-age: -1
|
||||||
|
contexts:
|
||||||
|
- 'languages:language_content'
|
||||||
|
- 'languages:language_interface'
|
||||||
|
- 'user.node_grants:view'
|
||||||
|
- user.permissions
|
||||||
|
tags: { }
|
|
@ -0,0 +1,4 @@
|
||||||
|
name: Custom Test
|
||||||
|
type: module
|
||||||
|
core_version_requirement: ^8 || ^9
|
||||||
|
hidden: true
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// phpcs:disable Drupal.Commenting.DocComment, Drupal.NamingConventions.ValidFunctionName
|
||||||
|
|
||||||
|
namespace Drupal\Tests\opdavies_talks\Kernel;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Drupal\node\NodeInterface;
|
||||||
|
use Drupal\opdavies_talks\Service\TalkCounter;
|
||||||
|
use PHPUnit\Framework\Assert;
|
||||||
|
|
||||||
|
final class CountPreviousTalksTest extends TalksTestBase {
|
||||||
|
|
||||||
|
private TalkCounter $talkCounter;
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function previous_talks_are_counted(): void {
|
||||||
|
$this->createTalk([
|
||||||
|
'field_events' => [
|
||||||
|
$this->createEvent(),
|
||||||
|
$this->createEvent(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->createTalk([
|
||||||
|
'field_events' => [
|
||||||
|
$this->createEvent(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
Assert::assertSame(3, $this->talkCounter->getCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function future_talks_are_not_counted(): void {
|
||||||
|
$this->createTalk([
|
||||||
|
'field_events' => [
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::now()->subDay(),
|
||||||
|
]),
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::now()->addDay(),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
Assert::assertSame(1, $this->talkCounter->getCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function unpublished_talks_are_not_counted(): void {
|
||||||
|
$this->createTalk([
|
||||||
|
'field_events' => [$this->createEvent()],
|
||||||
|
'status' => NodeInterface::NOT_PUBLISHED,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Assert::assertSame(0, $this->talkCounter->getCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->talkCounter = $this->container->get(TalkCounter::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
115
web/modules/custom/talks/tests/src/Kernel/ReorderEventsTest.php
Normal file
115
web/modules/custom/talks/tests/src/Kernel/ReorderEventsTest.php
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// phpcs:disable Drupal.Commenting.DocComment, Drupal.NamingConventions.ValidFunctionName
|
||||||
|
|
||||||
|
namespace Drupal\Tests\opdavies_talks\Kernel;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Drupal\paragraphs\ParagraphInterface;
|
||||||
|
|
||||||
|
final class ReorderEventsTest extends TalksTestBase {
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function the_events_are_ordered_by_date_when_a_talk_is_created(): void {
|
||||||
|
$events = [
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::today()->addWeeks(2),
|
||||||
|
'field_name' => 'Drupal Bristol',
|
||||||
|
]),
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::yesterday(),
|
||||||
|
'field_name' => 'DrupalCamp London',
|
||||||
|
]),
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::tomorrow(),
|
||||||
|
'field_name' => 'PHP UK conference',
|
||||||
|
]),
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::today()->addMonths(3),
|
||||||
|
'field_name' => 'CMS Philly',
|
||||||
|
]),
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::today()->subYear(),
|
||||||
|
'field_name' => 'PHP South Wales',
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
$talk = $this->createTalk([
|
||||||
|
'field_events' => $events,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'PHP South Wales',
|
||||||
|
'DrupalCamp London',
|
||||||
|
'PHP UK conference',
|
||||||
|
'Drupal Bristol',
|
||||||
|
'CMS Philly',
|
||||||
|
],
|
||||||
|
$talk->getEvents()
|
||||||
|
->map(fn(ParagraphInterface $event) => $event->get('field_name')
|
||||||
|
->getString())
|
||||||
|
->toArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function the_events_are_ordered_by_date_when_a_talk_is_updated(): void {
|
||||||
|
$events = [
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::today()->addWeeks(2),
|
||||||
|
'field_name' => 'Drupal Bristol',
|
||||||
|
]),
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::yesterday(),
|
||||||
|
'field_name' => 'DrupalCamp London',
|
||||||
|
]),
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::today()->addMonths(3),
|
||||||
|
'field_name' => 'CMS Philly',
|
||||||
|
]),
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::today()->subYear(),
|
||||||
|
'field_name' => 'PHP South Wales',
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
$talk = $this->createTalk([
|
||||||
|
'field_events' => $events,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'PHP South Wales',
|
||||||
|
'DrupalCamp London',
|
||||||
|
'Drupal Bristol',
|
||||||
|
'CMS Philly',
|
||||||
|
],
|
||||||
|
$talk->getEvents()
|
||||||
|
->map(fn(ParagraphInterface $event) => $event->get('field_name')
|
||||||
|
->getString())
|
||||||
|
->toArray()
|
||||||
|
);
|
||||||
|
|
||||||
|
$talk->addEvent($this->createEvent([
|
||||||
|
'field_date' => Carbon::tomorrow(),
|
||||||
|
'field_name' => 'PHP UK conference',
|
||||||
|
]));
|
||||||
|
$talk->save();
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'PHP South Wales',
|
||||||
|
'DrupalCamp London',
|
||||||
|
'PHP UK conference',
|
||||||
|
'Drupal Bristol',
|
||||||
|
'CMS Philly',
|
||||||
|
],
|
||||||
|
$talk->getEvents()
|
||||||
|
->map(fn(ParagraphInterface $event) => $event->get('field_name')
|
||||||
|
->getString())
|
||||||
|
->toArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// phpcs:disable Drupal.Commenting.DocComment, Drupal.NamingConventions.ValidFunctionName
|
||||||
|
|
||||||
|
namespace Drupal\Tests\opdavies_talks\Kernel\Repository;
|
||||||
|
|
||||||
|
use Drupal\node\NodeInterface;
|
||||||
|
use Drupal\opdavies_talks\Entity\Node\Talk;
|
||||||
|
use Drupal\opdavies_talks\Repository\TalkRepository;
|
||||||
|
use Drupal\Tests\node\Traits\NodeCreationTrait;
|
||||||
|
use Drupal\Tests\opdavies_talks\Kernel\TalksTestBase;
|
||||||
|
|
||||||
|
final class TalkRepositoryTest extends TalksTestBase {
|
||||||
|
|
||||||
|
use NodeCreationTrait;
|
||||||
|
|
||||||
|
private TalkRepository $talkRepository;
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function get_all_talks(): void {
|
||||||
|
$this->createTalk(['title' => 'TDD - Test Driven Drupal']);
|
||||||
|
$this->createTalk(['title' => 'Taking Flight with Tailwind CSS']);
|
||||||
|
$this->createTalk(['title' => 'Upgrading to Drupal 9']);
|
||||||
|
|
||||||
|
$talks = $this->talkRepository->findAll();
|
||||||
|
|
||||||
|
$this->assertCount(3, $talks);
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
1 => 'TDD - Test Driven Drupal',
|
||||||
|
2 => 'Taking Flight with Tailwind CSS',
|
||||||
|
3 => 'Upgrading to Drupal 9',
|
||||||
|
],
|
||||||
|
$talks->map(fn(Talk $talk) => $talk->label())->toArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function get_all_published_talks(): void {
|
||||||
|
$this->createTalk([
|
||||||
|
'title' => 'TDD - Test Driven Drupal',
|
||||||
|
'status' => NodeInterface::PUBLISHED,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->createTalk([
|
||||||
|
'title' => 'Taking Flight with Tailwind CSS',
|
||||||
|
'status' => NodeInterface::NOT_PUBLISHED,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$talks = $this->talkRepository->findAllPublished();
|
||||||
|
|
||||||
|
$this->assertCount(1, $talks);
|
||||||
|
$this->assertSame('TDD - Test Driven Drupal', $talks->first()->label());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_only_returns_talk_nodes(): void {
|
||||||
|
$this->createNode(['type' => 'page']);
|
||||||
|
|
||||||
|
$talks = $this->talkRepository->findAll();
|
||||||
|
|
||||||
|
$this->assertEmpty($talks);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->installConfig(['filter']);
|
||||||
|
|
||||||
|
$this->talkRepository = $this->container->get(TalkRepository::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
111
web/modules/custom/talks/tests/src/Kernel/TalkEventDateTest.php
Normal file
111
web/modules/custom/talks/tests/src/Kernel/TalkEventDateTest.php
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// phpcs:disable Drupal.Commenting.DocComment, Drupal.NamingConventions.ValidFunctionName
|
||||||
|
|
||||||
|
namespace Drupal\Tests\opdavies_talks\Kernel;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
|
||||||
|
use Drupal\node\Entity\Node;
|
||||||
|
use Drupal\opdavies_talks\Entity\Node\Talk;
|
||||||
|
use Drupal\opdavies_talks\Service\TalkDateUpdater;
|
||||||
|
|
||||||
|
final class TalkEventDateTest extends TalksTestBase {
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function talk_event_dates_are_set_to_the_next_future_date(): void {
|
||||||
|
$dateFormat = DateTimeItemInterface::DATE_STORAGE_FORMAT;
|
||||||
|
|
||||||
|
$talk = $this->createTalk([
|
||||||
|
'field_event_date' => NULL,
|
||||||
|
'field_events' => [
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::today()
|
||||||
|
->subWeeks(2)
|
||||||
|
->format($dateFormat),
|
||||||
|
]),
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::today()
|
||||||
|
->subDays(2)
|
||||||
|
->format($dateFormat),
|
||||||
|
]),
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::today()
|
||||||
|
->addDays(4)
|
||||||
|
->format($dateFormat),
|
||||||
|
]),
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::today()
|
||||||
|
->addDays(10)
|
||||||
|
->format($dateFormat),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$dateUpdater = $this->container->get(TalkDateUpdater::class);
|
||||||
|
$dateUpdater->__invoke();
|
||||||
|
|
||||||
|
$expected = Carbon::today()->addDays(4)->getTimestamp();
|
||||||
|
|
||||||
|
$node = Node::load($talk->id());
|
||||||
|
$talk = Talk::createFromNode($node);
|
||||||
|
|
||||||
|
$this->assertNextEventDateIs($talk, $expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function talk_event_dates_are_set_to_the_last_past_date(): void {
|
||||||
|
$dateFormat = DateTimeItemInterface::DATE_STORAGE_FORMAT;
|
||||||
|
|
||||||
|
$talk = $this->createTalk([
|
||||||
|
'field_event_date' => NULL,
|
||||||
|
'field_events' => [
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::today()
|
||||||
|
->subDays(4)
|
||||||
|
->format($dateFormat),
|
||||||
|
]),
|
||||||
|
$this->createEvent([
|
||||||
|
'field_date' => Carbon::today()
|
||||||
|
->subDays(2)
|
||||||
|
->format($dateFormat),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$dateUpdater = $this->container->get(TalkDateUpdater::class);
|
||||||
|
$dateUpdater->__invoke();
|
||||||
|
|
||||||
|
$expected = Carbon::today()->subDays(2)->getTimestamp();
|
||||||
|
|
||||||
|
$node = Node::load($talk->id());
|
||||||
|
$talk = Talk::createFromNode($node);
|
||||||
|
|
||||||
|
$this->assertNextEventDateIs($talk, $expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function next_event_date_is_empty_if_there_are_no_events(): void {
|
||||||
|
$talk = $this->createTalk([
|
||||||
|
'field_event_date' => NULL,
|
||||||
|
'field_events' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$dateUpdater = $this->container->get(TalkDateUpdater::class);
|
||||||
|
$dateUpdater->__invoke();
|
||||||
|
|
||||||
|
$node = Node::load($talk->id());
|
||||||
|
$talk = Talk::createFromNode($node);
|
||||||
|
|
||||||
|
$this->assertNoNextEventDate($talk);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assertNextEventDateIs(Talk $talk, $expected): void {
|
||||||
|
$this->assertSame($expected, $talk->getNextDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assertNoNextEventDate(Talk $talk): void {
|
||||||
|
$this->assertNull($talk->getNextDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// phpcs:disable Drupal.Commenting.DocComment, Drupal.NamingConventions.ValidFunctionName
|
||||||
|
|
||||||
|
namespace Drupal\Tests\opdavies_talks\Kernel;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Drupal\views\ResultRow;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
final class TalksPageSortTest extends TalksTestBase {
|
||||||
|
|
||||||
|
public static $modules = [
|
||||||
|
'views',
|
||||||
|
'opdavies_talks',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function upcoming_talks_are_shown_first_followed_by_past_talks_and_ordered_by_distance(): void {
|
||||||
|
$this->createTalk([
|
||||||
|
'field_event_date' => Carbon::today()->addDays(4)->getTimestamp(),
|
||||||
|
]);
|
||||||
|
$this->createTalk([
|
||||||
|
'field_event_date' => Carbon::today()->subDays(2)->getTimestamp(),
|
||||||
|
]);
|
||||||
|
$this->createTalk([
|
||||||
|
'field_event_date' => Carbon::today()->addDay()->getTimestamp(),
|
||||||
|
]);
|
||||||
|
$this->createTalk([
|
||||||
|
'field_event_date' => Carbon::today()->subDays(10)->getTimestamp(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$talkIds = (new Collection(views_get_view_result('talks')))
|
||||||
|
->map(fn(ResultRow $row) => (int) $row->_entity->id());
|
||||||
|
|
||||||
|
$this->assertSame([3, 1, 2, 4], $talkIds->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
66
web/modules/custom/talks/tests/src/Kernel/TalksTestBase.php
Normal file
66
web/modules/custom/talks/tests/src/Kernel/TalksTestBase.php
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\opdavies_talks\Kernel;
|
||||||
|
|
||||||
|
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
|
||||||
|
use Drupal\node\Entity\Node;
|
||||||
|
use Drupal\opdavies_talks\Entity\Node\Talk;
|
||||||
|
use Drupal\paragraphs\Entity\Paragraph;
|
||||||
|
use Drupal\paragraphs\ParagraphInterface;
|
||||||
|
|
||||||
|
abstract class TalksTestBase extends EntityKernelTestBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static $modules = [
|
||||||
|
// Core.
|
||||||
|
'node',
|
||||||
|
'file',
|
||||||
|
'datetime',
|
||||||
|
|
||||||
|
// Contrib.
|
||||||
|
'entity_reference_revisions',
|
||||||
|
'paragraphs',
|
||||||
|
'hook_event_dispatcher',
|
||||||
|
'core_event_dispatcher',
|
||||||
|
|
||||||
|
// Custom.
|
||||||
|
'opdavies_talks',
|
||||||
|
'opdavies_talks_test',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $strictConfigSchema = FALSE;
|
||||||
|
|
||||||
|
protected function createEvent(array $overrides = []): ParagraphInterface {
|
||||||
|
/** @var \Drupal\paragraphs\ParagraphInterface $event */
|
||||||
|
$event = Paragraph::create(array_merge([
|
||||||
|
'type' => 'event',
|
||||||
|
], $overrides));
|
||||||
|
|
||||||
|
$event->save();
|
||||||
|
|
||||||
|
return $event;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createTalk(array $overrides = []): Talk {
|
||||||
|
$node = Node::create(array_merge([
|
||||||
|
'title' => 'Test Driven Drupal',
|
||||||
|
'type' => 'talk',
|
||||||
|
], $overrides));
|
||||||
|
|
||||||
|
$node->save();
|
||||||
|
|
||||||
|
return Talk::createFromNode($node);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->installEntitySchema('paragraph');
|
||||||
|
$this->installSchema('node', ['node_access']);
|
||||||
|
|
||||||
|
$this->installConfig(['opdavies_talks_test']);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// phpcs:disable Drupal.Commenting.DocComment, Drupal.NamingConventions.ValidFunctionName
|
||||||
|
|
||||||
|
namespace Drupal\Tests\opdavies_talks\Kernel;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
|
||||||
|
|
||||||
|
final class UpdatesTalkCreatedDateTest extends TalksTestBase {
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function the_date_is_updated_when_a_talk_node_is_created(): void {
|
||||||
|
$eventDate = Carbon::today()->addWeek();
|
||||||
|
$eventDateFormat = $eventDate
|
||||||
|
->format(DateTimeItemInterface::DATE_STORAGE_FORMAT);
|
||||||
|
$eventDateTimestamp = $eventDate->getTimestamp();
|
||||||
|
|
||||||
|
$talk = $this->createTalk([
|
||||||
|
'field_events' => [
|
||||||
|
$this->createEvent(['field_date' => $eventDateFormat]),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertSame($eventDateTimestamp, $talk->getCreatedTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function the_date_is_updated_when_a_talk_node_is_updated(): void {
|
||||||
|
$talk = $this->createTalk();
|
||||||
|
$originalCreatedTime = $talk->getCreatedTime();
|
||||||
|
|
||||||
|
$eventDate = Carbon::today()->addWeek();
|
||||||
|
$eventDateFormat = $eventDate
|
||||||
|
->format(DateTimeItemInterface::DATE_STORAGE_FORMAT);
|
||||||
|
$eventDateTimestamp = $eventDate->getTimestamp();
|
||||||
|
|
||||||
|
$talk->addEvent(
|
||||||
|
$this->createEvent(['field_date' => $eventDateFormat])
|
||||||
|
);
|
||||||
|
$talk->save();
|
||||||
|
|
||||||
|
$this->assertNotSame($originalCreatedTime, $talk->getCreatedTime());
|
||||||
|
$this->assertSame($eventDateTimestamp, $talk->getCreatedTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue