module_matrix-1.0.2/module_matrix.module
module_matrix.module
<?php
/**
* @file
* Provides the module implementation for module_matrix.
*
* Contains template preprocessing and theme definitions for Views.
*
* Filename: module_matrix.module
* Website: https://www.flashwebcenter.com
* Description: template.
* Developer: Alaa Haddad https://www.alaahaddad.com.
*/
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
/**
* Implements hook_help().
*/
function module_matrix_help($route_name, RouteMatchInterface $route_match): ?string {
if ($route_name === 'help.page.module_matrix') {
return _module_matrix_helper_render_readme();
}
return NULL;
}
/**
* Helper function to render README.md.
*
* @return string
* The rendered content of README.md.
*/
function _module_matrix_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_registry_alter().
*/
function module_matrix_theme_registry_alter(array &$theme_registry): void {
$templates = [
'system_modules_uninstall' => 'system-modules-uninstall',
'system_modules_details' => 'system-modules-details',
];
$module_path = \Drupal::service('extension.list.module')->getPath('module_matrix') . '/templates';
foreach ($templates as $key => $template) {
if (isset($theme_registry[$key])) {
$theme_registry[$key]['path'] = $module_path;
$theme_registry[$key]['template'] = $template;
}
}
}
/**
* Implements hook_preprocess_HOOK() for system_modules_details.
*/
function module_matrix_preprocess_system_modules_details(array &$variables): void {
// Core uses this for module details.
$form = $variables['form'];
$config = \Drupal::config('module_matrix.settings');
$extension_list = \Drupal::service('extension.list.module')->getList();
// Get categorized modules (parents and submodules).
[$parent_modules, $sub_modules] = module_matrix_categorize_modules($extension_list);
// Enhance module details.
foreach ($variables['modules'] as &$module) {
$id = $module['name']['#parents'][1] ?? NULL;
if ($id && isset($extension_list[$id])) {
module_matrix_enhance_module($module, $extension_list[$id], $parent_modules, $sub_modules);
}
}
foreach ($variables['modules'] as &$module) {
// Process "requires" field to style missing/disabled modules.
if (isset($module['requires']) && is_array($module['requires']) && !empty($module['requires']['#markup'])) {
$module['requires']['#markup'] = module_matrix_style_dependency_status($module['requires']['#markup']);
}
// Process "required_by" field to style missing/disabled modules.
if (isset($module['required_by']) && is_array($module['required_by']) && !empty($module['required_by']['#markup'])) {
$module['required_by']['#markup'] = module_matrix_style_dependency_status($module['required_by']['#markup']);
}
}
// Add visibility settings and enabled count to variables.
$module_options = module_matrix_get_modules_options($config);
$variables['enabled_count'] = count(array_filter($module_options, fn($value) => $value === TRUE));
// Merge module options with visibility settings.
$variables['visibility'] = array_merge($module_options, module_matrix_get_visibility_settings($config));
}
/**
* Implements hook_preprocess_HOOK() for system_modules_uninstall.
*/
function module_matrix_preprocess_system_modules_uninstall(array &$variables): void {
$config = \Drupal::config('module_matrix.settings');
$variables['visibility'] = module_matrix_get_visibility_settings($config);
foreach ($variables['modules'] as &$module) {
// Process "required_by" field for uninstall page.
if (isset($module['required_by']) && is_array($module['required_by']) && !empty($module['required_by']['#markup'])) {
$module['required_by']['#markup'] = module_matrix_style_dependency_status($module['required_by']['#markup']);
}
}
}
/**
* Styles dependency status indicators.
*
* Wraps (missing), (disabled), and (enabled) indicators in styled spans.
*
* @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $dependency_list
* The dependency list string or renderable markup.
*
* @return \Drupal\Core\Render\Markup
* Styled dependency list with wrapped status indicators.
*/
function module_matrix_style_dependency_status(string|TranslatableMarkup $dependency_list): Markup {
// Convert to string if it's a TranslatableMarkup object.
$list_string = (string) $dependency_list;
// Define status patterns and their replacement styles.
$replacements = [
// Missing modules - red, bold, with background.
'/\(missing\)/i' => '<span class="admin-missing">(missing)</span>',
// Disabled modules - orange, bold, with background.
'/\(disabled\)/i' => '<span class="admin-disabled">(disabled)</span>',
// Enabled modules - subtle green (optional).
'/\(enabled\)/i' => '<span class="admin-enabled">(enabled)</span>',
];
// Apply all replacements using regex for case-insensitive matching.
foreach ($replacements as $pattern => $replacement) {
$list_string = preg_replace($pattern, $replacement, $list_string);
}
// Return as Markup to allow HTML rendering.
return Markup::create($list_string);
}
/**
* Retrieves module options settings for templates.
*/
function module_matrix_get_modules_options($config): array {
return [
// These are enabled by default to ensure they appear on first install.
'module_description' => TRUE,
'module_machine_name' => $config->get('module_machine_name') ?? TRUE,
'module_version' => $config->get('module_version') ?? TRUE,
'module_lifecycle' => $config->get('module_lifecycle') ?? TRUE,
'module_requires' => $config->get('module_requires') ?? TRUE,
'module_required_by' => $config->get('module_required_by') ?? TRUE,
'module_status' => $config->get('module_status') ?? TRUE,
'module_project' => $config->get('module_project') ?? TRUE,
'module_mtime' => $config->get('module_mtime') ?? TRUE,
'module_subpath' => $config->get('module_subpath') ?? TRUE,
'module_stability' => $config->get('module_stability') ?? TRUE,
'module_links' => $config->get('module_links') ?? TRUE,
'module_issue_link' => $config->get('module_issue_link') ?? TRUE,
'module_usage_link' => $config->get('module_usage_link') ?? TRUE,
];
}
/**
* Retrieves visibility settings for the template.
*/
function module_matrix_get_visibility_settings($config): array {
return [
// These are disabled by default but prevent null errors in templates.
'compact_layout' => $config->get('compact_layout') ?? FALSE,
'scrollable_sidebar' => $config->get('scrollable_sidebar') ?? FALSE,
'layout' => $config->get('layout') ?? 'left',
'disable_library' => $config->get('disable_library') ?? FALSE,
'grid_layout' => $config->get('grid_layout') ?? FALSE,
'style_mode' => $config->get('style_mode') ?? 'light',
'accent_color' => $config->get('accent_color') ?? 'neutral',
];
}
/**
* Categorizes modules into parent modules and submodules.
*/
function module_matrix_categorize_modules(array $extension_list): array {
$parent_modules = [];
$sub_modules = [];
foreach ($extension_list as $machine_name => $extension) {
$subpath = $extension->getPath();
$parts = explode('/', $subpath);
if (count($parts) >= 3 && $parts[0] === 'modules' && $parts[1] === 'contrib') {
$project_name = $parts[2];
if (count($parts) === 3) {
$parent_modules[$machine_name] = [
'project_name' => $project_name,
'name' => $extension->info['name'] ?? $machine_name,
];
}
else {
$sub_modules[$machine_name] = [
'subpath' => $subpath,
'project_name' => $project_name,
];
}
}
}
return [$parent_modules, $sub_modules];
}
/**
* Enhances a single module with additional data, links, and submodule messages.
*/
function module_matrix_enhance_module(array &$module, $extension, array $parent_modules, array $sub_modules): void {
$subpath = $extension->getPath();
$parts = explode('/', $subpath);
$project_name = $parts[2] ?? NULL;
// Initialize module attributes.
$package_name = $extension->info['package'] ?? 'Other';
$stability = module_matrix_get_stability($extension->info['version'] ?? '');
$lifecycle = $extension->info['lifecycle'] ?? 'Unknown';
// Build links and submodule message.
$issue_link = NULL;
$usage_link = NULL;
$submodule_message = NULL;
if (isset($parent_modules[$module['name']['#parents'][1]])) {
// Parent module.
$issue_link = Url::fromUri("https://www.drupal.org/node/add/project-issue/{$project_name}")->toString();
$usage_link = Url::fromUri("https://www.drupal.org/project/usage/{$project_name}")->toString();
}
elseif (isset($sub_modules[$module['name']['#parents'][1]])) {
// Submodule: Find its parent.
foreach ($parent_modules as $parent_data) {
if ($parent_data['project_name'] === $project_name) {
$submodule_message = "This is a submodule for the parent module: {$parent_data['name']}.";
$issue_link = Url::fromUri("https://www.drupal.org/node/add/project-issue/{$parent_data['project_name']}")->toString();
$usage_link = Url::fromUri("https://www.drupal.org/project/usage/{$parent_data['project_name']}")->toString();
break;
}
}
}
// Add data to the module.
$module += [
'package' => $package_name,
'lifecycle' => $lifecycle,
'status' => $module['status'] ?? (\Drupal::service('module_handler')->moduleExists($module['name']['#parents'][1]) ? 1 : 0),
'weight' => $module['weight'] ?? $extension->weight ?? 'N/A',
'project' => $project_name,
'mtime' => $module['mtime'] ?? (!empty($extension->info['mtime']) ? date('Y-m-d H:i:s', $extension->info['mtime']) : 'Unknown'),
'stability' => $stability,
'subpath' => $subpath,
'issue_link' => $issue_link,
'usage_link' => $usage_link,
'submodule_message' => $submodule_message,
];
}
/**
* Determines the stability of a module version.
*/
function module_matrix_get_stability(string $version): string {
if (str_contains($version, "-dev")) {
return 'dev';
}
elseif (str_contains($version, "-alpha")) {
return 'alpha';
}
elseif (str_contains($version, "-beta")) {
return 'beta';
}
elseif (str_contains($version, "-rc")) {
return 'rc';
}
return 'stable';
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function module_matrix_form_system_modules_alter(array &$form, FormStateInterface $form_state, $form_id): void {
// Remove the 'container' wrapper explicitly if it's part of the render array.
// Adjust module packages and elements similar to module_filter.
if (isset($form['modules']) && is_array($form['modules'])) {
foreach (Element::children($form['modules']) as $package_name) {
$package = &$form['modules'][$package_name];
// Modify the package-level details element.
if (isset($package['#type']) && $package['#type'] === 'details') {
$package['#type'] = 'container';
unset($package['#open']);
$package['#attributes']['class'][] = 'module-package-wrapper';
}
}
}
$form['intro_message'] = [
'#type' => 'markup',
'#markup' => '<p>' . t('<a href=":layout_url" target="_blank"><span class="material-icons" aria-hidden="true">settings</span> Adjust layout and options</a>', [
':layout_url' => Url::fromRoute('module_matrix.settings_form')->toString(),
]) . '</p>',
// Ensures it appears at the top.
'#weight' => -100,
'#prefix' => '<div class="intro-message-class">',
'#suffix' => '</div>',
];
$form['filters'] = [
'#type' => 'fieldset',
'#attributes' => [
'class' => ['module-matrix-filters'],
],
];
// Add the text filter field.
$form['filters']['text'] = [
'#type' => 'textfield',
'#title' => t('Search Modules'),
'#placeholder' => t('Enter module name or description'),
'#attributes' => [
'class' => ['form-control'],
],
];
// Add the status filter container.
$form['filters']['status'] = [
'#type' => 'container',
'#attributes' => ['class' => ['module-matrix-status', 'form-item']],
'checkboxes' => [
'#type' => 'checkboxes',
'#title' => t('Filter by status'),
'#options' => [
'1' => t('Enabled'),
'0' => t('Disabled'),
'unavailable' => t('Unavailable'),
],
'#default_value' => [],
'#attributes' => [
'class' => ['form-checkboxes', 'enable-filter'],
],
],
];
// Add the lifecycle filter container.
$form['filters']['lifecycle'] = [
'#type' => 'container',
'#attributes' => ['class' => ['module-matrix-lifecycle', 'form-item']],
'checkboxes' => [
'#type' => 'checkboxes',
'#title' => t('Filter by lifecycle'),
'#options' => [
'stable' => t('Stable'),
'deprecated' => t('Deprecated'),
'experimental' => t('Experimental'),
'obsolete' => t('Obsolete'),
],
'#default_value' => [],
'#attributes' => [
'class' => ['form-checkboxes', 'lifecycle-filter'],
],
],
];
// Add the stability filter container.
$form['filters']['stability'] = [
'#type' => 'container',
'#attributes' => ['class' => ['module-matrix-stability', 'form-item']],
'checkboxes' => [
'#type' => 'checkboxes',
'#title' => t('Filter by stability'),
'#options' => [
'stable' => t('Stable'),
'rc' => t('Release Candidate'),
'beta' => t('Beta'),
'alpha' => t('Alpha'),
'dev' => t('Development'),
],
'#default_value' => [],
'#attributes' => [
'class' => ['form-checkboxes', 'stability-filter'],
],
],
];
// Add the reset button.
$form['filters']['reset'] = [
'#type' => 'button',
'#value' => t('Reset'),
'#attributes' => [
'id' => 'reset-filters',
// Adding Drupal button classes.
'class' => ['button', 'reset-button', 'form-button'],
],
];
}
/**
* Implements hook_theme_suggestions_fieldset_alter().
*/
function module_matrix_theme_suggestions_fieldset_alter(array &$suggestions, array $variables) {
// Get the current route name.
$route_name = \Drupal::routeMatch()->getRouteName();
// Apply only on 'admin/modules' and 'module_matrix.settings_form'.
if (in_array($route_name, ['system.modules_list',
'module_matrix.settings_form',
])) {
$suggestions[] = 'fieldset__module_matrix';
}
}
/**
* Implements hook_theme().
*/
function module_matrix_theme($existing, $type, $theme, $path) {
return [
'fieldset__module_matrix' => [
'base hook' => 'fieldset',
'template' => 'fieldset--module-matrix',
'path' => $path . '/templates',
],
];
}
