Files
Rosary/admin/prayers.php
T
pguzman f3fa77da17 Add Rosary Builder — custom prayer-sequence sessions
Superuser+ can now build a custom prayer sequence from scratch:
- Two-panel builder UI: step sequence (left) + searchable prayer library (right)
- 16 standard prayers seeded globally; users can create private custom prayers
- Admin can promote private prayers to global and manage the library
- Four attribution modes per step: Leader/All, Leader only, All together, None
- Optional subject name/pronoun for variable substitution in prayers
- Custom sessions fully presented via the existing presenter (auto-split works)
- migrate_v4.php creates custom_prayers + builder_steps tables

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 19:07:11 -07:00

227 lines
11 KiB
PHP

<?php
/**
* admin/prayers.php — Global prayer library management.
* Admin and superadmin only.
*/
require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../includes/auth.php';
require_role('admin');
$pdo = get_pdo();
$user = current_user();
$uid = (int)$user['id'];
$site_name = get_setting('site_name', APP_NAME);
// Handle form actions
$msg = '';
$msg_type = 'success';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'delete') {
$id = (int)($_POST['prayer_id'] ?? 0);
if ($id) {
// Check if in use
$in_use = (int)$pdo->prepare("SELECT COUNT(*) FROM builder_steps WHERE prayer_id = ?")
->execute([$id]) ? 0 : 0;
$st = $pdo->prepare("SELECT COUNT(*) FROM builder_steps WHERE prayer_id = ?");
$st->execute([$id]);
if ((int)$st->fetchColumn() > 0) {
$msg = 'Cannot delete: this prayer is used in one or more sessions.';
$msg_type = 'error';
} else {
$pdo->prepare("DELETE FROM custom_prayers WHERE id = ?")->execute([$id]);
$msg = 'Prayer deleted.';
}
}
}
if ($action === 'toggle_global') {
$id = (int)($_POST['prayer_id'] ?? 0);
$global = (int)($_POST['new_global'] ?? 0);
if ($id) {
$pdo->prepare("UPDATE custom_prayers SET is_global=? WHERE id=?")->execute([$global, $id]);
$msg = $global ? 'Prayer made global.' : 'Prayer set to private.';
}
}
}
// Load all prayers visible to admins
$prayers = $pdo->prepare("
SELECT cp.*,
u.username AS creator_username, u.display_name AS creator_display, u.role AS creator_role,
(SELECT COUNT(*) FROM builder_steps WHERE prayer_id = cp.id) AS use_count
FROM custom_prayers cp
LEFT JOIN users u ON u.id = cp.created_by
ORDER BY (cp.is_global=1 AND u.role='superadmin') DESC, cp.is_global DESC, cp.name ASC
");
$prayers->execute();
$prayers = $prayers->fetchAll();
$filter = $_GET['filter'] ?? 'all';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/svg+xml" href="<?= BASE_URL ?>/favicon.svg">
<title>Prayer Library — <?= htmlspecialchars($site_name) ?></title>
<link rel="stylesheet" href="<?= BASE_URL ?>/assets/css/setup.css">
<style>
.filter-tabs { display:flex; gap:8px; margin-bottom:20px; flex-wrap:wrap; }
.filter-tabs a { padding:6px 16px; border:1px solid var(--border); border-radius:20px; font-size:13px; text-decoration:none; color:var(--muted); }
.filter-tabs a.active { background:var(--primary); border-color:var(--primary); color:#fff; font-weight:500; }
.prayer-table { width:100%; border-collapse:collapse; }
.prayer-table th, .prayer-table td { padding:10px 12px; text-align:left; border-bottom:1px solid var(--border); font-size:13px; vertical-align:top; }
.prayer-table th { font-size:11px; text-transform:uppercase; letter-spacing:.05em; color:var(--muted); background:var(--bg); font-weight:600; }
.prayer-table tbody tr:hover { background:var(--bg); }
.prayer-preview { color:var(--muted); font-size:12px; margin-top:3px; }
.badge-std { background:#fef9c3; color:#854d0e; }
.badge-glob { background:#dcfce7; color:#166534; }
.badge-priv { background:#ede9fe; color:#6d28d9; }
.use-badge { background:#e0e7ff; color:#3730a3; padding:2px 8px; border-radius:4px; font-size:11px; font-weight:600; }
.action-form { display:inline; }
details.preview-detail { font-size:12px; color:var(--muted); margin-top:4px; }
details.preview-detail summary { cursor:pointer; color:var(--primary); }
details.preview-detail pre { white-space:pre-wrap; font-family:inherit; margin-top:6px; line-height:1.5; }
</style>
</head>
<body>
<div class="admin-container">
<header class="admin-header">
<h1>&#x271D; <?= htmlspecialchars($site_name) ?></h1>
<div class="header-actions">
<a href="<?= BASE_URL ?>/" class="btn btn-ghost" style="font-size:13px">&#x2190; View Site</a>
<a href="<?= BASE_URL ?>/admin/builder.php" class="btn btn-ghost">Builder</a>
<a href="<?= BASE_URL ?>/admin/users.php" class="btn btn-ghost">Users</a>
<?php if (has_role('superadmin')): ?>
<a href="<?= BASE_URL ?>/admin/settings.php" class="btn btn-ghost">Settings</a>
<?php endif; ?>
<a href="<?= BASE_URL ?>/admin/" class="btn btn-ghost">&#x2190; Dashboard</a>
</div>
</header>
<main>
<?php if ($msg): ?>
<div class="alert alert-<?= $msg_type === 'error' ? 'error' : 'success' ?>" style="margin-bottom:16px">
<?= htmlspecialchars($msg) ?>
</div>
<?php endif; ?>
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px">
<div>
<h2 style="margin:0">Prayer Library</h2>
<p style="margin:4px 0 0;font-size:13px;color:var(--muted)">
Global prayers are available to all users in the Rosary Builder.
</p>
</div>
<a href="<?= BASE_URL ?>/admin/builder.php" class="btn btn-primary">+ Open Builder</a>
</div>
<!-- Filter tabs -->
<div class="filter-tabs">
<a href="?filter=all" class="<?= $filter==='all' ? 'active':'' ?>">All (<?= count($prayers) ?>)</a>
<a href="?filter=standard" class="<?= $filter==='standard' ? 'active':'' ?>">Standard (<?= count(array_filter($prayers, fn($p)=>$p['is_global']&&$p['creator_role']==='superadmin')) ?>)</a>
<a href="?filter=global" class="<?= $filter==='global' ? 'active':'' ?>">Global Custom (<?= count(array_filter($prayers, fn($p)=>$p['is_global']&&$p['creator_role']!=='superadmin')) ?>)</a>
<a href="?filter=private" class="<?= $filter==='private' ? 'active':'' ?>">Private (<?= count(array_filter($prayers, fn($p)=>!$p['is_global'])) ?>)</a>
</div>
<div class="sessions-table-wrap">
<table class="prayer-table">
<thead>
<tr>
<th style="width:30%">Name / Preview</th>
<th>Visibility</th>
<th>Created by</th>
<th>Used in</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
$shown = array_filter($prayers, function($p) use ($filter) {
if ($filter === 'standard') return $p['is_global'] && $p['creator_role'] === 'superadmin';
if ($filter === 'global') return $p['is_global'] && $p['creator_role'] !== 'superadmin';
if ($filter === 'private') return !$p['is_global'];
return true;
});
foreach ($shown as $p):
$is_standard = $p['is_global'] && $p['creator_role'] === 'superadmin';
$badge_class = $is_standard ? 'badge-std' : ($p['is_global'] ? 'badge-glob' : 'badge-priv');
$badge_label = $is_standard ? 'Standard' : ($p['is_global'] ? 'Global' : 'Private');
$creator = $p['creator_display'] ?: $p['creator_username'] ?: '—';
?>
<tr>
<td>
<strong><?= htmlspecialchars($p['name']) ?></strong>
<details class="preview-detail">
<summary>Preview</summary>
<?php if ($p['leader_text']): ?>
<pre><strong>Leader:</strong> <?= htmlspecialchars(mb_substr($p['leader_text'], 0, 200)) ?><?= strlen($p['leader_text'])>200?'…':'' ?></pre>
<?php endif; ?>
<?php if ($p['all_text']): ?>
<pre><strong>All:</strong> <?= htmlspecialchars(mb_substr($p['all_text'], 0, 200)) ?><?= strlen($p['all_text'])>200?'…':'' ?></pre>
<?php endif; ?>
</details>
</td>
<td>
<span class="source-badge <?= $badge_class ?>" style="padding:3px 8px;border-radius:4px;font-size:11px;font-weight:600">
<?= $badge_label ?>
</span>
</td>
<td style="color:var(--muted)"><?= htmlspecialchars($creator) ?></td>
<td>
<?php if ($p['use_count'] > 0): ?>
<span class="use-badge"><?= $p['use_count'] ?> session<?= $p['use_count']!==1?'s':'' ?></span>
<?php else: ?>
<span style="color:var(--muted);font-size:12px">—</span>
<?php endif; ?>
</td>
<td class="actions">
<?php if (!$is_standard): ?>
<?php if ($p['is_global']): ?>
<form method="post" class="action-form">
<input type="hidden" name="action" value="toggle_global">
<input type="hidden" name="prayer_id" value="<?= $p['id'] ?>">
<input type="hidden" name="new_global" value="0">
<button class="btn btn-sm btn-ghost">Make Private</button>
</form>
<?php else: ?>
<form method="post" class="action-form">
<input type="hidden" name="action" value="toggle_global">
<input type="hidden" name="prayer_id" value="<?= $p['id'] ?>">
<input type="hidden" name="new_global" value="1">
<button class="btn btn-sm btn-secondary">Make Global</button>
</form>
<?php endif; ?>
<?php if ($p['use_count'] == 0): ?>
<form method="post" class="action-form"
onsubmit="return confirm('Delete &quot;<?= htmlspecialchars(addslashes($p['name'])) ?>&quot;?')">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="prayer_id" value="<?= $p['id'] ?>">
<button class="btn btn-sm btn-danger">Delete</button>
</form>
<?php endif; ?>
<?php else: ?>
<span style="color:var(--muted);font-size:12px">Standard prayers cannot be deleted</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php if (!$shown): ?>
<tr>
<td colspan="5" style="text-align:center;padding:40px;color:var(--muted)">No prayers in this category.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</main>
</div>
</body>
</html>