diff --git a/README.md b/README.md index eca9394..1764932 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,109 @@ if ($photo instanceof \Drupal\media\MediaInterface) { } ``` +## Sub-módulo: Site Users Microsite + +O sub-módulo `site_users_microsite` provê micro-sites pessoais para cada usuário, +acessíveis em `/user/{id}`, com tema próprio e sem a navegação principal do site. + +### Funcionalidades + +- Tema alternativo aplicado automaticamente em todas as rotas `/user/{id}` e `/user/{id}/content` +- Página `/user/{id}/content` listando o conteúdo publicado pelo usuário +- Configuração de quais tipos de conteúdo cada papel pode publicar no micro-site +- Aba "Content" adicionada às páginas de perfil de usuário + +### Instalação + +O sub-módulo está neste repositório mas é ativado independentemente: + +```bash +drush en site_users_microsite +``` + +O tema `site_users_microsite_theme` também precisa ser implantado e ativado +(ver seção [Deploy do tema](#deploy-do-tema) abaixo). + +### Configuração + +Acesse as configurações em: +**Administração > Configuração > Módulos Locais > Site Users > User Microsite** + +`/admin/config/local-modules/site-users/microsite` + +Configure quais tipos de conteúdo cada papel pode publicar no micro-site. + +### Layout de blocos + +No tema `site_users_microsite_theme`, configure os blocos em +**Estrutura > Layout de blocos**: + +| Bloco | Região sugerida | +|---|---| +| Informações do Usuário (ou bloco customizado) | Microsite Header | +| Primary tabs | Tabs | +| Status messages | Messages | +| Main page content | Content | + +### Permissões + +| Permissão | Descrição | +|---|---| +| `administer site_users_microsite settings` | Configurar tipos de conteúdo por papel | + +### Variáveis disponíveis no tema + +O módulo injeta as seguintes variáveis em `page.html.twig` quando em rotas do micro-site: + +```twig +{{ microsite_user }} {# entidade UserInterface do dono do micro-site #} +{{ microsite_user_name }} {# nome de exibição (string) #} +{{ microsite_user_roles }} {# array de roles (exceto 'authenticated') #} +{{ microsite_user_photo }} {# render array da foto padrão (view mode 'thumbnail') #} +``` + +--- + +## Deploy do tema + +O tema `site_users_microsite_theme` está versionado neste repositório em +`themes/site_users_microsite_theme/`, mas o Drupal só descobre temas em +`themes/custom/`. É necessário implantá-lo manualmente no servidor. + +### Opção 1 — Symlink (recomendado em desenvolvimento) + +```bash +ln -s /caminho/para/site_users/themes/site_users_microsite_theme \ + /caminho/para/drupal/themes/custom/site_users_microsite_theme +``` + +### Opção 2 — Cópia (recomendado em produção) + +```bash +cp -r themes/site_users_microsite_theme \ + /caminho/para/drupal/themes/custom/site_users_microsite_theme +``` + +Após implantar, ative o tema: + +```bash +drush theme:enable site_users_microsite_theme +``` + +O tema não precisa ser definido como tema padrão do site — o módulo +`site_users_microsite` o aplica automaticamente via ThemeNegotiator apenas +nas rotas de micro-site. + +### Personalização do tema + +- `base theme` em `site_users_microsite_theme.info.yml` está definido como `stable9` + (tema base mínimo do core, sem estilos próprios). Altere para o tema principal + do site se quiser herdar fontes e variáveis CSS. +- O template `templates/layout/page.html.twig` define o layout completo da página. +- O CSS base está em `css/microsite.css`. + +--- + ## Estrutura do Módulo ``` @@ -127,6 +230,26 @@ site_users/ ├── templates/ │ ├── site-user-info-block.html.twig │ └── user--full.html.twig +├── site_users_microsite/ ← sub-módulo +│ ├── config/install/ +│ │ └── site_users_microsite.settings.yml +│ ├── src/ +│ │ ├── Controller/MicrositeContentController.php +│ │ ├── Form/MicrositeSettingsForm.php +│ │ └── Theme/MicrositeThemeNegotiator.php +│ ├── site_users_microsite.info.yml +│ ├── site_users_microsite.links.menu.yml +│ ├── site_users_microsite.links.task.yml +│ ├── site_users_microsite.module +│ ├── site_users_microsite.permissions.yml +│ ├── site_users_microsite.routing.yml +│ └── site_users_microsite.services.yml +├── themes/ +│ └── site_users_microsite_theme/ ← tema (deploy em themes/custom/) +│ ├── css/microsite.css +│ ├── templates/layout/page.html.twig +│ ├── site_users_microsite_theme.info.yml +│ └── site_users_microsite_theme.libraries.yml ├── site_users.info.yml ├── site_users.install ├── site_users.libraries.yml diff --git a/modules/site_users_microsite/config/install/site_users_microsite.settings.yml b/modules/site_users_microsite/config/install/site_users_microsite.settings.yml new file mode 100644 index 0000000..90f2a0c --- /dev/null +++ b/modules/site_users_microsite/config/install/site_users_microsite.settings.yml @@ -0,0 +1 @@ +role_content_types: {} diff --git a/modules/site_users_microsite/site_users_microsite.info.yml b/modules/site_users_microsite/site_users_microsite.info.yml new file mode 100644 index 0000000..0f026ad --- /dev/null +++ b/modules/site_users_microsite/site_users_microsite.info.yml @@ -0,0 +1,9 @@ +name: 'Site Users Microsite' +type: module +description: 'Micro-site pessoal para usuários do site, com tema próprio e conteúdo por papel.' +core_version_requirement: ^10 || ^11 +package: Custom +dependencies: + - drupal:user + - drupal:node + - site_users:site_users diff --git a/modules/site_users_microsite/site_users_microsite.links.menu.yml b/modules/site_users_microsite/site_users_microsite.links.menu.yml new file mode 100644 index 0000000..a9da2f0 --- /dev/null +++ b/modules/site_users_microsite/site_users_microsite.links.menu.yml @@ -0,0 +1,6 @@ +site_users_microsite.settings: + title: 'User Microsite' + description: 'Configure content types available per role in user micro-sites.' + route_name: site_users_microsite.settings + parent: site_users.settings + weight: 10 diff --git a/modules/site_users_microsite/site_users_microsite.links.task.yml b/modules/site_users_microsite/site_users_microsite.links.task.yml new file mode 100644 index 0000000..bfd2d58 --- /dev/null +++ b/modules/site_users_microsite/site_users_microsite.links.task.yml @@ -0,0 +1,6 @@ +# Aba "Conteúdo" nas páginas de perfil de usuário. +site_users_microsite.content_tab: + route_name: site_users_microsite.content + title: 'Content' + base_route: entity.user.canonical + weight: 10 diff --git a/modules/site_users_microsite/site_users_microsite.module b/modules/site_users_microsite/site_users_microsite.module new file mode 100644 index 0000000..fdd43fc --- /dev/null +++ b/modules/site_users_microsite/site_users_microsite.module @@ -0,0 +1,43 @@ +getRouteName(); + + $is_microsite = $route_name === 'entity.user.canonical' + || str_starts_with($route_name, 'site_users_microsite.'); + + if (!$is_microsite) { + return; + } + + $user = $route_match->getParameter('user'); + if (!($user instanceof UserInterface)) { + return; + } + + $variables['microsite_user'] = $user; + $variables['microsite_user_name'] = $user->getDisplayName(); + $variables['microsite_user_roles'] = $user->getRoles(TRUE); + + $photo = site_users_get_default_photo($user); + if ($photo) { + $render = \Drupal::entityTypeManager() + ->getViewBuilder('media') + ->view($photo, 'thumbnail'); + $variables['microsite_user_photo'] = \Drupal::service('renderer') + ->renderInIsolation($render); + } +} diff --git a/modules/site_users_microsite/site_users_microsite.permissions.yml b/modules/site_users_microsite/site_users_microsite.permissions.yml new file mode 100644 index 0000000..6426b64 --- /dev/null +++ b/modules/site_users_microsite/site_users_microsite.permissions.yml @@ -0,0 +1,4 @@ +administer site_users_microsite settings: + title: 'Administer User Microsite settings' + description: 'Configure which content types are available per role in micro-sites.' + restrict access: true diff --git a/modules/site_users_microsite/site_users_microsite.routing.yml b/modules/site_users_microsite/site_users_microsite.routing.yml new file mode 100644 index 0000000..9b211c7 --- /dev/null +++ b/modules/site_users_microsite/site_users_microsite.routing.yml @@ -0,0 +1,20 @@ +site_users_microsite.content: + path: '/user/{user}/content' + defaults: + _controller: '\Drupal\site_users_microsite\Controller\MicrositeContentController::content' + _title_callback: '\Drupal\site_users_microsite\Controller\MicrositeContentController::title' + requirements: + _permission: 'access content' + user: \d+ + options: + parameters: + user: + type: entity:user + +site_users_microsite.settings: + path: '/admin/config/local-modules/site-users/microsite' + defaults: + _form: '\Drupal\site_users_microsite\Form\MicrositeSettingsForm' + _title: 'User Microsite Settings' + requirements: + _permission: 'administer site_users_microsite settings' diff --git a/modules/site_users_microsite/site_users_microsite.services.yml b/modules/site_users_microsite/site_users_microsite.services.yml new file mode 100644 index 0000000..63b859d --- /dev/null +++ b/modules/site_users_microsite/site_users_microsite.services.yml @@ -0,0 +1,5 @@ +services: + site_users_microsite.theme_negotiator: + class: Drupal\site_users_microsite\Theme\MicrositeThemeNegotiator + tags: + - { name: theme_negotiator, priority: 100 } diff --git a/modules/site_users_microsite/src/Controller/MicrositeContentController.php b/modules/site_users_microsite/src/Controller/MicrositeContentController.php new file mode 100644 index 0000000..a412f8f --- /dev/null +++ b/modules/site_users_microsite/src/Controller/MicrositeContentController.php @@ -0,0 +1,84 @@ +getAllowedContentTypes($user); + + $cache = [ + 'contexts' => ['user'], + 'tags' => [ + 'node_list', + 'user:' . $user->id(), + 'config:site_users_microsite.settings', + ], + ]; + + if (empty($allowed_types)) { + return [ + '#markup' => $this->t('No content types are configured for this profile type.'), + '#cache' => $cache, + ]; + } + + $nids = $this->entityTypeManager()->getStorage('node') + ->getQuery() + ->condition('uid', $user->id()) + ->condition('type', $allowed_types, 'IN') + ->condition('status', 1) + ->accessCheck(TRUE) + ->sort('created', 'DESC') + ->execute(); + + if (empty($nids)) { + return [ + '#markup' => $this->t('No content published yet.'), + '#cache' => $cache, + ]; + } + + $nodes = $this->entityTypeManager()->getStorage('node')->loadMultiple($nids); + $build = $this->entityTypeManager()->getViewBuilder('node')->viewMultiple($nodes, 'teaser'); + $build['#cache'] = $cache; + + return $build; + } + + /** + * Callback de título para a página de conteúdo. + */ + public function title(UserInterface $user): TranslatableMarkup { + return $this->t("@name's content", ['@name' => $user->getDisplayName()]); + } + + /** + * Retorna os tipos de conteúdo permitidos para os papéis do usuário. + */ + protected function getAllowedContentTypes(UserInterface $user): array { + $role_content_types = $this->configFactory() + ->get('site_users_microsite.settings') + ->get('role_content_types') ?? []; + + $allowed = []; + foreach ($user->getRoles() as $role_id) { + foreach ($role_content_types[$role_id] ?? [] as $type) { + $allowed[$type] = $type; + } + } + + return array_keys($allowed); + } + +} diff --git a/modules/site_users_microsite/src/Form/MicrositeSettingsForm.php b/modules/site_users_microsite/src/Form/MicrositeSettingsForm.php new file mode 100644 index 0000000..4c025c0 --- /dev/null +++ b/modules/site_users_microsite/src/Form/MicrositeSettingsForm.php @@ -0,0 +1,94 @@ +config('site_users_microsite.settings'); + $content_types = $this->getContentTypes(); + + $form['role_content_types'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Content types per role'), + '#description' => $this->t('Select which content types each role can publish in their micro-site.'), + '#tree' => TRUE, + ]; + + if (empty($content_types)) { + $form['role_content_types']['_empty'] = [ + '#markup' => '
' . $this->t('No content types found.') . '
', + ]; + } + else { + foreach (Role::loadMultiple() as $role_id => $role) { + if (in_array($role_id, ['anonymous', 'authenticated'])) { + continue; + } + $form['role_content_types'][$role_id] = [ + '#type' => 'checkboxes', + '#title' => $role->label(), + '#options' => $content_types, + '#default_value' => $config->get('role_content_types.' . $role_id) ?? [], + ]; + } + } + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state): void { + $config = $this->config('site_users_microsite.settings'); + $raw = $form_state->getValue('role_content_types') ?? []; + + foreach (Role::loadMultiple() as $role_id => $role) { + if (in_array($role_id, ['anonymous', 'authenticated']) || !isset($raw[$role_id])) { + continue; + } + $config->set('role_content_types.' . $role_id, array_values(array_filter($raw[$role_id]))); + } + + $config->save(); + parent::submitForm($form, $form_state); + } + + /** + * Retorna todos os tipos de conteúdo disponíveis. + */ + protected function getContentTypes(): array { + $options = []; + foreach (\Drupal::entityTypeManager()->getStorage('node_type')->loadMultiple() as $type) { + $options[$type->id()] = $type->label(); + } + ksort($options); + return $options; + } + +} diff --git a/modules/site_users_microsite/src/Theme/MicrositeThemeNegotiator.php b/modules/site_users_microsite/src/Theme/MicrositeThemeNegotiator.php new file mode 100644 index 0000000..0af2911 --- /dev/null +++ b/modules/site_users_microsite/src/Theme/MicrositeThemeNegotiator.php @@ -0,0 +1,29 @@ +getRouteName(); + return $route_name === 'entity.user.canonical' + || str_starts_with($route_name, 'site_users_microsite.'); + } + + /** + * {@inheritdoc} + */ + public function determineActiveTheme(RouteMatchInterface $route_match): ?string { + return 'site_users_microsite_theme'; + } + +} diff --git a/themes/site_users_microsite_theme/css/microsite.css b/themes/site_users_microsite_theme/css/microsite.css new file mode 100644 index 0000000..f60ce4b --- /dev/null +++ b/themes/site_users_microsite_theme/css/microsite.css @@ -0,0 +1,195 @@ +/** + * Microsite theme — estilos base. + * Ponto de partida minimalista. Personalize conforme o design do site. + */ + +*, +*::before, +*::after { + box-sizing: border-box; +} + +body.microsite { + margin: 0; + font-family: system-ui, sans-serif; + background: #f5f5f5; + color: #222; +} + +/* Skip link */ +.skip-link { + position: absolute; + top: -100%; + left: 0; + padding: 0.5rem 1rem; + background: #000; + color: #fff; + z-index: 9999; +} +.skip-link:focus { + top: 0; +} + +/* Header ------------------------------------------------------------------- */ + +.microsite-header { + background: #1a1a2e; + color: #fff; + padding: 2rem 1.5rem; +} + +.microsite-header__inner { + display: flex; + align-items: center; + gap: 1.5rem; + max-width: 900px; + margin: 0 auto; +} + +.microsite-header__photo img { + width: 80px; + height: 80px; + border-radius: 50%; + object-fit: cover; + border: 3px solid rgba(255, 255, 255, 0.3); +} + +.microsite-header__name { + margin: 0 0 0.25rem; + font-size: 1.5rem; + font-weight: 700; +} + +.microsite-header__roles { + list-style: none; + margin: 0; + padding: 0; + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} + +.microsite-header__role { + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.05em; + background: rgba(255, 255, 255, 0.15); + padding: 0.2rem 0.6rem; + border-radius: 999px; +} + +/* Navigation --------------------------------------------------------------- */ + +.microsite-nav { + background: #16213e; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.microsite-nav ul.tabs { + list-style: none; + margin: 0; + padding: 0 1.5rem; + display: flex; + max-width: 900px; + margin: 0 auto; +} + +.microsite-nav ul.tabs li { + margin: 0; +} + +.microsite-nav ul.tabs a { + display: block; + padding: 0.75rem 1.25rem; + color: rgba(255, 255, 255, 0.7); + text-decoration: none; + font-size: 0.9rem; + border-bottom: 3px solid transparent; + transition: color 0.2s, border-color 0.2s; +} + +.microsite-nav ul.tabs a:hover, +.microsite-nav ul.tabs a:focus { + color: #fff; +} + +.microsite-nav ul.tabs li.is-active a, +.microsite-nav ul.tabs a.is-active { + color: #fff; + border-bottom-color: #e94560; +} + +/* Highlighted -------------------------------------------------------------- */ + +.microsite-highlighted { + max-width: 900px; + margin: 1.5rem auto 0; + padding: 0 1.5rem; +} + +/* Main + Sidebar ----------------------------------------------------------- */ + +.microsite-main-wrapper { + max-width: 900px; + margin: 2rem auto; + padding: 0 1.5rem; +} + +.microsite-main-wrapper--has-sidebar { + display: grid; + grid-template-columns: 1fr 280px; + gap: 2rem; + max-width: 1200px; +} + +.microsite-main { + min-width: 0; +} + +.microsite-sidebar { + min-width: 0; +} + +.microsite-messages { + margin-bottom: 1.5rem; +} + +/* Social ------------------------------------------------------------------- */ + +.microsite-social { + max-width: 900px; + margin: 0 auto 2rem; + padding: 0 1.5rem; +} + +/* Footer ------------------------------------------------------------------- */ + +.microsite-footer { + background: #16213e; + color: rgba(255, 255, 255, 0.7); + padding: 2rem 1.5rem; + margin-top: 2rem; +} + +.microsite-footer a { + color: rgba(255, 255, 255, 0.85); +} + +/* Responsividade básica ---------------------------------------------------- */ + +@media (max-width: 768px) { + .microsite-main-wrapper--has-sidebar { + grid-template-columns: 1fr; + } +} + +@media (max-width: 600px) { + .microsite-header__inner { + flex-direction: column; + text-align: center; + } + + .microsite-header__roles { + justify-content: center; + } +} diff --git a/themes/site_users_microsite_theme/site_users_microsite_theme.info.yml b/themes/site_users_microsite_theme/site_users_microsite_theme.info.yml new file mode 100644 index 0000000..0293310 --- /dev/null +++ b/themes/site_users_microsite_theme/site_users_microsite_theme.info.yml @@ -0,0 +1,19 @@ +name: 'Site Users Microsite Theme' +type: theme +description: 'Tema para micro-sites pessoais de usuários. Sem navegação principal do site.' +core_version_requirement: ^10 || ^11 +# Use o tema principal do site aqui se quiser herdar fontes e variáveis CSS. +# stable9 é o tema base mínimo do core (sem estilos próprios). +base theme: stable9 +libraries: + - site_users_microsite_theme/global + +regions: + header: Header + highlighted: Highlighted + tabs: Tabs + messages: Messages + content: Content + sidebar: Sidebar + social: Social + footer: Footer diff --git a/themes/site_users_microsite_theme/site_users_microsite_theme.libraries.yml b/themes/site_users_microsite_theme/site_users_microsite_theme.libraries.yml new file mode 100644 index 0000000..fe64819 --- /dev/null +++ b/themes/site_users_microsite_theme/site_users_microsite_theme.libraries.yml @@ -0,0 +1,4 @@ +global: + css: + theme: + css/microsite.css: {} diff --git a/themes/site_users_microsite_theme/templates/layout/page.html.twig b/themes/site_users_microsite_theme/templates/layout/page.html.twig new file mode 100644 index 0000000..ca2ee7a --- /dev/null +++ b/themes/site_users_microsite_theme/templates/layout/page.html.twig @@ -0,0 +1,85 @@ +{# +/** + * @file + * Template de página do micro-site pessoal. + * + * Renderiza apenas as regiões — a estrutura HTML completa (html, head, body) + * é responsabilidade do html.html.twig herdado do tema base (stable9). + * + * Variáveis adicionadas pelo site_users_microsite.module: + * - microsite_user: entidade UserInterface do usuário dono do micro-site. + * - microsite_user_name: nome de exibição do usuário (string). + * - microsite_user_roles: array de roles do usuário (exceto 'authenticated'). + * - microsite_user_photo: render array da foto padrão (view mode 'thumbnail'). + */ +#} +