1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2019 webtrees development team
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16namespace Fisharebest\Webtrees;
17
18/**
19 * Defined in session.php
20 *
21 * @global Tree $WT_TREE
22 */
23global $WT_TREE;
24
25use Fisharebest\Webtrees\Controller\PageController;
26use Fisharebest\Webtrees\Date\FrenchDate;
27use Fisharebest\Webtrees\Date\GregorianDate;
28use Fisharebest\Webtrees\Date\HijriDate;
29use Fisharebest\Webtrees\Date\JalaliDate;
30use Fisharebest\Webtrees\Date\JewishDate;
31use Fisharebest\Webtrees\Date\JulianDate;
32use Fisharebest\Webtrees\Functions\FunctionsDb;
33use Fisharebest\Webtrees\Functions\FunctionsPrint;
34
35define('WT_SCRIPT_NAME', 'calendar.php');
36require './includes/session.php';
37
38$CALENDAR_FORMAT = $WT_TREE->getPreference('CALENDAR_FORMAT');
39
40$cal      = Filter::get('cal', '@#D[A-Z ]+@');
41$day      = Filter::get('day', '\d\d?');
42$month    = Filter::get('month', '[A-Z]{3,5}');
43$year     = Filter::get('year', '\d{1,4}(?: B\.C\.)?|\d\d\d\d\/\d\d|\d+(-\d+|[?]+)?');
44$view     = Filter::get('view', 'day|month|year', 'day');
45$filterev = Filter::get('filterev', '[_A-Z-]*', 'BIRT-MARR-DEAT');
46$filterof = Filter::get('filterof', 'all|living|recent', 'all');
47$filtersx = Filter::get('filtersx', '[MF]', '');
48
49if ($cal . $day . $month . $year === '') {
50    // No date specified? Use the most likely calendar
51    $cal = I18N::defaultCalendar()->gedcomCalendarEscape();
52}
53
54// Create a CalendarDate from the parameters
55
56// We cannot display new-style/old-style years, so convert to new style
57if (preg_match('/^(\d\d)\d\d\/(\d\d)$/', $year, $match)) {
58    $year = $match[1] . $match[2];
59}
60
61// advanced-year "year range"
62if (preg_match('/^(\d+)-(\d+)$/', $year, $match)) {
63    if (strlen($match[1]) > strlen($match[2])) {
64        $match[2] = substr($match[1], 0, strlen($match[1]) - strlen($match[2])) . $match[2];
65    }
66    $ged_date = new Date("FROM {$cal} {$match[1]} TO {$cal} {$match[2]}");
67    $view     = 'year';
68} else {
69    // advanced-year "decade/century wildcard"
70    if (preg_match('/^(\d+)(\?+)$/', $year, $match)) {
71        $y1       = $match[1] . str_replace('?', '0', $match[2]);
72        $y2       = $match[1] . str_replace('?', '9', $match[2]);
73        $ged_date = new Date("FROM {$cal} {$y1} TO {$cal} {$y2}");
74        $view     = 'year';
75    } else {
76        if ($year < 0) {
77            $year = (-$year) . ' B.C.';
78        } // need BC to parse date
79        $ged_date = new Date("{$cal} {$day} {$month} {$year}");
80        $year     = $ged_date->minimumDate()->y; // need negative year for year entry field.
81    }
82}
83$cal_date = $ged_date->minimumDate();
84
85// Fill in any missing bits with todays date
86$today = $cal_date->today();
87if ($cal_date->d === 0) {
88    $cal_date->d = $today->d;
89}
90if ($cal_date->m === 0) {
91    $cal_date->m = $today->m;
92}
93if ($cal_date->y === 0) {
94    $cal_date->y = $today->y;
95}
96
97$cal_date->setJdFromYmd();
98
99if ($year === 0) {
100    $year = $cal_date->y;
101}
102
103// Extract values from date
104$days_in_month = $cal_date->daysInMonth();
105$days_in_week  = $cal_date->daysInWeek();
106$cal_month     = $cal_date->format('%O');
107$today_month   = $today->format('%O');
108
109// Invalid dates? Go to monthly view, where they'll be found.
110if ($cal_date->d > $days_in_month && $view === 'day') {
111    $view = 'month';
112}
113
114// All further uses of $cal are to generate URLs
115$cal = rawurlencode($cal);
116
117$controller = new PageController;
118$controller->setPageTitle(I18N::translate('Anniversary calendar'));
119
120switch ($view) {
121    case 'day':
122        $controller->setPageTitle(I18N::translate('On this day…') . ' ' . $ged_date->display(false));
123        break;
124    case 'month':
125        $controller->setPageTitle(I18N::translate('In this month…') . ' ' . $ged_date->display(false, '%F %Y'));
126        break;
127    case 'year':
128        $controller->setPageTitle(I18N::translate('In this year…') . ' ' . $ged_date->display(false, '%Y'));
129        break;
130}
131
132$controller->pageHeader();
133
134?>
135<div id="calendar-page">
136    <h2 class="center"><?php echo $controller->getPageTitle() ?></h2>
137
138    <form name="dateform">
139        <input type="hidden" name="cal" value="<?php echo $cal ?>">
140        <input type="hidden" name="day" value="<?php echo $cal_date->d ?>">
141        <input type="hidden" name="month" value="<?php echo $cal_month ?>">
142        <input type="hidden" name="year" value="<?php echo $cal_date->y ?>">
143        <input type="hidden" name="view" value="<?php echo $view ?>">
144        <input type="hidden" name="filterev" value="<?php echo $filterev ?>">
145        <input type="hidden" name="filtersx" value="<?php echo $filtersx ?>">
146        <input type="hidden" name="filterof" value="<?php echo $filterof ?>">
147
148        <table class="facts_table width100">
149            <tr>
150                <td class="descriptionbox vmiddle">
151                    <?php echo I18N::translate('Day') ?>
152                </td>
153                <td colspan="3" class="optionbox">
154                    <?php
155                    for ($d = 1; $d <= $days_in_month; $d++) {
156                        // Format the day number using the calendar
157                        $tmp   = new Date($cal_date->format("%@ {$d} %O %E"));
158                        $d_fmt = $tmp->minimumDate()->format('%j');
159                        if ($d === $cal_date->d) {
160                            echo '<span class="error">', $d_fmt, '</span>';
161                        } else {
162                            echo '<a href="?cal=', $cal, '&amp;day=', $d, '&amp;month=', $cal_month, '&amp;year=', $cal_date->y, '&amp;filterev=', $filterev, '&amp;filterof=', $filterof, '&amp;filtersx=', $filtersx, '&amp;view=', $view, '">', $d_fmt, '</a>';
163                        }
164                        echo ' | ';
165                    }
166                    ?>
167                    <a href="?cal=<?php echo $cal ?>&amp;day=<?php echo $today->d ?>&amp;month=<?php echo $today_month ?>&amp;year=<?php echo $today->y ?>&amp;filterev=<?php echo $filterev ?>&amp;filterof=<?php echo $filterof ?>&amp;filtersx=<?php echo $filtersx ?>&amp;view=<?php echo $view ?>">
168                        <b><?php $tmp = new Date($today->format('%@ %A %O %E')); echo $tmp->display() ?></b>
169                    </a>
170                </td>
171            </tr>
172            <tr>
173                <td class="descriptionbox">
174                    <?php echo I18N::translate('Month') ?>
175                </td>
176                <td class="optionbox" colspan="3">
177                    <?php
178                    for ($n = 1, $months_in_year = $cal_date->monthsInYear(); $n <= $months_in_year; ++$n) {
179                        $month_name = $cal_date->monthNameNominativeCase($n, $cal_date->isLeapYear());
180                        $m          = array_search($n, $cal_date::$MONTH_ABBREV);
181                        if ($n === 6 && $cal_date instanceof JewishDate && !$cal_date->isLeapYear()) {
182                            // No month 6 in Jewish non-leap years.
183                            continue;
184                        }
185                        if ($n === 7 && $cal_date instanceof JewishDate && !$cal_date->isLeapYear()) {
186                            // Month 7 is ADR in Jewish non-leap years.
187                            $m = 'ADR';
188                        }
189                        if ($n === $cal_date->m) {
190                            $month_name = '<span class="error">' . $month_name . '</span>';
191                        }
192                        echo '<a href="?cal=', $cal, '&amp;day=', $cal_date->d, '&amp;month=', $m, '&amp;year=', $cal_date->y, '&amp;filterev=', $filterev, '&amp;filterof=', $filterof, '&amp;filtersx=', $filtersx, '&amp;view=', $view, '">', $month_name, '</a>';
193                        echo ' | ';
194                    }
195                    ?>
196                    <a href="?cal=<?php echo $cal ?>&amp;day=<?php echo min($cal_date->d, $today->daysInMonth()) ?>&amp;month=<?php echo $today_month ?>&amp;year=<?php echo $today->y ?>&amp;filterev=<?php echo $filterev ?>&amp;filterof=<?php echo $filterof ?>&amp;filtersx=<?php echo $filtersx ?>&amp;view=<?php echo $view ?>">
197                        <b><?php echo $today->format('%F %Y') ?></b>
198                    </a>
199                </td>
200            </tr>
201            <tr>
202                <td class="descriptionbox vmiddle">
203                    <label for="year"><?php echo I18N::translate('Year') ?></label>
204                </td>
205                <td class="optionbox vmiddle">
206                    <a href="?cal=<?php echo $cal ?>&amp;day=<?php echo $cal_date->d ?>&amp;month=<?php echo $cal_month ?>&amp;year=<?php echo $cal_date->y === 1 ? -1 : $cal_date->y - 1 ?>&amp;filterev=<?php echo $filterev ?>&amp;filterof=<?php echo $filterof ?>&amp;filtersx=<?php echo $filtersx ?>&amp;view=<?php echo $view ?>">
207                        -1
208                    </a>
209                    <input type="text" id="year" name="year" value="<?php echo $year ?>" size="4">
210                    <a href="?cal=<?php echo $cal ?>&amp;day=<?php echo $cal_date->d ?>&amp;month=<?php echo $cal_month ?>&amp;year=<?php echo $cal_date->y === -1 ? 1 : $cal_date->y + 1 ?>&amp;filterev=<?php echo $filterev ?>&amp;filterof=<?php echo $filterof ?>&amp;filtersx=<?php echo $filtersx ?>&amp;view=<?php echo $view ?>">
211                        +1
212                    </a>
213                    |
214                    <a href="?cal=<?php echo $cal ?>&amp;day=<?php echo $cal_date->d ?>&amp;month=<?php echo $cal_month ?>&amp;year=<?php echo $today->y ?>&amp;filterev=<?php echo $filterev ?>&amp;filterof=<?php echo $filterof ?>&amp;filtersx=<?php echo $filtersx ?>&amp;view=<?php echo $view ?>">
215                        <?php echo $today->format('%Y') ?>
216                    </a>
217                    <?php echo FunctionsPrint::helpLink('annivers_year_select') ?>
218                </td>
219
220                <td class="descriptionbox vmiddle">
221                    <?php echo I18N::translate('Show') ?>
222                </td>
223
224                <td class="optionbox vmiddle">
225                    <?php if (!$WT_TREE->getPreference('HIDE_LIVE_PEOPLE') || Auth::check()): ?>
226                    <select class="list_value" name="filterof" onchange="document.dateform.submit();">
227                        <option value="all" <?php echo $filterof === 'all' ? 'selected' : '' ?>>
228                            <?php echo I18N::translate('All individuals') ?>
229                        </option>
230                        <option value="living" <?php echo $filterof === 'living' ? 'selected' : '' ?>>
231                            <?php echo I18N::translate('Living individuals') ?>
232                        </option>
233                        <option value="recent" <?php echo $filterof === 'recent' ? 'selected' : '' ?>>
234                            <?php echo I18N::translate('Recent years (&lt; 100 yrs)') ?>
235                        </option>
236                    </select>
237                    <?php endif; ?>
238
239                    <a title="<?php echo I18N::translate('All individuals') ?>" href="?cal=<?php echo $cal ?>&amp;day=<?php echo $cal_date->d ?>&amp;month=<?php echo $cal_month ?>&amp;year=<?php echo $cal_date->y ?>&amp;filterev=<?php echo $filterev ?>&amp;filterof=<?php echo $filterof ?>&amp;view=<?php echo $view ?>">
240                        <i class="<?php echo $filtersx === '' ? 'icon-sex_m_15x15' : 'icon-sex_m_9x9' ?>"></i>
241                        <i class="<?php echo $filtersx === '' ? 'icon-sex_f_15x15' : 'icon-sex_f_9x9' ?>"></i>
242                    </a>
243                    |
244                    <a title="<?php echo I18N::translate('Males') ?>" href="?cal=<?php echo $cal ?>&amp;day=<?php echo $cal_date->d ?>&amp;month=<?php echo $cal_month ?>&amp;year=<?php echo $cal_date->y ?>&amp;filterev=<?php echo $filterev ?>&amp;filterof=<?php echo $filterof ?>&amp;filtersx=M&amp;view=<?php echo $view ?>">
245                        <i class="<?php echo $filtersx === 'M' ? 'icon-sex_m_15x15' : 'icon-sex_m_9x9' ?>"></i>
246                    </a>
247                    |
248                    <a title="<?php echo I18N::translate('Females') ?>" href="?cal=<?php echo $cal ?>&amp;day=<?php echo $cal_date->d ?>&amp;month=<?php echo $cal_month ?>&amp;year=<?php echo $cal_date->y ?>&amp;filterev=<?php echo $filterev ?>&amp;filterof=<?php echo $filterof ?>&amp;filtersx=F&amp;view=<?php echo $view ?>">
249                        <i class="<?php echo $filtersx === 'F' ? 'icon-sex_f_15x15' : 'icon-sex_f_9x9' ?>"></i>
250                    </a>
251
252                    <select class="list_value" name="filterev" onchange="document.dateform.submit();">
253                        <option value="BIRT-MARR-DEAT" <?php echo $filterev === 'BIRT-MARR-DEAT' ? 'selected' : '' ?>>
254                            <?php echo I18N::translate('Vital records') ?>
255                        </option>
256                        <option value="" <?php echo $filterev === '' ? 'selected' : '' ?>>
257                            <?php echo I18N::translate('All') ?>
258                        </option>
259                        <option value="BIRT" <?php echo $filterev === 'BIRT' ? 'selected' : '' ?>>
260                            <?php echo GedcomTag::getLabel('BIRT') ?>
261                        </option>
262                        <option value="BAPM-CHR-CHRA" <?php echo $filterev === 'BAPM-CHR-CHRA' ? 'selected' : '' ?>>
263                            <?php echo GedcomTag::getLabel('BAPM') ?>
264                        </option>
265                        <option value="MARR-_COML-_NMR" <?php echo $filterev === 'MARR-_COML-_NMR' ? 'selected' : '' ?>>
266                            <?php echo GedcomTag::getLabel('MARR') ?>
267                        </option>
268                        <option value="DIV-_SEPR" <?php echo $filterev === 'DIV-_SEPR' ? 'selected' : '' ?>>
269                            <?php echo GedcomTag::getLabel('DIV') ?>
270                        </option>
271                        <option value="DEAT" <?php echo $filterev === 'DEAT' ? 'selected' : '' ?>>
272                            <?php echo GedcomTag::getLabel('DEAT') ?>
273                        </option>
274                        <option value="BURI" <?php echo $filterev === 'BURI' ? 'selected' : '' ?>>
275                            <?php echo GedcomTag::getLabel('BURI') ?>
276                        </option>
277                        <option value="IMMI,EMIG" <?php echo $filterev === 'IMMI,EMIG' ? 'selected' : '' ?>>
278                            <?php echo GedcomTag::getLabel('EMIG') ?>
279                        </option>
280                        <option value="EVEN" <?php echo $filterev === 'EVEN' ? 'selected' : '' ?>>
281                            <?php echo I18N::translate('Custom event') ?>
282                        </option>
283                    </select>
284                </td>
285            </tr>
286        </table>
287
288        <table class="width100">
289            <tr>
290                <td class="topbottombar width50">
291                    <a class="<?php echo $view === 'day' ? 'error' : '' ?>" href="?cal=<?php echo $cal ?>&amp;day=<?php echo $cal_date->d ?>&amp;month=<?php echo $cal_month ?>&amp;year=<?php echo $cal_date->y ?>&amp;filterev=<?php echo $filterev ?>&amp;filterof=<?php echo $filterof ?>&amp;filtersx=<?php echo $filtersx ?>&amp;view=day">
292                        <?php echo I18N::translate('View this day') ?>
293                    </a>
294                    |
295                    <a class="<?php echo $view === 'month' ? 'error' : '' ?>" href="?cal=<?php echo $cal ?>&amp;day=<?php echo $cal_date->d ?>&amp;month=<?php echo $cal_month ?>&amp;year=<?php echo $cal_date->y ?>&amp;filterev=<?php echo $filterev ?>&amp;filterof=<?php echo $filterof ?>&amp;filtersx=<?php echo $filtersx ?>&amp;view=month">
296                        <?php echo I18N::translate('View this month') ?>
297                    </a>
298                    |
299                    <a class="<?php echo $view === 'year' ? 'error' : '' ?>" href="?cal=<?php echo $cal ?>&amp;day=<?php echo $cal_date->d ?>&amp;month=<?php echo $cal_month ?>&amp;year=<?php echo $cal_date->y ?>&amp;filterev=<?php echo $filterev ?>&amp;filterof=<?php echo $filterof ?>&amp;filtersx=<?php echo $filtersx ?>&amp;view=year">
300                        <?php echo I18N::translate('View this year') ?>
301                    </a>
302                </td>
303                <td class="topbottombar width50">
304                    <?php
305                    $n = 0;
306                    foreach (Date::calendarNames() as $newcal => $cal_name) {
307                        $tmp = $cal_date->convertToCalendar($newcal);
308                        if ($tmp->inValidRange()) {
309                            if ($n++) {
310                                echo ' | ';
311                            }
312                            if (get_class($tmp) === get_class($cal_date)) {
313                                echo '<span class="error">', $cal_name, '</span>';
314                            } else {
315                                $newcalesc = urlencode($tmp->format('%@'));
316                                $tmpmonth  = $tmp->format('%O');
317                                echo '<a href="?cal=', $newcalesc, '&amp;day=', $tmp->d, '&amp;month=', $tmpmonth, '&amp;year=', $tmp->y, '&amp;filterev=', $filterev, '&amp;filterof=', $filterof, '&amp;filtersx=', $filtersx, '&amp;view=', $view, '">', $cal_name, '</a>';
318                            }
319                        }
320                    }
321                    ?>
322                </td>
323            </tr>
324        </table>
325    </form>
326<?php
327
328// Fetch data for day/month/year views
329$found_facts = array();
330
331switch ($view) {
332    case 'day':
333        $found_facts = apply_filter(FunctionsDb::getAnniversaryEvents($cal_date->minJD, $filterev, $WT_TREE), $filterof, $filtersx);
334        break;
335    case 'month':
336        $cal_date->d = 0;
337        $cal_date->setJdFromYmd();
338        // Make a separate list for each day. Unspecified/invalid days go in day 0.
339        for ($d = 0; $d <= $days_in_month; ++$d) {
340            $found_facts[$d] = array();
341        }
342        // Fetch events for each day
343        for ($jd = $cal_date->minJD; $jd <= $cal_date->maxJD; ++$jd) {
344            foreach (apply_filter(FunctionsDb::getAnniversaryEvents($jd, $filterev, $WT_TREE), $filterof, $filtersx) as $fact) {
345                $tmp = $fact->getDate()->minimumDate();
346                if ($tmp->d >= 1 && $tmp->d <= $tmp->daysInMonth()) {
347                    // If the day is valid (for its own calendar), display it in the
348                    // anniversary day (for the display calendar).
349                    $found_facts[$jd - $cal_date->minJD + 1][] = $fact;
350                } else {
351                    // Otherwise, display it in the "Day not set" box.
352                    $found_facts[0][] = $fact;
353                }
354            }
355        }
356        break;
357    case 'year':
358        $cal_date->m = 0;
359        $cal_date->setJdFromYmd();
360        $found_facts = apply_filter(FunctionsDb::getCalendarEvents($ged_date->minimumJulianDay(), $ged_date->maximumJulianDay(), $filterev, $WT_TREE), $filterof, $filtersx);
361        // Eliminate duplicates (e.g. BET JUL 1900 AND SEP 1900 will appear twice in 1900)
362        $found_facts = array_unique($found_facts);
363        break;
364}
365
366// Group the facts by family/individual
367$indis     = array();
368$fams      = array();
369$cal_facts = array();
370
371switch ($view) {
372    case 'year':
373    case 'day':
374        foreach ($found_facts as $fact) {
375            $record = $fact->getParent();
376            $xref   = $record->getXref();
377            if ($record instanceof Individual) {
378                if (empty($indis[$xref])) {
379                    $indis[$xref] = calendar_fact_text($fact, true);
380                } else {
381                    $indis[$xref] .= '<br>' . calendar_fact_text($fact, true);
382                }
383            } elseif ($record instanceof Family) {
384                if (empty($indis[$xref])) {
385                    $fams[$xref] = calendar_fact_text($fact, true);
386                } else {
387                    $fams[$xref] .= '<br>' . calendar_fact_text($fact, true);
388                }
389            }
390        }
391        break;
392    case 'month':
393        foreach ($found_facts as $d => $facts) {
394            $cal_facts[$d] = array();
395            foreach ($facts as $fact) {
396                $xref = $fact->getParent()->getXref();
397                if (empty($cal_facts[$d][$xref])) {
398                    $cal_facts[$d][$xref] = calendar_fact_text($fact, false);
399                } else {
400                    $cal_facts[$d][$xref] .= '<br>' . calendar_fact_text($fact, false);
401                }
402            }
403        }
404        break;
405}
406
407switch ($view) {
408    case 'year':
409    case 'day':
410        $males   = 0;
411        $females = 0;
412        echo '<table class="width100"><tr>';
413        echo '<td class="descriptionbox center width50"><i class="icon-indis"></i>', I18N::translate('Individuals'), '</td>';
414        echo '<td class="descriptionbox center width50"><i class="icon-cfamily"></i>', I18N::translate('Families'), '</td>';
415        echo '</tr><tr>';
416        echo '<td class="optionbox wrap">';
417
418        $content = calendar_list_text($indis, '<li>', '</li>', true);
419        if ($content) {
420            echo '<ul>', $content, '</ul>';
421        }
422
423        echo '</td>';
424        echo '<td class="optionbox wrap">';
425
426        $content = calendar_list_text($fams, '<li>', '</li>', true);
427        if ($content) {
428            echo '<ul>', $content, '</ul>';
429        }
430
431        echo '</td>';
432        echo '</tr><tr>';
433        echo '<td class="descriptionbox">', I18N::translate('Total individuals: %s', count($indis));
434        echo '<br>';
435        echo '<i class="icon-sex_m_15x15" title="', I18N::translate('Males'), '"></i> ', $males, ' ';
436        echo '<i class="icon-sex_f_15x15" title="', I18N::translate('Females'), '"></i> ', $females, ' ';
437        if (count($indis) !== $males + $females) {
438            echo '<i class="icon-sex_u_15x15" title="', I18N::translate('All individuals'), '"></i> ', count($indis) - $males - $females;
439        }
440        echo '</td>';
441        echo '<td class="descriptionbox">', I18N::translate('Total families: %s', count($fams)), '</td>';
442        echo '</tr></table>';
443
444        break;
445    case 'month':
446    // We use JD%7 = 0/Mon…6/Sun. Standard definitions use 0/Sun…6/Sat.
447        $week_start    = (I18N::firstDay() + 6) % 7;
448        $weekend_start = (I18N::weekendStart() + 6) % 7;
449        $weekend_end   = (I18N::weekendEnd() + 6) % 7;
450        // The french  calendar has a 10-day week, which starts on primidi
451        if ($days_in_week === 10) {
452            $week_start    = 0;
453            $weekend_start = -1;
454            $weekend_end   = -1;
455        }
456        echo '<table class="width100"><thead><tr>';
457        for ($week_day = 0; $week_day < $days_in_week; ++$week_day) {
458            $day_name = $cal_date->dayNames(($week_day + $week_start) % $days_in_week);
459            if ($week_day == $weekend_start || $week_day == $weekend_end) {
460                echo '<th class="descriptionbox weekend" width="' . (100 / $days_in_week) . '%">', $day_name, '</th>';
461            } else {
462                echo '<th class="descriptionbox" width="' . (100 / $days_in_week) . '%">', $day_name, '</th>';
463            }
464        }
465        echo '</tr>';
466        echo '</thead>';
467        echo '<tbody>';
468        // Print days 1 to n of the month, but extend to cover "empty" days before/after the month to make whole weeks.
469        // e.g. instead of 1 -> 30 (=30 days), we might have -1 -> 33 (=35 days)
470        $start_d = 1 - ($cal_date->minJD - $week_start) % $days_in_week;
471        $end_d   = $days_in_month + ($days_in_week - ($cal_date->maxJD - $week_start + 1) % $days_in_week) % $days_in_week;
472        // Make sure that there is an empty box for any leap/missing days
473        if ($start_d === 1 && $end_d === $days_in_month && count($found_facts[0]) > 0) {
474            $end_d += $days_in_week;
475        }
476        for ($d = $start_d; $d <= $end_d; ++$d) {
477            if (($d + $cal_date->minJD - $week_start) % $days_in_week === 1) {
478                echo '<tr>';
479            }
480            echo '<td class="optionbox wrap">';
481            if ($d < 1 || $d > $days_in_month) {
482                if (count($cal_facts[0]) > 0) {
483                    echo '<span class="cal_day">', I18N::translate('Day not set'), '</span><br style="clear: both;">';
484                    echo '<div class="details1" style="height: 180px; overflow: auto;">';
485                    echo calendar_list_text($cal_facts[0], '', '', false);
486                    echo '</div>';
487                    $cal_facts[0] = array();
488                }
489            } else {
490                // Format the day number using the calendar
491                $tmp   = new Date($cal_date->format("%@ {$d} %O %E"));
492                $d_fmt = $tmp->minimumDate()->format('%j');
493                if ($d === $today->d && $cal_date->m === $today->m) {
494                    echo '<span class="cal_day current_day">', $d_fmt, '</span>';
495                } else {
496                    echo '<span class="cal_day">', $d_fmt, '</span>';
497                }
498                // Show a converted date
499                foreach (explode('_and_', $CALENDAR_FORMAT) as $convcal) {
500                    switch ($convcal) {
501                        case 'french':
502                            $alt_date = new FrenchDate($cal_date->minJD + $d - 1);
503                            break;
504                        case 'gregorian':
505                            $alt_date = new GregorianDate($cal_date->minJD + $d - 1);
506                        break;
507                        case 'jewish':
508                            $alt_date = new JewishDate($cal_date->minJD + $d - 1);
509                        break;
510                        case 'julian':
511                            $alt_date = new JulianDate($cal_date->minJD + $d - 1);
512                        break;
513                        case 'hijri':
514                            $alt_date = new HijriDate($cal_date->minJD + $d - 1);
515                        break;
516                        case 'jalali':
517                            $alt_date = new JalaliDate($cal_date->minJD + $d - 1);
518                        break;
519                        default:
520                        break 2;
521                    }
522                    if (get_class($alt_date) !== get_class($cal_date) && $alt_date->inValidRange()) {
523                        echo '<span class="rtl_cal_day">' . $alt_date->format("%j %M") . '</span>';
524                        // Just show the first conversion
525                        break;
526                    }
527                }
528                echo '<br style="clear: both;"><div class="details1" style="height: 180px; overflow: auto;">';
529                echo calendar_list_text($cal_facts[$d], '', '', false);
530                echo '</div>';
531            }
532            echo '</td>';
533            if (($d + $cal_date->minJD - $week_start) % $days_in_week === 0) {
534                echo '</tr>';
535            }
536        }
537        echo '</tbody>';
538        echo '</table>';
539        break;
540}
541echo '</div>'; //close "calendar-page"
542
543/**
544 * Filter a list of anniversaries
545 *
546 * @param Fact[] $facts
547 * @param string $filterof
548 * @param string $filtersx
549 *
550 * @return array
551 */
552function apply_filter($facts, $filterof, $filtersx)
553{
554    $filtered      = array();
555    $hundred_years = WT_CLIENT_JD - 36525;
556    foreach ($facts as $fact) {
557        $record = $fact->getParent();
558        if ($filtersx) {
559            // Filter on sex
560            if ($record instanceof Individual && $filtersx !== $record->getSex()) {
561                continue;
562            }
563            // Can't display families if the sex filter is on.
564            if ($record instanceof Family) {
565                continue;
566            }
567        }
568        // Filter living individuals
569        if ($filterof === 'living') {
570            if ($record instanceof Individual && $record->isDead()) {
571                continue;
572            }
573            if ($record instanceof Family) {
574                $husb = $record->getHusband();
575                $wife = $record->getWife();
576                if ($husb && $husb->isDead() || $wife && $wife->isDead()) {
577                    continue;
578                }
579            }
580        }
581        // Filter on recent events
582        if ($filterof === 'recent' && $fact->getDate()->maximumJulianDay() < $hundred_years) {
583            continue;
584        }
585        $filtered[] = $fact;
586    }
587
588    return $filtered;
589}
590
591/**
592 * Format an anniversary display.
593 *
594 * @param Fact $fact
595 * @param bool $show_places
596 *
597 * @return string
598 */
599function calendar_fact_text(Fact $fact, $show_places)
600{
601    $text = $fact->getLabel() . ' — ' . $fact->getDate()->display(true, null, false);
602    if ($fact->anniv) {
603        $text .= ' (' . I18N::translate('%s year anniversary', $fact->anniv) . ')';
604    }
605    if ($show_places && $fact->getAttribute('PLAC')) {
606        $text .= ' — ' . $fact->getAttribute('PLAC');
607    }
608
609    return $text;
610}
611
612/**
613 * Format a list of facts for display
614 *
615 * @param Fact[] $list
616 * @param string $tag1
617 * @param string $tag2
618 * @param bool   $show_sex_symbols
619 *
620 * @return string
621 */
622function calendar_list_text($list, $tag1, $tag2, $show_sex_symbols)
623{
624    global $males, $females, $WT_TREE;
625
626    $html = '';
627
628    foreach ($list as $id => $facts) {
629        $tmp = GedcomRecord::getInstance($id, $WT_TREE);
630        $html .= $tag1 . '<a href="' . $tmp->getHtmlUrl() . '">' . $tmp->getFullName() . '</a> ';
631        if ($show_sex_symbols && $tmp instanceof Individual) {
632            switch ($tmp->getSex()) {
633                case 'M':
634                    $html .= '<i class="icon-sex_m_9x9" title="' . I18N::translate('Male') . '"></i>';
635                    ++$males;
636                    break;
637                case 'F':
638                    $html .= '<i class="icon-sex_f_9x9" title="' . I18N::translate('Female') . '"></i>';
639                    ++$females;
640                    break;
641                default:
642                    $html .= '<i class="icon-sex_u_9x9" title="' . I18N::translateContext('unknown gender', 'Unknown') . '"></i>';
643                    break;
644            }
645        }
646        $html .= '<div class="indent">' . $facts . '</div>' . $tag2;
647    }
648
649    return $html;
650}
651