vvjt-1.0.1/vvjt.module

vvjt.module
<?php

/**
 * @file
 * Provides the module implementation for vvjt.
 *
 * Filename:     vvjt.module
 * Website:      https://www.flashwebcenter.com
 * Description:  template.
 * Developer:    Alaa Haddad https://www.alaahaddad.com.
 */

declare(strict_types=1);

use Drupal\Component\Utility\Html;
use Drupal\Core\Render\Markup;
use Drupal\views\ViewExecutable;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Template\Attribute;
use Drupal\vvjt\Plugin\views\style\ViewsVanillaJavascriptTabs;
use Drupal\vvjt\VvjtConstants;

/**
 * Implements hook_help().
 */
function vvjt_help(string $route_name, RouteMatchInterface $route_match): ?string {
  if ($route_name === 'help.page.vvjt') {
    return _vvjt_helper_render_readme();
  }
  return NULL;
}

/**
 * Helper function to render README.md with enhanced error handling.
 *
 * @return string
 *   The rendered content of README.md or appropriate fallback message.
 */
function _vvjt_helper_render_readme(): string {
  $readme_path = __DIR__ . '/README.md';

  try {
    $text = file_get_contents($readme_path);

    if ($text === FALSE) {
      \Drupal::logger('vvjt')->warning('README.md file could not be read from path: @path', [
        '@path' => $readme_path,
      ]);
      return (string) t('README.md file not found.');
    }

    if (!\Drupal::moduleHandler()->moduleExists('markdown')) {
      return '<pre>' . htmlspecialchars($text, ENT_QUOTES | ENT_HTML5, 'UTF-8') . '</pre>';
    }

    return _vvjt_render_markdown($text);

  }
  catch (\Exception $e) {
    \Drupal::logger('vvjt')->error('Error rendering README.md: @message', [
      '@message' => $e->getMessage(),
    ]);
    return (string) t('Error loading help documentation.');
  }
}

/**
 * Render markdown content using the markdown filter.
 *
 * @param string $text
 *   The markdown text to render.
 *
 * @return string
 *   The rendered HTML content.
 */
function _vvjt_render_markdown(string $text): string {
  try {
    $filter_manager = \Drupal::service('plugin.manager.filter');
    $settings = \Drupal::config('markdown.settings')->getRawData();
    $filter = $filter_manager->createInstance('markdown', ['settings' => $settings]);
    return $filter->process($text, 'en')->getProcessedText();
  }
  catch (\Exception $e) {
    \Drupal::logger('vvjt')->warning('Markdown processing failed: @message', [
      '@message' => $e->getMessage(),
    ]);
    return '<pre>' . htmlspecialchars($text, ENT_QUOTES | ENT_HTML5, 'UTF-8') . '</pre>';
  }
}

/**
 * Implements hook_theme().
 */
function vvjt_theme(array $existing, string $type, string $theme, string $path): array {
  return [
    'views_view_vvjt_fields' => [
      'variables' => [
        'view' => NULL,
        'options' => [],
        'row' => NULL,
        'field_alias' => NULL,
        'attributes' => [],
        'title_attributes' => [],
        'content_attributes' => [],
        'title_prefix' => [],
        'title_suffix' => [],
        'fields' => [],
      ],
      'template' => 'views-view-vvjt-fields',
      'path' => $path . '/templates',
    ],
    'views_view_vvjt' => [
      'variables' => [
        'view' => NULL,
        'rows' => [],
        'options' => [],
      ],
      'template' => 'views-view-vvjt',
      'path' => $path . '/templates',
    ],
  ];
}

/**
 * Implements hook_preprocess_HOOK() for views_view_vvjt.
 */
function template_preprocess_views_view_vvjt(array &$variables): void {
  \Drupal::moduleHandler()->loadInclude('views', 'inc', 'views.theme');

  $handler = $variables['view']->style_plugin ?? NULL;
  if (!$handler) {
    \Drupal::logger('vvjt')->error('Style plugin handler not found in view preprocessing.');
    return;
  }

  $options = $handler->options ?? [];

  $variables['list_attributes'] = _vvjt_build_data_attributes($options);
  $variables['settings'] = _vvjt_build_template_settings($handler, $options);

  _vvjt_customize_row_theme_suggestions($variables);

  template_preprocess_views_view_unformatted($variables);
}

/**
 * Build data attributes array from tabs options.
 *
 * @param array $options
 *   The tabs configuration options.
 *
 * @return \Drupal\Core\Template\Attribute
 *   Attribute object containing data attributes.
 */
function _vvjt_build_data_attributes(array $options): Attribute {
  $list_attributes = [];

  foreach (VvjtConstants::DATA_ATTRIBUTE_MAP as $option_key => $data_key) {
    if (!empty($options[$option_key])) {
      $list_attributes[VvjtConstants::DATA_ATTRIBUTE_PREFIX . $data_key] = $options[$option_key];
    }
  }

  foreach (VvjtConstants::BOOLEAN_ATTRIBUTE_MAP as $option_key => $data_key) {
    if (isset($options[$option_key])) {
      $list_attributes[VvjtConstants::DATA_ATTRIBUTE_PREFIX . $data_key] = $options[$option_key] ? 'true' : 'false';
    }
  }

  if (!empty($options['background_buttons'])) {
    $list_attributes[VvjtConstants::DATA_ATTRIBUTE_PREFIX . 'background-buttons'] = $options['background_buttons'];
  }

  if (!empty($options['background_panes'])) {
    $list_attributes[VvjtConstants::DATA_ATTRIBUTE_PREFIX . 'background-panes'] = $options['background_panes'];
  }

  return new Attribute($list_attributes);
}

/**
 * Build template settings array.
 *
 * @param object $handler
 *   The style plugin handler.
 * @param array $options
 *   The tabs configuration options.
 *
 * @return array
 *   Settings array for template use.
 */
function _vvjt_build_template_settings(object $handler, array $options): array {
  $view_id = $handler->view->dom_id ?? 'unknown';

  return [
    'view_id' => $view_id,
    'animation' => $options['animation'] ?? '',
    'wrap_tabs' => $options['wrap_tabs'] ?? FALSE,
    'max_width' => $options['max_width'] ?? 0,
    'max_height' => $options['max_height'] ?? 0,
    'available_breakpoints' => $options['available_breakpoints'] ?? '',
    'unique_id' => $options['unique_id'] ?? 0,
    'tabs_position' => $options['tabs_position'] ?? 'top',
    'enable_css' => $options['enable_css'] ?? TRUE,
    'disable_background' => $options['disable_background'] ?? FALSE,
    'background_buttons' => $options['background_buttons'] ?? '',
    'background_panes' => $options['background_panes'] ?? '',
    'enable_deeplink' => $options['enable_deeplink'] ?? FALSE,
    'deeplink_identifier' => $options['deeplink_identifier'] ?? '',
  ];
}

/**
 * Customize theme hook suggestions for tabs rows.
 *
 * @param array $variables
 *   Template variables array (passed by reference).
 */
function _vvjt_customize_row_theme_suggestions(array &$variables): void {
  if (empty($variables['rows'])) {
    return;
  }

  foreach ($variables['rows'] as $key => $row) {
    if (!isset($row['#theme']) || !is_array($row['#theme'])) {
      continue;
    }

    foreach ($row['#theme'] as $idx => $theme_hook_suggestion) {
      $variables['rows'][$key]['#theme'][$idx] = str_replace(
        'views_view_fields',
        'views_view_vvjt_fields',
        $theme_hook_suggestion
      );
    }
  }
}

/**
 * Prepares variables for views_view_vvjt_fields template.
 *
 * @param array $variables
 *   Template variables array (passed by reference).
 */
function template_preprocess_views_view_vvjt_fields(array &$variables): void {
  \Drupal::moduleHandler()->loadInclude('views', 'inc', 'views.theme');
  template_preprocess_views_view_fields($variables);
}

/**
 * Implements hook_preprocess_views_view().
 */
function vvjt_preprocess_views_view(array &$variables): void {
  if (isset($variables['view']->style_plugin) &&
      $variables['view']->style_plugin instanceof ViewsVanillaJavascriptTabs) {
    $variables['attributes']['class'][] = 'vvj-tabs';
  }
}

/**
 * Implements hook_views_data_alter().
 */
function vvjt_views_data_alter(array &$data): void {
  $data['views_style_plugin']['views_vvjt'] = [
    'type' => 'views_style',
    'label' => t('Views Vanilla JavaScript Tabs'),
    'mapping' => [
      'animation' => [
        'type' => VvjtConstants::VIEWS_TYPE_STRING,
        'label' => t('Animation Type'),
        'description' => t('Type of animation effect used when switching between tabs.'),
        'constraints' => [
          'Choice' => ['none', 'a-top', 'a-bottom', 'a-left', 'a-right', 'a-zoom', 'a-opacity'],
        ],
      ],
      'tabs_position' => [
        'type' => VvjtConstants::VIEWS_TYPE_STRING,
        'label' => t('Tabs Position'),
        'description' => t('Position of the tab buttons relative to content.'),
        'constraints' => [
          'Choice' => ['top', 'right', 'bottom', 'left'],
        ],
      ],
      'max_height' => [
        'type' => VvjtConstants::VIEWS_TYPE_INTEGER,
        'label' => t('Max Height (pixels)'),
        'description' => t('Maximum height for vertical tabs buttons container.'),
      ],
      'max_width' => [
        'type' => VvjtConstants::VIEWS_TYPE_INTEGER,
        'label' => t('Max Width (pixels)'),
        'description' => t('Maximum width for tabs buttons.'),
      ],
      'available_breakpoints' => [
        'type' => VvjtConstants::VIEWS_TYPE_STRING,
        'label' => t('Responsive Breakpoint'),
        'description' => t('Breakpoint at which vertical tabs collapse to horizontal.'),
        'constraints' => [
          'Choice' => ['576', '768', '992', '1200', '1400'],
        ],
      ],
      'unique_id' => [
        'type' => VvjtConstants::VIEWS_TYPE_INTEGER,
        'label' => t('Unique Tabs Identifier'),
        'description' => t('Unique numeric identifier for this tabs instance (auto-generated).'),
        'constraints' => [
          'Range' => [
            'min' => 10000000,
            'max' => 99999999,
          ],
        ],
      ],
      'enable_css' => [
        'type' => VvjtConstants::VIEWS_TYPE_BOOLEAN,
        'label' => t('Enable Default CSS Styling'),
        'description' => t('Include the default CSS library for tabs styling.'),
      ],
      'wrap_tabs' => [
        'type' => VvjtConstants::VIEWS_TYPE_BOOLEAN,
        'label' => t('Wrap Tab Buttons'),
        'description' => t('Wrap tab buttons when they exceed available space.'),
      ],
      'disable_background' => [
        'type' => VvjtConstants::VIEWS_TYPE_BOOLEAN,
        'label' => t('Disable Background Colors'),
        'description' => t('Disable background colors for buttons and panes.'),
      ],
      'background_buttons' => [
        'type' => VvjtConstants::VIEWS_TYPE_STRING,
        'label' => t('Buttons Background Color'),
        'description' => t('Hex color code for tab buttons background.'),
        'constraints' => [
          'Regex' => [
            'pattern' => '/^#[0-9A-Fa-f]{6}$/',
            'message' => t('Must be a valid 6-digit hex color code (e.g., #ECECEC).'),
          ],
        ],
      ],
      'background_panes' => [
        'type' => VvjtConstants::VIEWS_TYPE_STRING,
        'label' => t('Panes Background Color'),
        'description' => t('Hex color code for content panes background.'),
        'constraints' => [
          'Regex' => [
            'pattern' => '/^#[0-9A-Fa-f]{6}$/',
            'message' => t('Must be a valid 6-digit hex color code (e.g., #F7F7F7).'),
          ],
        ],
      ],
    ],
  ];
}

/**
 * Implements hook_token_info().
 */
function vvjt_token_info(): array {
  return [
    'tokens' => [
      'view' => [
        VvjtConstants::TOKEN_NAMESPACE => [
          'name' => t('VVJT field output'),
          'description' => t("Use these tokens when you enable 'Use replacement tokens from the first row' in Views text areas such as the header, footer, or empty text. Use [vvjt:field_name] for rendered output, or [vvjt:field_name:plain] to strip HTML and return plain text. These tokens pull values from the first row of the View result."),
        ],
      ],
    ],
  ];
}

/**
 * Implements hook_tokens().
 */
function vvjt_tokens(string $type, array $tokens, array $data = [], array $options = []): array {
  $replacements = [];

  if (!_vvjt_validate_token_context($type, $data)) {
    return $replacements;
  }

  $view = $data['view'];

  if (empty($view->result)) {
    return $replacements;
  }

  if (!($view->style_plugin instanceof ViewsVanillaJavascriptTabs)) {
    return $replacements;
  }

  try {
    $first_row = $view->result[0];
    $field_handlers = $view->display_handler->getHandlers('field');
    $renderer = \Drupal::service('renderer');

    foreach ($tokens as $token => $name) {
      if (!preg_match(VvjtConstants::TOKEN_PATTERN, $token)) {
        \Drupal::logger('vvjt')->warning('Invalid token format rejected: @token', [
          '@token' => $token,
        ]);
        continue;
      }

      $replacement = _vvjt_process_single_token($token, $first_row, $field_handlers, $renderer);
      if ($replacement !== NULL) {
        $replacements["[" . VvjtConstants::TOKEN_NAMESPACE . ":$token]"] = $replacement;
      }
    }
  }
  catch (\Exception $e) {
    \Drupal::logger('vvjt')->error('Error processing VVJT tokens: @message', [
      '@message' => $e->getMessage(),
    ]);
  }

  return $replacements;
}

/**
 * Validate token processing context.
 *
 * @param string $type
 *   Token type.
 * @param array $data
 *   Token data array.
 *
 * @return bool
 *   TRUE if context is valid for processing.
 */
function _vvjt_validate_token_context(string $type, array $data): bool {
  return $type === 'view' &&
         isset($data['view']) &&
         $data['view'] instanceof ViewExecutable;
}

/**
 * Process a single token and return its replacement value.
 *
 * @param string $token
 *   The token to process.
 * @param object $first_row
 *   The first row of view results.
 * @param array $field_handlers
 *   Array of field handlers.
 * @param object $renderer
 *   The renderer service.
 *
 * @return \Drupal\Component\Render\MarkupInterface|string|null
 *   The replacement value or NULL if token cannot be processed.
 */
function _vvjt_process_single_token(string $token, object $first_row, array $field_handlers, object $renderer) {
  $plain = FALSE;
  $field_id = $token;

  if (str_ends_with($token, VvjtConstants::TOKEN_PLAIN_SUFFIX)) {
    $plain = TRUE;
    $field_id = substr($token, 0, -strlen(VvjtConstants::TOKEN_PLAIN_SUFFIX));
  }

  if (!isset($field_handlers[$field_id])) {
    return NULL;
  }

  try {
    $handler = $field_handlers[$field_id];

    $value = $plain && method_exists($handler, 'advancedRenderText')
      ? $handler->advancedRenderText($first_row)
      : $handler->advancedRender($first_row);

    if (is_array($value)) {
      $rendered = $renderer->renderPlain($value);
    }
    else {
      $rendered = (string) $value;
    }

    return $plain
      ? Html::decodeEntities(strip_tags($rendered))
      : Markup::create($rendered);

  }
  catch (\Throwable $e) {
    \Drupal::logger('vvjt')->warning('Token processing failed for field @field: @message', [
      '@field' => $field_id,
      '@message' => $e->getMessage(),
    ]);
    return '';
  }
}

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

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