Moodle Local Plugin v2.2

Force Profile Completion

Ensure every user completes their profile before accessing the platform. Regex and checksum validation, self-registration checks, per-field messages, event logging, admin dashboard, and completion tracking.

Moodle 4.5+ PHP 8.1+ GPL v3 34 tests
8
Major Features
34
Unit Tests
2
Languages
0
Personal Data Stored

Features

Everything you need to enforce profile completion on your Moodle site.

Any custom profile field
Regex validation per field
Named validators (checksum)
Self-registration validation
Per-field error messages
Admin status dashboard
Event logging
Completion tracking
Session caching
GDPR privacy provider
PHPUnit test suite
Form enhancement UX
English + Italian

The Problem

Moodle doesn't natively support a "complete your profile" gate for custom fields.

👤

Admin creates users manually

Often with only name, email, and password — missing important custom fields like tax code or profession.

🚫

"Required" blocks admins

Making fields required prevents admins from creating accounts without that data. A catch-22.

⚠️

Users ignore optional fields

Without enforcement, users never fill in their profile. Data stays incomplete forever.

This plugin solves the gap: Admins create accounts freely. Users are forced to complete their profile at first login. Everyone wins.

How It Works

A seamless two-step flow: admin creates, user completes.

📝
Admin creates user
Minimal data only
➔
🔒
User logs in
Plugin intercepts every page
➔
✍️
Fill & validate
Redirect until complete
➔
✓
Access granted
Completion tracked

The plugin hooks into Moodle's after_require_login callback, which fires on every protected page load. Users cannot bypass the check by navigating directly to any URL.

Installation

Two ways to install — pick your favorite.

Option A — Git clone

Terminal
$ cd /path/to/moodle/local
$ git clone https://github.com/Oltrematica/moodle-local_forceprofile.git forceprofile
Cloning into 'forceprofile'...
done.

Option B — Download ZIP

Download the latest .zip from GitHub Releases and extract into local/forceprofile/.

Finalize

  1. Trigger installationGo to Site administration → Notifications — Moodle detects the plugin.
  2. Configure the pluginSite administration → Plugins → Local plugins → Force Profile Completion
  3. Add your field shortnamesEnter one shortname per line for the fields you want to enforce.
  4. Enable the pluginFlip the toggle to activate enforcement.

Configuration

Everything is managed from the admin settings panel.

SettingDescriptionDefault
EnableActivate or deactivate the pluginDisabled
Fields to checkCustom profile field shortnames, one per line(empty)
Validation patternsOptional regex per field: shortname:/pattern/(empty)
Field validators v2.2Optional named validator per field: shortname:validatorname(empty)
Validate on self-registration v2.2Also check the configured fields on the signup formEnabled
MessageWarning message shown on redirect, followed by the list of fields to correct"You must complete your profile…"
Redirect URLLocal path for profile completion/user/edit.php

Validation Patterns

Optionally enforce a format for specific fields using regex. One pattern per line:

Validation patterns setting
# Italian tax code (Codice Fiscale), omocodia included
CF:/^[A-Z]{6}[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST][0-9LMNPQRSTUV]{2}[A-Z][0-9LMNPQRSTUV]{3}[A-Z]$/i

# International phone number
phone:/^\+?[0-9]{8,15}$/

# Italian ZIP code
zip_code:/^[0-9]{5}$/

Fields without a pattern only need to be non-empty. Fields with a pattern must match the regex. Invalid patterns are silently skipped.

Omocodia: when two people would receive the same tax code, the Agenzia delle Entrate replaces digits with letters (0–9 become L M N P Q R S T U V). A pattern that only allows digits in those positions rejects perfectly legitimate codes — hence the character classes above.

Field Validators v2.2

Some rules cannot be expressed as a regular expression. A tax code such as ADRNLT79P41D969B has a flawless shape, yet it is invalid: its control character should be C. Validators cover exactly that gap. One assignment per line:

Field validators setting
# Format and control character of the Italian tax code
CF:codicefiscale
ValidatorWhat it checks
codicefiscale Italian tax code: 16 character layout, valid month letter, omocodia substitutions, and the recomputed control character. The value is trimmed and uppercased first, so codes stored in lower case still validate.

A validator can be combined with a regex on the same field — the value then has to satisfy both. Unknown validator names are skipped with a developer notice, so a typo never locks users out.

Sites needing their own rule can write a class implementing \local_forceprofile\validator\validator_interface and reference it by its fully qualified name, for example vat:\local_myplugin\validator\vatnumber. No change to this plugin is required.

Per-field Messages v2.2

Until v2.1 a blocked user only saw a generic "complete your profile" warning — a dead end when the field looked filled in but held an invalid value. The warning now lists every field that needs attention and says whether it is missing or invalid:

Warning shown to the user
You must complete your profile before proceeding.

Fields to correct:
• Codice fiscale: the value entered is not a valid Italian tax code — check the format and the final control character
• Profession: this required field has not been filled in

The same wording is reused as the tooltip on the profile edit form and in the admin status dashboard. All of it comes from the language packs — nothing is hardcoded.

Self-registration Validation v2.2

When self-registration is open, the configured fields are validated on the signup form itself, through Moodle's supported validate_extend_signup_form callback. A user can no longer create an account with a malformed value and discover it only at the next page load, locked out of the site.

Only fields actually published on the signup form are checked (Display on signup page in the profile field settings); anything else is left alone. Turn the behaviour off with the Validate on self-registration setting.

Known limitation: Moodle runs this callback only on the web signup form. Accounts created through the mobile app or the core_auth_signup_user web service bypass it — core does not offer a hook there. Those users are still caught by the profile check at their first login.

Admin Status Dashboard

Monitor profile completion across your entire user base.

📊

Summary Counters

Total users, incomplete profiles, and complete profiles at a glance with colored badges.

📋

Detailed User Table

Username, full name, email, missing fields as badges, and last access date.

🛠

Quick Actions

Direct links to view and edit each user's profile. Pagination for large sites.

Navigate to Site administration → Plugins → Local plugins → Profile Completion Status. Requires local/forceprofile:viewstatus capability.

Form Enhancement

Smart UX improvements on the profile edit page — no theme modifications needed.

❗

Targeted Indicators

A red exclamation icon is injected next to the fields that are actually empty or invalid — and only those. Its tooltip carries the same explanation as the warning, so the user reads why the value was rejected.

📋

Smart Select Defaults

Dropdown menus (<select>) that hold no value get an empty "Choose…" option prepended, forcing users to make an explicit choice instead of accidentally submitting the first option.

⚡

Zero Configuration

Works automatically on /user/edit.php and /user/editadvanced.php. Uses Moodle's AMD module system — no theme hacks, no custom CSS.

Fixed in v2.2: earlier versions marked every configured field, so correctly filled values still carried a red icon reading "compilation required". The callback now passes only the problematic fields, each with its own localised message, and the labels come from the language packs instead of being hardcoded in Italian.

Event Logging & Completion Tracking

Full audit trail in Moodle's standard log system.

EventWhenData
profile_blocked User is redirected to complete profile User ID, list of incomplete fields
profile_completed User fills in all required fields User ID, completion record ID

When a user completes all fields, a timestamp is recorded in local_forceprofile_compl. This data can be queried for reports or exported via the Moodle privacy API.

GDPR compliant: Completion timestamps can be exported and deleted via Moodle's privacy API. Full provider implementation included.

Capabilities

Fine-grained access control for exemptions and dashboard access.

CapabilityDescriptionDefault Roles
local/forceprofile:exemptExempt from forced profile completionManager, Editing Teacher
local/forceprofile:viewstatusAccess the status dashboardManager

Site administrators are always exempt. Assign capabilities to additional roles via Site administration → Users → Permissions → Define roles.

Under the Hood

Security, performance, and architecture.

⚡

Session Caching

Once complete, cached in $SESSION. Zero DB queries for the rest of the session.

🛡

SQL Injection Safe

Parameterized queries via Moodle's $DB API. No raw SQL anywhere.

🔐

XSS Protected

All messages sanitized via format_string() before rendering.

🔗

Open Redirect Safe

Redirect URL validated as PARAM_LOCALURL. External URLs rejected.

🛠

Typo & Regex Resilient

Non-existent fields and invalid patterns are skipped with debug notices. No loops.

🎓

PHPUnit Tested

34 tests covering fields, regex and checksum validation, signup, events, completion, and edge cases.

Pages Excluded from Redirect

PathReason
/user/edit.phpProfile edit (where users fill in fields)
/user/editadvanced.phpAdvanced profile edit
/login/logout.phpUsers must always be able to log out
/login/change_password.phpPassword change flow
/lib/ajax/service.phpAJAX web services
/lib/ajax/service-nologin.phpAJAX (no login)

Plugin Structure

local/forceprofile/
├── version.php — Plugin metadata
├── settings.php — Admin settings + external page
├── lib.php — Core logic, validation, completion, form enhancement
├── status.php — Admin status dashboard
├── amd/
│   ├── src/
│   │   └── formenhancer.js — Form enhancement AMD module
│   └── build/
│       └── formenhancer.min.js — Minified build
├── db/
│   ├── access.php — Capability definitions
│   ├── install.xml — Database schema
│   └── upgrade.php — Upgrade steps
├── classes/
│   ├── validator_manager.php — Validator registry
│   ├── validator/
│   │   ├── validator_interface.php — Validator contract
│   │   └── codicefiscale.php — Italian tax code validator
│   ├── event/
│   │   ├── profile_blocked.php — Blocked event
│   │   └── profile_completed.php — Completed event
│   └── privacy/
│       └── provider.php — GDPR provider
├── tests/
│   ├── lib_test.php — Library tests (30)
│   └── validator/codicefiscale_test.php — Validator tests (4)
└── lang/
    ├── en/local_forceprofile.php
    └── it/local_forceprofile.php

Run Tests

PHPUnit
# The whole plugin suite
$ php vendor/bin/phpunit --testsuite local_forceprofile_testsuite

# Library functions only
$ php vendor/bin/phpunit local/forceprofile/tests/lib_test.php

# Tax code validator only
$ php vendor/bin/phpunit local/forceprofile/tests/validator/codicefiscale_test.php

Changelog

All notable changes to this project.

v2.2.0 Feature August 2026
  • Field validators — new shortname:validatorname setting for checks a regex cannot express; sites can register their own class implementing validator_interface
  • Italian tax code validator (codicefiscale) — layout, month letter, omocodia substitutions and recomputed control character; values are trimmed and uppercased before checking
  • Per-field messages — the warning, the form tooltips and the status dashboard now name each field and distinguish "missing" from "invalid"
  • Self-registration validation — configured fields are checked on the signup form through the validate_extend_signup_form callback, so a bad value is refused before the account exists
  • Fixed: the form enhancer flagged every configured field, including the ones already filled in correctly
  • Fixed: the documented tax code regex rejected legitimate codes carrying omocodia substitutions
  • Form enhancer labels moved to the language packs; values made only of spaces are now treated as empty
  • Test suite grown to 34 tests, including the real-world tax code cases that motivated the release
v2.1.0 Feature March 2026
  • Form Enhancement — red exclamation icon (❗) next to configured field labels on profile edit page
  • Smart Select Defaults — empty "Scegli..." option prepended to <select> fields that are incomplete
  • New AMD module formenhancer.js using Moodle's module system
  • New callback before_standard_html_head to inject JS only on edit pages
v2.0.0 Major March 2026
  • Regex Validation — optional per-field regex patterns (e.g. tax code, phone number)
  • Admin Status Dashboard — summary counters, paginated user table with missing fields as badges
  • Event Logging — profile_blocked and profile_completed events in Moodle logs
  • Completion Tracking — timestamp recorded in local_forceprofile_compl table
  • PHPUnit Tests — 17 tests covering all core logic and edge cases
  • Full GDPR privacy provider with export and delete support
  • New capability local/forceprofile:viewstatus
  • Plugin made fully generic — no hardcoded field defaults
  • Default state: disabled (opt-in activation)
v1.1.0 Improvements March 2026
  • Non-existent field shortnames now silently skipped (prevents permanent redirect loops)
  • Redirect URL validated as local path (PARAM_LOCALURL) — prevents open redirect
  • Notification message sanitized with format_string()
  • $PAGE->url access wrapped in try/catch for early page lifecycle safety
  • Exact path matching instead of loose strpos
  • AJAX and CLI requests excluded from redirect
  • Session caching to avoid DB query on every page load
  • Added privacy provider (null_provider)
v1.0.0 Initial March 2026
  • Initial release
  • Core after_require_login callback for profile enforcement
  • Configurable field shortnames, warning message, and redirect URL
  • Capability local/forceprofile:exempt for managers and editing teachers
  • English and Italian language packs