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

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

declare(strict_types=1);

namespace Drupal\countdown\Plugin\CountdownLibrary;

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

/**
 * FlipDown library plugin implementation.
 *
 * FlipDown is a lightweight, performant flip-style countdown timer
 * with a clean, modern design and smooth animations.
 *
 * @CountdownLibrary(
 *   id = "flipdown",
 *   label = @Translation("FlipDown"),
 *   description = @Translation("Lightweight flip-style timer with modern design"),
 *   type = "external",
 *   homepage = "https://pbutcher.uk/flipdown",
 *   repository = "https://github.com/PButcher/flipdown",
 *   version = "0.3.2",
 *   npm_package = "flipdown",
 *   folder_names = {
 *     "flipdown",
 *     "FlipDown",
 *     "flipdown.js",
 *     "flipdown-master",
 *     "PButcher-flipdown"
 *   },
 *   init_function = "FlipDown",
 *   author = "Peter Butcher",
 *   license = "MIT",
 *   dependencies = {
 *     "core/drupal",
 *     "core/once"
 *   },
 *   weight = 1,
 *   experimental = false,
 *   api_version = "1.0"
 * )
 */
final class FlipDown extends CountdownLibraryPluginBase {

  /**
   * {@inheritdoc}
   */
  public function getAssetMap(): array {
    return [
      'local' => [
        'js' => [
          'development' => 'dist/flipdown.js',
          'production' => 'dist/flipdown.min.js',
        ],
        'css' => [
          'development' => 'dist/flipdown.css',
          'production' => 'dist/flipdown.min.css',
        ],
      ],
      'cdn' => [
        'jsdelivr' => [
          'js' => '//cdn.jsdelivr.net/npm/flipdown@0.3.2/dist/flipdown.min.js',
          'css' => '//cdn.jsdelivr.net/npm/flipdown@0.3.2/dist/flipdown.min.css',
        ],
        'unpkg' => [
          'js' => '//unpkg.com/flipdown@0.3.2/dist/flipdown.min.js',
          'css' => '//unpkg.com/flipdown@0.3.2/dist/flipdown.min.css',
        ],
      ],
    ];
  }

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

  /**
   * {@inheritdoc}
   */
  public function getHomepage(): ?string {
    return 'https://pbutcher.uk/flipdown';
  }

  /**
   * {@inheritdoc}
   */
  protected function detectVersionCustom(string $base_path): ?string {
    // Try multiple strategies to detect FlipDown version.
    // Strategy 1: Check the main JS file for version information.
    $js_files = [
      '/dist/flipdown.js',
      '/dist/flipdown.min.js',
      '/src/flipdown.js',
      '/example/js/flipdown/flipdown.js',
    ];

    foreach ($js_files as $js_file) {
      $file_path = $base_path . $js_file;

      if (file_exists($file_path)) {
        try {
          // Read first 10KB of file.
          $handle = fopen($file_path, 'r');
          $content = fread($handle, 10240);
          fclose($handle);

          // Look for version patterns specific to FlipDown.
          $patterns = [
            '/this\.version\s*=\s*["\']([0-9]+\.[0-9]+(?:\.[0-9]+)?)["\']/',
            '/\*\s+FlipDown\s+v?([0-9]+\.[0-9]+(?:\.[0-9]+)?)/',
            '/\*\s+@version\s+([0-9]+\.[0-9]+(?:\.[0-9]+)?)/',
            '/version["\']?\s*[:=]\s*["\']([0-9]+\.[0-9]+(?:\.[0-9]+)?)["\']/',
            '/FlipDown\.version\s*=\s*["\']([^"\']+)["\']/',
            '/const\s+version\s*=\s*["\']([0-9]+\.[0-9]+(?:\.[0-9]+)?)["\']/',
          ];

          foreach ($patterns as $pattern) {
            if (preg_match($pattern, $content, $matches)) {
              $this->logger->info('FlipDown version detected: @version from @file', [
                '@version' => $matches[1],
                '@file' => $js_file,
              ]);
              return $this->normalizeVersion($matches[1]);
            }
          }
        }
        catch (\Exception $e) {
          $this->logger->error('Error reading FlipDown file @file: @message', [
            '@file' => $js_file,
            '@message' => $e->getMessage(),
          ]);
        }
      }
    }

    // Strategy 2: Check CSS file for version comments.
    $css_files = [
      '/dist/flipdown.css',
      '/dist/flipdown.min.css',
      '/src/flipdown.css',
    ];

    foreach ($css_files as $css_file) {
      $file_path = $base_path . $css_file;

      if (file_exists($file_path)) {
        try {
          $handle = fopen($file_path, 'r');
          $content = fread($handle, 2048);
          fclose($handle);

          $pattern = '/FlipDown\s+v?([0-9]+\.[0-9]+(?:\.[0-9]+)?)/i';
          if (preg_match($pattern, $content, $matches)) {
            return $this->normalizeVersion($matches[1]);
          }
        }
        catch (\Exception $e) {
          $this->logger->warning('Could not read FlipDown CSS file: @file', [
            '@file' => $css_file,
          ]);
        }
      }
    }

    // Strategy 3: Check documentation files.
    $doc_files = [
      '/README.md',
      '/CHANGELOG.md',
      '/VERSION',
      '/.version',
    ];

    foreach ($doc_files as $doc_file) {
      $file_path = $base_path . $doc_file;

      if (file_exists($file_path)) {
        try {
          $content = file_get_contents($file_path);

          // Look for version patterns.
          $pattern = '/(?:version|v)\s*([0-9]+\.[0-9]+(?:\.[0-9]+)?)/i';
          if (preg_match($pattern, $content, $matches)) {
            return $this->normalizeVersion($matches[1]);
          }
        }
        catch (\Exception $e) {
          $this->logger->warning('Could not read FlipDown documentation: @file', [
            '@file' => $doc_file,
          ]);
        }
      }
    }

    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function validateInstallation(string $path): bool {
    // First check parent validation.
    if (!parent::validateInstallation($path)) {
      return FALSE;
    }

    // Additional FlipDown-specific validation.
    $path = ltrim($path, '/');
    $full_path = DRUPAL_ROOT . '/' . $path;

    // Check for FlipDown-specific indicators.
    $flipdown_indicators = [
      '/dist/flipdown.js',
      '/dist/flipdown.css',
      '/src/flipdown.js',
      '/example/js/flipdown',
    ];

    $found_indicator = FALSE;
    foreach ($flipdown_indicators as $indicator) {
      $check_path = $full_path . $indicator;
      if (file_exists($check_path) || is_dir($check_path)) {
        $found_indicator = TRUE;
        break;
      }
    }

    if (!$found_indicator) {
      $this->logger->warning('FlipDown library structure not recognized at @path', [
        '@path' => $path,
      ]);
      return FALSE;
    }

    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getRequiredFiles(): array {
    return [
      'dist/flipdown.js',
      'dist/flipdown.css',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getAlternativePaths(): array {
    return [
      [
        'src/flipdown.js',
        'src/flipdown.css',
      ],
      [
        'example/js/flipdown/flipdown.js',
        'example/css/flipdown/flipdown.css',
      ],
      [
        'lib/flipdown.js',
        'lib/flipdown.css',
      ],
    ];
  }

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

  /**
   * {@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 FlipDown-specific fields to the library_specific fieldset.
    // Theme selection based on library capabilities.
    $form['library_specific']['theme'] = [
      '#type' => 'select',
      '#title' => $this->t('Theme'),
      '#description' => $this->t('Select the FlipDown visual theme.'),
      '#options' => [
        'dark' => $this->t('Dark'),
        'light' => $this->t('Light'),
      ],
      '#default_value' => $this->getConfigValue($default_values, 'theme', 'dark'),
    ];

    // Custom headings for each unit.
    $form['library_specific']['headings'] = [
      '#type' => 'details',
      '#title' => $this->t('Unit Labels'),
      '#description' => $this->t('Customize the labels displayed above each time unit.'),
      '#open' => TRUE,
    ];

    $default_headings = $this->getConfigValue($default_values, 'headings', $this->getDefaultHeadings());

    $form['library_specific']['headings']['days'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Days Label'),
      '#default_value' => $default_headings[0] ?? 'Days',
      '#size' => 20,
      '#maxlength' => 20,
      '#description' => $this->t('Label displayed above days counter.'),
    ];

    $form['library_specific']['headings']['hours'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Hours Label'),
      '#default_value' => $default_headings[1] ?? 'Hours',
      '#size' => 20,
      '#maxlength' => 20,
      '#description' => $this->t('Label displayed above hours counter.'),
    ];

    $form['library_specific']['headings']['minutes'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Minutes Label'),
      '#default_value' => $default_headings[2] ?? 'Minutes',
      '#size' => 20,
      '#maxlength' => 20,
      '#description' => $this->t('Label displayed above minutes counter.'),
    ];

    $form['library_specific']['headings']['seconds'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Seconds Label'),
      '#default_value' => $default_headings[3] ?? 'Seconds',
      '#size' => 20,
      '#maxlength' => 20,
      '#description' => $this->t('Label displayed above seconds counter.'),
    ];

    // Enable custom CSS class for additional theming.
    $form['library_specific']['custom_class'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Custom CSS Class'),
      '#description' => $this->t('Add a custom CSS class to the FlipDown container for additional styling.'),
      '#default_value' => $this->getConfigValue($default_values, 'custom_class', ''),
      '#size' => 40,
      '#maxlength' => 100,
    ];

    // Option to control rotor animation behavior.
    $form['library_specific']['enable_animation'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable flip animations'),
      '#description' => $this->t('Enable smooth flip animations when values change.'),
      '#default_value' => $this->getConfigValue($default_values, 'enable_animation', TRUE),
    ];

    // Option to show/hide specific time units.
    $form['library_specific']['show_units'] = [
      '#type' => 'checkboxes',
      '#title' => $this->t('Display Units'),
      '#description' => $this->t('Select which time units to display in the countdown.'),
      '#options' => [
        'days' => $this->t('Days'),
        'hours' => $this->t('Hours'),
        'minutes' => $this->t('Minutes'),
        'seconds' => $this->t('Seconds'),
      ],
      '#default_value' => $this->getConfigValue($default_values, 'show_units', [
        'days' => 'days',
        'hours' => 'hours',
        'minutes' => 'minutes',
        'seconds' => 'seconds',
      ]),
    ];

    // Option for responsive behavior.
    $form['library_specific']['responsive'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable responsive mode'),
      '#description' => $this->t('Automatically adjust the timer size based on container width.'),
      '#default_value' => $this->getConfigValue($default_values, 'responsive', FALSE),
    ];

    // Option to enable debug mode for development.
    $form['library_specific']['debug_mode'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable debug mode'),
      '#description' => $this->t('Log FlipDown initialization and events to 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 theme selection.
    $theme = $form_state->getValue(['library_specific', 'theme']);
    if ($theme && !in_array($theme, ['dark', 'light'], TRUE)) {
      $form_state->setError($form['library_specific']['theme'],
        $this->t('Invalid theme selected.'));
    }

    // Validate that at least one time unit is selected.
    $show_units = array_filter($form_state->getValue(['library_specific', 'show_units'], []));
    if (empty($show_units)) {
      $form_state->setError($form['library_specific']['show_units'],
        $this->t('At least one time unit must be displayed.'));
    }

    // Validate custom CSS class format.
    $custom_class = $form_state->getValue(['library_specific', 'custom_class']);
    if ($custom_class && !preg_match('/^[a-zA-Z][a-zA-Z0-9\-_]*$/', $custom_class)) {
      $form_state->setError($form['library_specific']['custom_class'],
        $this->t('Custom CSS class must be a valid CSS class name.'));
    }

    // Validate heading labels are not empty.
    $headings = $form_state->getValue(['library_specific', 'headings']);
    foreach (['days', 'hours', 'minutes', 'seconds'] as $unit) {
      if (isset($headings[$unit]) && trim($headings[$unit]) === '') {
        $form_state->setError($form['library_specific']['headings'][$unit],
          $this->t('@unit label cannot be empty.', ['@unit' => ucfirst($unit)]));
      }
    }
  }

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

    $defaults['theme'] = 'dark';
    $defaults['headings'] = $this->getDefaultHeadings();
    $defaults['custom_class'] = '';
    $defaults['enable_animation'] = TRUE;
    $defaults['show_units'] = [
      'days' => 'days',
      'hours' => 'hours',
      'minutes' => 'minutes',
      'seconds' => 'seconds',
    ];
    $defaults['responsive'] = FALSE;
    $defaults['debug_mode'] = FALSE;

    return $defaults;
  }

  /**
   * Get default heading labels.
   *
   * @return array
   *   Array of default heading labels.
   */
  protected function getDefaultHeadings(): array {
    return ['Days', 'Hours', 'Minutes', 'Seconds'];
  }

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

    // Build headings array from individual fields.
    $headings = [
      $configuration['headings']['days'] ?? 'Days',
      $configuration['headings']['hours'] ?? 'Hours',
      $configuration['headings']['minutes'] ?? 'Minutes',
      $configuration['headings']['seconds'] ?? 'Seconds',
    ];

    // Convert show_units checkboxes to array of selected keys.
    // This is important: we send the selected units as an array.
    $show_units = isset($configuration['show_units'])
      ? array_filter($configuration['show_units'])
      : ['days' => 'days', 'hours' => 'hours', 'minutes' => 'minutes', 'seconds' => 'seconds'];

    // Send as array of selected unit names for JavaScript.
    $selected_units = [];
    foreach ($show_units as $unit => $value) {
      if ($value) {
        $selected_units[] = $unit;
      }
    }

    $settings['theme'] = $this->getConfigValue($configuration, 'theme', 'dark');
    $settings['headings'] = $headings;
    $settings['custom_class'] = $this->getConfigValue($configuration, 'custom_class', '');
    $settings['enable_animation'] = $this->getConfigValue($configuration, 'enable_animation', TRUE);
    // Send selected units as array for easier checking in JavaScript.
    $settings['show_units'] = $selected_units;
    $settings['responsive'] = $this->getConfigValue($configuration, 'responsive', FALSE);
    $settings['debug_mode'] = $this->getConfigValue($configuration, 'debug_mode', FALSE);

    return $settings;
  }

}

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

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