Arquitetura do SGMBlocks
Este documento descreve cada arquivo e cada pasta do módulo com foco no contrato técnico. Use-o como referência ao criar um bloco novo, trocar a factory de um formulário do core ou anexar handlers a eventos do Omeka S.
Visão geral
O SGMBlocks é um módulo padrão de Omeka S e roda sobre o Laminas Framework. O módulo é declarado por Module.php na raiz, que o Omeka carrega automaticamente ao descobrir a pasta do módulo. O ciclo de vida segue a ordem: carregamento de module.config.php, merge com a configuração do Omeka, bootstrap do ServiceManager do Laminas, disparo de Module::onBootstrap quando existe, e anexo dos listeners em Module::attachListeners. Toda a lógica do módulo entra na aplicação por esses dois ganchos.
Module.php
O arquivo Module.php estende Omeka\Module\AbstractModule e expõe dois métodos principais.
getConfigretorna o array deconfig/module.config.php. O Omeka funde esse array à sua configuração global. Toda vez que o módulo precisa expor um serviço, uma factory, um formulário ou um bloco, o registro acontece nesse arquivo.attachListenersrecebe oLaminas\EventManager\SharedEventManagerInterfacee anexa os handlers do módulo. No SGMBlocks existem quatro anexações:
// PageCustomFieldsHandler
$sharedEventManager->attach(
\Omeka\Form\PageLayoutDataForm::class,
'form.add_elements',
[$pageHandler, 'handlePageLayoutForm']
);
// SiteCustomFieldsHandler, três eventos
$sharedEventManager->attach(\Omeka\Form\SiteSettingsForm::class, 'form.add_elements', [$siteHandler, 'handleSiteSettings']);
$sharedEventManager->attach(\Omeka\Form\SiteSettingsForm::class, 'form.add_input_filters', [$siteHandler, 'handleSiteSettingsFilters']);
$sharedEventManager->attach('Omeka\Controller\SiteAdmin\Index', 'view.layout', [$siteHandler, 'handleSiteSettingsAssets']);
O Module.php não mantém lógica de domínio. Toda lógica fica nos handlers e nas classes de bloco.
config/
module.config.php
O arquivo expõe as seguintes chaves:
| Chave | Propósito técnico |
|---|---|
view_manager.template_path_stack | Adiciona dirname(__DIR__) . '/view' à pilha do Laminas\View\Resolver\TemplatePathStack. O tema ainda tem prioridade. |
block_layouts.factories | Associa cada block_layout a uma factory do ServiceManager. O Omeka usa esse mapa ao renderizar ou editar blocos. |
form_elements.invokables | Mapeia classes de Fieldset e Form para o FormElementManager. Permite FormElementManager::get sem factory. |
form_elements.factories | Reescreve Omeka\Form\PageLayoutDataForm para a PageLayoutDataFormFactory do módulo. É a única troca de factory do core. |
translator.translation_file_patterns | Registra o padrão %s.mo em language/ com type = gettext. Strings marcadas com // @translate entram no fluxo de i18n do Omeka. |
module.ini
Metadados lidos pela tela de módulos do admin:
[info]
name = "SGM Blocks"
version = "1.3"
author = "Forma UFES"
configurable = false
description = "Módulo customizado para o site do SGM."
omeka_version_constraint = "^4.0.1"
A flag configurable = false significa que o módulo não expõe uma tela própria de configuração. Toda a configuração relevante acontece nos formulários do Omeka onde o módulo injeta campos.
src/
A pasta src/ segue a convenção PSR-4 com o namespace raiz SGMBlocks mapeado ao diretório. Cada subpasta tem um papel específico.
src/Form
Define os formulários Laminas dos blocos. Cada classe estende Laminas\Form\Fieldset e implementa init em vez de usar o construtor. Os nomes dos elementos seguem sempre o padrão o:block[__blockIndex__][o:data][chave] para que o Omeka substitua __blockIndex__ pelo índice real do bloco ao renderizar o formulário e para que os dados caiam corretamente em o:data.
Classes presentes:
AccordionBlockForm. Dois campos estáticostitleedescription. Itens dinâmicos são injetados peloaccordion-block-admin.js, não por esseFieldset.BannerPageForm. Sete campos:imagecomoOmeka\Form\Element\Asset,title,description,content_align(center/left),use_overlay,show_search_bar,show_search_suggestions. Os três últimos levam a classebanner-page-center-only.ButtonBlockForm. Três campos:button_text,button_url,button_style.IconBlockForm. Quatro campos:icon,title,description,alignment.PageDescriptionForm. Um campodescriptioncomclass="block-html full wysiwyg"para ativar o CKEditor do Omeka.SearchBarBlockForm. Um camposhow_suggestions.ThumbnailBlockForm. Um campoassetcom tipoOmeka\Form\Element\Asset.
src/Site/BlockLayout
Cada classe estende Omeka\Site\BlockLayout\AbstractBlockLayout e implementa pelo menos três métodos:
getLabel. Retorna o rótulo traduzível exibido na lista de blocos do editor de página.form. RecebePhpRenderer $view,SiteRepresentation $site,SitePageRepresentation $page = null,SitePageBlockRepresentation $block = null. Obtém oFieldsetvia$this->formElementManager->get(...), chamapopulateValuesse$blockexistir, e retorna o HTML doformCollection.render. RecebePhpRenderer $vieweSitePageBlockRepresentation $block. Lê os valores com$block->dataValue('chave')e delega ao partial via$view->partial('common/block-layout/nome', [...]).
Alguns blocos implementam também prepareForm para carregar CSS ou JS no admin:
AccordionBlock::prepareFormcarregaadmin.csseaccordion-block-admin.js.BannerPage::prepareFormcarregaadmin.cssebanner-page-admin.js.IconBlock::prepareFormcarregaadmin.css.ThumbnailBlock::prepareFormcarregaadmin.css.
ButtonBlock, PageDescription e SearchBarBlock não injetam assets no admin.
src/Service/BlockLayout
Factories que o ServiceManager usa ao resolver cada block_layout. Todas implementam Laminas\ServiceManager\Factory\FactoryInterface e compartilham o mesmo padrão:
public function __invoke(ContainerInterface $services, $requestedName, array $options = null)
{
return new AccordionBlock($services->get('FormElementManager'));
}
A dependência única é o FormElementManager. Os blocos não recebem outros serviços porque obtêm a API do Omeka pelo PhpRenderer em tempo de render.
src/Service/Form
Três classes com papéis distintos:
PageCustomFieldsHandler. Classe pura sem dependências, instanciada diretamente emModule::attachListeners. ExportahandlePageLayoutForm, que adiciona o checkboxo:layout_data[fixed_header]ao formulário alvo do evento.SiteCustomFieldsHandler. Recebe oContainerInterfaceno construtor. Expõe três handlers, um por evento:handleSiteSettingslêOmeka\Settings\Site, normaliza o JSON salvo, registra oelement_groupsgmblockse adiciona o campo hiddensgmblocks_search_suggestions.handleSiteSettingsFiltersadiciona um filterCallbackao input filter dosgmblocks_search_suggestions. O callback aceita array ou string JSON e retorna array.handleSiteSettingsAssetsverificaaction === 'edit'noparams fromRoutee injetaadmin.cssesite-settings.jsno layout.
PageLayoutDataFormFactory. Substitui a factory padrão doPageLayoutDataFormpara injetarEventManagerno formulário. Sem essa troca,form.add_elementsnão seria disparado e oPageCustomFieldsHandlernunca rodaria.
view/
Pilha de templates
O Laminas View procura partials usando a pilha registrada em view_manager.template_path_stack. O Omeka S adiciona primeiro o view/ do tema ativo e depois o view/ de cada módulo, na ordem de registro. O partial do tema vence quando existe. Se o tema não tem o arquivo, o resolver usa o partial do módulo como fallback.
O module.config.php registra o diretório com:
'view_manager' => [
'template_path_stack' => [
dirname(__DIR__) . '/view',
]
],
Estrutura em common/block-layout
Todos os partials do módulo vivem em view/common/block-layout. Cada bloco chama seu partial com um caminho relativo à pilha. Mapeamento completo:
| Partial | Classe consumidora | Observação técnica |
|---|---|---|
common/block-layout/accordion-block.phtml | AccordionBlock | Carrega style.css, gera <section> com role="region", aria-expanded, aria-controls, e inclui script inline de toggle. |
common/block-layout/banner-page.phtml | BannerPage | Carrega style.css, aplica style="background-image: url(...)" e classes sgm-banner-content-{content_align}. |
common/block-layout/button-block.phtml | ButtonBlock | Renderiza <a> com classes sgm-button-filled ou sgm-button-outline a depender do button_style. |
common/block-layout/icon-block.phtml | IconBlock | Carrega style.css, aplica sgm-icon-block--left ou sgm-icon-block--center. |
common/block-layout/page-description.phtml | PageDescription | Imprime o HTML da descrição sem escapeHtml. Assume conteúdo confiável vindo do CKEditor. |
common/block-layout/search-bar-block.phtml | SearchBarBlock | Resolve a URL de busca pelo siteSetting('search_type') entre site/cross-site-search e site/resource. Lê sugestões do setting. |
common/block-layout/thumbnail-block/render.phtml | ThumbnailBlock | Carrega style.css, aplica max-width: 200px inline e renderiza <img> com alt proveniente de altText ou fallback fixo. |
Override pelo tema
Para sobrescrever um partial, o tema cria o mesmo caminho relativo dentro de sua pasta view/. Exemplo: para customizar o banner, o tema cria themes/<nome>/view/common/block-layout/banner-page.phtml. O resolver encontra primeiro o do tema e ignora o do módulo. Nenhuma configuração adicional é necessária.
asset/
asset/css
style.css. Consumido pelo frontend. Define estilos para thumbnail, banner, acordeão e bloco de ícone. Os partials anexam o arquivo via$this->headLink()->appendStylesheet($this->assetUrl('css/style.css', 'SGMBlocks')).admin.css. Consumido pelo admin. Carregado porprepareFormdos blocos que precisam de preview de imagem e peloSiteCustomFieldsHandler::handleSiteSettingsAssetsna tela de edição do site.
asset/js
accordion-block-admin.js. jQuery, IIFE e estado local. Escuta o eventoo:block-addedpara inicializar blocos de acordeão novos e também inicializa nodocument.ready. Renderiza campos com nomeso:block[__blockIndex__][o:data][items][i][title]e[...][text], inicializa CKEditor em cada textarea com a classewysiwyg, reindexa ao remover itens e destrói instâncias do CKEditor no unload do item.site-settings.js. Manipula a lista de sugestões de busca no admin. Lê e grava o campo hiddensgmblocks-search-suggestionsem JSON.
Helper assetUrl
O helper assetUrl recebe o caminho relativo e o nome do módulo. No SGMBlocks, sempre SGMBlocks. A URL final aponta para modules/SGMBlocks/asset/<caminho> servida pelo Omeka.
language/
Diretório para arquivos .po e .mo seguindo o padrão gettext. O module.config.php registra:
'translator' => [
'translation_file_patterns' => [
[
'type' => 'gettext',
'base_dir' => dirname(__DIR__) . '/language',
'pattern' => '%s.mo',
'text_domain' => null,
],
],
],
Strings marcadas com // @translate em qualquer arquivo PHP podem ser extraídas por ferramentas compatíveis e traduzidas em arquivos pt_BR.mo, en_US.mo etc. No estado atual o módulo usa apenas pt_BR implícito pelas strings em português.
Contratos importantes para lembrar
__blockIndex__é um placeholder literal que o Omeka substitui pelo índice do bloco no formulário. Sempre use-o nosnamedos elementos de formulário de bloco.populateValuesnão aceita o formato de array. Sempre passe um array com chaves comoo:block[__blockIndex__][o:data][campo]para preencher inputs existentes.$block->dataValue('chave')é o acesso canônico a um valor deo:data. Retornanullquando ausente.$view->api()->read('assets', $id)lança exceção quando o asset não existe. Todas as classes que leem assets envolvem a chamada emtry catche seguem com$asset = null.$view->partialresolve templates pela pilha dotemplate_path_stack. A ordem entre tema e módulo é determinada pelo Omeka S no bootstrap do site.