Initial commit: Site Structure module for Drupal

Drupal module that provides hierarchical site structure management
with support for sections, categories, and content items. Includes
path aliases with tokens, breadcrumb integration, and admin interface.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 19:55:00 -03:00
commit 8a42a6f1c1
31 changed files with 2633 additions and 0 deletions

View File

@@ -0,0 +1,319 @@
<?php
declare(strict_types=1);
namespace Drupal\site_structure\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configuration form for Site Structure module.
*/
class SiteStructureSettingsForm extends ConfigFormBase {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* The entity type bundle info service.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected EntityTypeBundleInfoInterface $entityTypeBundleInfo;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected ModuleHandlerInterface $moduleHandler;
/**
* Entity types that can be used as parents.
*
* @var array
*/
protected array $supportedEntityTypes = [
'node' => 'Content Types (node)',
'taxonomy_term' => 'Taxonomy Vocabularies (taxonomy_term)',
'user' => 'Users (user)',
'group' => 'Groups (group)',
];
/**
* Constructs a SiteStructureSettingsForm object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle info service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(
ConfigFactoryInterface $config_factory,
EntityTypeManagerInterface $entity_type_manager,
EntityTypeBundleInfoInterface $entity_type_bundle_info,
ModuleHandlerInterface $module_handler,
) {
parent::__construct($config_factory);
$this->entityTypeManager = $entity_type_manager;
$this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container): static {
return new static(
$container->get('config.factory'),
$container->get('entity_type.manager'),
$container->get('entity_type.bundle.info'),
$container->get('module_handler'),
);
}
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'site_structure_settings_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames(): array {
return ['site_structure.settings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
$config = $this->config('site_structure.settings');
$allowed_targets = $config->get('allowed_parent_targets') ?? [];
// Build a keyed array for easier lookup.
$enabled_targets = [];
foreach ($allowed_targets as $target) {
$key = $target['entity_type'] . ':' . $target['bundle'];
$enabled_targets[$key] = TRUE;
// Handle wildcard bundles.
if ($target['bundle'] === '*') {
$enabled_targets[$target['entity_type'] . ':*'] = TRUE;
}
}
$form['description'] = [
'#type' => 'markup',
'#markup' => '<p>' . $this->t('Select which entity types and bundles can be used as parent for Content Page nodes. This allows creating hierarchical structures where content pages can be children of different entity types.') . '</p>',
];
$form['context_info'] = [
'#type' => 'markup',
'#markup' => '<p>' . $this->t('<strong>Context behavior:</strong><br>
- <em>Node/Taxonomy</em>: Content pages inherit the site section from the parent.<br>
- <em>User</em>: Content pages are associated with the user profile page.<br>
- <em>Group</em>: Content pages are associated with the group.') . '</p>',
];
$form['allowed_parent_targets'] = [
'#type' => 'fieldset',
'#title' => $this->t('Allowed Parent Targets'),
'#tree' => TRUE,
];
foreach ($this->supportedEntityTypes as $entity_type => $label) {
// Check if entity type exists.
if (!$this->entityTypeManager->hasDefinition($entity_type)) {
continue;
}
// Special handling for 'group' - check if module is enabled.
if ($entity_type === 'group' && !$this->moduleHandler->moduleExists('group')) {
continue;
}
$form['allowed_parent_targets'][$entity_type] = [
'#type' => 'details',
'#title' => $this->t($label),
'#open' => TRUE,
];
// User entity type typically has only one bundle.
if ($entity_type === 'user') {
$key = 'user:user';
$form['allowed_parent_targets'][$entity_type]['user'] = [
'#type' => 'checkbox',
'#title' => $this->t('User accounts'),
'#description' => $this->t('Allow content pages to be children of user profiles.'),
'#default_value' => isset($enabled_targets[$key]),
];
continue;
}
$bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type);
// For groups, add an "all types" option.
if ($entity_type === 'group') {
$key = 'group:*';
$form['allowed_parent_targets'][$entity_type]['_all'] = [
'#type' => 'checkbox',
'#title' => $this->t('All group types'),
'#description' => $this->t('Allow all current and future group types as parents.'),
'#default_value' => isset($enabled_targets[$key]),
];
}
foreach ($bundles as $bundle_id => $bundle_info) {
$key = $entity_type . ':' . $bundle_id;
$form['allowed_parent_targets'][$entity_type][$bundle_id] = [
'#type' => 'checkbox',
'#title' => $bundle_info['label'],
'#default_value' => isset($enabled_targets[$key]),
];
// If "all group types" is selected, disable individual checkboxes.
if ($entity_type === 'group') {
$form['allowed_parent_targets'][$entity_type][$bundle_id]['#states'] = [
'disabled' => [
':input[name="allowed_parent_targets[group][_all]"]' => ['checked' => TRUE],
],
];
}
}
}
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
$targets = [];
$values = $form_state->getValue('allowed_parent_targets');
foreach ($this->supportedEntityTypes as $entity_type => $label) {
if (empty($values[$entity_type])) {
continue;
}
// Handle "all group types" wildcard.
if ($entity_type === 'group' && !empty($values[$entity_type]['_all'])) {
$targets[] = [
'entity_type' => 'group',
'bundle' => '*',
];
continue;
}
foreach ($values[$entity_type] as $bundle => $enabled) {
// Skip the special "_all" key for groups if not enabled.
if ($bundle === '_all') {
continue;
}
if ($enabled) {
$targets[] = [
'entity_type' => $entity_type,
'bundle' => $bundle,
];
}
}
}
$this->config('site_structure.settings')
->set('allowed_parent_targets', $targets)
->save();
// Update field configuration to reflect new targets.
$this->updateFieldConfiguration($targets);
parent::submitForm($form, $form_state);
}
/**
* Updates the field_parent_page configuration with new targets.
*
* @param array $targets
* The allowed targets.
*/
protected function updateFieldConfiguration(array $targets): void {
$field_config = $this->entityTypeManager
->getStorage('field_config')
->load('node.content_page.field_parent_page');
if (!$field_config) {
return;
}
$settings = $field_config->getSettings();
// Build the entity type settings for dynamic_entity_reference.
$entity_type_ids = [];
$entity_type_settings = [];
foreach ($targets as $target) {
$entity_type = $target['entity_type'];
$bundle = $target['bundle'];
if (!in_array($entity_type, $entity_type_ids)) {
$entity_type_ids[] = $entity_type;
}
if (!isset($entity_type_settings[$entity_type])) {
$sort_field = match($entity_type) {
'taxonomy_term' => 'name',
'user' => 'name',
'group' => 'label',
default => 'title',
};
$entity_type_settings[$entity_type] = [
'handler' => 'default:' . $entity_type,
'handler_settings' => [
'target_bundles' => [],
'sort' => [
'field' => $sort_field,
'direction' => 'asc',
],
'auto_create' => FALSE,
],
];
}
// Handle wildcard bundles.
if ($bundle === '*') {
$entity_type_settings[$entity_type]['handler_settings']['target_bundles'] = NULL;
}
elseif ($entity_type_settings[$entity_type]['handler_settings']['target_bundles'] !== NULL) {
$entity_type_settings[$entity_type]['handler_settings']['target_bundles'][$bundle] = $bundle;
}
}
$settings['entity_type_ids'] = $entity_type_ids;
foreach ($entity_type_settings as $entity_type => $type_settings) {
$settings[$entity_type] = $type_settings;
}
$field_config->setSettings($settings);
$field_config->save();
$this->messenger()->addStatus($this->t('Field configuration updated successfully.'));
}
}