. */ namespace Fisharebest\Webtrees; /** * Defined in session.php * * @global Tree $WT_TREE */ global $WT_TREE; use Fisharebest\Webtrees\Controller\PageController; use Fisharebest\Webtrees\Date\FrenchDate; use Fisharebest\Webtrees\Date\GregorianDate; use Fisharebest\Webtrees\Date\HijriDate; use Fisharebest\Webtrees\Date\JalaliDate; use Fisharebest\Webtrees\Date\JewishDate; use Fisharebest\Webtrees\Date\JulianDate; use Fisharebest\Webtrees\Functions\FunctionsDb; use Fisharebest\Webtrees\Functions\FunctionsPrint; define('WT_SCRIPT_NAME', 'calendar.php'); require './includes/session.php'; $CALENDAR_FORMAT = $WT_TREE->getPreference('CALENDAR_FORMAT'); $controller = new PageController; $controller->setPageTitle(I18N::translate('Anniversary calendar')); $controller->pageHeader(); $cal = Filter::get('cal', '@#D[A-Z ]+@'); $day = Filter::get('day', '\d\d?'); $month = Filter::get('month', '[A-Z]{3,5}'); $year = Filter::get('year', '\d{1,4}(?: B\.C\.)?|\d\d\d\d\/\d\d|\d+(-\d+|[?]+)?'); $view = Filter::get('view', 'day|month|year', 'day'); $filterev = Filter::get('filterev', 'all|bdm|' . WT_REGEX_TAG, 'bdm'); $filterof = Filter::get('filterof', 'all|living|recent', 'all'); $filtersx = Filter::get('filtersx', '[MF]'); if ($cal . $day . $month . $year === '') { // No date specified? Use the most likely calendar $cal = I18N::defaultCalendar()->gedcomCalendarEscape(); } // Create a CalendarDate from the parameters // We cannot display new-style/old-style years, so convert to new style if (preg_match('/^(\d\d)\d\d\/(\d\d)$/', $year, $match)) { $year = $match[1] . $match[2]; } // advanced-year "year range" if (preg_match('/^(\d+)-(\d+)$/', $year, $match)) { if (strlen($match[1]) > strlen($match[2])) { $match[2] = substr($match[1], 0, strlen($match[1]) - strlen($match[2])) . $match[2]; } $ged_date = new Date("FROM {$cal} {$match[1]} TO {$cal} {$match[2]}"); $view = 'year'; } else { // advanced-year "decade/century wildcard" if (preg_match('/^(\d+)(\?+)$/', $year, $match)) { $y1 = $match[1] . str_replace('?', '0', $match[2]); $y2 = $match[1] . str_replace('?', '9', $match[2]); $ged_date = new Date("FROM {$cal} {$y1} TO {$cal} {$y2}"); $view = 'year'; } else { if ($year < 0) { $year = (-$year) . ' B.C.'; } // need BC to parse date $ged_date = new Date("{$cal} {$day} {$month} {$year}"); $year = $ged_date->minimumDate()->y; // need negative year for year entry field. } } $cal_date = $ged_date->minimumDate(); // Fill in any missing bits with todays date $today = $cal_date->today(); if ($cal_date->d === 0) { $cal_date->d = $today->d; } if ($cal_date->m === 0) { $cal_date->m = $today->m; } if ($cal_date->y === 0) { $cal_date->y = $today->y; } $cal_date->setJdFromYmd(); if ($year === 0) { $year = $cal_date->y; } // Extract values from date $days_in_month = $cal_date->daysInMonth(); $days_in_week = $cal_date->daysInWeek(); $cal_month = $cal_date->format('%O'); $today_month = $today->format('%O'); // Invalid dates? Go to monthly view, where they'll be found. if ($cal_date->d > $days_in_month && $view === 'day') { $view = 'month'; } echo '
'; // Calendar form echo '
'; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; // Day selector echo ''; // Month selector echo ''; echo ''; // Year selector echo ''; echo ' '; // Filtering options echo ''; echo ''; echo '

'; // All further uses of $cal are to generate URLs $cal = rawurlencode($cal); switch ($view) { case 'day': echo I18N::translate('On this day…') . '
' . $ged_date->display(); break; case 'month': echo I18N::translate('In this month…') . '
' . $ged_date->display(false, '%F %Y'); break; case 'year': echo I18N::translate('In this year…') . '
' . $ged_date->display(false, '%Y'); break; } echo '

'; echo I18N::translate('Day'), ''; for ($d = 1; $d <= $days_in_month; $d++) { // Format the day number using the calendar $tmp = new Date($cal_date->format("%@ {$d} %O %E")); $d_fmt = $tmp->minimumDate()->format('%j'); if ($d === $cal_date->d) { echo '', $d_fmt, ''; } else { echo '', $d_fmt, ''; } echo ' | '; } $tmp = new Date($today->format('%@ %A %O %E')); // Need a Date object to get localisation echo '', $tmp->display(), ''; echo '
'; echo I18N::translate('Month'), ''; for ($n = 1, $months_in_year = $cal_date->monthsInYear(); $n <= $months_in_year; ++$n) { $month_name = $cal_date->monthNameNominativeCase($n, $cal_date->isLeapYear()); $m = array_search($n, $cal_date::$MONTH_ABBREV); if ($n === 6 && $cal_date instanceof JewishDate && !$cal_date->isLeapYear()) { // No month 6 in Jewish non-leap years. continue; } if ($n === 7 && $cal_date instanceof JewishDate && !$cal_date->isLeapYear()) { // Month 7 is ADR in Jewish non-leap years. $m = 'ADR'; } if ($n === $cal_date->m) { $month_name = '' . $month_name . ''; } echo '', $month_name, ''; echo ' | '; } echo '' . $today->format('%F %Y') . '
'; echo I18N::translate('Year'), ''; echo '-1'; echo ' '; echo '+1'; echo ' | ' . $today->format('%Y') . ''; echo FunctionsPrint::helpLink('annivers_year_select'); echo ''; echo I18N::translate('Show'), ''; echo ''; echo '   '; if ($filtersx === '') { echo ''; echo ' | '; } else { echo ''; echo ''; echo ' | '; } if ($filtersx === 'M') { echo ' | '; } else { echo ' | '; } if ($filtersx === 'F') { echo ''; } else { echo ''; } echo '   '; echo ''; echo ''; echo '
'; echo ''; // Day/Month/Year and calendar selector echo ''; echo '
'; if ($view === 'day') { echo '', I18N::translate('View day'), ''; } else { echo '', I18N::translate('View day'), ''; } if ($view === 'month') { echo ' | ', I18N::translate('View month'), ''; } else { echo ' | ', I18N::translate('View month'), ''; } if ($view === 'year') { echo ' | ', I18N::translate('View year'), ''; } else { echo ' | ', I18N::translate('View year'), ''; } echo ''; $n = 0; foreach (Date::calendarNames() as $newcal => $cal_name) { $tmp = $cal_date->convertToCalendar($newcal); if ($tmp->inValidRange()) { if ($n++) { echo ' | '; } if (get_class($tmp) === get_class($cal_date)) { echo '', $cal_name, ''; } else { $newcalesc = urlencode($tmp->format('%@')); $tmpmonth = $tmp->format('%O'); echo '', $cal_name, ''; } } } echo '
'; // Convert event filter option to a list of gedcom event codes if ($filterev === 'all') { $events = ''; } else { if ($filterev === 'bdm') { $events = 'BIRT MARR DEAT'; } else { $events = $filterev; } } // Fetch data for day/month/year views switch ($view) { case 'day': $found_facts = apply_filter(FunctionsDb::getAnniversaryEvents($cal_date->minJD, $events, $WT_TREE), $filterof, $filtersx); break; case 'month': $cal_date->d = 0; $cal_date->setJdFromYmd(); // Make a separate list for each day. Unspecified/invalid days go in day 0. $found_facts = array(); for ($d = 0; $d <= $days_in_month; ++$d) { $found_facts[$d] = array(); } // Fetch events for each day for ($jd = $cal_date->minJD; $jd <= $cal_date->maxJD; ++$jd) { foreach (apply_filter(FunctionsDb::getAnniversaryEvents($jd, $events, $WT_TREE), $filterof, $filtersx) as $fact) { $tmp = $fact->getDate()->minimumDate(); if ($tmp->d >= 1 && $tmp->d <= $tmp->daysInMonth()) { // If the day is valid (for its own calendar), display it in the // anniversary day (for the display calendar). $found_facts[$jd - $cal_date->minJD + 1][] = $fact; } else { // Otherwise, display it in the "Day not set" box. $found_facts[0][] = $fact; } } } break; case 'year': $cal_date->m = 0; $cal_date->setJdFromYmd(); $found_facts = apply_filter(FunctionsDb::getCalendarEvents($ged_date->minimumJulianDay(), $ged_date->maximumJulianDay(), $events, $WT_TREE), $filterof, $filtersx); // Eliminate duplicates (e.g. BET JUL 1900 AND SEP 1900 will appear twice in 1900) $found_facts = array_unique($found_facts); break; } // Group the facts by family/individual switch ($view) { case 'year': case 'day': $indis = array(); $fams = array(); foreach ($found_facts as $fact) { $record = $fact->getParent(); $xref = $record->getXref(); if ($record instanceof Individual) { if (empty($indis[$xref])) { $indis[$xref] = calendar_fact_text($fact, true); } else { $indis[$xref] .= '
' . calendar_fact_text($fact, true); } } elseif ($record instanceof Family) { if (empty($indis[$xref])) { $fams[$xref] = calendar_fact_text($fact, true); } else { $fams[$xref] .= '
' . calendar_fact_text($fact, true); } } } break; case 'month': $cal_facts = array(); foreach ($found_facts as $d => $facts) { $cal_facts[$d] = array(); foreach ($facts as $fact) { $xref = $fact->getParent()->getXref(); if (empty($cal_facts[$d][$xref])) { $cal_facts[$d][$xref] = calendar_fact_text($fact, false); } else { $cal_facts[$d][$xref] .= '
' . calendar_fact_text($fact, false); } } } break; } switch ($view) { case 'year': case 'day': echo ''; // Table headings echo ''; echo ''; echo ''; // Table rows $males = 0; $females = 0; $numfams = 0; echo ''; echo ''; echo ''; // Table footers echo ''; echo ''; echo '
', I18N::translate('Individuals'), '', I18N::translate('Families'), '
'; // Avoid an empty unordered list $content = calendar_list_text($indis, '
  • ', '
  • ', true); if ($content) { echo '
      ', $content, '
    '; } echo '
    '; // Avoid an empty unordered list $content = calendar_list_text($fams, '
  • ', '
  • ', true); if ($content) { echo '
      ', $content, '
    '; } echo '
    ', I18N::translate('Total individuals: %s', count($indis)); echo '
    '; echo ' ', $males, '    '; echo ' ', $females, '    '; if (count($indis) !== $males + $females) { echo ' ', count($indis) - $males - $females; } echo '
    ', I18N::translate('Total families: %s', count($fams)), '
    '; break; case 'month': // We use JD%7 = 0/Mon...6/Sun. Standard definitions use 0/Sun...6/Sat. $week_start = (I18N::firstDay() + 6) % 7; $weekend_start = (I18N::weekendStart() + 6) % 7; $weekend_end = (I18N::weekendEnd() + 6) % 7; // The french calendar has a 10-day week, which starts on primidi if ($days_in_week === 10) { $week_start = 0; $weekend_start = -1; $weekend_end = -1; } echo ''; for ($week_day = 0; $week_day < $days_in_week; ++$week_day) { $day_name = $cal_date->dayNames(($week_day + $week_start) % $days_in_week); if ($week_day == $weekend_start || $week_day == $weekend_end) { echo ''; } else { echo ''; } } echo ''; // Print days 1-n of the month... // ...but extend to cover "empty" days before/after the month to make whole weeks. // e.g. instead of 1 -> 30 (=30 days), we might have -1 -> 33 (=35 days) $start_d = 1 - ($cal_date->minJD - $week_start) % $days_in_week; $end_d = $days_in_month + ($days_in_week - ($cal_date->maxJD - $week_start + 1) % $days_in_week) % $days_in_week; // Make sure that there is an empty box for any leap/missing days if ($start_d === 1 && $end_d === $days_in_month && count($found_facts[0]) > 0) { $end_d += $days_in_week; } for ($d = $start_d; $d <= $end_d; ++$d) { if (($d + $cal_date->minJD - $week_start) % $days_in_week === 1) { echo ''; } echo ''; if (($d + $cal_date->minJD - $week_start) % $days_in_week === 0) { echo ''; } } echo '
    ', $day_name, '', $day_name, '
    '; if ($d < 1 || $d > $days_in_month) { if (count($cal_facts[0]) > 0) { echo '', I18N::translate('Day not set'), '
    '; echo '
    '; echo calendar_list_text($cal_facts[0], '', '', false); echo '
    '; $cal_facts[0] = array(); } else { echo ' '; } } else { // Format the day number using the calendar $tmp = new Date($cal_date->format("%@ {$d} %O %E")); $d_fmt = $tmp->minimumDate()->format('%j'); if ($d === $today->d && $cal_date->m === $today->m) { echo '', $d_fmt, ''; } else { echo '', $d_fmt, ''; } // Show a converted date foreach (explode('_and_', $CALENDAR_FORMAT) as $convcal) { switch ($convcal) { case 'french': $alt_date = new FrenchDate($cal_date->minJD + $d - 1); break; case 'gregorian': $alt_date = new GregorianDate($cal_date->minJD + $d - 1); break; case 'jewish': $alt_date = new JewishDate($cal_date->minJD + $d - 1); break; case 'julian': $alt_date = new JulianDate($cal_date->minJD + $d - 1); break; case 'hijri': $alt_date = new HijriDate($cal_date->minJD + $d - 1); break; case 'jalali': $alt_date = new JalaliDate($cal_date->minJD + $d - 1); break; default: break 2; } if (get_class($alt_date) !== get_class($cal_date)) { echo '' . $alt_date->format("%j %M") . ''; // Just show the first conversion break; } } echo '
    '; echo calendar_list_text($cal_facts[$d], '', '', false); echo '
    '; } echo '
    '; break; } echo '
    '; //close "calendar-page" /** * Filter a list of anniversaries * * @param Fact[] $facts * @param string $filterof * @param string $filtersx * * @return array */ function apply_filter($facts, $filterof, $filtersx) { $filtered = array(); $hundred_years = WT_CLIENT_JD - 36525; foreach ($facts as $fact) { $record = $fact->getParent(); if ($filtersx) { // Filter on sex if ($record instanceof Individual && $filtersx !== $record->getSex()) { continue; } // Can't display families if the sex filter is on. if ($record instanceof Family) { continue; } } // Filter living individuals if ($filterof === 'living') { if ($record instanceof Individual && $record->isDead()) { continue; } if ($record instanceof Family) { $husb = $record->getHusband(); $wife = $record->getWife(); if ($husb && $husb->isDead() || $wife && $wife->isDead()) { continue; } } } // Filter on recent events if ($filterof === 'recent' && $fact->getDate()->MaxJD() < $hundred_years) { continue; } $filtered[] = $fact; } return $filtered; } /** * Format an anniversary display. * * @param Fact $fact * @param bool $show_places * * @return string */ function calendar_fact_text(Fact $fact, $show_places) { $text = $fact->getLabel() . ' — ' . $fact->getDate()->display(true, null, false); if ($fact->anniv) { $text .= ' (' . I18N::translate('%s year anniversary', $fact->anniv) . ')'; } if ($show_places && $fact->getAttribute('PLAC')) { $text .= ' — ' . $fact->getAttribute('PLAC'); } return $text; } /** * Format a list of facts for display * * @param Fact[] $list * @param string $tag1 * @param string $tag2 * @param bool $show_sex_symbols * * @return string */ function calendar_list_text($list, $tag1, $tag2, $show_sex_symbols) { global $males, $females, $WT_TREE; $html = ''; foreach ($list as $id => $facts) { $tmp = GedcomRecord::GetInstance($id, $WT_TREE); $html .= $tag1 . '' . $tmp->getFullName() . ' '; if ($show_sex_symbols && $tmp instanceof Individual) { switch ($tmp->getSex()) { case 'M': $html .= ''; ++$males; break; case 'F': $html .= ''; ++$females; break; default: $html .= ''; break; } } $html .= '
    ' . $facts . '
    ' . $tag2; } return $html; }