Files
Rosary/includes/build_slides.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

362 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* includes/build_slides.php
*
* build_slides(array $session): array
*
* Assembles the full slide sequence for a rosary session.
* Applies variable substitution for {name}, {pronoun}, {pronoun_obj}, {pronoun_poss}.
*/
require_once __DIR__ . '/../data/prayers.php';
/**
* Fetch ordered builder steps (with prayer text) for a custom session.
*/
function get_builder_steps(PDO $pdo, int $session_id): array {
$st = $pdo->prepare("
SELECT bs.attribution, cp.name, cp.leader_text, cp.all_text
FROM builder_steps bs
JOIN custom_prayers cp ON cp.id = bs.prayer_id
WHERE bs.session_id = ?
ORDER BY bs.step_order ASC
");
$st->execute([$session_id]);
return $st->fetchAll();
}
/**
* Returns the slide array for one decade of the Chaplet of Divine Mercy.
*
* Large bead : "Eternal Father…"
* Small beads: "For the sake of His sorrowful Passion…" × 10
* No mystery announcement; no Glory Be between decades.
*
* @param int $decade_num 15
* @param int $of_bead_index bead_index for the large (Our Father) bead
* @param int $hm_bead_start bead_index for the first small bead of this decade
*/
function build_chaplet_decade_slides(int $decade_num, int $of_bead_index, int $hm_bead_start): array {
$slides = [];
$slides[] = [
'id' => 'dm_eternal_father_d' . $decade_num,
'type' => 'prayer',
'section' => 'dm_chaplet_decade_' . $decade_num,
'title' => 'Eternal Father',
'leader' => "Eternal Father, I offer You the Body and Blood,\nSoul and Divinity of Your dearly beloved Son,\nOur Lord Jesus Christ,",
'all' => "in atonement for our sins and those of the whole world.",
'bead' => 'large',
'bead_index' => $of_bead_index,
];
for ($i = 0; $i < 10; $i++) {
$slides[] = [
'id' => 'dm_for_sake_d' . $decade_num . '_' . ($i + 1),
'type' => 'prayer',
'section' => 'dm_chaplet_decade_' . $decade_num,
'title' => 'For the Sake of His Sorrowful Passion',
'leader' => 'For the sake of His sorrowful Passion,',
'all' => 'have mercy on us and on the whole world.',
'bead' => 'small',
'bead_index' => $hm_bead_start + $i,
];
}
return $slides;
}
/**
* Build the complete slide array for a session.
*
* @param array $session Row from the sessions table (keys match column names)
* @return array Flat array of slide arrays
*/
function build_slides(array $session): array {
global $opening, $mysteries, $hail_holy_queen, $rosary_closing_prayer,
$litany_passion, $novena_prayers, $litany_departed, $closing,
$divine_mercy_opening, $divine_mercy_novena_prayers,
$divine_mercy_chaplet_opening, $divine_mercy_chaplet_close;
$slides = [];
// -----------------------------------------------------------------------
// Custom builder session — build directly from builder_steps
// -----------------------------------------------------------------------
if ($session['occasion'] === 'custom') {
$pdo = get_pdo();
$steps = get_builder_steps($pdo, (int)$session['id']);
// Cover slide
$cover_all = '';
if (!empty($session['subject_name'])) {
$cover_all = "For {$session['subject_name']}";
}
$slides[] = [
'id' => 'cover',
'type' => 'cover',
'section' => 'cover',
'title' => $session['name'],
'leader' => '',
'all' => $cover_all,
'bead' => null,
'bead_index' => null,
'photo_path' => $session['photo_path'] ?? null,
];
foreach ($steps as $i => $step) {
$attr = $step['attribution'];
$leader = '';
$all = '';
if ($attr === 'leader_all' || $attr === 'leader_only') {
$leader = $step['leader_text'] ?? '';
}
if ($attr === 'leader_all' || $attr === 'all_only') {
$all = $step['all_text'] ?? '';
}
if ($attr === 'none') {
// Show prayer text without attribution labels — put in leader field
$leader = ($step['leader_text'] ?: $step['all_text']) ?? '';
}
$slides[] = [
'id' => 'custom_' . $i,
'type' => 'prayer',
'section' => $step['name'],
'title' => $step['name'],
'leader' => $leader,
'all' => $all,
'bead' => null,
'bead_index' => null,
];
}
// Closing slide
$slides[] = [
'id' => 'closing_custom',
'type' => 'closing',
'section' => 'closing',
'title' => '',
'leader' => '',
'all' => "May the souls of the faithful departed,\nthrough the mercy of God, rest in peace.",
'bead' => null,
'bead_index' => null,
];
// Apply variable substitution and return early
$name = $session['subject_name'] ?? '';
$pronoun = $session['subject_pronoun'] ?? 'he';
$dates = $session['subject_dates'] ?? '';
$pronoun_obj = ($pronoun === 'she') ? 'her' : 'him';
$pronoun_poss = ($pronoun === 'she') ? 'her' : 'his';
$find = ['{name}', '{pronoun}', '{pronoun_obj}', '{pronoun_poss}', '{subject_dates}'];
$replace = [$name, $pronoun, $pronoun_obj, $pronoun_poss, $dates];
foreach ($slides as &$slide) {
$slide['title'] = str_replace($find, $replace, $slide['title'] ?? '');
$slide['leader'] = str_replace($find, $replace, $slide['leader'] ?? '');
$slide['all'] = str_replace($find, $replace, $slide['all'] ?? '');
}
unset($slide);
return $slides;
}
// -----------------------------------------------------------------------
// 1. Cover slide
// -----------------------------------------------------------------------
$cover_title = '';
$cover_all = '';
switch ($session['occasion']) {
case 'novena_deceased':
$day = (int)($session['novena_day'] ?? 1);
$cover_title = 'Nine-Day Novena Rosary';
$cover_all = "In Loving Memory of\n{name}\n{subject_dates}\n\nDay {$day} of 9";
break;
case 'memorial':
$cover_title = 'Memorial Rosary';
$cover_all = "In Loving Memory of\n{name}\n{subject_dates}";
break;
case 'divine_mercy_novena':
$day = (int)($session['novena_day'] ?? 1);
$cover_title = 'Divine Mercy Novena';
$cover_all = "Chaplet of Divine Mercy\n\nDay {$day} of 9";
break;
case 'general_rosary':
default:
$cover_title = 'The Holy Rosary';
$cover_all = ucfirst($session['mystery_set']) . ' Mysteries';
break;
}
$slides[] = [
'id' => 'cover',
'type' => 'cover',
'section' => 'cover',
'title' => $cover_title,
'leader' => '',
'all' => $cover_all,
'bead' => null,
'bead_index' => null,
'photo_path' => $session['photo_path'] ?? null,
];
// -----------------------------------------------------------------------
// 2. Opening prayers
// -----------------------------------------------------------------------
if ($session['occasion'] === 'divine_mercy_novena') {
// Pre-chaplet devotion: opening prayer + O Blood and Water × 3
foreach ($divine_mercy_opening as $slide) {
$slides[] = $slide;
}
// Day-specific intention and prayer
$day = (int)($session['novena_day'] ?? 1);
if (isset($divine_mercy_novena_prayers[$day])) {
foreach ($divine_mercy_novena_prayers[$day] as $slide) {
$slides[] = $slide;
}
}
// Chaplet opening on stem beads (Sign of Cross, Our Father, Hail Mary, Creed)
foreach ($divine_mercy_chaplet_opening as $slide) {
$slides[] = $slide;
}
} else {
foreach ($opening as $slide) {
$slides[] = $slide;
}
}
// -----------------------------------------------------------------------
// 3. Five decades
//
// Bead layout:
// Bead 0: Crucifix (Sign of Cross / Creed)
// Bead 1: large (Our Father, stem)
// Beads 24: small (3 Hail Marys / Creed for chaplet, stem)
// Bead 5: large (Our Father, Decade 1)
// Beads 615: small (Decade 1, 10 HMs / For the sake…)
// Bead 16: large (Our Father, Decade 2)
// Beads 1726: small (Decade 2)
// Bead 27: large (Our Father, Decade 3)
// Beads 2837: small (Decade 3)
// Bead 38: large (Our Father, Decade 4)
// Beads 3948: small (Decade 4)
// Bead 49: large (Our Father, Decade 5)
// Beads 5059: small (Decade 5)
// -----------------------------------------------------------------------
$decade_bead_map = [
// decade => [our_father_index, first_hm_index]
1 => [5, 6],
2 => [16, 17],
3 => [27, 28],
4 => [38, 39],
5 => [49, 50],
];
if ($session['occasion'] === 'divine_mercy_novena') {
for ($d = 1; $d <= 5; $d++) {
[$of_idx, $hm_idx] = $decade_bead_map[$d];
foreach (build_chaplet_decade_slides($d, $of_idx, $hm_idx) as $slide) {
$slides[] = $slide;
}
}
} else {
$mystery_set = $mysteries[$session['mystery_set']] ?? $mysteries['sorrowful'];
for ($d = 1; $d <= 5; $d++) {
[$of_idx, $hm_idx] = $decade_bead_map[$d];
$mystery_slide = $mystery_set[$d - 1];
$decade_slides = build_decade_slides($d, $mystery_slide, $of_idx, $hm_idx);
foreach ($decade_slides as $slide) {
$slides[] = $slide;
}
}
}
// -----------------------------------------------------------------------
// 4. Post-decade prayers by occasion
// -----------------------------------------------------------------------
switch ($session['occasion']) {
case 'novena_deceased':
// Hail Holy Queen
foreach ($hail_holy_queen as $slide) {
$slides[] = $slide;
}
// Day-specific novena prayer
$day = (int)($session['novena_day'] ?? 1);
if (isset($novena_prayers[$day])) {
$slides[] = $novena_prayers[$day];
}
// Litany of the Passion
foreach ($litany_passion as $slide) {
$slides[] = $slide;
}
// Litany for the Departed
foreach ($litany_departed as $slide) {
$slides[] = $slide;
}
break;
case 'divine_mercy_novena':
// Holy God, Holy Mighty One, Holy Immortal One × 3
foreach ($divine_mercy_chaplet_close as $slide) {
$slides[] = $slide;
}
break;
case 'memorial':
case 'general_rosary':
default:
foreach ($hail_holy_queen as $slide) {
$slides[] = $slide;
}
$slides[] = $rosary_closing_prayer;
break;
}
// -----------------------------------------------------------------------
// 5. Closing slide — inject photo for personal occasions
// -----------------------------------------------------------------------
$occasion_key = $session['occasion'];
if (!isset($closing[$occasion_key])) {
$occasion_key = 'general_rosary';
}
$closing_slide = $closing[$occasion_key];
if (in_array($occasion_key, ['novena_deceased', 'memorial'])) {
$closing_slide['photo_path'] = $session['photo_path'] ?? null;
}
$slides[] = $closing_slide;
// -----------------------------------------------------------------------
// 6. Variable substitution
// -----------------------------------------------------------------------
$name = $session['subject_name'] ?? '';
$pronoun = $session['subject_pronoun'] ?? 'he'; // 'he' or 'she'
$dates = $session['subject_dates'] ?? '';
$pronoun_obj = ($pronoun === 'she') ? 'her' : 'him';
$pronoun_poss = ($pronoun === 'she') ? 'her' : 'his';
$pronoun_cap = ucfirst($pronoun);
$find = ['{name}', '{pronoun}', '{pronoun_obj}', '{pronoun_poss}', '{subject_dates}'];
$replace = [$name, $pronoun, $pronoun_obj, $pronoun_poss, $dates];
foreach ($slides as &$slide) {
$slide['title'] = str_replace($find, $replace, $slide['title'] ?? '');
$slide['leader'] = str_replace($find, $replace, $slide['leader'] ?? '');
$slide['all'] = str_replace($find, $replace, $slide['all'] ?? '');
if (isset($slide['subtitle'])) {
$slide['subtitle'] = str_replace($find, $replace, $slide['subtitle']);
}
}
unset($slide);
return $slides;
}