countdown-8.x-1.8/src/Plugin/CountdownLibrary/CoreTimer.php

src/Plugin/CountdownLibrary/CoreTimer.php
<?php

declare(strict_types=1);

namespace Drupal\countdown\Plugin\CountdownLibrary;

use Drupal\Core\Form\FormStateInterface;
use Drupal\countdown\Plugin\CountdownLibraryPluginBase;

/**
 * Core countdown timer library plugin implementation.
 *
 * This is the built-in countdown timer that ships with the module.
 * It provides a lightweight, high-performance countdown/countup timer
 * with drift compensation and no external dependencies.
 *
 * @CountdownLibrary(
 *   id = "countdown",
 *   label = @Translation("Countdown Timer"),
 *   description = @Translation("Built-in timer with drift compensation"),
 *   type = "core",
 *   homepage = "https://drupal.org/project/countdown",
 *   repository = "https://git.drupalcode.org/project/countdown",
 *   version = "1.0.0",
 *   init_function = "CountdownTimer",
 *   author = "Drupal Community",
 *   license = "GPL-2.0+",
 *   dependencies = {
 *     "core/drupal",
 *     "core/drupalSettings",
 *     "core/once"
 *   },
 *   weight = -10,
 *   experimental = false,
 *   api_version = "1.0"
 * )
 */
final class CoreTimer extends CountdownLibraryPluginBase {

  /**
   * {@inheritdoc}
   */
  public function isInstalled(): bool {
    // Core library is always installed.
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getLibraryPath(): ?string {
    // Core library files are in the module directory.
    $module_handler = \Drupal::moduleHandler();
    $module_path = $module_handler->getModule('countdown')->getPath();
    return '/' . $module_path;
  }

  /**
   * {@inheritdoc}
   */
  public function validateInstallation(string $path): bool {
    // Core timer validation checks for required files.
    $module_handler = \Drupal::moduleHandler();
    $module_path = $module_handler->getModule('countdown')->getPath();

    // Check for required core timer files.
    $required_files = [
      '/js/lib/countdown.js',
      '/js/countdown.integration.js',
      '/css/countdown.timer.css',
    ];

    foreach ($required_files as $file) {
      $file_path = DRUPAL_ROOT . '/' . $module_path . $file;
      if (!file_exists($file_path)) {
        $this->logger->error('Core timer file missing: @file', [
          '@file' => $file,
        ]);
        return FALSE;
      }
    }

    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function detectVersion(string $path): ?string {
    // Core library version is fixed and matches the module version.
    return $this->getRequiredVersion();
  }

  /**
   * {@inheritdoc}
   */
  protected function detectVersionCustom(string $base_path): ?string {
    // Core library version is defined in the module info file.
    $module_handler = \Drupal::moduleHandler();
    $module = $module_handler->getModule('countdown');

    if ($module) {
      $info = \Drupal::service('extension.list.module')
        ->getExtensionInfo('countdown');
      if (isset($info['version'])) {
        // Remove Drupal version prefix if present.
        $version = preg_replace('/^\d+\.x-/', '', $info['version']);
        return $this->normalizeVersion($version);
      }
    }

    // Fallback to default version.
    return '1.0.0';
  }

  /**
   * {@inheritdoc}
   */
  public function getInstalledVersion(): ?string {
    // Core library version is always the current version.
    return $this->detectVersionCustom('');
  }

  /**
   * {@inheritdoc}
   */
  public function versionMeetsRequirements(): bool {
    // Core library always meets requirements.
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getAssetMap(): array {
    // Core library assets are defined in countdown.libraries.yml.
    // We return an empty map as the module handles core assets directly.
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getInitFunction(): ?string {
    return 'CountdownTimer';
  }

  /**
   * {@inheritdoc}
   */
  public function getDependencies(): array {
    return [
      'core/drupal',
      'core/drupalSettings',
      'core/once',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getHomepage(): ?string {
    return 'https://drupal.org/project/countdown';
  }

  /**
   * {@inheritdoc}
   */
  public function getRequiredFiles(): array {
    // Core library files are always present.
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getPossibleFolderNames(): array {
    // Core library doesn't need folder discovery.
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getStatus(): array {
    return [
      'installed' => TRUE,
      'version_status' => 'ok',
      'messages' => [
        $this->t('Core library is built-in and always available.'),
        $this->t('Version: @version', [
          '@version' => $this->getInstalledVersion(),
        ]),
        $this->t('High-performance timer with drift compensation.'),
        $this->t('No external dependencies required.'),
      ],
      'severity' => 'info',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function isExperimental(): bool {
    // Core library is stable.
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getWeight(): int {
    // Core library has highest priority.
    return -10;
  }

  /**
   * {@inheritdoc}
   */
  public function resetCache(): void {
    // Core library doesn't need cache reset as it's always available.
    // But we'll clear Drupal's library cache for consistency.
    \Drupal::service('library.discovery')->clearCachedDefinitions();
  }

  /**
   * {@inheritdoc}
   */
  public function hasExtensions(): bool {
    // Core library doesn't have extensions.
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getExtensionGroups(): array {
    // No extension groups for core library.
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function getAvailableExtensions(): array {
    // No extensions for core library.
    return [];
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array &$form, FormStateInterface $form_state, array $default_values = []): void {
    // First, build the common fields from parent.
    parent::buildConfigurationForm($form, $form_state, $default_values);

    // Now add CoreTimer-specific fields to the library_specific fieldset.
    $form['library_specific']['style'] = [
      '#type' => 'select',
      '#title' => $this->t('Display Style'),
      '#description' => $this->t('Choose how to display the countdown.'),
      '#options' => [
        'default' => $this->t('Default (automatic)'),
        'compact' => $this->t('Compact (minimal spacing)'),
        'expanded' => $this->t('Expanded (with labels)'),
      ],
      '#default_value' => $this->getConfigValue($default_values, 'style', 'default'),
    ];

    // Add precision control for time display accuracy.
    $form['library_specific']['precision'] = [
      '#type' => 'select',
      '#title' => $this->t('Time Precision'),
      '#description' => $this->t('Select the precision level for time display.'),
      '#options' => [
        'milliseconds' => $this->t('Milliseconds (HH:MM:SS.mmm)'),
        'hundredths' => $this->t('Hundredths (HH:MM:SS.mm)'),
        'tenths' => $this->t('Tenths (HH:MM:SS.m)'),
        'seconds' => $this->t('Seconds (HH:MM:SS)'),
        'minutes' => $this->t('Minutes (HH:MM)'),
      ],
      '#default_value' => $this->getConfigValue($default_values, 'precision', 'seconds'),
    ];

    // Add checkbox to enable custom format.
    $form['library_specific']['enable_custom_format'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Use custom format template'),
      '#description' => $this->t('Enable to specify a custom time display format.'),
      '#default_value' => !empty($this->getConfigValue($default_values, 'format_template', '')),
    ];

    // Add custom format template field.
    $form['library_specific']['format_template'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Custom Format Template'),
      '#description' => $this->t('Custom time format. Use: DD (days), HH (hours), MM (minutes), SS (seconds), mmm (milliseconds). Leave empty for automatic format based on precision.'),
      '#default_value' => $this->getConfigValue($default_values, 'format_template', ''),
      '#maxlength' => 64,
      '#states' => [
        'visible' => [
          ':input[name$="[enable_custom_format]"]' => ['checked' => TRUE],
        ],
      ],
    ];

    // Create a performance settings group.
    $form['library_specific']['performance'] = [
      '#type' => 'details',
      '#title' => $this->t('Performance Settings'),
      '#description' => $this->t('Configure timer performance and accuracy settings.'),
      '#open' => TRUE,
    ];

    // Add auto-start option.
    $form['library_specific']['performance']['auto_start'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Auto-start Timer'),
      '#description' => $this->t('Automatically start the timer when the page loads.'),
      '#default_value' => $this->getConfigValue($default_values, 'auto_start', TRUE),
    ];

    // Add drift compensation toggle.
    $form['library_specific']['performance']['drift_compensation'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable Drift Compensation'),
      '#description' => $this->t('Automatically compensates for JavaScript timer drift to maintain accuracy. Recommended for long-running timers.'),
      '#default_value' => $this->getConfigValue($default_values, 'drift_compensation', TRUE),
    ];

    // Add initial offset field.
    $form['library_specific']['performance']['initial_offset'] = [
      '#type' => 'number',
      '#title' => $this->t('Initial Offset'),
      '#description' => $this->t('Start time offset in milliseconds. Useful for synchronization.'),
      '#default_value' => $this->getConfigValue($default_values, 'initial_offset', 0),
      '#min' => 0,
      '#max' => 86400000,
      '#step' => 100,
      '#field_suffix' => $this->t('ms'),
    ];

    // Create developer tools group (only for admins).
    $current_user = \Drupal::currentUser();
    if ($current_user->hasPermission('administer site configuration')) {
      $form['library_specific']['developer'] = [
        '#type' => 'details',
        '#title' => $this->t('Developer Tools'),
        '#description' => $this->t('Advanced tools for debugging and performance analysis.'),
        '#open' => FALSE,
      ];

      // Add benchmark mode toggle.
      $form['library_specific']['developer']['benchmark_mode'] = [
        '#type' => 'checkbox',
        '#title' => $this->t('Enable Benchmark Mode'),
        '#description' => $this->t('Collect performance metrics including drift statistics and timing accuracy.'),
        '#default_value' => $this->getConfigValue($default_values, 'benchmark_mode', FALSE),
      ];

      // Add debug mode toggle.
      $form['library_specific']['developer']['debug_mode'] = [
        '#type' => 'checkbox',
        '#title' => $this->t('Enable Debug Mode'),
        '#description' => $this->t('Output detailed timer information to the browser console.'),
        '#default_value' => $this->getConfigValue($default_values, 'debug_mode', FALSE),
      ];
    }
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void {
    // First validate common fields.
    parent::validateConfigurationForm($form, $form_state);

    // Validate custom format template if enabled.
    $enable_custom = $form_state->getValue(['library_specific', 'enable_custom_format']);
    $format_template = $form_state->getValue(['library_specific', 'format_template']);

    if ($enable_custom && !empty($format_template)) {
      // Check for valid format tokens only.
      if (!preg_match('/^[DHMSm:\s\-\.\/]+$/', $format_template)) {
        $form_state->setError(
          $form['library_specific']['format_template'],
          $this->t('Invalid format template. Use only: DD, D, HH, H, MM, M, SS, S, mmm, mm, m and separators.')
        );
      }

      // Check if template contains at least one time token.
      if (!preg_match('/(DD|D|HH|H|MM|M|SS|S|mmm|mm|m)/', $format_template)) {
        $form_state->setError(
          $form['library_specific']['format_template'],
          $this->t('Format template must contain at least one time token.')
        );
      }
    }

    // Validate initial offset value.
    $offset = $form_state->getValue(['library_specific', 'performance', 'initial_offset']);
    if ($offset !== NULL && $offset !== '') {
      if (!is_numeric($offset) || $offset < 0) {
        $form_state->setError(
          $form['library_specific']['performance']['initial_offset'],
          $this->t('Initial offset must be a positive number.')
        );
      }
    }

    // Validate precision compatibility with format template.
    $precision = $form_state->getValue(['library_specific', 'precision']);
    if ($enable_custom && !empty($format_template) && $precision) {
      // Check for incompatible combinations.
      if ($precision === 'minutes' && preg_match('/(SS|S|mmm|mm|m)/', $format_template)) {
        $form_state->setWarning(
          $this->t('Format template contains seconds or milliseconds tokens, but precision is set to minutes. These values will not update.')
        );
      }
      elseif ($precision === 'seconds' && preg_match('/(mmm|mm|m)/', $format_template)) {
        $form_state->setWarning(
          $this->t('Format template contains milliseconds tokens, but precision is set to seconds. These values will remain at zero.')
        );
      }
    }

    // Validate benchmark and debug modes are not both enabled in production.
    $benchmark = $form_state->getValue(['library_specific', 'developer', 'benchmark_mode']);
    $debug = $form_state->getValue(['library_specific', 'developer', 'debug_mode']);

    if (($benchmark || $debug) && !\Drupal::service('config.factory')->get('system.performance')->get('debug')) {
      $form_state->setWarning(
        $this->t('Developer tools are enabled. Remember to disable them in production for better performance.')
      );
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getDefaultConfiguration(): array {
    // Get parent defaults then add CoreTimer-specific defaults.
    $defaults = parent::getDefaultConfiguration();

    $defaults['style'] = 'default';
    $defaults['precision'] = 'seconds';
    $defaults['format_template'] = '';
    $defaults['enable_custom_format'] = FALSE;
    $defaults['auto_start'] = TRUE;
    $defaults['drift_compensation'] = TRUE;
    $defaults['initial_offset'] = 0;
    $defaults['benchmark_mode'] = FALSE;
    $defaults['debug_mode'] = FALSE;

    return $defaults;
  }

  /**
   * {@inheritdoc}
   */
  public function getJavaScriptSettings(array $configuration): array {
    // Get parent settings then add CoreTimer-specific settings.
    $settings = parent::getJavaScriptSettings($configuration);

    // Add display style configuration.
    $settings['style'] = $this->getConfigValue($configuration, 'style', 'default');

    // Add precision setting for time accuracy.
    $settings['precision'] = $this->getConfigValue($configuration, 'precision', 'seconds');

    // Add drift compensation setting.
    $settings['driftCompensation'] = (bool) $this->getConfigValue($configuration, 'drift_compensation', TRUE);

    // Add auto-start configuration.
    $settings['autoStart'] = (bool) $this->getConfigValue($configuration, 'auto_start', TRUE);

    // Add initial offset if specified.
    $offset = $this->getConfigValue($configuration, 'initial_offset', 0);
    if ($offset > 0) {
      $settings['offset'] = (int) $offset;
    }

    // Handle custom format template correctly.
    // Check if we have a non-empty format_template value.
    $format_template = $this->getConfigValue($configuration, 'format_template', '');
    if (!empty($format_template)) {
      // Add the format template to settings.
      $settings['formatTemplate'] = $format_template;
      // Also set a flag to indicate custom format is enabled.
      $settings['useCustomFormat'] = TRUE;
    }

    // Add benchmark mode if enabled (admin only).
    $benchmark = (bool) $this->getConfigValue($configuration, 'benchmark_mode', FALSE);
    if ($benchmark) {
      $settings['benchmark'] = TRUE;
    }

    // Add debug mode if enabled (admin only).
    $debug = (bool) $this->getConfigValue($configuration, 'debug_mode', FALSE);
    if ($debug) {
      $settings['debug'] = TRUE;
    }

    // Add mode configuration for countdown vs countup.
    $direction = $this->getConfigValue($configuration, 'direction', 'countdown');
    $settings['mode'] = $direction === 'countup' ? 'countup' : 'countdown';

    // Calculate and add start/target values based on configuration.
    $target_date = $this->getConfigValue($configuration, 'target_date', '');
    $timezone = $this->getConfigValue($configuration, 'timezone', date_default_timezone_get());

    if (!empty($target_date)) {
      // Convert target date to timestamp considering timezone.
      $date = new \DateTime($target_date, new \DateTimeZone($timezone));
      $now = new \DateTime('now', new \DateTimeZone($timezone));
      $diff = $date->getTimestamp() - $now->getTimestamp();

      if ($settings['mode'] === 'countdown') {
        // For countdown, set the start time based on difference.
        if ($diff > 0) {
          $settings['start'] = $diff * 1000;
        }
      }
      else {
        // For countup, set the target time.
        $settings['target'] = abs($diff) * 1000;
      }
    }

    return $settings;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc