Shortcodes¶
Shortcodes sao tags de conteudo dinamico processadas em campos de texto do Moodle. Usam a sintaxe [middag type="xyz" attr="val"] e sao substituidos por HTML em tempo de request.
Quando um administrador insere [middag type="widget" id="5"] na descricao de um curso, o framework localiza o handler registrado para widget e injeta o HTML retornado no lugar da tag.
Exemplo rapido¶
Registrar um shortcode e implementar o handler:
namespace local_meuplugin\extensions\catalogo;
use local_middag\facade\shortcode_manager;
class catalogo_extension extends \local_middag\base\extension
{
public function boot(): void
{
shortcode_manager::register('product_card', function (array $attrs): string {
$id = (int) ($attrs['id'] ?? 0);
return "<span>Produto #{$id}</span>";
});
}
}
Uso no editor Moodle:
Resultado renderizado:
Como funciona¶
- Extensions registram shortcodes via
shortcode_manager::register(tag, callback)duranteboot(). - O framework integra o processamento como Moodle text filter.
- Quando o Moodle renderiza um campo de texto, o filter faz early-return se
[middagnao esta presente (otimizacao por string check antes de regex). - Cada tag encontrada e parseada, os atributos sao extraidos, e o callback registrado e invocado.
- O HTML retornado substitui a tag original no texto.
Referencia da API¶
shortcode_manager (facade)¶
| Metodo | Descricao |
|---|---|
register(string $tag, callable $callback) |
Registra um handler para a tag |
render(string $text) |
Processa todas as tags [middag ...] no texto |
has(string $tag) |
Verifica se uma tag esta registrada |
Callback do handler¶
O callback recebe um array associativo com os atributos parseados e deve retornar uma string HTML:
Atributos sao parseados a partir da sintaxe key="value" ou key='value'. Exemplo:
Gera:
O atributo type identifica qual handler sera invocado. Os demais atributos sao livres.
Sanitizacao e #[trusted_output]¶
Por padrao, o output de todo shortcode e sanitizado via clean_text() antes de ser injetado no HTML. Isso remove tags potencialmente perigosas.
Handlers que precisam retornar HTML estruturado (divs, classes CSS, atributos data-*) devem declarar #[trusted_output] para desabilitar a sanitizacao automatica.
Handler como callable class com trusted_output¶
namespace local_meuplugin\extensions\catalogo;
use local_middag\framework\contract\attributes\trusted_output;
#[trusted_output]
class product_widget_shortcode
{
public function __invoke(array $attrs): string
{
$id = (int) ($attrs['id'] ?? 0);
$product = $this->load_product($id);
return '<div class="product-widget" data-id="' . $id . '">'
. '<h3>' . s($product->name) . '</h3>'
. '<p>' . s($product->description) . '</p>'
. '</div>';
}
private function load_product(int $id): object
{
// Consulta ao repositorio...
}
}
Registro no boot():
Responsabilidade de seguranca
Ao usar #[trusted_output], voce assume total responsabilidade pela seguranca do output. Use s() ou format_text() para escapar dados de usuario. Nunca injete dados nao-tratados no HTML.
O atributo #[trusted_output] pode ser aplicado em classes, funcoes ou metodos.
Handler como metodo de classe¶
Para handlers que precisam de dependencias injetadas, use um metodo de classe:
namespace local_meuplugin\extensions\catalogo;
use local_middag\framework\contract\attributes\trusted_output;
class catalogo_shortcodes
{
public function __construct(
private readonly catalogo_repository_interface $catalogo,
) {}
#[trusted_output]
public function render_banner(array $attrs): string
{
$slug = $attrs['slug'] ?? '';
$banner = $this->catalogo->get_banner($slug);
return '<div class="banner">' . s($banner->html) . '</div>';
}
}
Registro no boot():
$shortcodes = new catalogo_shortcodes($this->container->get(catalogo_repository_interface::class));
shortcode_manager::register('banner', [$shortcodes, 'render_banner']);
Tratamento de falhas¶
| Cenario | Comportamento |
|---|---|
| Tag nao registrada | Ignorada silenciosamente (fail safe). Em DEBUG_DEVELOPER, exibe warning. |
| Shortcode malformado | Ignorado (regex nao faz match) |
| Excecao no handler | Propaga no fluxo do text filter (o handler compoe o output final) |
Texto sem [middag |
Early-return imediato, sem custo de regex |
Patterns¶
Data lookup shortcode¶
Shortcode que busca um registro e exibe dados formatados:
shortcode_manager::register('user_count', function (array $attrs): string {
global $DB;
$count = $DB->count_records('user', ['confirmed' => 1]);
return (string) $count;
});
Uso: Temos [middag type="user_count"] usuarios confirmados.
Widget embedding¶
Shortcode que renderiza um widget complexo com markup HTML. Requer #[trusted_output]:
#[trusted_output]
class chart_shortcode
{
public function __invoke(array $attrs): string
{
$metric = s($attrs['metric'] ?? 'enrollments');
$period = s($attrs['period'] ?? '30d');
return '<div class="middag-chart" '
. 'data-metric="' . $metric . '" '
. 'data-period="' . $period . '">'
. '</div>';
}
}
Shortcode com conteudo condicional (segments)¶
Combine shortcodes com o sistema de segmentos para conteudo direcionado:
shortcode_manager::register('if_segment', function (array $attrs): string {
$segment_id = (int) ($attrs['segment_id'] ?? 0);
$content = $attrs['content'] ?? '';
$segments = \local_middag\facade\segments::get_subquery($segment_id);
global $DB, $USER;
if ($DB->record_exists_sql(
"SELECT 1 FROM ({$segments->sql}) sub WHERE sub.userid = ?",
array_merge($segments->params, [$USER->id])
)) {
return s($content);
}
return '';
});
Anti-patterns¶
Computacao pesada no handler¶
Shortcodes sao processados em cada renderizacao de texto. Operacoes custosas degradam a performance da pagina inteira:
// ERRADO: query pesada executada a cada render
shortcode_manager::register('report', function (array $attrs): string {
// Agregacao de milhares de registros a cada pageview...
$data = $DB->get_records_sql("SELECT ... GROUP BY ... HAVING ...");
return format_report($data);
});
Use cache ou pre-calcule os dados fora do handler.
Output nao-sanitizado sem trusted_output¶
Sem #[trusted_output], o output passa por clean_text(). Nao tente injetar HTML estruturado sem a anotacao — as tags serao removidas:
// ERRADO: HTML sera limpo pelo sanitizador
shortcode_manager::register('card', function (array $attrs): string {
return '<div class="card"><h3>Titulo</h3></div>'; // sera stripped
});
// CORRETO: declarar trusted_output na callable class
Dados de usuario sem escape em trusted_output¶
Com #[trusted_output], o framework confia no developer. Nunca injete dados crus:
// ERRADO: XSS vulneravel
#[trusted_output]
class vuln_shortcode
{
public function __invoke(array $attrs): string
{
return '<div>' . $attrs['name'] . '</div>'; // XSS!
}
}
// CORRETO: usar s() para escapar
return '<div>' . s($attrs['name']) . '</div>';
Referencias¶
- ADR-704 -- Processamento de Shortcodes
- Hooks -- sistema reativo (shortcodes nao sao hooks)
- Modos de Integracao -- como registrar shortcodes de fora do framework