Main Service · Formação TécnicaÓtica de programação — capacitação de um colaborador interno na arquitetura, no desenvolvimento de plugins, na customização de temas e nas boas práticas de código.
Como o Moodle está construído.
Desenvolver e personalizar.
PHP · HTML · CSS · JS.
Segurança e manutenção.
utilizadores ativos
cursos · 561 categorias
inscrições
registos de resultados
local_uniutils, local_secretaria, block_uniblock, tema edutor.| Camada | Tecnologia |
|---|---|
| Linguagem | PHP 8.4 |
| Base de dados | PostgreSQL (prefixo mdl_) |
| Servidor web | Apache · webroot public/ |
| Ficheiros | moodledata/ (fora do webroot) |
| Front-end | Mustache · SCSS · JS (AMD) |
web (PHP+Apache) e db (PostgreSQL).code/public/ está montado no contentor — editar = ver de imediato.http://localhost:8888Compreender como o Moodle está organizado: diretórios, base de dados, ciclo de vida de um pedido, o sistema de plugins e as APIs do núcleo — a base de tudo o resto.
Pensa no Moodle como um telemóvel: o sistema base é pequeno e estável, e tudo o resto são apps (plugins) que se instalam por cima — sem nunca abrir o telemóvel.
Um núcleo pequeno e estável + centenas de plugins. A regra de ouro: nunca se altera o núcleo — estende-se através de plugins, hooks e APIs. Assim, cada atualização do Moodle não destrói o nosso trabalho.
Como numa casa: só uma divisão (public/) tem porta para a rua; os ficheiros privados e a despensa (moodledata) ficam trancados lá dentro, longe dos visitantes.
# raiz do projeto
config.php # BD, wwwroot, dataroot
public/ # ← WEBROOT (Apache aponta aqui)
index.php
lib/ # APIs do núcleo
admin/ course/ user/ …
mod/ block/ theme/ local/ # plugins
admin/cli/ # scripts CLI (fora do webroot)
moodledata/ # ficheiros, cache (fora do webroot)
public/config.php só o carrega.Os dados moram em três sítios diferentes — o programa (disco), as informações (base de dados) e os ficheiros enviados (moodledata) — e nunca se misturam.
Os plugins e o núcleo em public/. Versionado em Git.
Tabelas mdl_* em PostgreSQL: utilizadores, cursos, notas, configurações.
Ficheiros enviados, sessões, caches, ficheiros temporários.
config.php (ligação) e na tabela mdl_config / mdl_config_plugins (definições).$DB (Módulo 4).Sempre que alguém abre uma página, o Moodle repete os mesmos passos por ordem — como uma recepção que confirma quem és e o que podes ver antes de te entregar o conteúdo.
Estas variáveis globais — $CFG, $DB, $USER, $PAGE, $OUTPUT — estão disponíveis em qualquer página depois de incluir config.php.
São ferramentas já feitas: em vez de reinventar a roda, chamamos a função do Moodle que já sabe guardar na base de dados, desenhar HTML ou verificar permissões.
$DB + XMLDB para esquema independente do motor.
$OUTPUT, Mustache, renderers personalizáveis.
contexto, URL, layout, título, recursos JS/CSS.
papéis, contextos, capacidades, require_capability.
menus e árvore de navegação (via lib.php).
reagir a acontecimentos do sistema.
camada de cache unificada para desempenho.
moodleform — validação e sesskey.
APIs externas e tarefas agendadas (cron).
É como crachás num edifício: a capacidade é uma chave, o papel é o crachá com várias chaves, e o contexto é onde esse crachá funciona — um curso, uma categoria, ou todo o site.
mod/quiz:grade.Exemplo real desta instalação:
| Papel | Edita conteúdo? |
|---|---|
| teacher (tutor) | ❌ só avalia/interage |
| editingteacher | ✅ sim |
| manager | ✅ administração |
No código: require_capability('x/y:z',$context).
O Moodle não inventa notas nem matrículas — recebe-as do sistema académico (eSURA) e mostra-as. Quem manda nos dados oficiais é o eSURA.
Criar plugins do zero, construir blocos e integrar com sistemas externos — usando os padrões reais do uniutils, secretaria e uniblock.
tipo_nomeCada plugin tem um nome único tipo_nome, como um apelido: a primeira parte diz que tipo é, a segunda é o nome próprio.
| Tipo | Pasta |
|---|---|
| local | local/ — o mais flexível |
| block | blocks/ |
| mod | mod/ — atividades |
| theme | theme/ |
| auth / enrol | autenticação / inscrição |
local_uniutils // tipo=local nome=uniutils
block_uniblock // tipo=block nome=uniblock
theme_edutor
Este nome (o componente) usa-se em tudo: classes, strings, capacidades, tabelas.
Um plugin é só uma pasta com ficheiros de nomes combinados — o Moodle sabe onde "espreitar" (version.php, lang/, db/…) para o reconhecer e instalar.
local/exemplo/
├── version.php # identidade (obrigatório)
├── lib.php # callbacks por convenção
├── settings.php # definições de admin
├── index.php # uma página
├── lang/en/… # strings (obrigatório)
├── db/access.php # capacidades
├── db/install.xml # tabelas (XMLDB)
├── db/services.php # web services
├── db/tasks.php # tarefas agendadas
└── classes/ # código com autoload
É o bilhete de identidade do plugin: diz o nome e o número da versão. Subir esse número avisa o Moodle de que há novidades para instalar.
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'local_exemplo'; // = à pasta. Frankenstyle.
$plugin->version = 2026060200; // AAAAMMDDXX — subir a cada mudança de BD
$plugin->requires = 2024100700; // versão mínima do Moodle
$plugin->maturity = MATURITY_STABLE;
$plugin->release = '1.0.0';
Mudou o esquema da base de dados? Incremente $plugin->version. É esse número que dispara a instalação/upgrade.
Antes de mostrar fosse o que fosse, a página pergunta: estás autenticado? tens permissão? — só depois vai buscar dados e desenha o ecrã. Sempre por esta ordem.
require_once(__DIR__.'/../../config.php');
require_login(); // 1) exige sessão
$context = context_system::instance();
require_capability('local/exemplo:view', $context); // 2) permissão
$PAGE->set_url(new moodle_url('/local/exemplo/index.php'));
$PAGE->set_context($context);
$PAGE->set_title(get_string('pluginname','local_exemplo'));
$avisos = $DB->get_records('local_exemplo_aviso'); // 3) dados
echo $OUTPUT->header();
// … conteúdo …
echo $OUTPUT->footer();
block_uniblockUm bloco é uma "caixa" lateral. O método get_content() é o sítio onde decidimos o que essa caixa mostra.
class block_exemplo extends block_base {
function init() { $this->title = get_string('pluginname','block_exemplo'); }
function applicable_formats() { return ['all'=>true]; }
function get_content() { // ← a lógica do bloco
if ($this->content !== null) return $this->content;
$this->content = new stdClass();
$this->content->text = html_writer::link(
new moodle_url('/secretaria/resultados'),
get_string('examinationsdetails','local_uniutils'));
return $this->content;
}
}
É exatamente assim que o bloco real gera os atalhos da Secretaria Online.
Um web service é uma "porta" por onde outro sistema entra e fala com o Moodle — com chave (token) e regras — para lhe entregar dados, como as notas.
Declarar — db/services.php
$functions = [
'local_exemplo_save' => [
'classname'=>'local_exemplo\\external\\save',
'methodname'=>'execute',
'type'=>'write',
'capabilities'=>'local/exemplo:manage',
],
];
Implementar — classes/external/save.php
class save extends external_api {
static function execute_parameters(){…}
static function execute($dados){
self::validate_context($ctx);
require_capability(…);
// gravar com $DB
}
static function execute_returns(){…}
}
Ativa-se em Admin ▸ Web services, cria-se um utilizador de serviço e um token — como a conta esura@unisced.edu.mz.
Cada parâmetro é validado e a permissão verificada antes de tocar na base de dados.
Duas formas de o código agir sozinho: a tarefa agendada corre a horas certas (um despertador); o observer reage quando algo acontece (um sensor de movimento).
Tarefa agendada (cron)
namespace local_exemplo\task;
class export_task
extends \core\task\scheduled_task {
function execute() {
mtrace('a exportar…');
}
}
Regista-se em db/tasks.php (como o export_grades_task real).
Observer (reagir a eventos)
// db/events.php
$observers = [[
'eventname'=>'\\mod_quiz\\event\\
attempt_submitted',
'callback'=>'\\local_exemplo\\
observer::quiz_submitted',
]];
O uniutils usa isto (quiz_observer) para tratar resultados de testes.
Quando o Moodle precisa de pedir algo a outro sistema, faz uma "chamada" pela internet — mas nunca durante o clique do utilizador, senão a página fica à espera.
// REST — cliente cURL do Moodle (respeita proxy/segurança)
$curl = new \curl();
$curl->setHeader('Authorization: Basic '.base64_encode("$user:$pass"));
$resposta = $curl->post('https://esura.…/service/...', $payload);
// SOAP — como o secretaria faz para os tickets (osTicket)
$client = new \SoapClient($wsdlurl);
$client->__soapCall('ostTicket.open', $dados);
Nunca faça chamadas externas durante o pedido do estudante (bloqueiam a página). Faça-as numa tarefa agendada/adhoc. Credenciais sempre em settings.php, nunca fixas no código.
Controlar a aparência com PHP, HTML (Mustache), CSS (SCSS) e JavaScript — sobre o tema real edutor, um tema-filho do Boost.
Para montar uma página, o Moodle junta quatro coisas: a moldura (layout), o molde do HTML (Mustache), quem preenche os dados (renderer) e o estilo (SCSS).
PHP que define a moldura (cabeçalho, colunas, rodapé).
Templates HTML .mustache para cada componente.
PHP que prepara os dados para os templates.
Estilo, compilado para CSS e servido por styles.php.
Um tema personaliza qualquer uma destas peças — sem tocar no núcleo.
O nosso tema herda tudo de um tema base (Boost) e só muda o necessário — como decorar uma casa já construída, em vez de a levantar de raiz.
# theme/edutor/config.php
$THEME->name = 'edutor';
$THEME->parents = ['boost']; # herda do Boost
$THEME->scss = fn($t)=>
theme_edutor_get_main_scss_content($t);
$THEME->prescsscallback = '…get_pre_scss';
$THEME->extrascsscallback = '…get_extra_scss';
$THEME->rendererfactory =
'theme_overridden_renderer_factory';
$THEME->layouts = [ … ];
.scss a CSS no browserEscrevemos o estilo em SCSS (um CSS turbinado, com variáveis); o Moodle "cozinha-o" para CSS normal e guarda em cache — por isso é preciso limpar a cache para ver mudanças.
scss/theme/: _header.scss, _footer.scss, _courseformat.scss…themedesignermode em dev).Para mudar o HTML de algo, copiamos o seu "molde" (.mustache) para o nosso tema e editamos — o Moodle passa a usar a nossa versão.
Copiar o template do componente para o tema, mantendo o caminho — o Moodle usa a versão do tema:
# o tema sobrepõe o formulário de login do núcleo:
theme/edutor/templates/core/loginform.mustache
# dentro: HTML + variáveis {{…}} e secções {{#…}}
{{#showloginform}}
<form class="login-form" action="{{loginurl}}">
<input name="username" …>
</form>
{{/showloginform}}
Lógica de zero no template: só apresentação. Os dados vêm do renderer.
O renderer prepara em PHP aquilo que vai aparecer; o JavaScript (módulos AMD) acrescenta a interatividade depois de a página carregar.
Renderer — sobrepor saída do núcleo
namespace theme_edutor\output;
class core_renderer
extends \core_renderer {
function favicon() { … }
}
Ativado por theme_overridden_renderer_factory.
JavaScript — módulos AMD
// amd/src/main.js
export const init = () => { … };
// no PHP:
$PAGE->requires->js_call_amd(
'theme_edutor/main','init');
JS compila com grunt amd → amd/build/.
Para não perder o trabalho na próxima atualização, criamos um "tema-filho" por cima do edutor — as mudanças pequenas cabem só no campo de SCSS personalizado.
$THEME->parents=['edutor'] e sobreponha só o necessário.Caso real desta instalação — o login escondia o formulário:
/* SCSS personalizado (admin) */
.form-col.col-12 {
display: none; /* ← escondia user/pass */
}
Uma linha de SCSS muda o comportamento visível — sem editar código.
Escrever código seguro, testável e fácil de manter — para que a plataforma dure e evolua com confiança.
Três hábitos que evitam dores de cabeça: nunca mexer no núcleo, seguir o estilo de código combinado, e guardar tudo em Git.
Só plugins, callbacks e hooks. Caso contrário, perde-se na atualização.
Seguir o estilo oficial do Moodle. Validar com o linter / Plugin CI.
Tudo versionado. version.php sobe a cada entrega.
get_string() — nunca fixo no código.classes/ com namespaces e autoload; lib.php só para callbacks.Nunca confiar no que vem do utilizador: limpar a entrada e usar "campos" (?) nas consultas, para ninguém conseguir injetar comandos na base de dados.
// ENTRADA: limpar sempre com PARAM_*
$id = required_param('id', PARAM_INT);
// BD: NUNCA concatenar variáveis (injeção SQL)
$DB->get_records_sql(
'SELECT * FROM {local_exemplo_aviso} WHERE titulo = ?',
[$titulo]); // ✅ parâmetro seguro
// ❌ NUNCA: "... WHERE titulo = '$titulo'"
require_capability() — não confiar na navegação.{nometabela} (sem mdl_).Exigir sessão, proteger os formulários contra fraude (sesskey), "escapar" o texto antes de o mostrar e respeitar o RGPD se guardarmos dados pessoais.
Toda a página exige sessão. Formulários via moodleform validam sesskey (anti-CSRF).
format_string() (títulos), format_text() (HTML), s() (atributos) — contra XSS.
Guarda dados pessoais? classes/privacy/provider.php é obrigatório (exportar/eliminar).
Capacidades específicas; contas de serviço só com o necessário.
Testes automáticos são um "alarme": correm sozinhos e avisam se uma alteração partiu algo que já funcionava.
Testes unitários da lógica (em tests/). Base de dados de teste isolada.
Testes de comportamento no browser (fluxos do utilizador).
Linters + estilo + testes automáticos a cada commit (GitHub Actions).
Mesmo poucos testes nos pontos críticos (web services, cálculos) evitam regressões caras.
Ligar o modo de erros para ver o que se passa, limpar caches quando algo "não atualiza", e ter sempre backups dos três sítios (código, base de dados, ficheiros).
Depurar (em config.php):
$CFG->debug = (E_ALL | E_STRICT);
$CFG->debugdisplay = 1;
Comandos do dia-a-dia:
php admin/cli/upgrade.php
php admin/cli/purge_caches.php
php admin/cli/cron.php
moodledata — os três.Editar é imediato (código montado no contentor) · purgar caches resolve 90% dos "não atualiza".
Um percurso sugerido para tornar o colaborador autónomo, em fases com marcos verificáveis.
| Fase | Foco | Marco |
|---|---|---|
| 1 · Fundações | Arquitetura, ambiente Docker, BD, ciclo de pedido | Mapear o sistema; navegar o código |
| 2 · Primeiro plugin | Plugin local + bloco + capacidades + BD | local_exemplo a funcionar |
| 3 · Integração | Web services, tarefas, observers, eSURA | Ler/escrever via serviço externo |
| 4 · Temas | SCSS, Mustache, renderers, tema-filho | Personalizar o edutor com segurança |
| 5 · Qualidade | Segurança, testes, CI, manutenção | Plugin com testes + revisão de segurança |
moodledev.io — APIs, tipos de plugin, coding style.
moodle.org/plugins — exemplos reais e referência.
uniutils · secretaria · uniblock · edutor.
moodlehq/moodle-plugin-ci.
Fim · ObrigadoArquitetura · Plugins · Temas · Boas práticas — a base para evoluir a Sala Virtual com autonomia, segurança e qualidade.
Main Service · Formação Técnica em Moodle