Web application for running a national HIV rapid-test personnel certification programme: testers and their training, written and practical examinations, certification and recertification, and the certificates themselves, including the online question bank and the printed exam papers.
Laminas MVC on PHP 8.4, MySQL 8, Apache. Licensed under the GNU AGPL v3.
| PHP | 8.4 or later, with gd, imagick, intl, mbstring, pdo_mysql, sodium and zip |
| MySQL | 8.0 or later |
| Web server | Apache with mod_rewrite, document root at public/ |
| Composer | 2.x |
git clone https://github.com/deforay/rtcert.git
cd rtcert
composer install --no-dev --optimize-autoloaderCreate the database, then the two configuration files. Both are gitignored, so each instance keeps its own and an update never overwrites them.
cp config/autoload/global.dist.php config/autoload/global.php
cp config/custom.config.dist.ini config/custom.config.ini
cp config/acl.config.dist.php config/acl.config.phpSet the database name and credentials in config/autoload/global.php, then
the deployment settings in config/custom.config.ini: the site URL, the
administrator's address, the SMTP account, and password.salt.
password.salt must be set before the first account is created and must
never change afterwards. Every stored password is hashed with it, so
changing it locks out every user. On a new instance, use a long random
string; on an existing one, keep the value that instance already uses.
Load the schema and create the first administrator:
php bin/migrate
php bin/console create-adminbin/migrate loads migrations/init.sql when the database is empty. That
file carries the schema, the roles, the access-control resources and neutral
defaults — no country, logo, locations, testers or question bank. Set those
up in the application after signing in, under Global Config.
Point Apache at public/, and give the web server user write access to the
directories the application writes to:
sudo chown -R www-data:www-data public/uploads public/temporary data/cache backup configconfig is writable because the role editor saves access rules to
config/acl.config.php.
Sign in at /login with the account create-admin made.
cd /var/www/rtcert
git pull
composer install --no-dev --optimize-autoloader
php bin/migratebin/migrate applies whatever migrations the database has not seen, and is
safe to run when there is nothing to apply.
Updating from a release before this one: config/autoload/global.php,
config/autoload/local.php, config/custom.config.ini and
config/acl.config.php used to be tracked in git and are now ignored, so
the first git pull deletes them. Copy them somewhere safe first and put
them back afterwards:
cp -a config /var/tmp/rtcert-config-backup
git pull
cp -a /var/tmp/rtcert-config-backup/autoload/global.php config/autoload/
cp -a /var/tmp/rtcert-config-backup/custom.config.ini config/
cp -a /var/tmp/rtcert-config-backup/acl.config.php config/Settings that lived in config/autoload/local.php belong in
config/autoload/global.php now; both are still read, later files winning.
Schema changes are plain SQL files under migrations/, applied by
bin/migrate and tracked in global_config.db_version. See
migrations/README.md for the file format and the
rules that keep a migration safe to re-run.
php bin/migrate --status # what is pending
php bin/migrate --dry-run # print the statements, change nothing
php bin/migrate # apply themphp bin/console list # every command
php bin/console create-admin # create an administrator account
php bin/console send-mail # flush the queued outbound mail
php bin/console send-test-alert-mail # send the due online-test invitationsScheduling is handled by crunz, so the crontab holds one entry and the schedule itself lives in the repository:
* * * * * /var/www/rtcert/cron.shcron.sh runs crunz every minute, and crunz runs whatever is due according
to sys/cron/ScheduledTasks.php: the queued mail every five minutes, and
the online-test invitations for expiring certificates daily at 07:00 in the
server's timezone. Both are set to preventOverlapping(), so a slow run
cannot stack up behind itself.
composer cron-list # what is scheduled, and when it next runs
composer cron # run whatever is due nowTask errors go to var/log/scheduled-tasks.log; each task also appends its
own output under var/log/. To add a task, add it to
sys/cron/ScheduledTasks.php — no crontab change is needed.
| File | Holds | Tracked |
|---|---|---|
config/autoload/global.php |
database credentials | no |
config/custom.config.ini |
site URL, SMTP, password salt | no |
config/acl.config.php |
per-role access rules, written by the role editor | no |
config/application.config.php |
the module list | yes |
module/*/config/module.config.php |
routes, services, console commands | yes |
Each untracked file has a .dist template beside it.
composer install
composer serve # http://localhost:8080Point config/autoload/global.php at a scratch database and load it with
php bin/migrate, which gives a clean instance with no data in it.
To see PHP errors in the browser rather than the friendly error page, add
to config/autoload/global.php:
'view_manager' => ['display_exceptions' => true],bin/ console and migration runners
config/ application config and the per-deployment files
migrations/ init.sql and the versioned schema changes
module/
Application/ users, roles, access control, dashboard, online tests, config
Certification/ testers, training, examinations, certification, facilities
public/ document root; assets, uploads and generated PDFs
sys/cron/ the crunz schedule
Copyright © Deforay Technical Services and contributors. Distributed under the GNU Affero General Public License v3 or later; see LICENSE.