Initial commit — Rosary Presenter App

Full source for loveandrosary.com: slide-based Rosary/novena/Divine Mercy
Chaplet presentation tool with multi-user roles, SVG bead ring, audio uploads,
donate strip, and public session profiles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 18:44:08 -07:00
commit 663fde3909
46 changed files with 10902 additions and 0 deletions
+220
View File
@@ -0,0 +1,220 @@
<?php
/**
* api/save_session.php
* POST: insert or update a session.
*
* NEW novena_deceased (no id): inserts group + 9 rows (Day 19), returns {"novena":true,"ids":[...]}
* EDIT any session (id provided): updates single row, returns {"id": N}
* NEW other occasion: inserts 1 row, returns {"id": N}
*/
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../includes/auth.php';
header('Content-Type: application/json');
require_auth();
$user = current_user();
$uid = (int)$user['id'];
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
exit;
}
// Collect and sanitize input
$id = isset($_POST['id']) && $_POST['id'] !== '' ? (int)$_POST['id'] : null;
$name = trim($_POST['name'] ?? '');
$occasion = trim($_POST['occasion'] ?? '');
$mystery_set = trim($_POST['mystery_set'] ?? '');
$novena_mystery_mode = trim($_POST['novena_mystery_mode'] ?? '');
$novena_day = isset($_POST['novena_day']) && $_POST['novena_day'] !== '' ? (int)$_POST['novena_day'] : null;
$subject_name = trim($_POST['subject_name'] ?? '') ?: null;
$subject_pronoun = trim($_POST['subject_pronoun'] ?? '') ?: null;
$subject_dates = trim($_POST['subject_dates'] ?? '') ?: null;
$photo_path = trim($_POST['photo_path'] ?? '') ?: null;
$is_public = isset($_POST['is_public']) ? 1 : 0;
// For novena sessions, mystery_set is determined by novena_mystery_mode
if ($occasion === 'novena_deceased') {
$mystery_set = ($novena_mystery_mode === 'by_day_of_week') ? 'by_day_of_week' : 'sorrowful';
}
// Divine Mercy Novena uses the chaplet — no mystery set
if ($occasion === 'divine_mercy_novena') {
$mystery_set = 'chaplet';
}
// Validate required fields
$valid_occasions = ['novena_deceased', 'divine_mercy_novena', 'general_rosary', 'memorial'];
$valid_mysteries = ['sorrowful', 'joyful', 'glorious', 'luminous', 'by_day_of_week', 'chaplet'];
if ($name === '') {
http_response_code(400);
echo json_encode(['error' => 'Session name is required']);
exit;
}
if (!in_array($occasion, $valid_occasions, true)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid occasion']);
exit;
}
if (!in_array($mystery_set, $valid_mysteries, true)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid mystery set']);
exit;
}
try {
$pdo = get_pdo();
// ------------------------------------------------------------------
// EDIT: update single existing session
// ------------------------------------------------------------------
if ($id !== null) {
// Verify ownership or admin
$chk = $pdo->prepare('SELECT user_id FROM sessions WHERE id = ?');
$chk->execute([$id]);
$row = $chk->fetch();
if (!$row) {
http_response_code(404);
echo json_encode(['error' => 'Session not found']);
exit;
}
if (!has_role('admin') && (int)$row['user_id'] !== $uid) {
http_response_code(403);
echo json_encode(['error' => 'Permission denied']);
exit;
}
// Update slug if name changed and user owns it
$new_slug = null;
if (!has_role('admin') || (int)$row['user_id'] === $uid) {
$owner_id = (int)$row['user_id'];
$new_slug = unique_slug($name, $owner_id, 'sessions', $id);
}
$stmt = $pdo->prepare('
UPDATE sessions
SET name = ?, occasion = ?, mystery_set = ?,
subject_name = ?, subject_pronoun = ?, subject_dates = ?,
photo_path = COALESCE(?, photo_path),
is_public = ?' .
($new_slug !== null ? ', slug = ?' : '') . '
WHERE id = ?
');
$params = [
$name, $occasion, $mystery_set,
$subject_name, $subject_pronoun, $subject_dates,
$photo_path, $is_public,
];
if ($new_slug !== null) $params[] = $new_slug;
$params[] = $id;
$stmt->execute($params);
echo json_encode(['id' => $id]);
exit;
}
// ------------------------------------------------------------------
// Creating new: check rosary limit
// ------------------------------------------------------------------
if (!can_create_rosary($uid, $user['rosary_limit'])) {
http_response_code(429);
echo json_encode(['error' => 'Rosary limit reached. Contact an administrator to increase your limit.']);
exit;
}
// ------------------------------------------------------------------
// CREATE NEW NOVENA: create a group record, then 9 day sessions
// ------------------------------------------------------------------
if ($occasion === 'divine_mercy_novena') {
$grp_slug = unique_slug($name, $uid, 'novena_groups');
$grp = $pdo->prepare('
INSERT INTO novena_groups
(name, mystery_set, subject_name, subject_pronoun, subject_dates, photo_path, user_id, is_public, slug)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
');
$grp->execute([$name, $mystery_set, null, null, null, $photo_path, $uid, $is_public, $grp_slug]);
$group_id = (int)$pdo->lastInsertId();
$insert = $pdo->prepare('
INSERT INTO sessions
(name, occasion, mystery_set, novena_day,
subject_name, subject_pronoun, subject_dates, photo_path, novena_group_id,
user_id, is_public, slug)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
');
$created_ids = [];
for ($day = 1; $day <= 9; $day++) {
$day_name = $name . ' — Day ' . $day;
$day_slug = unique_slug($day_name, $uid, 'sessions');
$insert->execute([
$day_name, $occasion, $mystery_set, $day,
null, null, null, $photo_path, $group_id,
$uid, $is_public, $day_slug,
]);
$created_ids[] = (int)$pdo->lastInsertId();
}
echo json_encode(['novena' => true, 'group_id' => $group_id, 'ids' => $created_ids]);
exit;
}
if ($occasion === 'novena_deceased') {
$grp_slug = unique_slug($name, $uid, 'novena_groups');
$grp = $pdo->prepare('
INSERT INTO novena_groups
(name, mystery_set, subject_name, subject_pronoun, subject_dates, photo_path, user_id, is_public, slug)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
');
$grp->execute([$name, $mystery_set, $subject_name, $subject_pronoun, $subject_dates, $photo_path, $uid, $is_public, $grp_slug]);
$group_id = (int)$pdo->lastInsertId();
$insert = $pdo->prepare('
INSERT INTO sessions
(name, occasion, mystery_set, novena_day,
subject_name, subject_pronoun, subject_dates, photo_path, novena_group_id,
user_id, is_public, slug)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
');
$created_ids = [];
for ($day = 1; $day <= 9; $day++) {
$day_name = $name . ' — Day ' . $day;
$day_slug = unique_slug($day_name, $uid, 'sessions');
$insert->execute([
$day_name, $occasion, $mystery_set, $day,
$subject_name, $subject_pronoun, $subject_dates, $photo_path, $group_id,
$uid, $is_public, $day_slug,
]);
$created_ids[] = (int)$pdo->lastInsertId();
}
echo json_encode(['novena' => true, 'group_id' => $group_id, 'ids' => $created_ids]);
exit;
}
// ------------------------------------------------------------------
// CREATE NEW: single session (general_rosary or memorial)
// ------------------------------------------------------------------
$slug = unique_slug($name, $uid, 'sessions');
$stmt = $pdo->prepare('
INSERT INTO sessions
(name, occasion, mystery_set, novena_day,
subject_name, subject_pronoun, subject_dates, photo_path,
user_id, is_public, slug)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
');
$stmt->execute([
$name, $occasion, $mystery_set, $novena_day,
$subject_name, $subject_pronoun, $subject_dates, $photo_path,
$uid, $is_public, $slug,
]);
echo json_encode(['id' => (int)$pdo->lastInsertId()]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
}