swup-1.0.0-alpha1/swup_ui/swup_ui.module
swup_ui/swup_ui.module
<?php
/**
* @file
* Drupal's integration with Swup.js library UI module.
*
* Swup is a versatile and extensible page transition library for
* server-rendered websites. It makes your site feel like a single-page
* application with smooth transitions between pages.
*
* GitHub: https://github.com/swup/swup
* Website: https://swup.js.org/
* License: MIT licensed
*
* Copyright (C) 2016-2025 Georgy Marchuk
*/
use Drupal\Core\Installer\InstallerKernel;
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Implements hook_help().
*/
function swup_ui_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.swup_ui':
$settings = Link::createFromRoute(
t('settings page'),
'swup.settings'
)->toString();
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t(
'Swup UI provides a configuration interface for the Swup.js library. Use it to control how and where the library is attached.'
) . '</p>';
$output .= '<h3>' . t('Configuration') . '</h3>';
$output .= '<ul>';
$output .= '<li>' . t(
'<strong>Load Swup.js</strong>: Global on/off for auto-attach.'
) . '</li>';
$output .= '<li>' . t(
'<strong>CDN Provider</strong>: Choose between unpkg or jsDelivr.'
) . '</li>';
$output .= '<li>' . t(
'<strong>Build variant</strong>: ESM for modern browsers, UMD for older.'
) . '</li>';
$output .= '<li>' . t(
'<strong>Core & Plugins</strong>: Enable core and optional plugins (JS, Preload, Scripts, Forms, Debug).'
) . '</li>';
$output .= '<li>' . t(
'<strong>Themes</strong>: Restrict loading to specific themes or all except a list.'
) . '</li>';
$output .= '<li>' . t(
'<strong>Pages</strong>: Path-based visibility with wildcard support.'
) . '</li>';
$output .= '</ul>';
$output .= '<h3>' . t('Notes') . '</h3>';
$output .= '<ul>';
$output .= '<li>' . t(
'When core is disabled, global loading is disabled and settings reset to defaults.'
) . '</li>';
$output .= '<li>' . t(
'Core must be enabled for plugins to work.'
) . '</li>';
$output .= '<li>' . t(
'Access requires the "@perm" permission.',
['@perm' => 'administer swup']
) . '</li>';
$output .= '</ul>';
$output .= '<p>' . t(
'Open the @settings to configure.',
['@settings' => $settings]
) . '</p>';
return $output;
}
}
/**
* Implements hook_page_attachments().
*/
function swup_ui_page_attachments(array &$attachments) {
// Do not attach libraries during installation.
if (InstallerKernel::installationAttempted()) {
return;
}
// Read configuration.
$config = \Drupal::config('swup_ui.settings');
// Enforce visibility rules and global loading flag.
if (
!$config->get('load') ||
!_swup_ui_check_theme() ||
!_swup_ui_check_path()
) {
return;
}
// Check if local installation exists.
$local_exists = swup_check_installed();
$cdn_provider = $config->get('cdn_provider');
$variant = $config->get('build.variant');
$plugins = (array) $config->get('plugins');
// Attach core library.
if ($config->get('core')) {
if ($local_exists && $config->get('prefer_local')) {
// Use local version.
$lib_id = $variant === 'minified' ? 'swup.local.min' : 'swup.local';
$attachments['#attached']['library'][] = 'swup/' . $lib_id;
}
else {
// Use CDN version.
if ($variant === 'esm') {
$cdn_lib = ($cdn_provider === 'jsdelivr') ? 'swup.cdn.jsdelivr.esm' : 'swup.cdn.esm';
}
else {
$cdn_lib = ($cdn_provider === 'jsdelivr') ? 'swup.cdn.jsdelivr.umd' : 'swup.cdn.umd';
}
$attachments['#attached']['library'][] = 'swup/' . $cdn_lib;
}
// Attach enabled plugins (only if core is enabled).
foreach ($plugins as $plugin_name => $enabled) {
if (!$enabled) {
continue;
}
// All plugins are CDN-only for now.
$plugin_lib = 'swup.plugin.' . $plugin_name . '.cdn';
$attachments['#attached']['library'][] = 'swup/' . $plugin_lib;
}
}
}
/**
* Check if Swup should be active for the current URL.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request to use if provided, otherwise \Drupal::request() will be used.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
*
* @return bool
* TRUE if Swup should be active for the current page.
*/
function _swup_ui_check_path(Request $request = NULL, RequestStack $request_stack = NULL): bool {
// Use the provided request or get the current request.
$request = $request ?: \Drupal::request();
// Initialize match status as FALSE.
$page_match = FALSE;
// Check for the ?swup=no parameter in the URL to deactivate library.
$query = $request->query;
if ($query->get('swup') !== NULL && $query->get('swup') == 'no') {
return $page_match;
}
// Load the module configuration.
$config = \Drupal::config('swup_ui.settings');
// Get the list of pages where Swup should be active.
$pages = _swup_ui_array_to_string($config->get('request_path.pages'));
$pages = $pages ? mb_strtolower($pages) : '';
// If no specific pages are configured, Swup is active on all pages.
if (!$pages) {
return TRUE;
}
// Use the provided request stack or get the current request stack.
$request_stack = $request_stack ?: \Drupal::requestStack();
$current_request = $request_stack->getCurrentRequest();
// Get the current path.
$path = \Drupal::service('path.current')->getPath($current_request);
// Do not trim the trailing slash if it is the complete path.
$path = $path === '/' ? $path : rtrim($path, '/');
// Get the current language code.
$langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();
// Get the path alias and convert to lowercase.
$path_alias = mb_strtolower(\Drupal::service('path_alias.manager')->getAliasByPath($path, $langcode));
// Check if the path alias matches the configured pages.
$page_match = \Drupal::service('path.matcher')->matchPath($path_alias, $pages);
// If the path alias is different from the internal path,
// check the internal path as well.
if ($path_alias != $path) {
$page_match = $page_match || \Drupal::service('path.matcher')->matchPath($path, $pages);
}
// Negate the match if configured to load on all pages except those listed.
$page_match = $config->get('request_path.negate') == 0 ? !$page_match : $page_match;
return $page_match;
}
/**
* Verify if the current theme is selected in the module settings.
*
* @return bool
* TRUE if the current theme is selected, otherwise FALSE.
*/
function _swup_ui_check_theme(): bool {
// Load the Swup UI module configuration.
$config = \Drupal::config('swup_ui.settings');
// Get the name of the active theme.
$active_theme = \Drupal::theme()->getActiveTheme()->getName();
// Retrieve the list of valid themes from the configuration.
$valid_themes = (array) $config->get('theme_groups.themes');
// If no themes are configured, Swup should be active.
if (empty($valid_themes) || count($valid_themes) === 0) {
return TRUE;
}
// Get the visibility setting from the configuration.
$visibility = $config->get('theme_groups.negate');
// Check if the active theme is in the list of valid themes.
$theme_match = in_array($active_theme, $valid_themes);
// With XOR we flip match for "all except" mode.
return !($visibility xor $theme_match);
}
/**
* Flatten a multidimensional array to a single-level array.
*
* @param array $array
* Input array.
*
* @return array
* Flattened values.
*/
function _swup_ui_array_flatten(array $array): array {
$result = [];
array_walk_recursive($array, function ($v) use (&$result) {
$result[] = $v;
});
return $result;
}
/**
* Convert newline-separated text into an array of trimmed lines.
*
* @param string $text
* Raw text.
*
* @return array|null
* Lines or NULL if input is not a string.
*/
function _swup_ui_string_to_array($text): ?array {
if (!is_string($text)) {
return NULL;
}
$text = str_replace("\r\n", "\n", $text);
return array_values(array_filter(array_map('trim', explode("\n", $text))));
}
/**
* Convert an array of lines to CRLF text.
*
* @param array $array
* Lines.
*
* @return string|null
* CRLF-joined string or NULL if input is not an array.
*/
function _swup_ui_array_to_string($array): ?string {
if (!is_array($array)) {
return NULL;
}
return implode("\r\n", array_map('trim', $array));
}
/**
* Recursively convert arrays/objects to associative arrays.
*
* @param mixed $data
* Input data.
*
* @return array|mixed
* Converted array or the original value.
*/
function _swup_ui_object_to_array($data) {
if (is_array($data) || is_object($data)) {
$out = [];
foreach ($data as $k => $v) {
$out[$k] = _swup_ui_object_to_array($v);
}
return $out;
}
return $data;
}
