selectify-1.0.3/selectify.module
selectify.module
<?php
/**
* @file
* Provides the module implementation for selectify.
*
* Filename: selectify.module
* Website: https://www.flashwebcenter.com
* Description: template.
* Developer: Alaa Haddad https://www.alaahaddad.com.
*/
use Drupal\Core\Template\Attribute;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_preprocess_html().
*/
function selectify_preprocess_html(&$variables) {
// Get the Selectify configuration to check if radio/checkbox is enabled.
$config = \Drupal::config('selectify.settings');
$radioEnabled = $config->get('enable_radio');
$checkboxEnabled = $config->get('enable_checkbox');
// Only add classes if at least one is enabled.
if (!$radioEnabled && !$checkboxEnabled) {
return;
}
// Get CSS classes from the style service.
/** @var \Drupal\selectify\Service\SelectifyStyleService $styleService */
$styleService = \Drupal::service('selectify.style_service');
$bodyClasses = $styleService->getBodyClasses();
// Add classes to the body tag.
if (!empty($bodyClasses)) {
$classes = explode(' ', $bodyClasses);
foreach ($classes as $class) {
$variables['attributes']['class'][] = $class;
}
}
}
/**
* Implements hook_help().
*/
function selectify_help($route_name, RouteMatchInterface $route_match): ?string {
if ($route_name === 'help.page.selectify') {
return _selectify_helper_render_readme();
}
return NULL;
}
/**
* Helper function to render README.md.
*
* @return string
* The rendered content of README.md.
*/
function _selectify_helper_render_readme(): string {
$readme_path = __DIR__ . '/README.md';
$text = file_get_contents($readme_path);
if ($text === FALSE) {
return t('README.md file not found.');
}
if (!\Drupal::moduleHandler()->moduleExists('markdown')) {
return '<pre>' . htmlspecialchars($text) . '</pre>';
}
// Use the Markdown filter to render the README.
$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();
}
/**
* Implements hook_theme_suggestions_select_alter().
*/
function selectify_theme_suggestions_select_alter(array &$suggestions, array $variables) {
// Ensure the element exists and has attributes.
if (empty($variables['element']['#attributes']['class'])) {
return;
}
$classes = (array) $variables['element']['#attributes']['class'];
// Remove `form_element` if it's applied.
foreach ($suggestions as $key => $suggestion) {
if (strpos($suggestion, 'form_element') !== FALSE) {
unset($suggestions[$key]);
}
}
// Add the correct select template based on class.
if (in_array('selectify-apply-dropdown', $classes)) {
$suggestions[] = 'select__selectify_dropdown';
}
if (in_array('selectify-apply-checkbox', $classes)) {
$suggestions[] = 'select__selectify_dropdown_checkbox';
}
if (in_array('selectify-apply-searchable', $classes)) {
$suggestions[] = 'select__selectify_dropdown_searchable';
}
if (in_array('selectify-apply-tags', $classes)) {
$suggestions[] = 'select__selectify_dropdown_tags';
}
if (in_array('selectify-apply-dual', $classes)) {
$suggestions[] = 'select__selectify_dual';
}
}
/**
* Implements hook_theme().
*/
function selectify_theme($existing, $type, $theme, $path) {
return [
'select__selectify_dropdown' => [
'base hook' => 'select',
'template' => 'select--selectify-dropdown',
'path' => $path . '/templates',
],
'select__selectify_dropdown_checkbox' => [
'base hook' => 'select',
'template' => 'select--selectify-dropdown-checkbox',
'path' => $path . '/templates',
],
'select__selectify_dropdown_searchable' => [
'base hook' => 'select',
'template' => 'select--selectify-dropdown-searchable',
'path' => $path . '/templates',
],
'select__selectify_dropdown_tags' => [
'base hook' => 'select',
'template' => 'select--selectify-dropdown-tags',
'path' => $path . '/templates',
],
'select__selectify_dual' => [
'base hook' => 'select',
'template' => 'select--selectify-dual',
'path' => $path . '/templates',
],
];
}
/**
* Preprocess function for Selectify form widgets.
*/
function selectify_preprocess_select__selectify_dropdown(&$variables) {
_selectify_preprocess_widget($variables);
}
/**
* Preprocess function for Selectify form widgets.
*/
function selectify_preprocess_select__selectify_dropdown_checkbox(&$variables) {
_selectify_preprocess_widget($variables);
}
/**
* Preprocess function for Selectify form widgets.
*/
function selectify_preprocess_select__selectify_dropdown_searchable(&$variables) {
_selectify_preprocess_widget($variables);
}
/**
* Preprocess function for Selectify form widgets.
*/
function selectify_preprocess_select__selectify_dropdown_tags(&$variables) {
_selectify_preprocess_widget($variables);
}
/**
* Preprocess function for Selectify form widgets.
*/
function selectify_preprocess_select__selectify_dual(&$variables) {
_selectify_preprocess_widget($variables);
}
/**
* Cleans a theme name to allow only letters, numbers, and dashes.
*/
function selectify_clean_theme_name($name) {
// Convert spaces and underscores to dashes.
$name = str_replace([' ', '_'], '-', $name);
// Remove any character that is not a letter, number, or dash.
return preg_replace('/[^a-zA-Z0-9-]/', '', $name);
}
/**
* Recursively finds the topmost base theme (the original parent).
*/
function selectify_get_root_parent_theme($theme_name) {
$theme_handler = \Drupal::service('theme_handler');
$theme_info = $theme_handler->getTheme($theme_name);
// If this theme has a base theme, continue checking recursively.
if (!empty($theme_info->info['base theme'])) {
$base_themes = (array) $theme_info->info['base theme'];
// Recursive call.
return selectify_get_root_parent_theme(reset($base_themes));
}
// If no base theme, this is the root theme.
return $theme_name;
}
/**
* Common preprocessing logic for all Selectify widgets.
*/
function _selectify_preprocess_widget(array &$variables) {
if (!isset($variables['element'])) {
return;
}
$element = $variables['element'];
$raw_attributes = $element['#attributes'] ?? [];
$variables['attributes'] = new Attribute($raw_attributes);
// Determine if a visible label exists.
$has_label = !empty($element['#title']) && ($element['#label_display'] ?? 'visible') !== 'hidden';
$variables['has_label'] = $has_label;
if ($has_label) {
$label_id = $raw_attributes['id'] . '-label';
$variables['label_id'] = $label_id;
}
$variables['has_description'] = !empty($element['#description']);
// Generate fallback aria-label if no label will be printed.
if (!$has_label && isset($raw_attributes['id'])) {
$id = (string) $raw_attributes['id'];
$clean_label = preg_replace('/^edit-/', '', $id);
$clean_label = ucwords(str_replace('-', ' ', $clean_label));
$variables['aria_label'] = t('Select @label', ['@label' => $clean_label]);
}
// Detect if it's part of a Views exposed form.
$is_views = !empty($raw_attributes['class']) && in_array('selectify-views', (array) $raw_attributes['class']);
$variables['is_views'] = $is_views;
if ($is_views) {
$variables['unique_id'] = $raw_attributes['id'] ?? '';
$variables['is_multiple'] = ($raw_attributes['multiple'] ?? '') === 'multiple';
$variables['max_selected'] = $variables['is_multiple'] ? NULL : 1;
$variables['field_name'] = $raw_attributes['name'] ?? '';
$variables['views_attributes'] = [
'data-drupal-selector' => $raw_attributes['data-drupal-selector'] ?? '',
'class' => implode(' ', (array) $raw_attributes['class'] ?? []),
'size' => $raw_attributes['size'] ?? '',
'aria-invalid' => $raw_attributes['aria-invalid'] ?? '',
];
}
else {
$variables['unique_id'] = $raw_attributes['id'] ?? 'selectify-default-id';
$variables['field_name'] = $raw_attributes['data-field-name'] ?? 'unknown';
$variables['max_selected'] = $raw_attributes['data-max-selections'] ?? NULL;
$variables['is_multiple'] = filter_var($raw_attributes['data-multiple'] ?? FALSE, FILTER_VALIDATE_BOOLEAN);
$variables['placeholder'] = $raw_attributes['data-placeholder'] ?? '';
$variables['is_required'] = filter_var($raw_attributes['aria-required'] ?? FALSE, FILTER_VALIDATE_BOOLEAN);
$variables['views_attributes'] = [];
}
// Generate clean field name for use in IDs.
$clean_field_name = str_replace(['_', '[]'], ['-', ''], $variables['field_name']);
$variables['clean_field_name'] = $clean_field_name;
// background:Get the actual active theme for the request.
$theme_manager = \Drupal::service('theme.manager');
$active_theme = $theme_manager->getActiveTheme();
// The theme currently in use.
$theme_name = $active_theme->getName();
// background:Get the root parent theme.
$parent_theme = selectify_get_root_parent_theme($theme_name);
// Add the theme classes, ensuring the real parent is first.
$theme_classes = [];
if (!empty($parent_theme) && $parent_theme !== $theme_name) {
// background:Print the last (root) parent.
$theme_classes[] = 'theme-parent-' . selectify_clean_theme_name($parent_theme);
}
// background:Print the currently active theme.
$theme_classes[] = 'theme-' . selectify_clean_theme_name($theme_name);
// Get the Selectify checkbox configuration.
$config = selectify_get_radio_checkbox_config();
$variables['enable_checkbox'] = !empty($config['enable_checkbox']);
// Merge theme classes.
$variables['attributes']->addClass($theme_classes);
}
/**
* Retrieves Selectify settings with admin route check.
*
* @return array|false
* Returns array of settings if Selectify should be applied, otherwise false.
*/
function selectify_get_radio_checkbox_config() {
$config = \Drupal::config('selectify.settings');
$is_admin = \Drupal::service('router.admin_context')->isAdminRoute();
// Check if Disable on Admin Route is enabled and if we are on an admin page.
if ($config->get('disable_on_admin_routes') && $is_admin) {
// Prevent applying styles and libraries.
return FALSE;
}
return [
'enable_radio' => $config->get('enable_radio'),
'enable_checkbox' => $config->get('enable_checkbox'),
'disable_on_admin_routes' => $config->get('disable_on_admin_routes'),
'accent_color' => $config->get('accent_color'),
];
}
/**
* Implements hook_preprocess_form_element() for Selectify.
*/
function selectify_preprocess_form_element(array &$variables): void {
$config = selectify_get_radio_checkbox_config();
// If Selectify is disabled (e.g., on admin routes), stop processing.
if ($config === FALSE) {
return;
}
if (!isset($variables['element']['#type'])) {
return;
}
$type = $variables['element']['#type'];
// Add the correct wrapper class only if the feature is enabled.
if ($type === 'radio' && ($config['enable_radio'] === TRUE)) {
$variables['attributes']['class'][] = 'selectify-form-item selectify-radio-wrapper';
}
elseif ($type === 'checkbox' && ($config['enable_checkbox'] === TRUE)) {
$variables['attributes']['class'][] = 'selectify-form-item selectify-checkbox-wrapper';
}
}
/**
* Implements hook_preprocess_input() for Selectify.
*/
function selectify_preprocess_input(array &$variables): void {
$config = selectify_get_radio_checkbox_config();
// If Selectify is disabled (e.g., on admin routes), stop processing.
if ($config === FALSE) {
return;
}
if (!isset($variables['attributes']['type'])) {
return;
}
$type = $variables['attributes']['type'];
// Add the correct input class only if the feature is enabled.
if ($type === 'radio' && ($config['enable_radio'] === TRUE)) {
$variables['attributes']['class'][] = 'selectify-radio';
}
elseif ($type === 'checkbox' && ($config['enable_checkbox'] === TRUE)) {
$variables['attributes']['class'][] = 'selectify-checkbox';
}
}
/**
* Implements hook_page_attachments() for Selectify.
*/
function selectify_page_attachments(array &$attachments) {
$config = selectify_get_radio_checkbox_config();
// If the function returns FALSE, do not attach any libraries.
if ($config === FALSE) {
return;
}
// Attach base styles only if radio or checkbox styling is enabled.
if (($config['enable_checkbox'] === TRUE) || ($config['enable_radio'] === TRUE)) {
$attachments['#attached']['library'][] = 'selectify/selectify-base';
$attachments['#attached']['library'][] = 'selectify/selectify-radio-checkbox';
$selected_accent = $config['accent_color'];
if (!empty($selected_accent) && $selected_accent !== 'none') {
$accent_color_mode = \Drupal::config('selectify.settings')->get('accent_color_mode') ?: 'light';
$attachments['#attached']['library'][] = "selectify/selectify-color-{$selected_accent}-{$accent_color_mode}";
}
}
}
/**
* Adds the 'selectify-views' class to all select elements Views exposed form.
*
* @param array &$form
* The Views exposed form array.
*/
function selectify_add_views_class(array &$form) {
foreach ($form as &$element) {
if (is_array($element) && isset($element['#type']) && $element['#type'] === 'select') {
// Ensure #attributes array exists.
$element['#attributes'] = $element['#attributes'] ?? [];
// Ensure class array exists.
$element['#attributes']['class'] = $element['#attributes']['class'] ?? [];
// Add the selectify-views class.
$element['#attributes']['class'][] = 'selectify-views';
}
}
}
/**
* Loads Selectify configuration settings.
*
* @return array
* An associative array containing Selectify settings.
*/
function selectify_load_config() {
$config = \Drupal::config('selectify.settings');
return [
'disable_on_admin_routes' => $config->get('disable_on_admin_routes'),
'widget_map' => $config->get('widget_for_filters') ?? [],
'apply_site_wide' => $config->get('apply_site_wide'),
'global_widget' => $config->get('global_selectify_widget'),
];
}
/**
* Checks if Selectify is disabled on admin routes.
*
* @return bool
* TRUE if Selectify is disabled on an admin route, FALSE otherwise.
*/
function selectify_disabled_on_admin_routes() {
// Fetch the configuration.
$config = selectify_load_config();
$disable_on_admin_routes = $config['disable_on_admin_routes'];
if ($disable_on_admin_routes && \Drupal::routeMatch()->getRouteName() !== NULL) {
$current_path = \Drupal::service('path.current')->getPath();
if (strpos($current_path, '/admin') === 0) {
// \Drupal::messenger()->addMessage("Selectify is disabled on admin
// routes. Skipping.", 'warning');
return TRUE;
}
}
return FALSE;
}
/**
* Extracts and normalizes the form ID by replacing underscores with hyphens.
*
* @param array $form
* The Views exposed form array.
*
* @return string
* The normalized form ID.
*/
function selectify_normalize_form_id(array $form) {
$form_id = $form['#id'] ?? ($form['#attributes']['id'] ?? '');
if (is_array($form_id)) {
$form_id = reset($form_id);
}
return str_replace('_', '-', $form_id);
}
/**
* Retrieves the library dependencies for each Selectify widget type.
*
* @return array
* An associative array where keys are widget names and values are arrays
*/
function selectify_get_widget_libraries() {
return [
'selectify_dropdown' => [
'selectify/selectify-base',
'selectify/selectify-helper',
'selectify/selectify-dropdowns',
'selectify/selectify-dropdown',
],
'selectify_tags' => [
'selectify/selectify-base',
'selectify/selectify-helper',
'selectify/selectify-dropdowns',
'selectify/selectify-dropdown-tags',
],
'selectify_searchable' => [
'selectify/selectify-base',
'selectify/selectify-helper',
'selectify/selectify-dropdowns',
'selectify/selectify-dropdown-searchable',
],
'selectify_dual' => [
'selectify/selectify-base',
'selectify/selectify-helper',
'selectify/selectify-dual',
],
'selectify_checkbox' => [
'selectify/selectify-base',
'selectify/selectify-helper',
'selectify/selectify-dropdowns',
'selectify/selectify-dropdown-checkbox',
],
];
}
/**
* Applies the global Selectify widget if site-wide setting is enabled.
*
* @param array &$form
* The Views exposed form array.
* @param string $global_widget
* The global widget type.
* @param array $widget_libraries
* The widget-to-library mapping.
* @param array $widget_classes
* The widget-to-class mapping.
*/
function selectify_global_widget(array &$form, $global_widget, array $widget_libraries, array $widget_classes) {
if (!empty($global_widget) && isset($widget_libraries[$global_widget])) {
// \Drupal::messenger()->addMessage("Applying Global Widget:
// {$global_widget}", 'status');
// Attach libraries.
foreach ($widget_libraries[$global_widget] as $library) {
$form['#attached']['library'][] = $library;
}
// \Drupal::messenger()->addMessage("Global Libraries
// Attached: " . implode(', ', $widget_libraries[$global_widget]),
// 'status');
// Apply widget-specific class if available.
if (isset($widget_classes[$global_widget])) {
$form['#attributes']['class'][] = 'selectify-views-form';
foreach ($form as &$element) {
if (is_array($element) && isset($element['#type']) && $element['#type'] === 'select') {
$element['#attributes']['class'][] = $widget_classes[$global_widget];
}
}
}
}
}
/**
* Applies Selectify widgets based on the Views exposed filter configuration.
*
* @param array &$form
* The Views exposed form array.
* @param array $widget_map
* The stored configuration mapping of Views filters to widgets.
* @param string $clean_form_id
* The current normalized form ID.
* @param array $widget_libraries
* The widget-to-library mapping.
* @param array $widget_classes
* The widget-to-class mapping.
*/
function selectify_single_widget(array &$form, array $widget_map, $clean_form_id, array $widget_libraries, array $widget_classes) {
foreach ($widget_map as $widget_data) {
$stored_exposed_form_id = $widget_data['exposed_form_id'] ?? '';
$normalized_stored_form_id = str_replace('_', '-', $stored_exposed_form_id);
if (!empty($normalized_stored_form_id) && $normalized_stored_form_id === $clean_form_id) {
$selected_widget = $widget_data['widget'];
if (isset($widget_libraries[$selected_widget])) {
foreach ($widget_libraries[$selected_widget] as $library) {
$form['#attached']['library'][] = $library;
}
}
// Apply widget class if available.
if (isset($widget_classes[$selected_widget])) {
$form['#attributes']['class'][] = 'selectify-views-form';
foreach ($form as &$element) {
if (is_array($element) && isset($element['#type']) && $element['#type'] === 'select') {
$element['#attributes']['class'][] = $widget_classes[$selected_widget];
}
}
}
}
}
}
/**
* Check if the current page matches any disabled page pattern.
*
* @return bool
* TRUE if the current page should have Selectify disabled, FALSE otherwise.
*/
function selectify_is_page_disabled(): bool {
// Use static caching to avoid multiple checks per request.
static $is_disabled = NULL;
if ($is_disabled !== NULL) {
return $is_disabled;
}
$config = \Drupal::config('selectify.settings');
$disabled_pages = $config->get('disabled_pages') ?? '';
// If no disabled pages configured, return FALSE.
if (empty(trim($disabled_pages))) {
$is_disabled = FALSE;
return $is_disabled;
}
/** @var \Drupal\Core\Path\PathMatcherInterface $path_matcher */
$path_matcher = \Drupal::service('path.matcher');
/** @var \Drupal\Core\Path\CurrentPathStack $current_path_service */
$current_path_service = \Drupal::service('path.current');
$current_path = ltrim($current_path_service->getPath(), '/');
/** @var \Drupal\path_alias\AliasManagerInterface $alias_manager */
$alias_manager = \Drupal::service('path_alias.manager');
$path_alias = ltrim($alias_manager->getAliasByPath('/' . $current_path), '/');
// Check both system path and alias.
$is_disabled = $path_matcher->matchPath($current_path, $disabled_pages)
|| ($path_alias !== $current_path && $path_matcher->matchPath($path_alias, $disabled_pages));
return $is_disabled;
}
/**
* Implements hook_page_attachments_alter().
*/
function selectify_page_attachments_alter(array &$attachments): void {
// Check if current page is in the disabled pages list.
if (selectify_is_page_disabled()) {
// Remove all Selectify libraries.
if (isset($attachments['#attached']['library'])) {
foreach ($attachments['#attached']['library'] as $key => $library) {
if (is_string($library) && strpos($library, 'selectify/') === 0) {
unset($attachments['#attached']['library'][$key]);
}
}
}
// Remove any Selectify drupalSettings.
if (isset($attachments['#attached']['drupalSettings']['selectify'])) {
unset($attachments['#attached']['drupalSettings']['selectify']);
}
return;
}
// Load configuration.
$config = \Drupal::config('selectify.settings');
// Attach radio-checkbox library and inject CSS variables if enabled.
$radioEnabled = (bool) $config->get('enable_radio');
$checkboxEnabled = (bool) $config->get('enable_checkbox');
if ($radioEnabled || $checkboxEnabled) {
// Attach the base radio-checkbox library.
$attachments['#attached']['library'][] = 'selectify/selectify-radio-checkbox';
// Generate and attach inline CSS variables.
/** @var \Drupal\selectify\Service\SelectifyStyleService $styleService */
$styleService = \Drupal::service('selectify.style_service');
$inlineCSS = $styleService->getConfiguredInlineCss();
if (!empty($inlineCSS)) {
$attachments['#attached']['html_head'][] = [
[
'#tag' => 'style',
'#value' => $inlineCSS,
'#attributes' => ['id' => 'selectify-inline-variables'],
],
'selectify_inline_variables',
];
}
// Attach accent color library if configured.
$accentColor = $config->get('accent_color');
$accentColorMode = $config->get('accent_color_mode') ?? 'light';
if (!empty($accentColor) && $accentColor !== 'none') {
$colorLibrary = "selectify/selectify-color-{$accentColor}-{$accentColorMode}";
$attachments['#attached']['library'][] = $colorLibrary;
}
}
// Normal Selectify library attachment for non-disabled pages.
$gin_info = selectify_detect_gin_theme();
if ($gin_info && $gin_info['is_gin']) {
// Attach base Gin library.
$attachments['#attached']['library'][] = 'selectify/selectify-gin-base';
// Attach mode-specific library based on color scheme.
$color_scheme = $gin_info['color_scheme'];
if ($color_scheme === 'dark') {
$attachments['#attached']['library'][] = 'selectify/selectify-gin-dark';
}
elseif ($color_scheme === 'light') {
$attachments['#attached']['library'][] = 'selectify/selectify-gin-light';
}
elseif ($color_scheme === 'auto') {
$attachments['#attached']['library'][] = 'selectify/selectify-gin-auto';
}
// Pass color scheme and accent color to JavaScript.
$attachments['#attached']['drupalSettings']['selectify']['gin'] = [
'colorScheme' => $color_scheme,
'accentColor' => $gin_info['accent_color'],
];
}
// Detect Claro theme.
$theme_manager = \Drupal::service('theme.manager');
$active_theme = $theme_manager->getActiveTheme();
$active_theme_name = $active_theme->getName();
// Check if Claro theme or Claro sub-theme.
$is_claro = FALSE;
if ($active_theme_name === 'claro') {
$is_claro = TRUE;
}
else {
$base_themes = $active_theme->getBaseThemeExtensions();
foreach ($base_themes as $base_theme) {
if ($base_theme->getName() === 'claro') {
$is_claro = TRUE;
break;
}
}
}
if ($is_claro) {
$attachments['#attached']['library'][] = 'selectify/selectify-claro';
}
// Check if Olivero theme or Olivero sub-theme.
$is_olivero = FALSE;
if ($active_theme_name === 'olivero') {
$is_olivero = TRUE;
}
else {
$base_themes = $active_theme->getBaseThemeExtensions();
foreach ($base_themes as $base_theme) {
if ($base_theme->getName() === 'olivero') {
$is_olivero = TRUE;
break;
}
}
}
if ($is_olivero) {
$attachments['#attached']['library'][] = 'selectify/selectify-olivero';
}
}
/**
* Better Gin color scheme detection using DOM attribute.
*/
function selectify_get_gin_color_scheme() {
// Gin sets data-gin-color-scheme on the HTML element
// Values: 'dark', 'light', or 'auto'.
$gin_config = \Drupal::config('gin.settings');
$enable_darkmode = (int) ($gin_config->get('enable_darkmode') ?? 0);
// Gin darkmode values:
// 0 = Light mode
// 1 = Dark mode
// 2 = Auto (system preference)
if ($enable_darkmode === 1) {
return 'dark';
}
elseif ($enable_darkmode === 2) {
return 'auto';
}
else {
return 'light';
}
}
/**
* Detects if Gin theme is active and returns color scheme.
*
* @return array|false
* Array with 'is_gin', 'color_scheme', and 'accent_color' keys, or FALSE.
*/
function selectify_detect_gin_theme() {
$theme_manager = \Drupal::service('theme.manager');
$active_theme = $theme_manager->getActiveTheme();
$active_theme_name = $active_theme->getName();
// Check if the active theme is Gin or a Gin sub-theme.
$is_gin = FALSE;
if ($active_theme_name === 'gin') {
$is_gin = TRUE;
}
else {
// Check if any base theme is Gin.
$base_themes = $active_theme->getBaseThemeExtensions();
foreach ($base_themes as $base_theme) {
if ($base_theme->getName() === 'gin') {
$is_gin = TRUE;
break;
}
}
}
// If not Gin or Gin sub-theme, return FALSE.
if (!$is_gin) {
return FALSE;
}
// Get Gin configuration.
$gin_config = \Drupal::config('gin.settings');
// Get accent color.
$accent_color = $gin_config->get('preset_accent_color') ?? 'blue';
// Get dark mode setting (cast to int since config stores as string)
$dark_mode = (int) ($gin_config->get('enable_darkmode') ?? 0);
// Determine color scheme
// 0 = Light mode
// 1 = Dark mode
// 2 = Auto (system preference)
$color_scheme = 'light';
if ($dark_mode === 2) {
$color_scheme = 'auto';
}
elseif ($dark_mode === 1) {
$color_scheme = 'dark';
}
return [
'is_gin' => TRUE,
'color_scheme' => $color_scheme,
'accent_color' => $accent_color,
];
}
/**
* Implements hook_preprocess_container().
*/
function selectify_preprocess_container(array &$variables): void {
if (empty($variables['attributes'])) {
return;
}
$attr = $variables['attributes'];
$classes = [];
// Normalize classes to a clean string[] in all cases.
if ($attr instanceof Attribute) {
$classes = $attr->offsetGet('class') ?? [];
}
elseif (is_array($attr) && array_key_exists('class', $attr)) {
$classes = $attr['class'];
}
elseif (isset($attr['class'])) {
$classes = $attr['class'];
}
if (is_string($classes)) {
// Split on any whitespace, drop empties.
$classes = preg_split('/\s+/', trim($classes), -1, PREG_SPLIT_NO_EMPTY) ?: [];
}
elseif (!is_array($classes)) {
$classes = [];
}
if (!$classes) {
return;
}
// Detect Selectify widget container and add single/multi class.
foreach ($classes as $class) {
if (is_string($class) && str_starts_with($class, 'field--widget-selectify-')) {
$max = $variables['element']['widget'][0]['#attributes']['data-max-selections'] ?? NULL;
$new_class = ($max === '1') ? 'selectify-single' : 'selectify-multi';
if ($attr instanceof Attribute) {
$variables['attributes']->addClass($new_class);
}
else {
// Ensure the destination is an array.
if (!isset($variables['attributes']['class']) || !is_array($variables['attributes']['class'])) {
$variables['attributes']['class'] = (array) ($variables['attributes']['class'] ?? []);
}
$variables['attributes']['class'][] = $new_class;
}
break;
}
}
}
/**
* Implements hook_preprocess_views_exposed_form() for Selectify.
*/
function selectify_preprocess_views_exposed_form(&$variables) {
$form = &$variables['form'];
if (selectify_is_page_disabled()) {
return;
}
// Check if Selectify is disabled on admin routes.
if (selectify_disabled_on_admin_routes()) {
return;
}
// Add the 'selectify-views' class to all selects.
selectify_add_views_class($form);
// Retrieve the Selectify configuration.
$config = selectify_load_config();
$apply_site_wide = $config['apply_site_wide'];
$global_widget = $config['global_widget'];
$widget_map = $config['widget_map'];
// Extract and normalize the form ID (convert underscores to dashes).
$clean_form_id = selectify_normalize_form_id($form);
// These css classes must match the css classes in field widget and in
// selectify_theme_suggestions_alter()
$widget_libraries = selectify_get_widget_libraries();
$widget_classes = [
'selectify_dropdown' => 'selectify-apply-dropdown',
'selectify_tags' => 'selectify-apply-tags',
'selectify_searchable' => 'selectify-apply-searchable',
'selectify_checkbox' => 'selectify-apply-checkbox',
'selectify_dual' => 'selectify-apply-dual',
];
// Apply global widget if site-wide setting is enabled,
// otherwise process specific widgets.
if ($apply_site_wide) {
selectify_global_widget($form, $global_widget, $widget_libraries, $widget_classes);
}
else {
selectify_single_widget($form, $widget_map, $clean_form_id, $widget_libraries, $widget_classes);
}
foreach ($form as &$element) {
if (is_array($element) && isset($element['#type']) && $element['#type'] === 'select') {
$is_multiple = isset($element['#multiple']) && $element['#multiple'] === TRUE;
$element['#wrapper_attributes']['class'][] = $is_multiple ? 'selectify-multi' : 'selectify-single';
}
}
}
