Files
pguzman 8c047f5b28 Beads are now a property of prayer steps, not separate steps
Each prayer in the library has an optional default_bead_type (small/large/
crucifix). Standard prayers get sensible defaults: Our Father=large,
Hail Mary=small, Sign of Cross=crucifix, Divine Mercy beads accordingly.

In the sequence, each step card shows a bead selector (—/○/●/✝) so users
can override the default per step. Adding a prayer pre-fills its default.

Bead library icon hints (○●✝) appear on prayer cards in the library.
Modal now includes a Bead selector for creating/editing prayers.

Remove the separate Bead Markers library section — beads live on prayers.
build_slides: prayer steps with bead_type now get a real bead_index so
the ring advances correctly during presentation.

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

380 lines
14 KiB
PHP
Raw Permalink 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.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 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,
];
$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']) ?? '';
}
$bead_type = $step['bead_type'] ?? null;
$slides[] = [
'id' => 'custom_' . $i,
'type' => 'prayer',
'section' => $step['name'],
'title' => $step['name'],
'leader' => $leader,
'all' => $all,
'bead' => $bead_type,
'bead_index' => $bead_type ? $bead_idx++ : 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;
}