mirror of
https://gitlab.unicamp.br/infimecc_drupal11_modules/structural_pages.git
synced 2026-05-04 15:30:40 -03:00
Campo booleano (padrão: ativo) que controla se a página aparece no menu de navegação. Quando desmarcado, oculta field_menu_title no formulário via #states e exclui a página da query em getChildPages(). O campo field_weight permanece sempre visível, pois a ordenação se aplica independentemente da exibição no menu. Hook update_10015 cria storage + instância, atualiza o form display e retroativamente define o valor como 1 para páginas existentes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
992 lines
34 KiB
Plaintext
992 lines
34 KiB
Plaintext
<?php
|
||
|
||
/**
|
||
* @file
|
||
* Install, update and uninstall functions for the Structural Pages module.
|
||
*/
|
||
|
||
declare(strict_types=1);
|
||
|
||
use Drupal\taxonomy\Entity\Term;
|
||
|
||
/**
|
||
* Implements hook_install().
|
||
*/
|
||
function structural_pages_install(): void {
|
||
// Create default terms for the configured vocabulary.
|
||
_structural_pages_create_default_terms();
|
||
|
||
// Display success message.
|
||
\Drupal::messenger()->addStatus(t('Structural Pages module installed successfully. Configure Pathauto patterns at /admin/config/search/path/patterns'));
|
||
}
|
||
|
||
/**
|
||
* Creates default terms for the configured site section vocabulary.
|
||
*/
|
||
function _structural_pages_create_default_terms(): void {
|
||
$vocabulary = _structural_pages_get_vocabulary();
|
||
|
||
// Check if terms already exist (avoid duplication on reinstall).
|
||
$existing = \Drupal::entityQuery('taxonomy_term')
|
||
->accessCheck(FALSE)
|
||
->condition('vid', $vocabulary)
|
||
->count()
|
||
->execute();
|
||
|
||
if ($existing > 0) {
|
||
return;
|
||
}
|
||
|
||
// Structure: name => children.
|
||
$terms_structure = [
|
||
'Notícias' => [],
|
||
'Eventos' => [],
|
||
'Pessoas' => [],
|
||
'Institucional' => [
|
||
'Sobre',
|
||
'Comunicação',
|
||
'Informações e Serviços',
|
||
'Equipe',
|
||
'Gestão',
|
||
'Inclusão e Pertencimento',
|
||
],
|
||
'Graduação' => [
|
||
'Estatística',
|
||
'Matemática',
|
||
'Matemática Aplicada',
|
||
'Licenciatura em Matemática',
|
||
],
|
||
'Pós-Graduação' => [
|
||
'Programa de Estatística',
|
||
'Programa de Matemática',
|
||
'Programa de Matemática Aplicada',
|
||
],
|
||
'Pesquisa' => [],
|
||
'Extensão' => [],
|
||
'Administração' => [],
|
||
'Departamentos' => [
|
||
'Departamento de Estatística',
|
||
'Departamento de Matemática',
|
||
'Departamento de Matemática Aplicada',
|
||
],
|
||
'Biblioteca' => [],
|
||
'Informática' => [],
|
||
];
|
||
|
||
$created_terms = [];
|
||
|
||
$weight = 0;
|
||
foreach ($terms_structure as $parent_name => $children) {
|
||
// Create parent term.
|
||
$parent_term = Term::create([
|
||
'vid' => $vocabulary,
|
||
'name' => $parent_name,
|
||
'weight' => $weight++,
|
||
]);
|
||
$parent_term->save();
|
||
$created_terms[] = $parent_term;
|
||
|
||
// Create child terms.
|
||
$child_weight = 0;
|
||
foreach ($children as $child_name) {
|
||
$child_term = Term::create([
|
||
'vid' => $vocabulary,
|
||
'name' => $child_name,
|
||
'parent' => $parent_term->id(),
|
||
'weight' => $child_weight++,
|
||
]);
|
||
$child_term->save();
|
||
$created_terms[] = $child_term;
|
||
}
|
||
}
|
||
|
||
// Add content translations if available.
|
||
_structural_pages_add_term_translations($created_terms);
|
||
}
|
||
|
||
/**
|
||
* Adds content translations to taxonomy terms.
|
||
*
|
||
* @param \Drupal\taxonomy\Entity\Term[] $terms
|
||
* The terms to translate.
|
||
*/
|
||
function _structural_pages_add_term_translations(array $terms): void {
|
||
if (!\Drupal::moduleHandler()->moduleExists('content_translation')) {
|
||
return;
|
||
}
|
||
|
||
$translations = _structural_pages_term_translations();
|
||
$language_manager = \Drupal::languageManager();
|
||
|
||
foreach ($translations as $langcode => $name_map) {
|
||
if (!$language_manager->getLanguage($langcode)) {
|
||
continue;
|
||
}
|
||
|
||
foreach ($terms as $term) {
|
||
$name = $term->getName();
|
||
if (!isset($name_map[$name])) {
|
||
continue;
|
||
}
|
||
|
||
$translated_name = $name_map[$name];
|
||
|
||
if ($term->hasTranslation($langcode)) {
|
||
// Update existing translation if name doesn't match.
|
||
$translation = $term->getTranslation($langcode);
|
||
if ($translation->getName() !== $translated_name) {
|
||
$translation->setName($translated_name);
|
||
$translation->save();
|
||
}
|
||
}
|
||
else {
|
||
$term->addTranslation($langcode, ['name' => $translated_name]);
|
||
$term->save();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Returns term name translations keyed by langcode.
|
||
*
|
||
* @return array
|
||
* Nested array: langcode => [english_name => translated_name].
|
||
*/
|
||
function _structural_pages_term_translations(): array {
|
||
return [
|
||
'pt-br' => [
|
||
'News' => 'Notícias',
|
||
'Events' => 'Eventos',
|
||
'People' => 'Pessoas',
|
||
'Institutional' => 'Institucional',
|
||
'About' => 'Sobre',
|
||
'Communication' => 'Comunicação',
|
||
'Information and Services' => 'Informações e Serviços',
|
||
'Team' => 'Equipe',
|
||
'Management' => 'Gestão',
|
||
'Inclusion and Belonging' => 'Inclusão e Pertencimento',
|
||
'Undergraduate' => 'Graduação',
|
||
'Statistics' => 'Estatística',
|
||
'Mathematics' => 'Matemática',
|
||
'Applied Mathematics' => 'Matemática Aplicada',
|
||
'Mathematics Teaching' => 'Licenciatura em Matemática',
|
||
'Graduate' => 'Pós-Graduação',
|
||
'Statistics Program' => 'Programa de Estatística',
|
||
'Mathematics Program' => 'Programa de Matemática',
|
||
'Applied Mathematics Program' => 'Programa de Matemática Aplicada',
|
||
'Research' => 'Pesquisa',
|
||
'Extension' => 'Extensão',
|
||
'Administration' => 'Administração',
|
||
'Departments' => 'Departamentos',
|
||
'Statistics Department' => 'Departamento de Estatística',
|
||
'Mathematics Department' => 'Departamento de Matemática',
|
||
'Applied Mathematics Department' => 'Departamento de Matemática Aplicada',
|
||
'Library' => 'Biblioteca',
|
||
'IT Services' => 'Informática',
|
||
],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Implements hook_uninstall().
|
||
*/
|
||
function structural_pages_uninstall(): void {
|
||
$entity_type_manager = \Drupal::entityTypeManager();
|
||
|
||
// Remove nodes from module's content types.
|
||
foreach (['section_page', 'content_page'] as $bundle) {
|
||
$nids = \Drupal::entityQuery('node')
|
||
->accessCheck(FALSE)
|
||
->condition('type', $bundle)
|
||
->execute();
|
||
if ($nids) {
|
||
$nodes = $entity_type_manager->getStorage('node')->loadMultiple($nids);
|
||
$entity_type_manager->getStorage('node')->delete($nodes);
|
||
\Drupal::messenger()->addWarning(t('Deleted @count @bundle nodes.', [
|
||
'@count' => count($nids),
|
||
'@bundle' => $bundle,
|
||
]));
|
||
}
|
||
}
|
||
|
||
// Remove terms from the configured vocabulary.
|
||
$vocabulary = _structural_pages_get_vocabulary();
|
||
$tids = \Drupal::entityQuery('taxonomy_term')
|
||
->accessCheck(FALSE)
|
||
->condition('vid', $vocabulary)
|
||
->execute();
|
||
if ($tids) {
|
||
$terms = $entity_type_manager->getStorage('taxonomy_term')->loadMultiple($tids);
|
||
$entity_type_manager->getStorage('taxonomy_term')->delete($terms);
|
||
}
|
||
|
||
// Remove configurations in correct order (dependencies first).
|
||
$configs_to_delete = [
|
||
// Views.
|
||
'views.view.child_pages',
|
||
// Pathauto patterns.
|
||
'pathauto.pattern.section_page',
|
||
'pathauto.pattern.content_page',
|
||
'pathauto.pattern.' . $vocabulary . '_term',
|
||
// Entity displays.
|
||
'core.entity_form_display.node.section_page.default',
|
||
'core.entity_view_display.node.section_page.default',
|
||
'core.entity_form_display.node.content_page.default',
|
||
'core.entity_view_display.node.content_page.default',
|
||
// Field instances.
|
||
'field.field.node.section_page.field_site_section',
|
||
'field.field.node.section_page.body',
|
||
'field.field.node.content_page.field_parent_page',
|
||
'field.field.node.content_page.field_site_section',
|
||
'field.field.node.content_page.body',
|
||
// Field storages (only if not used by other bundles).
|
||
'field.storage.node.field_site_section',
|
||
'field.storage.node.field_parent_page',
|
||
// Node types.
|
||
'node.type.section_page',
|
||
'node.type.content_page',
|
||
// Vocabulary.
|
||
'taxonomy.vocabulary.' . $vocabulary,
|
||
];
|
||
|
||
$config_factory = \Drupal::configFactory();
|
||
foreach ($configs_to_delete as $config_name) {
|
||
$config = $config_factory->getEditable($config_name);
|
||
if (!$config->isNew()) {
|
||
$config->delete();
|
||
}
|
||
}
|
||
|
||
\Drupal::messenger()->addStatus(t('Structural Pages module uninstalled successfully.'));
|
||
}
|
||
|
||
/**
|
||
* Update content_page content type name from "Guide Page" to "Content Page".
|
||
*/
|
||
function structural_pages_update_10001(): void {
|
||
$config = \Drupal::configFactory()->getEditable('node.type.content_page');
|
||
if (!$config->isNew()) {
|
||
$current_name = $config->get('name');
|
||
// Update if still using old name.
|
||
if ($current_name === 'Guide Page') {
|
||
$config->set('name', 'Content Page');
|
||
$config->set('description', 'Pages with hierarchical parent-child structure for content organization.');
|
||
$config->save();
|
||
\Drupal::messenger()->addStatus(t('Updated content_page content type name to "Content Page".'));
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Add content translations to site_sections taxonomy terms (no-op, see 10003).
|
||
*/
|
||
function structural_pages_update_10002(): void {
|
||
}
|
||
|
||
/**
|
||
* Add content translations to site_sections taxonomy terms (no-op, see 10004).
|
||
*/
|
||
function structural_pages_update_10003(): void {
|
||
}
|
||
|
||
/**
|
||
* Fix content translation config and update term translations.
|
||
*/
|
||
function structural_pages_update_10004(): void {
|
||
// Ensure content translation is properly configured for the vocabulary.
|
||
_structural_pages_ensure_content_translation();
|
||
|
||
$vocabulary = _structural_pages_get_vocabulary();
|
||
$terms = \Drupal::entityTypeManager()
|
||
->getStorage('taxonomy_term')
|
||
->loadByProperties(['vid' => $vocabulary]);
|
||
|
||
if ($terms) {
|
||
_structural_pages_add_term_translations($terms);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Ensures content translation settings exist for the configured vocabulary.
|
||
*/
|
||
function _structural_pages_ensure_content_translation(): void {
|
||
if (!\Drupal::moduleHandler()->moduleExists('content_translation')) {
|
||
return;
|
||
}
|
||
|
||
$vocabulary = _structural_pages_get_vocabulary();
|
||
$config = \Drupal\language\Entity\ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', $vocabulary);
|
||
$config->setDefaultLangcode('site_default');
|
||
$config->setLanguageAlterable(TRUE);
|
||
$config->setThirdPartySetting('content_translation', 'enabled', TRUE);
|
||
$config->save();
|
||
}
|
||
|
||
/**
|
||
* Corrige handler_settings nulo no field_parent_page (compatibilidade PHP 8).
|
||
*
|
||
* O widget DynamicEntityReferenceWidget faz $settings[$type]['handler_settings']
|
||
* + array, que lança TypeError quando handler_settings é null no PHP 8.
|
||
* Re-aplica as settings completas a partir do YAML de install do módulo.
|
||
*/
|
||
function structural_pages_update_10006(): string {
|
||
$config = \Drupal::configFactory()
|
||
->getEditable('field.field.node.content_page.field_parent_page');
|
||
|
||
if ($config->isNew()) {
|
||
return 'Campo field_parent_page não encontrado — nada a corrigir.';
|
||
}
|
||
|
||
// Re-aplica as settings completas a partir do YAML de install.
|
||
$module_path = \Drupal::service('extension.list.module')->getPath('structural_pages');
|
||
$yaml_file = $module_path . '/config/install/field.field.node.content_page.field_parent_page.yml';
|
||
|
||
if (!file_exists($yaml_file)) {
|
||
return 'Arquivo YAML de install não encontrado.';
|
||
}
|
||
|
||
$yaml_settings = \Symfony\Component\Yaml\Yaml::parse(file_get_contents($yaml_file));
|
||
$config->set('settings', $yaml_settings['settings']);
|
||
$config->save(TRUE);
|
||
|
||
return 'Settings de field_parent_page re-sincronizadas a partir do YAML de install.';
|
||
}
|
||
|
||
/**
|
||
* Corrige exclude_entity_types no storage do field_parent_page (PHP 8 TypeError).
|
||
*
|
||
* defaultStorageSettings() retorna exclude_entity_types: TRUE, o que fazia
|
||
* getTargetTypes() retornar todos os entity types EXCETO node/taxonomy_term.
|
||
*/
|
||
function structural_pages_update_10007(): string {
|
||
$config = \Drupal::configFactory()
|
||
->getEditable('field.storage.node.field_parent_page');
|
||
if ($config->isNew()) {
|
||
return 'field.storage.node.field_parent_page não encontrado.';
|
||
}
|
||
$config->set('settings.exclude_entity_types', FALSE)->save(TRUE);
|
||
return 'exclude_entity_types corrigido para FALSE em field_parent_page storage.';
|
||
}
|
||
|
||
/**
|
||
* Adiciona coluna field_parent_page_target_type ausente no schema do banco.
|
||
*
|
||
* O campo foi originalmente criado como entity_reference simples e depois
|
||
* migrado para dynamic_entity_reference no config, mas a coluna target_type
|
||
* nunca foi adicionada ao banco. Popula o tipo de cada linha consultando as
|
||
* tabelas node e taxonomy_term para determinar o tipo correto da referência.
|
||
*/
|
||
function structural_pages_update_10008(): string {
|
||
$database = \Drupal::database();
|
||
$schema = $database->schema();
|
||
|
||
$column_spec = [
|
||
'type' => 'varchar_ascii',
|
||
'length' => 128,
|
||
'not null' => FALSE,
|
||
'description' => 'The entity type of the field_parent_page reference.',
|
||
];
|
||
|
||
$tables = [
|
||
'node__field_parent_page',
|
||
'node_revision__field_parent_page',
|
||
];
|
||
|
||
$updated = [];
|
||
|
||
foreach ($tables as $table) {
|
||
if (!$schema->tableExists($table)) {
|
||
continue;
|
||
}
|
||
|
||
if (!$schema->fieldExists($table, 'field_parent_page_target_type')) {
|
||
$schema->addField($table, 'field_parent_page_target_type', $column_spec);
|
||
}
|
||
|
||
// Determina o tipo de cada referência existente comparando o target_id
|
||
// com os IDs existentes em node e taxonomy_term.
|
||
$rows = $database->select($table, 't')
|
||
->fields('t', ['entity_id', 'revision_id', 'field_parent_page_target_id'])
|
||
->isNull('field_parent_page_target_type')
|
||
->execute()
|
||
->fetchAll();
|
||
|
||
foreach ($rows as $row) {
|
||
$target_id = $row->field_parent_page_target_id;
|
||
|
||
$is_node = (bool) $database->select('node', 'n')
|
||
->fields('n', ['nid'])
|
||
->condition('n.nid', $target_id)
|
||
->countQuery()
|
||
->execute()
|
||
->fetchField();
|
||
|
||
$target_type = $is_node ? 'node' : 'taxonomy_term';
|
||
|
||
$database->update($table)
|
||
->fields(['field_parent_page_target_type' => $target_type])
|
||
->condition('entity_id', $row->entity_id)
|
||
->condition('revision_id', $row->revision_id)
|
||
->execute();
|
||
}
|
||
|
||
$updated[] = $table;
|
||
}
|
||
|
||
if (empty($updated)) {
|
||
return 'Tabelas não encontradas — nada a corrigir.';
|
||
}
|
||
|
||
return 'Coluna field_parent_page_target_type adicionada e populada em: ' . implode(', ', $updated) . '.';
|
||
}
|
||
|
||
/**
|
||
* Define entity_type_ids no storage do field_parent_page para node e taxonomy_term.
|
||
*
|
||
* Com entity_type_ids vazio e exclude_entity_types FALSE, o widget DER não
|
||
* conseguia determinar quais entity types exibir e deixava o campo em branco.
|
||
*/
|
||
function structural_pages_update_10009(): string {
|
||
$config = \Drupal::configFactory()
|
||
->getEditable('field.storage.node.field_parent_page');
|
||
if ($config->isNew()) {
|
||
return 'field.storage.node.field_parent_page não encontrado.';
|
||
}
|
||
$config->set('settings.entity_type_ids', ['node', 'taxonomy_term'])->save(TRUE);
|
||
return 'entity_type_ids definido como [node, taxonomy_term] em field_parent_page storage.';
|
||
}
|
||
|
||
/**
|
||
* Adiciona suporte ao entity type user no campo field_parent_page.
|
||
*
|
||
* Atualiza o storage (entity_type_ids) e a instância do campo (handler settings
|
||
* para user) para que o DER module possa referenciar usuários como pai de
|
||
* content_page nodes.
|
||
*/
|
||
function structural_pages_update_10010(): string {
|
||
$messages = [];
|
||
|
||
// 1. Storage: adiciona 'user' a entity_type_ids.
|
||
$storage_config = \Drupal::configFactory()
|
||
->getEditable('field.storage.node.field_parent_page');
|
||
if (!$storage_config->isNew()) {
|
||
$entity_type_ids = $storage_config->get('settings.entity_type_ids') ?? [];
|
||
if (!in_array('user', $entity_type_ids, TRUE)) {
|
||
$entity_type_ids[] = 'user';
|
||
$storage_config->set('settings.entity_type_ids', $entity_type_ids)->save(TRUE);
|
||
$messages[] = 'Storage atualizado: user adicionado a entity_type_ids.';
|
||
}
|
||
else {
|
||
$messages[] = 'Storage: user já presente em entity_type_ids.';
|
||
}
|
||
}
|
||
else {
|
||
$messages[] = 'field.storage.node.field_parent_page não encontrado.';
|
||
}
|
||
|
||
// 2. Instância: adiciona handler settings para user.
|
||
$field_config = \Drupal::configFactory()
|
||
->getEditable('field.field.node.content_page.field_parent_page');
|
||
if (!$field_config->isNew()) {
|
||
$settings = $field_config->get('settings') ?? [];
|
||
$type_ids = $settings['entity_type_ids'] ?? [];
|
||
if (!in_array('user', $type_ids, TRUE)) {
|
||
$type_ids[] = 'user';
|
||
$settings['entity_type_ids'] = $type_ids;
|
||
}
|
||
if (!isset($settings['user'])) {
|
||
$settings['user'] = [
|
||
'handler' => 'default:user',
|
||
'handler_settings' => [
|
||
'include_anonymous' => FALSE,
|
||
'filter' => ['type' => '_none'],
|
||
'target_bundles' => NULL,
|
||
'sort' => ['field' => 'name', 'direction' => 'asc'],
|
||
'auto_create' => FALSE,
|
||
],
|
||
];
|
||
}
|
||
$field_config->set('settings', $settings)->save(TRUE);
|
||
$messages[] = 'Instância atualizada: handler settings de user adicionadas.';
|
||
}
|
||
else {
|
||
$messages[] = 'field.field.node.content_page.field_parent_page não encontrado.';
|
||
}
|
||
|
||
return implode(' ', $messages);
|
||
}
|
||
|
||
/**
|
||
* Migra field_site_section de entity_reference para dynamic_entity_reference.
|
||
*
|
||
* O campo precisa armazenar o domínio raiz da página (taxonomy_term OU user),
|
||
* por isso deve se tornar um campo DER em vez de entity_reference simples.
|
||
*
|
||
* Passos:
|
||
* 1. Adiciona coluna target_type nas tabelas do campo.
|
||
* 2. Preenche todas as linhas existentes com target_type = 'taxonomy_term'.
|
||
* 3. Atualiza o storage config para dynamic_entity_reference.
|
||
* 4. Atualiza as instâncias (content_page e section_page).
|
||
*/
|
||
function structural_pages_update_10011(): string {
|
||
$database = \Drupal::database();
|
||
$schema = $database->schema();
|
||
$messages = [];
|
||
|
||
$column_spec = [
|
||
'type' => 'varchar_ascii',
|
||
'length' => 128,
|
||
'not null' => FALSE,
|
||
'description' => 'The entity type of the field_site_section reference.',
|
||
];
|
||
|
||
foreach (['node__field_site_section', 'node_revision__field_site_section'] as $table) {
|
||
if (!$schema->tableExists($table)) {
|
||
continue;
|
||
}
|
||
if (!$schema->fieldExists($table, 'field_site_section_target_type')) {
|
||
$schema->addField($table, 'field_site_section_target_type', $column_spec);
|
||
// Populate existing rows as taxonomy_term.
|
||
$database->update($table)
|
||
->fields(['field_site_section_target_type' => 'taxonomy_term'])
|
||
->isNull('field_site_section_target_type')
|
||
->execute();
|
||
$messages[] = "Coluna target_type adicionada em $table.";
|
||
}
|
||
else {
|
||
$messages[] = "Coluna target_type já existe em $table.";
|
||
}
|
||
}
|
||
|
||
// Update storage config.
|
||
$storage = \Drupal::configFactory()->getEditable('field.storage.node.field_site_section');
|
||
if (!$storage->isNew()) {
|
||
$storage->set('type', 'dynamic_entity_reference');
|
||
$storage->set('module', 'dynamic_entity_reference');
|
||
$storage->set('settings', [
|
||
'exclude_entity_types' => FALSE,
|
||
'entity_type_ids' => ['taxonomy_term', 'user'],
|
||
]);
|
||
$deps = $storage->get('dependencies') ?? [];
|
||
$mods = $deps['module'] ?? [];
|
||
if (!in_array('dynamic_entity_reference', $mods, TRUE)) {
|
||
$mods[] = 'dynamic_entity_reference';
|
||
}
|
||
$deps['module'] = $mods;
|
||
$storage->set('dependencies', $deps);
|
||
$storage->save(TRUE);
|
||
$messages[] = 'Storage config atualizado para dynamic_entity_reference.';
|
||
}
|
||
|
||
// Update content_page instance config.
|
||
$field_cp = \Drupal::configFactory()->getEditable('field.field.node.content_page.field_site_section');
|
||
if (!$field_cp->isNew()) {
|
||
$field_cp->set('field_type', 'dynamic_entity_reference');
|
||
$field_cp->set('settings', [
|
||
'entity_type_ids' => ['taxonomy_term', 'user'],
|
||
'taxonomy_term' => [
|
||
'handler' => 'default:taxonomy_term',
|
||
'handler_settings' => [
|
||
'target_bundles' => ['site_sections' => 'site_sections'],
|
||
'sort' => ['field' => 'name', 'direction' => 'asc'],
|
||
'auto_create' => FALSE,
|
||
],
|
||
],
|
||
'user' => [
|
||
'handler' => 'default:user',
|
||
'handler_settings' => [
|
||
'include_anonymous' => FALSE,
|
||
'filter' => ['type' => '_none'],
|
||
'target_bundles' => NULL,
|
||
'sort' => ['field' => 'name', 'direction' => 'asc'],
|
||
'auto_create' => FALSE,
|
||
],
|
||
],
|
||
]);
|
||
$deps = $field_cp->get('dependencies') ?? [];
|
||
$mods = $deps['module'] ?? [];
|
||
if (!in_array('dynamic_entity_reference', $mods, TRUE)) {
|
||
$mods[] = 'dynamic_entity_reference';
|
||
}
|
||
$deps['module'] = $mods;
|
||
$field_cp->set('dependencies', $deps);
|
||
$field_cp->save(TRUE);
|
||
$messages[] = 'Instância content_page atualizada.';
|
||
}
|
||
|
||
// Update section_page instance config (taxonomy_term only).
|
||
$field_sp = \Drupal::configFactory()->getEditable('field.field.node.section_page.field_site_section');
|
||
if (!$field_sp->isNew()) {
|
||
$field_sp->set('field_type', 'dynamic_entity_reference');
|
||
$field_sp->set('settings', [
|
||
'entity_type_ids' => ['taxonomy_term'],
|
||
'taxonomy_term' => [
|
||
'handler' => 'default:taxonomy_term',
|
||
'handler_settings' => [
|
||
'target_bundles' => ['site_sections' => 'site_sections'],
|
||
'sort' => ['field' => 'name', 'direction' => 'asc'],
|
||
'auto_create' => FALSE,
|
||
],
|
||
],
|
||
]);
|
||
$deps = $field_sp->get('dependencies') ?? [];
|
||
$mods = $deps['module'] ?? [];
|
||
if (!in_array('dynamic_entity_reference', $mods, TRUE)) {
|
||
$mods[] = 'dynamic_entity_reference';
|
||
}
|
||
$deps['module'] = $mods;
|
||
$field_sp->set('dependencies', $deps);
|
||
$field_sp->save(TRUE);
|
||
$messages[] = 'Instância section_page atualizada.';
|
||
}
|
||
|
||
// Rebuild field definitions cache.
|
||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||
|
||
return implode(' ', $messages);
|
||
}
|
||
|
||
/**
|
||
* Restringe field_parent_page a content_page nodes e limpa referências inválidas.
|
||
*
|
||
* Na arquitetura anterior, field_parent_page podia apontar para taxonomy_term
|
||
* ou user (como raiz do domínio). Na nova arquitetura, field_site_section é o
|
||
* portador do domínio raiz, e field_parent_page aponta somente para
|
||
* content_page nodes pai (ou é nulo para páginas raiz).
|
||
*
|
||
* Passos:
|
||
* 1. Zera linhas onde target_type != 'node' (eram raízes de domínio).
|
||
* 2. Atualiza storage e instância para entity_type_ids: [node].
|
||
*/
|
||
function structural_pages_update_10012(): string {
|
||
$database = \Drupal::database();
|
||
$schema = $database->schema();
|
||
$messages = [];
|
||
|
||
foreach (['node__field_parent_page', 'node_revision__field_parent_page'] as $table) {
|
||
if (!$schema->tableExists($table)) {
|
||
continue;
|
||
}
|
||
// Clear rows that pointed to non-node entities (taxonomy_term, user, etc.).
|
||
$deleted = $database->delete($table)
|
||
->condition('field_parent_page_target_type', 'node', '<>')
|
||
->execute();
|
||
if ($deleted) {
|
||
$messages[] = "Removidas $deleted linhas não-node em $table.";
|
||
}
|
||
}
|
||
|
||
// Update storage config.
|
||
$storage = \Drupal::configFactory()->getEditable('field.storage.node.field_parent_page');
|
||
if (!$storage->isNew()) {
|
||
$storage->set('settings.entity_type_ids', ['node'])->save(TRUE);
|
||
$messages[] = 'Storage field_parent_page restrito a [node].';
|
||
}
|
||
|
||
// Update content_page instance config.
|
||
$field_cp = \Drupal::configFactory()->getEditable('field.field.node.content_page.field_parent_page');
|
||
if (!$field_cp->isNew()) {
|
||
$field_cp->set('settings', [
|
||
'entity_type_ids' => ['node'],
|
||
'node' => [
|
||
'handler' => 'default:node',
|
||
'handler_settings' => [
|
||
'target_bundles' => ['content_page' => 'content_page'],
|
||
'sort' => ['field' => 'title', 'direction' => 'asc'],
|
||
'auto_create' => FALSE,
|
||
],
|
||
],
|
||
]);
|
||
// Remove dependencies no longer needed (taxonomy, user).
|
||
$deps = $field_cp->get('dependencies') ?? [];
|
||
$config_deps = $deps['config'] ?? [];
|
||
$config_deps = array_filter($config_deps, fn($c) => !str_contains($c, 'taxonomy.vocabulary') && !str_contains($c, 'node.type.section_page'));
|
||
$deps['config'] = array_values($config_deps);
|
||
$field_cp->set('dependencies', $deps);
|
||
$field_cp->save(TRUE);
|
||
$messages[] = 'Instância field_parent_page restrita a content_page.';
|
||
}
|
||
|
||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||
|
||
return empty($messages) ? 'Nada a migrar.' : implode(' ', $messages);
|
||
}
|
||
|
||
/**
|
||
* Corrige o tipo do storage field_parent_page para dynamic_entity_reference.
|
||
*
|
||
* Os update hooks anteriores (10006–10012) alteraram apenas as settings do
|
||
* campo (entity_type_ids, exclude_entity_types) sem mudar o campo 'type' de
|
||
* 'entity_reference' para 'dynamic_entity_reference'. Isso fazia com que o
|
||
* sistema de entity queries do Drupal rejeitasse o especificador 'target_type'
|
||
* com "Invalid specifier 'target_type'".
|
||
*
|
||
* Além disso, se o módulo event_management estiver instalado, adiciona 'event'
|
||
* ao entity_type_ids do field_site_section para que content_pages possam
|
||
* referenciar eventos como domínio raiz.
|
||
*/
|
||
function structural_pages_update_10013(): string {
|
||
$messages = [];
|
||
|
||
// 1. Migra field_parent_page de entity_reference para dynamic_entity_reference.
|
||
$storage = \Drupal::configFactory()->getEditable('field.storage.node.field_parent_page');
|
||
if (!$storage->isNew() && $storage->get('type') !== 'dynamic_entity_reference') {
|
||
$database = \Drupal::database();
|
||
$schema = $database->schema();
|
||
$col_spec = [
|
||
'type' => 'varchar_ascii',
|
||
'length' => 128,
|
||
'not null' => FALSE,
|
||
'description' => 'Entity type of the field_parent_page reference.',
|
||
];
|
||
foreach (['node__field_parent_page', 'node_revision__field_parent_page'] as $table) {
|
||
if ($schema->tableExists($table) && !$schema->fieldExists($table, 'field_parent_page_target_type')) {
|
||
$schema->addField($table, 'field_parent_page_target_type', $col_spec);
|
||
$database->update($table)
|
||
->fields(['field_parent_page_target_type' => 'node'])
|
||
->isNull('field_parent_page_target_type')
|
||
->execute();
|
||
}
|
||
}
|
||
$storage->set('type', 'dynamic_entity_reference');
|
||
$storage->set('module', 'dynamic_entity_reference');
|
||
$deps = $storage->get('dependencies') ?? [];
|
||
$mods = $deps['module'] ?? [];
|
||
if (!in_array('dynamic_entity_reference', $mods, TRUE)) {
|
||
$mods[] = 'dynamic_entity_reference';
|
||
$deps['module'] = $mods;
|
||
$storage->set('dependencies', $deps);
|
||
}
|
||
$storage->save(TRUE);
|
||
$messages[] = 'field_parent_page migrado para dynamic_entity_reference.';
|
||
}
|
||
else {
|
||
$messages[] = 'field_parent_page já é dynamic_entity_reference.';
|
||
}
|
||
|
||
// 2. Adiciona 'event' ao field_site_section se event_management estiver instalado.
|
||
if (\Drupal::moduleHandler()->moduleExists('event_management')) {
|
||
$fss_storage = \Drupal::configFactory()->getEditable('field.storage.node.field_site_section');
|
||
if (!$fss_storage->isNew() && $fss_storage->get('type') === 'dynamic_entity_reference') {
|
||
$ids = $fss_storage->get('settings.entity_type_ids') ?? [];
|
||
if (!in_array('event', $ids, TRUE)) {
|
||
$ids[] = 'event';
|
||
$fss_storage->set('settings.entity_type_ids', $ids)->save(TRUE);
|
||
$messages[] = "'event' adicionado ao entity_type_ids de field_site_section.";
|
||
}
|
||
else {
|
||
$messages[] = "'event' já presente no entity_type_ids de field_site_section.";
|
||
}
|
||
}
|
||
|
||
// Adiciona handler settings de event na instância content_page.
|
||
$field_cp = \Drupal::configFactory()->getEditable('field.field.node.content_page.field_site_section');
|
||
if (!$field_cp->isNew() && $field_cp->get('field_type') === 'dynamic_entity_reference') {
|
||
$settings = $field_cp->get('settings') ?? [];
|
||
$type_ids = $settings['entity_type_ids'] ?? [];
|
||
if (!in_array('event', $type_ids, TRUE)) {
|
||
$type_ids[] = 'event';
|
||
$settings['entity_type_ids'] = $type_ids;
|
||
}
|
||
if (!isset($settings['event'])) {
|
||
$settings['event'] = [
|
||
'handler' => 'default:event',
|
||
'handler_settings' => [
|
||
'filter' => ['type' => '_none'],
|
||
'sort' => ['field' => 'title', 'direction' => 'asc'],
|
||
'auto_create' => FALSE,
|
||
],
|
||
];
|
||
}
|
||
$field_cp->set('settings', $settings)->save(TRUE);
|
||
$messages[] = "Handler settings de event adicionadas ao field_site_section de content_page.";
|
||
}
|
||
}
|
||
|
||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||
|
||
return implode(' ', $messages);
|
||
}
|
||
|
||
/**
|
||
* Implements hook_requirements().
|
||
*/
|
||
function structural_pages_requirements(string $phase): array {
|
||
$requirements = [];
|
||
|
||
if ($phase === 'runtime') {
|
||
// Check if the configured vocabulary has terms.
|
||
$vocabulary = _structural_pages_get_vocabulary();
|
||
$term_count = \Drupal::entityQuery('taxonomy_term')
|
||
->accessCheck(FALSE)
|
||
->condition('vid', $vocabulary)
|
||
->count()
|
||
->execute();
|
||
|
||
if ($term_count === 0) {
|
||
$requirements['structural_pages_terms'] = [
|
||
'title' => t('Structural Pages'),
|
||
'value' => t('No terms in @vocabulary vocabulary', ['@vocabulary' => $vocabulary]),
|
||
'description' => t('The Structural Pages module requires terms in the site section vocabulary. <a href=":url">Add terms</a>.', [
|
||
':url' => '/admin/structure/taxonomy/manage/' . $vocabulary . '/add',
|
||
]),
|
||
'severity' => REQUIREMENT_WARNING,
|
||
];
|
||
}
|
||
else {
|
||
$requirements['structural_pages_terms'] = [
|
||
'title' => t('Structural Pages'),
|
||
'value' => t('@count terms configured', ['@count' => $term_count]),
|
||
'severity' => REQUIREMENT_OK,
|
||
];
|
||
}
|
||
}
|
||
|
||
return $requirements;
|
||
}
|
||
|
||
/**
|
||
* Adiciona field_menu_title e field_weight ao tipo content_page.
|
||
*/
|
||
function structural_pages_update_10014(): string {
|
||
$etm = \Drupal::entityTypeManager();
|
||
|
||
// --- field_menu_title ---
|
||
if (!\Drupal\field\Entity\FieldStorageConfig::loadByName('node', 'field_menu_title')) {
|
||
\Drupal\field\Entity\FieldStorageConfig::create([
|
||
'field_name' => 'field_menu_title',
|
||
'entity_type' => 'node',
|
||
'type' => 'string',
|
||
'settings' => ['max_length' => 128],
|
||
'cardinality' => 1,
|
||
'translatable' => TRUE,
|
||
])->save();
|
||
}
|
||
if (!\Drupal\field\Entity\FieldConfig::loadByName('node', 'content_page', 'field_menu_title')) {
|
||
\Drupal\field\Entity\FieldConfig::create([
|
||
'field_name' => 'field_menu_title',
|
||
'entity_type' => 'node',
|
||
'bundle' => 'content_page',
|
||
'label' => 'Título no menu',
|
||
'description' => 'Título abreviado para o menu de navegação. Se vazio, usa o título da página.',
|
||
'required' => FALSE,
|
||
'translatable' => TRUE,
|
||
])->save();
|
||
}
|
||
|
||
// --- field_weight ---
|
||
if (!\Drupal\field\Entity\FieldStorageConfig::loadByName('node', 'field_weight')) {
|
||
\Drupal\field\Entity\FieldStorageConfig::create([
|
||
'field_name' => 'field_weight',
|
||
'entity_type' => 'node',
|
||
'type' => 'integer',
|
||
'cardinality' => 1,
|
||
'translatable' => FALSE,
|
||
])->save();
|
||
}
|
||
if (!\Drupal\field\Entity\FieldConfig::loadByName('node', 'content_page', 'field_weight')) {
|
||
\Drupal\field\Entity\FieldConfig::create([
|
||
'field_name' => 'field_weight',
|
||
'entity_type' => 'node',
|
||
'bundle' => 'content_page',
|
||
'label' => 'Peso (ordenação)',
|
||
'description' => 'Valores menores aparecem primeiro entre páginas com o mesmo pai. Padrão: 0.',
|
||
'required' => FALSE,
|
||
'translatable' => FALSE,
|
||
'default_value' => [['value' => 0]],
|
||
])->save();
|
||
}
|
||
|
||
// Adiciona os campos ao form display padrão.
|
||
$form_display = $etm->getStorage('entity_form_display')
|
||
->load('node.content_page.default');
|
||
if ($form_display) {
|
||
if (!$form_display->getComponent('field_menu_title')) {
|
||
$form_display->setComponent('field_menu_title', [
|
||
'type' => 'string_textfield',
|
||
'weight' => 1,
|
||
'region' => 'content',
|
||
'settings' => ['size' => 60, 'placeholder' => ''],
|
||
]);
|
||
}
|
||
if (!$form_display->getComponent('field_weight')) {
|
||
$form_display->setComponent('field_weight', [
|
||
'type' => 'number',
|
||
'weight' => 27,
|
||
'region' => 'content',
|
||
'settings' => ['placeholder' => '0'],
|
||
]);
|
||
}
|
||
$form_display->save();
|
||
}
|
||
|
||
return 'Campos field_menu_title e field_weight adicionados ao content_page.';
|
||
}
|
||
|
||
/**
|
||
* Adiciona field_show_in_menu (boolean) ao content_page.
|
||
*
|
||
* Retroativamente define o valor como 1 (exibir) para todas as páginas
|
||
* existentes, preservando o comportamento anterior.
|
||
*/
|
||
function structural_pages_update_10015(): string {
|
||
$etm = \Drupal::entityTypeManager();
|
||
|
||
// --- field_show_in_menu ---
|
||
if (!\Drupal\field\Entity\FieldStorageConfig::loadByName('node', 'field_show_in_menu')) {
|
||
\Drupal\field\Entity\FieldStorageConfig::create([
|
||
'field_name' => 'field_show_in_menu',
|
||
'entity_type' => 'node',
|
||
'type' => 'boolean',
|
||
'settings' => ['on_label' => 'Sim', 'off_label' => 'Não'],
|
||
'cardinality' => 1,
|
||
'translatable' => FALSE,
|
||
])->save();
|
||
}
|
||
if (!\Drupal\field\Entity\FieldConfig::loadByName('node', 'content_page', 'field_show_in_menu')) {
|
||
\Drupal\field\Entity\FieldConfig::create([
|
||
'field_name' => 'field_show_in_menu',
|
||
'entity_type' => 'node',
|
||
'bundle' => 'content_page',
|
||
'label' => 'Exibir no menu',
|
||
'description' => 'Se desmarcado, a página não aparecerá no menu de navegação.',
|
||
'required' => FALSE,
|
||
'translatable' => FALSE,
|
||
'default_value' => [['value' => 1]],
|
||
])->save();
|
||
}
|
||
|
||
// Adiciona ao form display padrão.
|
||
$form_display = $etm->getStorage('entity_form_display')
|
||
->load('node.content_page.default');
|
||
if ($form_display && !$form_display->getComponent('field_show_in_menu')) {
|
||
$form_display->setComponent('field_show_in_menu', [
|
||
'type' => 'boolean_checkbox',
|
||
'weight' => 1,
|
||
'region' => 'content',
|
||
'settings' => ['display_label' => TRUE],
|
||
]);
|
||
$form_display->save();
|
||
}
|
||
|
||
// Retroativamente define field_show_in_menu = 1 para content_pages existentes
|
||
// que ainda não têm valor (NULL é tratado como 0 nas queries).
|
||
$nids = $etm->getStorage('node')->getQuery()
|
||
->accessCheck(FALSE)
|
||
->condition('type', 'content_page')
|
||
->notExists('field_show_in_menu')
|
||
->execute();
|
||
|
||
foreach ($nids as $nid) {
|
||
/** @var \Drupal\node\NodeInterface $node */
|
||
$node = $etm->getStorage('node')->load($nid);
|
||
if ($node) {
|
||
$node->set('field_show_in_menu', 1);
|
||
$node->save();
|
||
}
|
||
}
|
||
|
||
return 'Campo field_show_in_menu adicionado ao content_page; ' . count($nids) . ' página(s) retroativamente marcada(s) como visíveis.';
|
||
}
|