Version: 1.0.0 | PHP 8.5 (backward-compatible with 8.4) | MySQL 8.0+ | DreamHost shared hosting
A modular internal portal platform for organisations, providing centralised access to internal tools, calendar / events, attendance, expenses, leadership directory, prayer requests, announcements, document library, tasks/reminders, multi-site support, and more.
π For the current per-app feature inventory (what's shipped, what's in flight, what's planned with issue numbers), see FEATURES.md.
| Layer | Choice | Rationale |
|---|---|---|
| Backend | PHP 8.5 (strict types, backward-compatible with 8.4), MySQL 8.0 | Ubiquitous LAMP stack; DreamHost-friendly |
| Routing | Front-controller + DB-backed router (tblRoutes) | Clean URLs, app isolation, easy overrides |
| Auth | Local accounts, MS365 OAuth, Google OAuth, WebAuthn/PassKeys, account linking | Multi-provider SSO, passwordless support |
| Multi-Site | Umbrella multi-site with subdomain, path-prefix, and session detection modes | One install serves multiple locations/divisions |
| UI | Bootstrap 5.3.3 + design tokens; light/dark/auto theme + CB-safe palette | Responsive, WCAG-conscious, per-site themable |
dompdf 3.1.5 (fetched at deploy time by tools/download-dompdf.sh) |
Server-side PDF; pinned version vendored in CI | |
| Microsoft Graph "SendAs" via shared mailbox | DKIM/DMARC compliance, modern auth | |
| Bot Protection | CloudFlare Turnstile (preferred) / reCAPTCHA | Reduces spam without degrading UX |
WebMS-Intra/ # Git repository root (NOT deployed)
βββ .claude/ # Claude AI context and project brief
βββ .github/workflows/deploy.yml # CI/CD pipeline (syncs web/ to server)
βββ CHANGELOG.md # Project-wide changelog
βββ DEV_NOTES.md # Developer guide
βββ README.md # This file
βββ web/ # ALL deployable server files
β
β ββ Server-side (NOT web-accessible β above DocumentRoot) ββ
βββ _core/ # Framework classes (Portal\Core namespace)
β βββ App.php # Application registry (db, settings, user)
β βββ ApiResponse.php # JSON API response builder
β βββ Site.php # Multi-site context manager (detection, branding)
β βββ Asset.php # CDN-with-fallback asset loader (SRI)
β βββ Auth.php # Authentication (MS365, Google, local, WebAuthn, CSRF, JWT)
β βββ Avatar.php # Avatar cascade (MS365 > local > Gravatar > SVG)
β βββ bootstrap.php # Environment, DB, settings, autoloader
β βββ Captcha.php # Turnstile / reCAPTCHA helper
β βββ Debug.php # Debug panel (admin + ?debug=true)
β βββ ExpenseMailer.php # Expense email notification helper
β βββ ExpensePdf.php # Expense claim PDF generator
β βββ Gatekeeper.php # Dev/channel access control
β βββ Logger.php # Activity and error logging
β βββ Mailer.php # Email via Microsoft Graph API
β βββ Migrator.php # Web-based SQL migration runner
β βββ Pdf.php # dompdf wrapper (conditional load)
β βββ RateLimiter.php # Login rate limiting
β βββ Router.php # Front-controller URL dispatcher
β βββ ApiRouter.php # Dedicated API route dispatcher
β βββ Container.php # Lightweight dependency injection container
β βββ CsvExporter.php # CSV export helper (expenses, attendance, etc.)
β βββ Validator.php # Input validation framework (pipe-separated rules)
β βββ WebAuthn.php # WebAuthn/PassKey server-side helper
β βββ I18n.php # Internationalisation framework (translations, RTL, formatting)
β βββ templates/ # Shared page templates
βββ _lang/ # Translation files (en.php, cy.php, etc.)
βββ _vendor/simplejwt/ # Lightweight RS256 JWT verifier
βββ _sql/ # Numbered SQL migration files
βββ _install/ # 6-step install wizard (run before first boot)
βββ _auth_keys/ # DB credentials, encryption key (gitignored)
βββ _includes/ # Shared includes (future)
βββ _functions/ # Shared functions (future)
βββ _libraries/ # Self-hosted libs e.g. dompdf (gitignored)
βββ _uploads/ # User file uploads (gitignored)
βββ _backups/ # Server backups (gitignored)
β
β
β ββ App controllers (outside webroot, #159) ββ
βββ _apps/ # Every app's PHP handlers
β βββ auth/ # Login, forgot/reset password, account
β βββ dashboard/ # Portal home with app cards
β βββ expenses/ # Expense claim lifecycle
β βββ attendance/ # Attendance tracker app
β βββ leadership/ # Leadership roles & assignments
β βββ calendar/ # Calendar / Events / Preaching Plan
β βββ admin/ # Admin panel (sites, users, logs, migrations)
β βββ help/ # Help centre pages
β βββ site/ # Site switcher handler
β βββ settings/ # Admin settings UI
β βββ β¦ # ~35 more app dirs (account, announcements,
β api, care, directory, documents, events,
β giving, invites, live, milestones,
β newsletter, offboarding, payments,
β photos, praise, prayer-requests, privacy,
β projects, reading-plans, recordings,
β resources, rota, service-plans, tasks,
β users, visitors)
β
β ββ Apache DocumentRoot β the ONLY web-accessible tree ββ
βββ public_html/ # Production web root β lean (#159)
β βββ index.php # Front controller (only PHP entry point)
β βββ error.php # Apache ErrorDocument target
β βββ .htaccess # URL rewriting + .php deny
β βββ assets/ # CSS, JS, images, webfonts
β βββ api-docs/ # Swagger UI (DirectoryIndex)
β βββ offline/ # PWA offline fallback page
β βββ manifest.json
β βββ openapi.json
β βββ robots.txt
β βββ sw.js # Service worker
βββ private_html/ # Private / non-live files
βββ public_html_landing/ # Pre-launch landing page
βββ public_html_redir/ # Redirect page
The single public_html/ directory is the source for every branch's deploy β alpha lands at the server's public_html_dev/, beta at public_html_beta/, and main at public_html/. There's no per-channel front controller in the repo.
Browser -> .htaccess -> index.php -> bootstrap.php -> Router::dispatch()
|-- Special route? (login/ms365, login/google, login/webauthn, logout, api/*, health) -> handle directly
|-- Query tblRoutes for matching routeKey
|-- If isProtected=1, enforce Auth::requireLogin()
'-- Include target app file -> header.php -> content -> footer.php
- PHP 8.4+ with extensions:
mysqli,openssl,sodium,curl,mbstring - MySQL 8.0+
- Apache with
mod_rewrite
- Upload
web/contents to the server domain directory - Navigate to the portal URL in your browser β the installation wizard will start automatically
- Follow the 6-step wizard: prerequisites check, database config, schema install, admin account, encryption key generation
- Upload dompdf to
_libraries/dompdf/(download from github.com/dompdf/dompdf) - Log in and configure site settings and OAuth providers as needed
- Upload the updated
web/files to the server (FTP sync) - Navigate to Admin > Upgrade (or
/admin/upgrade.php) - Review and run any pending SQL migrations
- Verify the portal is working correctly
- Create
_auth_keys/auth_creds.phpreturning:['db_host'=>..., 'db_user'=>..., 'db_pass'=>..., 'db_name'=>..., 'db_port'=>3306] - Generate encryption key:
openssl rand -hex 32 > _auth_keys/enc.key - Import
_sql/full_schema.sqlinto your database - Create a lock file:
touch _auth_keys/.installed
cd web
export PORTAL_ENV=dev
php -S localhost:8080 -t public_htmlCI/CD via GitHub Actions syncs web/ to DreamHost over SFTP (SSH key
preferred, password fallback) on a three-branch model:
| Branch | Channel | Public dir on server | Auto-bump rule |
|---|---|---|---|
alpha |
alpha/dev | public_html_dev/ |
PATCH (always) |
beta |
beta | public_html_beta/ |
Conventional Commits |
main |
production | public_html/ |
none β tag v* manually |
Everything else inside web/ (_core/, _vendor/, _sql/, _lang/,
_install/) deploys to the shared remote base from every branch.
Workflows in .github/workflows/:
deploy.ymlβ SFTP sync; key-first / password-fallback authversion-bump.ymlβ updatesweb/_core/App.phpon alpha/beta pusheschangelog.ymlβ appends commit-message entries to CHANGELOG.mdrelease.ymlβ creates a GitHub Release onv*tag pushauto-merge-alpha.ymlβ enables auto-merge on PRs whose base isalpha
dompdf is fetched at deploy time by tools/download-dompdf.sh (pinned
version) and uploaded as part of the shared sync. Other server-managed
directories (_auth_keys/, _uploads/, _backups/) stay on the server and
are excluded from upload.
Required repo configuration (one-time): see DEV_NOTES.md β CI/CD Secrets Setup for the step-by-step guide (SSH keypair, GitHub secrets, kill switch, branch protection).
- MySQLi prepared statements (no raw SQL interpolation)
- Sensitive settings encrypted at rest (libsodium XSalsa20 + Poly1305)
- Session cookies:
HttpOnly,Secure,SameSite=Lax - CSRF tokens with rotation after use
- Rate limiting on login attempts (configurable via settings)
- RS256 JWT verification with JWKS key fetching for MS365 tokens
- SRI integrity hashes on CDN resources
- Security headers:
X-Content-Type-Options,X-Frame-Options,Referrer-Policy,Content-Security-Policy,Permissions-Policy
declare(strict_types=1)at the top of every PHP file- Full IF notation (
if ($x === true)notif ($x)) - Platform-neutral paths using
DIRECTORY_SEPARATOR - Emoji-annotated comments for major code sections
- No
<table>tags for data display - useportal-data-listresponsive component - MySQLi prepared statements only - never interpolate user input into SQL
Phase-level milestones (granular per-feature state lives in FEATURES.md).
| Phase | Description | Status |
|---|---|---|
| 1 | Core Framework | Done |
| 2 | Local Auth Enhancement (forgot/reset password, account page, policy engine) | Done |
| 2.5 | Directory Restructure (web/ consolidation, deploy fix, bug fixes) | Done |
| 3 | Admin UI (error logs, activity logs, user management, migration runner) | Done |
| 4 | Calendar / Events / Preaching Plan (incl. seven view modes β see #136/#137) | Done |
| 5 | Attendance Tracker | Done |
| 6 | Expenses β Multi-Approver, Email, PDF, Treasury | Done |
| 7 | SSO & Auth Enhancement (Google OAuth, WebAuthn/PassKeys, Account Linking) | Done |
| 8 | Translations / i18n (I18n framework, RTL support, language switcher) | Done |
| 9 | Polish & Hardening (PWA, WCAG 2.1, Security Hardening β incl. #53 #54) | Done |
| 10 | Multi-Site Support (umbrella orgs, site detection, 4-tier permissions) | Done |
| 11 | UI Refresh + Design System (themes, CB-safe palette, per-site branding) | Done |
| 12 | Prayer Requests app (incl. anonymous public route β #129) | Done |
| 13 | Multi-provider Captcha (Turnstile + reCAPTCHA v2/v3 + hCaptcha β #130) | Done |
Currently in flight (open PRs):
- #137 β Calendar seven view modes (Day, Week, Weekdays, Weekend, Month, Year planner, List).
- #138 β Calendar per-month strap-lines + category
displayStyletoggle (background-band vs text-only) β stacked on #137.
Tracked but not started: WordPress Multisite integration (#127), Order of Service planner with iHymns (#128), BookIT integration cluster (#97β#103), composite IP+username login rate-limit (#52), Privacy / GDPR helpers (#47), Payment integration prep (#40). See FEATURES.md for the full backlog with scope notes.
All Rights Reserved. Copyright 2025-present MWBM Partners Ltd (t/a MWservices). No licence is granted for use, modification, or distribution without explicit written permission.