# Rosary Presenter A multi-user web app for leading the Rosary, novenas, and the Divine Mercy Chaplet — built for live presentation at prayer services. Live at **[loveandrosary.com](https://loveandrosary.com)**. ## What It Does - **Slide-based presentation** — navigate prayer-by-prayer with leader/congregation text split on screen - **Rosary bead ring** — SVG visualization tracks which bead is active in real time - **Session types** — General Rosary, Memorial Rosary, Deceased Novena, Divine Mercy Chaplet - **Novena groups** — link 9 daily sessions into one group with a public day-picker page - **Audio uploads** — attach MP3/audio per session (up to 50 MB) - **Multi-user** — role hierarchy: `superadmin` → `admin` → `superuser` → `user` - **Public profiles** — each user gets a `/username` page with their public sessions - **Donate strip** — optional PayPal / Venmo / Buy Me a Coffee link on public pages ## Stack - PHP 8 + PDO (no framework, no Composer dependencies) - MySQL 8 / MariaDB - Vanilla JS (no build step) - Apache/Nginx with `.htaccess` rewrite rules ## Setup ### 1. Configure database ```bash cp config/db.example.php config/db.php # Edit config/db.php — fill in DB_HOST, DB_NAME, DB_USER, DB_PASS # Set BASE_URL if deploying to a subdirectory (e.g. '/rosary') ``` ### 2. Create the database schema Visit `install.php` in your browser once — it creates all tables (matching `schema.sql`, kept as the canonical structure reference) and seeds `site_settings` defaults, the superadmin account, and the standard prayer library. **Delete `install.php` immediately after.** Default superadmin credentials: `supadmin` / `supadmin` — **change these immediately**. `schema.sql` documents the current database structure; there is no separate migration-script chain to run. ### 3. Configure the web server #### Apache — `.htaccess` is included. Enable `mod_rewrite` and set `AllowOverride All`. #### Nginx — add to your server block: ```nginx location / { try_files $uri $uri/ @php; } location @php { rewrite ^/([^/]+)/([^/]+)$ /present.php?username=$1&slug=$2 last; rewrite ^/([^/]+)$ /profile.php?username=$1 last; } ``` ### 4. Uploads directory ```bash chmod 755 uploads/ ``` ### 5. SMTP (optional) Configure outbound email in **Admin → Settings** for registration confirmation and password reset emails. If left blank, the app will auto-confirm new users instead. ### 6. Bot protection (optional) `register.php` always runs a built-in honeypot + timing trap against scripted signups — no setup needed. On top of that, you can enable Google reCAPTCHA v3: register your domain at [google.com/recaptcha](https://www.google.com/recaptcha/admin) (choose **reCAPTCHA v3**), then enter the Site Key and Secret Key in **Admin → Settings → Bot Protection**. ### 7. Scheduled cleanup of unconfirmed accounts (optional) `cron/cleanup_unconfirmed.php` permanently deletes accounts that are still unconfirmed 3 days after registering — useful for clearing out bot signups that get past the defenses above. It's CLI-only (refuses to run over HTTP) and is not wired up automatically; schedule it yourself as a cron job. In Hostinger's hPanel: **Advanced → Cron Jobs** → run daily: ```bash php /home//domains/loveandrosary.com/public_html/cron/cleanup_unconfirmed.php ``` (Adjust the path to match your actual hosting account.) Confirmed accounts are never touched — only rows with `email_confirmed = 0`. ## Upgrading an Existing Install `schema.sql` reflects the current database structure. For a production database that predates the `failed_login_attempts` / `locked_until` login-lockout columns, run this once against it manually — it's not applied automatically since there's no migration runner against a live database: ```sql ALTER TABLE users ADD COLUMN failed_login_attempts INT NOT NULL DEFAULT 0, ADD COLUMN locked_until DATETIME NULL; ``` Also needed for the photo reposition/zoom tool (`photo_focal_x`/`photo_focal_y`/`photo_zoom` on both `sessions` and `novena_groups` — defaults reproduce today's centered, no-zoom crop, so this is safe to run any time): ```sql ALTER TABLE sessions ADD COLUMN photo_focal_x FLOAT NOT NULL DEFAULT 50, ADD COLUMN photo_focal_y FLOAT NOT NULL DEFAULT 50, ADD COLUMN photo_zoom FLOAT NOT NULL DEFAULT 1; ALTER TABLE novena_groups ADD COLUMN photo_focal_x FLOAT NOT NULL DEFAULT 50, ADD COLUMN photo_focal_y FLOAT NOT NULL DEFAULT 50, ADD COLUMN photo_zoom FLOAT NOT NULL DEFAULT 1; ``` ## Deployment Checklist - [ ] `config/db.php` filled in with production credentials - [ ] `install.php` deleted after first run - [ ] `uploads/` is writable by the web server - [ ] `BASE_URL` matches your subdirectory path (leave empty for domain root) - [ ] Superadmin password and email changed - [ ] SMTP configured in Admin → Settings ## Project Structure ``` Rosary/ ├── admin/ # Admin dashboard (auth-gated) │ ├── index.php # Dashboard home │ ├── setup.php # Create/edit a session │ ├── novena_group.php │ ├── users.php │ ├── settings.php # Site-wide settings (superadmin only) │ └── audio.php ├── api/ # JSON endpoints (upload, save, delete) ├── assets/ │ ├── css/ # present.css, public.css, setup.css │ └── js/ # presenter.js, rosary.js, setup.js ├── config/ │ ├── db.example.php # Copy → db.php and fill in credentials │ └── db.php # (gitignored — contains real credentials) ├── cron/ │ └── cleanup_unconfirmed.php # CLI-only; schedule via host cron ├── data/ │ └── prayers.php # All prayer text + build_decade_slides() ├── includes/ │ ├── auth.php # require_auth(), current_user(), has_role(), login lockout │ ├── csrf.php # csrf_token(), csrf_field(), csrf_verify() │ ├── recaptcha.php # recaptcha_enabled(), verify_recaptcha() │ ├── build_slides.php │ ├── donate.php │ └── mailer.php ├── uploads/ # User-uploaded audio (gitignored) ├── index.php # Public home — card grid of sessions ├── present.php # Presentation player (public) ├── novena_public.php # Novena day-picker (public) ├── schema.sql # Canonical database schema (structure only) ├── install.php # Run once, then delete └── .htaccess # URL rewriting ``` ## URL Routing | URL | Resolves to | |-----|-------------| | `/username/slug` | `present.php?username=X&slug=Y` | | `/username` | `profile.php?username=X` | | `/username/novena-slug` | Redirects to `novena_public.php?group_id=X` | ## License Private project — all rights reserved.