3cc002a6da
- Bead Markers section in library panel with Small, Large, Crucifix cards - Bead steps render as amber-tinted cards in the sequence (no attribution) - migrate_v5.php: adds step_type/bead_type columns, makes prayer_id nullable - Bead steps produce slides with proper bead+bead_index so the ring advances during presentation — allows participants to follow along on physical beads - Prayer steps between beads still show bead_index=null (ring stays on last bead) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
379 lines
14 KiB
PHP
379 lines
14 KiB
PHP
<?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.step_type, bs.bead_type, bs.attribution,
|
||
cp.name, cp.leader_text, cp.all_text
|
||
FROM builder_steps bs
|
||
LEFT 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 1–5
|
||
* @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,
|
||
];
|
||
|
||
$bead_idx = 0; // increments for each bead separator step
|
||
foreach ($steps as $i => $step) {
|
||
$type = $step['step_type'] ?? 'prayer';
|
||
|
||
if ($type === 'bead') {
|
||
$slides[] = [
|
||
'id' => 'bead_' . $i,
|
||
'type' => 'prayer',
|
||
'section' => 'bead',
|
||
'title' => '',
|
||
'leader' => '',
|
||
'all' => '',
|
||
'bead' => $step['bead_type'] ?? 'small',
|
||
'bead_index' => $bead_idx++,
|
||
];
|
||
continue;
|
||
}
|
||
|
||
$attr = $step['attribution'] ?? 'leader_all';
|
||
$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') {
|
||
$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 2–4: small (3 Hail Marys / Creed for chaplet, stem)
|
||
// Bead 5: large (Our Father, Decade 1)
|
||
// Beads 6–15: small (Decade 1, 10 HMs / For the sake…)
|
||
// Bead 16: large (Our Father, Decade 2)
|
||
// Beads 17–26: small (Decade 2)
|
||
// Bead 27: large (Our Father, Decade 3)
|
||
// Beads 28–37: small (Decade 3)
|
||
// Bead 38: large (Our Father, Decade 4)
|
||
// Beads 39–48: small (Decade 4)
|
||
// Bead 49: large (Our Father, Decade 5)
|
||
// Beads 50–59: 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;
|
||
}
|