countdown-8.x-1.8/modules/countdown_field/src/Plugin/Field/FieldType/CountdownItem.php
modules/countdown_field/src/Plugin/Field/FieldType/CountdownItem.php
<?php
declare(strict_types=1);
namespace Drupal\countdown_field\Plugin\Field\FieldType;
use Drupal\Component\Utility\Random;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataDefinition;
/**
* Plugin implementation of the 'countdown' field type.
*
* Stores countdown timer configurations with library support. The target date
* is stored as a UTC UNIX timestamp for consistency and validation simplicity.
*
* @FieldType(
* id = "countdown",
* label = @Translation("Countdown"),
* description = @Translation("Field for countdown timers with library support"),
* default_widget = "countdown_default",
* default_formatter = "countdown_default",
* )
*/
class CountdownItem extends FieldItemBase {
use StringTranslationTrait;
/**
* {@inheritdoc}
*/
public static function defaultStorageSettings() {
return [
'default_library' => '',
] + parent::defaultStorageSettings();
}
/**
* {@inheritdoc}
*/
public static function defaultFieldSettings() {
return [
'allowed_libraries' => [],
'allow_event_details' => TRUE,
'require_future_date' => TRUE,
] + parent::defaultFieldSettings();
}
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
// Define the library property.
$properties['library'] = DataDefinition::create('string')
->setLabel(new TranslatableMarkup('Library'))
->setDescription(new TranslatableMarkup('The countdown library to use'))
->setRequired(FALSE);
// Define the target date as a timestamp (integer).
$properties['target_date'] = DataDefinition::create('integer')
->setLabel(new TranslatableMarkup('Target date'))
->setDescription(new TranslatableMarkup('The target date/time as a UTC UNIX timestamp'))
->setRequired(FALSE);
// Define the event name property.
$properties['event_name'] = DataDefinition::create('string')
->setLabel(new TranslatableMarkup('Event name'))
->setDescription(new TranslatableMarkup('Optional name of the event'))
->setRequired(FALSE);
// Define the event URL property.
$properties['event_url'] = DataDefinition::create('uri')
->setLabel(new TranslatableMarkup('Event URL'))
->setDescription(new TranslatableMarkup('Optional URL for event details'))
->setRequired(FALSE);
// Define the finish message property.
$properties['finish_message'] = DataDefinition::create('string')
->setLabel(new TranslatableMarkup('Finish message'))
->setDescription(new TranslatableMarkup('Message to display when countdown completes'))
->setRequired(FALSE);
// Define library_settings as any type to support arrays.
$properties['library_settings'] = DataDefinition::create('any')
->setLabel(new TranslatableMarkup('Library settings'))
->setDescription(new TranslatableMarkup('Library-specific configuration'))
->setRequired(FALSE);
return $properties;
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
return [
'columns' => [
'library' => [
'type' => 'varchar',
'length' => 32,
],
'target_date' => [
'type' => 'int',
'size' => 'big',
'not null' => FALSE,
],
'event_name' => [
'type' => 'varchar',
'length' => 255,
],
'event_url' => [
'type' => 'varchar',
'length' => 2048,
],
'finish_message' => [
'type' => 'varchar',
'length' => 255,
],
'library_settings' => [
'type' => 'blob',
'size' => 'normal',
'serialize' => TRUE,
],
],
'indexes' => [
'target_date' => ['target_date'],
'library' => ['library'],
],
];
}
/**
* {@inheritdoc}
*/
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
$element = [];
// Get available libraries from the manager service.
/** @var \Drupal\countdown\Service\CountdownLibraryManagerInterface $library_manager */
$library_manager = \Drupal::service('countdown.library_manager');
$libraries = $library_manager->getAvailableLibraryOptions();
$element['default_library'] = [
'#type' => 'select',
'#title' => $this->t('Default library'),
'#description' => $this->t('The default countdown library for new fields.'),
'#options' => ['' => $this->t('- Use global default -')] + $libraries,
'#default_value' => $this->getSetting('default_library'),
'#disabled' => $has_data,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
$element = [];
// Get available libraries from the manager service.
/** @var \Drupal\countdown\Service\CountdownLibraryManagerInterface $library_manager */
$library_manager = \Drupal::service('countdown.library_manager');
$libraries = $library_manager->getAvailableLibraryOptions();
$element['allowed_libraries'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Allowed libraries'),
'#description' => $this->t('Select which countdown libraries can be used. If none are selected, all available libraries are allowed.'),
'#options' => $libraries,
'#default_value' => $this->getSetting('allowed_libraries') ?: [],
];
$element['allow_event_details'] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow event details'),
'#description' => $this->t('Allow users to add event name and URL to countdown timers.'),
'#default_value' => $this->getSetting('allow_event_details'),
];
$element['require_future_date'] = [
'#type' => 'checkbox',
'#title' => $this->t('Require future date for countdown'),
'#description' => $this->t('When counting down, require the target date to be in the future.'),
'#default_value' => $this->getSetting('require_future_date'),
];
return $element;
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
// Field is empty if target_date is not set or is zero.
$value = $this->get('target_date')->getValue();
return $value === NULL || $value === '' || $value === 0;
}
/**
* {@inheritdoc}
*/
public function preSave() {
// Ensure library is set.
if (empty($this->library)) {
$this->library = $this->getDefaultLibrary();
}
// Ensure finish_message has a default value.
if (empty($this->finish_message)) {
$this->finish_message = (string) $this->t("Time's up!");
}
// Ensure target_date is properly formatted if set.
if (!empty($this->target_date) && !is_numeric($this->target_date)) {
if ($this->target_date instanceof DrupalDateTime) {
$this->target_date = $this->target_date->getTimestamp();
}
elseif (is_string($this->target_date)) {
$this->setTargetDateFromIso($this->target_date);
}
}
// Log what we're saving.
\Drupal::logger('countdown_field')->debug('preSave - library_settings value: @settings', [
'@settings' => json_encode($this->get('library_settings')->getValue()),
]);
}
/**
* {@inheritdoc}
*/
public function setValue($values, $notify = TRUE) {
// Handle DrupalDateTime objects for target_date.
if (isset($values['target_date']) && $values['target_date'] instanceof DrupalDateTime) {
$values['target_date'] = $values['target_date']->getTimestamp();
}
// Log incoming values for debugging.
if (isset($values['library_settings'])) {
\Drupal::logger('countdown_field')->debug('setValue called with library_settings: @settings', [
'@settings' => json_encode($values['library_settings']),
]);
}
parent::setValue($values, $notify);
}
/**
* Get allowed libraries for this field.
*
* @return array
* Array of allowed library IDs.
*/
public function getAllowedLibraries(): array {
$allowed = $this->getFieldDefinition()->getSetting('allowed_libraries');
if (empty($allowed)) {
// If no restrictions, all libraries are allowed.
/** @var \Drupal\countdown\Service\CountdownLibraryManagerInterface $library_manager */
$library_manager = \Drupal::service('countdown.library_manager');
$method = $library_manager->getLoadingMethod();
return array_keys($library_manager->getAvailableLibraryOptions($method));
}
// Filter out unchecked options (value = 0).
return array_values(array_filter($allowed));
}
/**
* Get the default library for this field.
*
* @return string
* The default library ID.
*/
protected function getDefaultLibrary(): string {
// Check field storage settings first.
$default = $this->getFieldDefinition()
->getFieldStorageDefinition()
->getSetting('default_library');
if (!empty($default)) {
return $default;
}
// Fall back to global default.
/** @var \Drupal\countdown\Service\CountdownLibraryManagerInterface $library_manager */
$library_manager = \Drupal::service('countdown.library_manager');
return $library_manager->getActiveLibrary();
}
/**
* Get library-specific settings.
*
* @return array
* The library settings array.
*/
public function getLibrarySettings(): array {
// Use the proper get() method with property name.
$settings = $this->get('library_settings')->getValue();
// The value is automatically unserialized by Drupal.
return is_array($settings) ? $settings : [];
}
/**
* Set library-specific settings.
*
* @param array $settings
* The settings array to store.
*/
public function setLibrarySettings(array $settings): void {
// Use the proper set() method with property name.
$this->set('library_settings', $settings);
}
/**
* Get the effective loading method for this field.
*
* @return string
* The loading method ('local' or 'cdn').
*/
public function getEffectiveMethod(): string {
/** @var \Drupal\countdown\Service\CountdownLibraryManagerInterface $library_manager */
$library_manager = \Drupal::service('countdown.library_manager');
return $library_manager->getLoadingMethod();
}
/**
* Get the target date as an ISO-8601 string.
*
* Converts the stored UTC timestamp to ISO-8601 format for JavaScript
* consumption and display purposes.
*
* @return string|null
* The ISO-8601 formatted date string, or NULL if no date is set.
*/
public function getTargetDateIso(): ?string {
$timestamp = $this->get('target_date')->getValue();
if (empty($timestamp) || !is_numeric($timestamp) || $timestamp <= 0) {
return NULL;
}
// Format timestamp as ISO-8601 in UTC.
return gmdate('Y-m-d\TH:i:s\Z', (int) $timestamp);
}
/**
* Set the target date from an ISO-8601 string.
*
* Converts an ISO-8601 date string to a UTC timestamp for storage.
*
* @param string $iso_date
* The ISO-8601 formatted date string.
*/
public function setTargetDateFromIso(string $iso_date): void {
try {
$datetime = new \DateTime($iso_date);
$this->set('target_date', $datetime->getTimestamp());
}
catch (\Exception $e) {
// Invalid date format, set to NULL.
$this->set('target_date', NULL);
}
}
/**
* {@inheritdoc}
*/
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
$random = new Random();
// Generate a future timestamp between 1 hour and 1 year from now.
$timestamp = \Drupal::time()->getRequestTime() + rand(3600, 31536000);
$values = [
'target_date' => $timestamp,
'event_name' => $random->sentences(mt_rand(1, 3)),
'finish_message' => $random->sentences(1),
];
// Set a default library.
/** @var \Drupal\countdown\Service\CountdownLibraryManagerInterface $library_manager */
$library_manager = \Drupal::service('countdown.library_manager');
$values['library'] = $library_manager->getActiveLibrary();
return $values;
}
}
