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.