From c96268e09d439a77a6a4959667cb7c53b0902b62 Mon Sep 17 00:00:00 2001 From: "Quintino A. G. Souza" Date: Thu, 19 Mar 2026 14:28:21 -0300 Subject: [PATCH] =?UTF-8?q?Adiciona=20menu=20'Adicionar'=20configur=C3=A1v?= =?UTF-8?q?el=20no=20menu=20da=20conta=20do=20usu=C3=A1rio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Item pai 'Adicionar' no menu account com subitens derivados dinamicamente a partir de site_users.settings:add_content_links. O pai fica oculto quando o usuário não tem acesso a nenhum dos routes configurados. - Rota site_users.add_content com _custom_access via AddContentAccessCheck - hook_menu_links_discovered_alter() gera os subitens com IDs estáveis - Formulário de settings com tabela editável (label, rota, parâmetro, peso) - CSS do microsite atualizado com dropdown ao hover/focus-within Co-Authored-By: Claude Sonnet 4.6 --- config/install/site_users.settings.yml | 1 + config/schema/site_users.schema.yml | 22 +++++ site_users.links.menu.yml | 7 ++ site_users.module | 42 +++++++++ site_users.routing.yml | 8 ++ site_users.services.yml | 6 ++ src/Access/AddContentAccessCheck.php | 47 ++++++++++ src/Controller/AddContentController.php | 48 +++++++++++ src/Form/SiteUsersSettingsForm.php | 86 +++++++++++++++++++ src/Plugin/Menu/AddContentMenuLink.php | 18 ++++ src/Plugin/Menu/AddContentMenuLinkDeriver.php | 64 ++++++++++++++ .../css/microsite.css | 34 ++++++++ 12 files changed, 383 insertions(+) create mode 100644 src/Access/AddContentAccessCheck.php create mode 100644 src/Controller/AddContentController.php create mode 100644 src/Plugin/Menu/AddContentMenuLink.php create mode 100644 src/Plugin/Menu/AddContentMenuLinkDeriver.php diff --git a/config/install/site_users.settings.yml b/config/install/site_users.settings.yml index d94e22b..f02de05 100644 --- a/config/install/site_users.settings.yml +++ b/config/install/site_users.settings.yml @@ -9,3 +9,4 @@ user_editable_fields: field_user_social_links: true field_user_photos: true role_view_modes: { } +add_content_links: [ ] diff --git a/config/schema/site_users.schema.yml b/config/schema/site_users.schema.yml index acf8135..d14adf0 100644 --- a/config/schema/site_users.schema.yml +++ b/config/schema/site_users.schema.yml @@ -30,3 +30,25 @@ site_users.settings: sequence: type: string label: 'View mode machine name' + add_content_links: + type: sequence + label: 'Add content menu items' + sequence: + type: mapping + label: 'Add content menu item' + mapping: + label: + type: label + label: 'Menu item label' + route_name: + type: string + label: 'Route name' + route_parameters: + type: sequence + label: 'Route parameters' + sequence: + type: string + label: 'Parameter value' + weight: + type: integer + label: 'Weight' diff --git a/site_users.links.menu.yml b/site_users.links.menu.yml index 797d5ef..8a018dc 100644 --- a/site_users.links.menu.yml +++ b/site_users.links.menu.yml @@ -4,3 +4,10 @@ site_users.settings: route_name: site_users.settings parent: site_tools.admin_config weight: 10 + +site_users.add_content: + title: 'Adicionar' + route_name: site_users.add_content + menu_name: account + weight: -5 + expanded: true diff --git a/site_users.module b/site_users.module index 22ca1cd..57940ea 100644 --- a/site_users.module +++ b/site_users.module @@ -11,6 +11,7 @@ use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Menu\MenuLinkDefault; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; use Drupal\field\FieldConfigInterface; @@ -604,3 +605,44 @@ function site_users_get_default_photo(UserInterface $user): ?MediaInterface { return NULL; } + +/** + * Implements hook_menu_links_discovered_alter(). + * + * Adiciona dinamicamente os subitens do menu "Adicionar" configurados em + * site_users.settings:add_content_links. + */ +function site_users_menu_links_discovered_alter(array &$links): void { + $items = \Drupal::config('site_users.settings')->get('add_content_links') ?? []; + + foreach ($items as $item) { + if (empty($item['route_name']) || empty($item['label'])) { + continue; + } + + // ID estável derivado da rota e dos parâmetros. + $parts = [preg_replace('/[^a-z0-9]/', '_', strtolower($item['route_name']))]; + foreach ($item['route_parameters'] ?? [] as $value) { + $parts[] = preg_replace('/[^a-z0-9]/', '_', strtolower((string) $value)); + } + $id = 'site_users.add_content_child.' . implode('_', $parts); + + $links[$id] = [ + 'id' => $id, + 'title' => $item['label'], + 'route_name' => $item['route_name'], + 'route_parameters' => $item['route_parameters'] ?? [], + 'menu_name' => 'account', + 'parent' => 'site_users.add_content', + 'weight' => (int) ($item['weight'] ?? 0), + 'provider' => 'site_users', + 'class' => MenuLinkDefault::class, + 'form_class' => 'Drupal\Core\Menu\Form\MenuLinkDefaultForm', + 'metadata' => [], + 'options' => [], + 'expanded' => FALSE, + 'enabled' => TRUE, + 'discovered' => TRUE, + ]; + } +} diff --git a/site_users.routing.yml b/site_users.routing.yml index 67926d8..7750e1a 100644 --- a/site_users.routing.yml +++ b/site_users.routing.yml @@ -5,3 +5,11 @@ site_users.settings: _title: 'Site Users Settings' requirements: _permission: 'administer site_users settings' + +site_users.add_content: + path: '/add-content' + defaults: + _controller: '\Drupal\site_users\Controller\AddContentController::redirectToFirst' + _title: 'Adicionar' + requirements: + _custom_access: 'site_users.add_content_access::access' diff --git a/site_users.services.yml b/site_users.services.yml index 459b64e..967c35b 100644 --- a/site_users.services.yml +++ b/site_users.services.yml @@ -6,3 +6,9 @@ services: - '@entity_type.manager' - '@file.repository' - '@file_system' + + site_users.add_content_access: + class: Drupal\site_users\Access\AddContentAccessCheck + arguments: + - '@config.factory' + - '@access_manager' diff --git a/src/Access/AddContentAccessCheck.php b/src/Access/AddContentAccessCheck.php new file mode 100644 index 0000000..6f161ed --- /dev/null +++ b/src/Access/AddContentAccessCheck.php @@ -0,0 +1,47 @@ +configFactory + ->get('site_users.settings') + ->get('add_content_links') ?? []; + + foreach ($items as $item) { + if (empty($item['route_name'])) { + continue; + } + $params = $item['route_parameters'] ?? []; + if ($this->accessManager->checkNamedRoute($item['route_name'], $params, $account)) { + return AccessResult::allowed() + ->addCacheContexts(['user.permissions', 'user.roles']); + } + } + + return AccessResult::forbidden() + ->addCacheContexts(['user.permissions', 'user.roles']); + } + +} diff --git a/src/Controller/AddContentController.php b/src/Controller/AddContentController.php new file mode 100644 index 0000000..3a518c4 --- /dev/null +++ b/src/Controller/AddContentController.php @@ -0,0 +1,48 @@ +get('access_manager'), + ); + } + + /** + * Redirects to the first accessible add route, or to the front page. + */ + public function redirectToFirst(): RedirectResponse { + $items = $this->config('site_users.settings') + ->get('add_content_links') ?? []; + $account = $this->currentUser(); + + foreach ($items as $item) { + if (empty($item['route_name'])) { + continue; + } + $params = $item['route_parameters'] ?? []; + if ($this->accessManager->checkNamedRoute($item['route_name'], $params, $account)) { + return $this->redirect($item['route_name'], $params); + } + } + + return $this->redirect(''); + } + +} diff --git a/src/Form/SiteUsersSettingsForm.php b/src/Form/SiteUsersSettingsForm.php index 6b3db8e..e64a2c7 100644 --- a/src/Form/SiteUsersSettingsForm.php +++ b/src/Form/SiteUsersSettingsForm.php @@ -177,6 +177,66 @@ class SiteUsersSettingsForm extends ConfigFormBase { } } + // Fieldset para itens do menu "Adicionar". + $form['add_content_links'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Add content menu items'), + '#description' => $this->t('Configure items shown under the "Adicionar" entry in the account menu. Each item points to an entity add route. Leave "Label" empty to remove the row.'), + '#tree' => TRUE, + ]; + + $saved_items = $config->get('add_content_links') ?? []; + // Append one blank row for adding a new item. + $saved_items[] = ['label' => '', 'route_name' => '', 'route_parameters' => [], 'weight' => 0]; + + $form['add_content_links']['table'] = [ + '#type' => 'table', + '#header' => [ + $this->t('Label'), + $this->t('Route name'), + $this->t('Parameter name'), + $this->t('Parameter value'), + $this->t('Weight'), + ], + '#empty' => $this->t('No items yet. Fill in the row below to add one.'), + ]; + + foreach ($saved_items as $delta => $item) { + $params = $item['route_parameters'] ?? []; + $param_name = array_key_first($params) ?? ''; + $param_value = $params[$param_name] ?? ''; + + $form['add_content_links']['table'][$delta]['label'] = [ + '#type' => 'textfield', + '#default_value' => $item['label'] ?? '', + '#size' => 20, + '#placeholder' => $this->t('e.g. Artigo'), + ]; + $form['add_content_links']['table'][$delta]['route_name'] = [ + '#type' => 'textfield', + '#default_value' => $item['route_name'] ?? '', + '#size' => 30, + '#placeholder' => 'e.g. node.add', + ]; + $form['add_content_links']['table'][$delta]['param_name'] = [ + '#type' => 'textfield', + '#default_value' => $param_name, + '#size' => 20, + '#placeholder' => 'e.g. node_type', + ]; + $form['add_content_links']['table'][$delta]['param_value'] = [ + '#type' => 'textfield', + '#default_value' => $param_value, + '#size' => 20, + '#placeholder' => 'e.g. article', + ]; + $form['add_content_links']['table'][$delta]['weight'] = [ + '#type' => 'number', + '#default_value' => (int) ($item['weight'] ?? 0), + '#size' => 4, + ]; + } + return parent::buildForm($form, $form_state); } @@ -230,6 +290,32 @@ class SiteUsersSettingsForm extends ConfigFormBase { } } + // Salvar add_content_links: ignorar linhas sem label ou route_name. + $links_raw = $form_state->getValue(['add_content_links', 'table']) ?? []; + $add_content_links = []; + foreach ($links_raw as $row) { + $label = trim($row['label'] ?? ''); + $route_name = trim($row['route_name'] ?? ''); + if ($label === '' || $route_name === '') { + continue; + } + $params = []; + $param_name = trim($row['param_name'] ?? ''); + $param_value = trim($row['param_value'] ?? ''); + if ($param_name !== '') { + $params[$param_name] = $param_value; + } + $add_content_links[] = [ + 'label' => $label, + 'route_name' => $route_name, + 'route_parameters' => $params, + 'weight' => (int) ($row['weight'] ?? 0), + ]; + } + // Reordena por weight. + usort($add_content_links, fn($a, $b) => $a['weight'] <=> $b['weight']); + $config->set('add_content_links', $add_content_links); + // Salvar role_view_modes: apenas os valores marcados (filtrar 0). $role_view_modes_raw = $form_state->getValue('role_view_modes') ?? []; $roles = \Drupal\user\Entity\Role::loadMultiple(); diff --git a/src/Plugin/Menu/AddContentMenuLink.php b/src/Plugin/Menu/AddContentMenuLink.php new file mode 100644 index 0000000..944cd6e --- /dev/null +++ b/src/Plugin/Menu/AddContentMenuLink.php @@ -0,0 +1,18 @@ +get('config.factory')); + } + + /** + * {@inheritdoc} + */ + public function getDerivativeDefinitions($base_plugin_definition): array { + $this->derivatives = []; + + $items = $this->configFactory + ->get('site_users.settings') + ->get('add_content_links') ?? []; + + foreach ($items as $item) { + if (empty($item['route_name']) || empty($item['label'])) { + continue; + } + + $id = $this->buildId($item); + $this->derivatives[$id] = $base_plugin_definition; + $this->derivatives[$id]['title'] = $item['label']; + $this->derivatives[$id]['route_name'] = $item['route_name']; + $this->derivatives[$id]['route_parameters'] = $item['route_parameters'] ?? []; + $this->derivatives[$id]['weight'] = (int) ($item['weight'] ?? 0); + $this->derivatives[$id]['menu_name'] = 'account'; + $this->derivatives[$id]['parent'] = 'site_users.add_content'; + } + + return $this->derivatives; + } + + /** + * Builds a stable derivative ID from the item's route and parameters. + */ + private function buildId(array $item): string { + $parts = [preg_replace('/[^a-z0-9]/', '_', strtolower($item['route_name']))]; + foreach ($item['route_parameters'] ?? [] as $value) { + $parts[] = preg_replace('/[^a-z0-9]/', '_', strtolower((string) $value)); + } + return implode('_', $parts); + } + +} diff --git a/themes/site_users_microsite_theme/css/microsite.css b/themes/site_users_microsite_theme/css/microsite.css index 96bfc3d..453f43c 100644 --- a/themes/site_users_microsite_theme/css/microsite.css +++ b/themes/site_users_microsite_theme/css/microsite.css @@ -310,8 +310,42 @@ body.microsite { margin-inline-end: 18px; } +.microsite-top-bar li.menu__item { + position: relative; +} + .microsite-top-bar ul.menu ul.menu { display: none; + position: absolute; + top: 100%; + left: 0; + min-width: 160px; + flex-direction: column; + background-color: hsl(201, 15%, 15%); + border-radius: 4px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35); + z-index: 100; + padding: 4px 0; +} + +.microsite-top-bar li.menu__item:hover > ul.menu, +.microsite-top-bar li.menu__item:focus-within > ul.menu { + display: flex; +} + +.microsite-top-bar ul.menu ul.menu li.menu__item { + margin-inline-end: 0; + white-space: nowrap; +} + +.microsite-top-bar ul.menu ul.menu a.menu__link { + padding: 6px 16px; + width: 100%; + box-sizing: border-box; +} + +.microsite-top-bar ul.menu ul.menu a.menu__link:hover { + background-color: hsl(201, 15%, 25%); } .microsite-top-bar a.menu__link {