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\Functions;
17
18use Fisharebest\Webtrees\Auth;
19use Fisharebest\Webtrees\Family;
20use Fisharebest\Webtrees\I18N;
21use Fisharebest\Webtrees\Individual;
22use Fisharebest\Webtrees\Theme;
23
24/**
25 * Class FunctionsCharts - common functions
26 */
27class FunctionsCharts
28{
29    /**
30     * print a table cell with sosa number
31     *
32     * @param int $sosa
33     * @param string $pid optional pid
34     * @param string $arrowDirection direction of link arrow
35     */
36    public static function printSosaNumber($sosa, $pid = "", $arrowDirection = "up")
37    {
38        if (substr($sosa, -1, 1) == ".") {
39            $personLabel = substr($sosa, 0, -1);
40        } else {
41            $personLabel = $sosa;
42        }
43        if ($arrowDirection == "blank") {
44            $visibility = "hidden";
45        } else {
46            $visibility = "normal";
47        }
48        echo "<td class=\"subheaders center\" style=\"vertical-align: middle; text-indent: 0px; margin-top: 0px; white-space: nowrap; visibility: ", $visibility, ";\">";
49        echo $personLabel;
50        if ($sosa != "1" && $pid != "") {
51            if ($arrowDirection == "left") {
52                $dir = 0;
53            } elseif ($arrowDirection == "right") {
54                $dir = 1;
55            } elseif ($arrowDirection == "down") {
56                $dir = 3;
57            } else {
58                $dir = 2; // either 'blank' or 'up'
59            }
60            echo '<br>';
61            self::printUrlArrow('#' . $pid, $pid, $dir);
62        }
63        echo '</td>';
64    }
65
66    /**
67     * print the parents table for a family
68     *
69     * @param Family $family family gedcom ID
70     * @param int $sosa child sosa number
71     * @param string $label indi label (descendancy booklet)
72     * @param string $parid parent ID (descendancy booklet)
73     * @param string $gparid gd-parent ID (descendancy booklet)
74     * @param int $show_full large or small box
75     */
76    public static function printFamilyParents(Family $family, $sosa = 0, $label = '', $parid = '', $gparid = '', $show_full = 1)
77    {
78        if ($show_full) {
79            $pbheight = Theme::theme()->parameter('chart-box-y') + 14;
80        } else {
81            $pbheight = Theme::theme()->parameter('compact-chart-box-y') + 14;
82        }
83
84        $husb = $family->getHusband();
85        if ($husb) {
86            echo '<a name="', $husb->getXref(), '"></a>';
87        } else {
88            $husb = new Individual('M', "0 @M@ INDI\n1 SEX M", null, $family->getTree());
89        }
90        $wife = $family->getWife();
91        if ($wife) {
92            echo '<a name="', $wife->getXref(), '"></a>';
93        } else {
94            $wife = new Individual('F', "0 @F@ INDI\n1 SEX F", null, $family->getTree());
95        }
96
97        if ($sosa) {
98            echo '<p class="name_head">', $family->getFullName(), '</p>';
99        }
100
101        /**
102         * husband side
103         */
104        echo '<table cellspacing="0" cellpadding="0" border="0"><tr><td rowspan="2">';
105        echo '<table border="0"><tr>';
106
107        if ($parid) {
108            if ($husb->getXref() == $parid) {
109                self::printSosaNumber($label);
110            } else {
111                self::printSosaNumber($label, '', 'blank');
112            }
113        } elseif ($sosa) {
114            self::printSosaNumber($sosa * 2);
115        }
116        if ($husb->isPendingAddtion()) {
117            echo '<td class="facts_value new">';
118        } elseif ($husb->isPendingDeletion()) {
119            echo '<td class="facts_value old">';
120        } else {
121            echo '<td>';
122        }
123        FunctionsPrint::printPedigreePerson($husb, $show_full);
124        echo '</td></tr></table>';
125        echo '</td>';
126        // husband’s parents
127        $hfam = $husb->getPrimaryChildFamily();
128        if ($hfam) {
129            // remove the|| test for $sosa
130            echo '<td rowspan="2"><img src="' . Theme::theme()->parameter('image-hline') . '"></td><td rowspan="2"><img src="' . Theme::theme()->parameter('image-vline') . '" width="3" height="' . ($pbheight + 9) . '"></td>';
131            echo '<td><img class="line5" src="' . Theme::theme()->parameter('image-hline') . '"></td><td>';
132            // husband’s father
133            if ($hfam && $hfam->getHusband()) {
134                echo '<table border="0"><tr>';
135                if ($sosa > 0) {
136                    self::printSosaNumber($sosa * 4, $hfam->getHusband()->getXref(), 'down');
137                }
138                if (!empty($gparid) && $hfam->getHusband()->getXref() == $gparid) {
139                    self::printSosaNumber(trim(substr($label, 0, -3), '.') . '.');
140                }
141                echo '<td>';
142                FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
143                echo '</td></tr></table>';
144            } elseif ($hfam && !$hfam->getHusband()) {
145                // Empty box for grandfather
146                echo '<table border="0"><tr>';
147                echo '<td>';
148                FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
149                echo '</td></tr></table>';
150            }
151            echo '</td>';
152        }
153        if ($hfam && ($sosa != -1)) {
154            echo '<td rowspan="2">';
155            self::printUrlArrow(($sosa == 0 ? '?famid=' . $hfam->getXref() . '&amp;ged=' . $hfam->getTree()->getNameUrl() : '#' . $hfam->getXref()), $hfam->getXref(), 1);
156            echo '</td>';
157        }
158        if ($hfam) {
159            // husband’s mother
160            echo '</tr><tr><td><img src="' . Theme::theme()->parameter('image-hline') . '"></td><td>';
161            if ($hfam && $hfam->getWife()) {
162                echo '<table border=\'0\'><tr>';
163                if ($sosa > 0) {
164                    self::printSosaNumber($sosa * 4 + 1, $hfam->getWife()->getXref(), 'down');
165                }
166                if (!empty($gparid) && $hfam->getWife()->getXref() == $gparid) {
167                    self::printSosaNumber(trim(substr($label, 0, -3), '.') . '.');
168                }
169                echo '<td>';
170                FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
171                echo '</td></tr></table>';
172            } elseif ($hfam && !$hfam->getWife()) {
173                // Empty box for grandmother
174                echo '<table border="0"><tr>';
175                echo '<td>';
176                FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
177                echo '</td></tr></table>';
178            }
179            echo '</td>';
180        }
181        echo '</tr></table>';
182        if ($sosa && $family->canShow()) {
183            foreach ($family->getFacts(WT_EVENTS_MARR) as $fact) {
184                echo '<a href="', $family->getHtmlUrl(), '" class="details1">';
185                echo str_repeat('&nbsp;', 10);
186                echo $fact->summary();
187                echo '</a>';
188            }
189        } else {
190            echo '<br>';
191        }
192
193        /**
194         * wife side
195         */
196        echo '<table cellspacing="0" cellpadding="0" border="0"><tr><td rowspan="2">';
197        echo '<table><tr>';
198        if ($parid) {
199            if ($wife->getXref() == $parid) {
200                self::printSosaNumber($label);
201            } else {
202                self::printSosaNumber($label, '', 'blank');
203            }
204        } elseif ($sosa) {
205            self::printSosaNumber($sosa * 2 + 1);
206        }
207        if ($wife->isPendingAddtion()) {
208            echo '<td class="facts_value new">';
209        } elseif ($wife->isPendingDeletion()) {
210            echo '<td class="facts_value old">';
211        } else {
212            echo '<td>';
213        }
214        FunctionsPrint::printPedigreePerson($wife, $show_full);
215        echo '</td></tr></table>';
216        echo '</td>';
217        // wife’s parents
218        $hfam = $wife->getPrimaryChildFamily();
219
220        if ($hfam) {
221            echo '<td rowspan="2"><img src="' . Theme::theme()->parameter('image-hline') . '"></td><td rowspan="2"><img src="' . Theme::theme()->parameter('image-vline') . '" width="3" height="' . ($pbheight + 9) . '"></td>';
222            echo '<td><img class="line5" src="' . Theme::theme()->parameter('image-hline') . '"></td><td>';
223            // wife’s father
224            if ($hfam && $hfam->getHusband()) {
225                echo '<table><tr>';
226                if ($sosa > 0) {
227                    self::printSosaNumber($sosa * 4 + 2, $hfam->getHusband()->getXref(), 'down');
228                }
229                if (!empty($gparid) && $hfam->getHusband()->getXref() == $gparid) {
230                    self::printSosaNumber(trim(substr($label, 0, -3), '.') . '.');
231                }
232                echo '<td>';
233                FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
234                echo '</td></tr></table>';
235            } elseif ($hfam && !$hfam->getHusband()) {
236                // Empty box for grandfather
237                echo '<table border="0"><tr>';
238                echo '<td>';
239                FunctionsPrint::printPedigreePerson($hfam->getHusband(), $show_full);
240                echo '</td></tr></table>';
241            }
242            echo '</td>';
243        }
244        if ($hfam && ($sosa != -1)) {
245            echo '<td rowspan="2">';
246            self::printUrlArrow(($sosa == 0 ? '?famid=' . $hfam->getXref() . '&amp;ged=' . $hfam->getTree()->getNameUrl() : '#' . $hfam->getXref()), $hfam->getXref(), 1);
247            echo '</td>';
248        }
249        if ($hfam) {
250            // wife’s mother
251            echo '</tr><tr><td><img src="' . Theme::theme()->parameter('image-hline') . '"></td><td>';
252            if ($hfam && $hfam->getWife()) {
253                echo '<table><tr>';
254                if ($sosa > 0) {
255                    self::printSosaNumber($sosa * 4 + 3, $hfam->getWife()->getXref(), 'down');
256                }
257                if (!empty($gparid) && $hfam->getWife()->getXref() == $gparid) {
258                    self::printSosaNumber(trim(substr($label, 0, -3), '.') . '.');
259                }
260                echo '<td>';
261                FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
262                echo '</td></tr></table>';
263            } elseif ($hfam && !$hfam->getWife()) {
264                // Empty box for grandmother
265                echo '<table border="0"><tr>';
266                echo '<td>';
267                FunctionsPrint::printPedigreePerson($hfam->getWife(), $show_full);
268                echo '</td></tr></table>';
269            }
270            echo '</td>';
271        }
272        echo '</tr></table>';
273    }
274
275    /**
276     * print the children table for a family
277     *
278     * @param Family $family family
279     * @param string $childid child ID
280     * @param int $sosa child sosa number
281     * @param string $label indi label (descendancy booklet)
282     * @param int $show_cousins display cousins on chart
283     * @param int $show_full large or small box
284     */
285    public static function printFamilyChildren(Family $family, $childid = '', $sosa = 0, $label = '', $show_cousins = 0, $show_full = 1)
286    {
287        if ($show_full) {
288            $bheight = Theme::theme()->parameter('chart-box-y');
289        } else {
290            $bheight = Theme::theme()->parameter('compact-chart-box-y');
291        }
292
293        $pbheight = $bheight + 14;
294
295        $children = $family->getChildren();
296        $numchil  = count($children);
297
298        echo '<table border="0" cellpadding="0" cellspacing="2"><tr>';
299        if ($sosa > 0) {
300            echo '<td></td>';
301        }
302        echo '<td><span class="subheaders">';
303        if ($numchil == 0) {
304            echo I18N::translate('No children');
305        } else {
306            echo I18N::plural('%s child', '%s children', $numchil, $numchil);
307        }
308        echo '</span>';
309
310        if ($sosa == 0 && Auth::isEditor($family->getTree())) {
311            echo '<br>';
312            echo "<a href=\"#\" onclick=\"return add_child_to_family('", $family->getXref(), "', 'U');\">" . I18N::translate('Add a child to this family') . "</a>";
313            echo ' <a class="icon-sex_m_15x15" href="#" onclick="return add_child_to_family(\'', $family->getXref(), '\', \'M\');" title="', I18N::translate('son'), '"></a>';
314            echo ' <a class="icon-sex_f_15x15" href="#" onclick="return add_child_to_family(\'', $family->getXref(), '\', \'F\');" title="', I18N::translate('daughter'), '"></a>';
315            echo '<br><br>';
316        }
317        echo '</td>';
318        if ($sosa > 0) {
319            echo '<td></td><td></td>';
320        }
321        echo '</tr>';
322
323        $nchi = 1;
324        if ($children) {
325            foreach ($children as $child) {
326                echo '<tr>';
327                if ($sosa != 0) {
328                    if ($child->getXref() == $childid) {
329                        self::printSosaNumber($sosa, $childid);
330                    } elseif (empty($label)) {
331                        self::printSosaNumber("");
332                    } else {
333                        self::printSosaNumber($label . ($nchi++) . ".");
334                    }
335                }
336                if ($child->isPendingAddtion()) {
337                    echo '<td class="new">';
338                } elseif ($child->isPendingDeletion()) {
339                    echo '<td class="old">';
340                } else {
341                    echo '<td>';
342                }
343                FunctionsPrint::printPedigreePerson($child, $show_full);
344                echo '</td>';
345                if ($sosa != 0) {
346                    // loop for all families where current child is a spouse
347                    $famids = $child->getSpouseFamilies();
348
349                    $maxfam = count($famids) - 1;
350                    for ($f = 0; $f <= $maxfam; $f++) {
351                        $famid_child = $famids[$f]->getXref();
352                        // multiple marriages
353                        if ($f > 0) {
354                            echo '</tr><tr><td></td>';
355                            echo '<td style="text-align:end; vertical-align: top;">';
356
357                            //find out how many cousins there are to establish vertical line on second families
358                            $fchildren = $famids[$f]->getChildren();
359                            $kids      = count($fchildren);
360                            $Pheader   = ($bheight - 1) * $kids;
361                            $PBadj     = 6; // default
362                            if ($show_cousins > 0) {
363                                if ($kids) {
364                                    $PBadj = max(0, $Pheader / 2 + $kids * 4.5);
365                                }
366                            }
367
368                            if ($f == $maxfam) {
369                                echo '<img height="' . ((($bheight / 2)) + $PBadj) . 'px"';
370                            } else {
371                                echo '<img height="' . $pbheight . 'px"';
372                            }
373                            echo ' width="3" src="' . Theme::theme()->parameter('image-vline') . '">';
374                            echo '</td>';
375                        }
376                        echo '<td class="details1" style="text-align:center;">';
377                        $spouse = $famids[$f]->getSpouse($child);
378
379                        $marr = $famids[$f]->getFirstFact('MARR');
380                        $div  = $famids[$f]->getFirstFact('DIV');
381                        if ($marr) {
382                            // marriage date
383                            echo $marr->getDate()->minimumDate()->format('%Y');
384                            // divorce date
385                            if ($div) {
386                                echo '–', $div->getDate()->minimumDate()->format('%Y');
387                            }
388                        }
389                        echo "<br><img width=\"100%\" class=\"line5\" height=\"3\" src=\"" . Theme::theme()->parameter('image-hline') . "\" alt=\"\">";
390                        echo "</td>";
391                        // spouse information
392                        echo "<td style=\"vertical-align: center;";
393                        if (!empty($divrec)) {
394                            echo " filter:alpha(opacity=40);opacity:0.4;\">";
395                        } else {
396                            echo "\">";
397                        }
398                        FunctionsPrint::printPedigreePerson($spouse, $show_full);
399                        echo "</td>";
400                        // cousins
401                        if ($show_cousins) {
402                            self::printCousins($famid_child, $show_full);
403                        }
404                    }
405                }
406                echo "</tr>";
407            }
408        } elseif ($sosa < 1) {
409            // message 'no children' except for sosa
410            if (preg_match('/\n1 NCHI (\d+)/', $family->getGedcom(), $match) && $match[1] == 0) {
411                echo '<tr><td><i class="icon-childless"></i> ' . I18N::translate('This family remained childless') . '</td></tr>';
412            }
413        }
414        echo "</table><br>";
415    }
416
417    /**
418     * print a family with Sosa-Stradonitz numbering system
419     * ($rootid=1, father=2, mother=3 ...)
420     *
421     * @param string $famid family gedcom ID
422     * @param string $childid tree root ID
423     * @param int $sosa starting sosa number
424     * @param string $label indi label (descendancy booklet)
425     * @param string $parid parent ID (descendancy booklet)
426     * @param string $gparid gd-parent ID (descendancy booklet)
427     * @param int $show_cousins display cousins on chart
428     * @param int $show_full large or small box
429     */
430    public static function printSosaFamily($famid, $childid, $sosa, $label = '', $parid = '', $gparid = '', $show_cousins = 0, $show_full = 1)
431    {
432        global $WT_TREE;
433
434        echo '<hr>';
435        echo '<p style="page-break-before: always;">';
436        if (!empty($famid)) {
437            echo '<a name="', $famid, '"></a>';
438        }
439        self::printFamilyParents(Family::getInstance($famid, $WT_TREE), $sosa, $label, $parid, $gparid, $show_full);
440        echo '<br>';
441        echo '<table><tr><td>';
442        self::printFamilyChildren(Family::getInstance($famid, $WT_TREE), $childid, $sosa, $label, $show_cousins, $show_full);
443        echo '</td></tr></table>';
444        echo '<br>';
445    }
446
447    /**
448     * print an arrow to a new url
449     *
450     * @param string $url target url
451     * @param string $label arrow label
452     * @param int $dir arrow direction 0=left 1=right 2=up 3=down (default=2)
453     */
454    public static function printUrlArrow($url, $label, $dir = 2)
455    {
456        if ($url === '') {
457            return;
458        }
459
460        // arrow direction
461        $adir = $dir;
462        if (I18N::direction() === 'rtl' && $dir === 0) {
463            $adir = 1;
464        }
465        if (I18N::direction() === 'rtl' && $dir === 1) {
466            $adir = 0;
467        }
468
469        // arrow style     0         1         2         3
470        $array_style = array('icon-larrow', 'icon-rarrow', 'icon-uarrow', 'icon-darrow');
471        $astyle      = $array_style[$adir];
472
473        // Labels include people’s names, which may contain markup
474        echo '<a href="' . $url . '" title="' . strip_tags($label) . '" class="' . $astyle . '"></a>';
475    }
476
477    /**
478     * builds and returns sosa relationship name in the active language
479     *
480     * @param string $sosa sosa number
481     *
482     * @return string
483     */
484    public static function getSosaName($sosa)
485    {
486        $path = '';
487        while ($sosa > 1) {
488            if ($sosa % 2 == 1) {
489                $sosa -= 1;
490                $path = 'mot' . $path;
491            } else {
492                $path = 'fat' . $path;
493            }
494            $sosa /= 2;
495        }
496
497        return Functions::getRelationshipNameFromPath($path, null, null);
498    }
499
500    /**
501     * print cousins list
502     *
503     * @param string $famid family ID
504     * @param int $show_full large or small box
505     */
506    public static function printCousins($famid, $show_full = 1)
507    {
508        global $WT_TREE;
509
510        if ($show_full) {
511            $bheight = Theme::theme()->parameter('chart-box-y');
512        } else {
513            $bheight = Theme::theme()->parameter('compact-chart-box-y');
514        }
515
516        $family    = Family::getInstance($famid, $WT_TREE);
517        $fchildren = $family->getChildren();
518
519        $kids = count($fchildren);
520
521        echo '<td>';
522        if ($kids) {
523            echo '<table cellspacing="0" cellpadding="0" border="0" ><tr>';
524            if ($kids > 1) {
525                echo '<td rowspan="', $kids, '"><img width="3px" height="', (($bheight + 9) * ($kids - 1)), 'px" src="', Theme::theme()->parameter('image-vline'), '"></td>';
526            }
527            $ctkids = count($fchildren);
528            $i      = 1;
529            foreach ($fchildren as $fchil) {
530                if ($i == 1) {
531                    echo '<td><img width="10px" height="3px" style="vertical-align:top"';
532                } else {
533                    echo '<td><img width="10px" height="3px"';
534                }
535                if (I18N::direction() === 'ltr') {
536                    echo ' style="padding-right: 2px;"';
537                } else {
538                    echo ' style="padding-left: 2px;"';
539                }
540                echo ' src="', Theme::theme()->parameter('image-hline'), '"></td><td>';
541                FunctionsPrint::printPedigreePerson($fchil, $show_full);
542                echo '</td></tr>';
543                if ($i < $ctkids) {
544                    echo '<tr>';
545                    $i++;
546                }
547            }
548            echo '</table>';
549        } else {
550            // If there is known that there are no children (as opposed to no known children)
551            if (preg_match('/\n1 NCHI (\d+)/', $family->getGedcom(), $match) && $match[1] == 0) {
552                echo ' <i class="icon-childless" title="', I18N::translate('This family remained childless'), '"></i>';
553            }
554        }
555        echo '</td>';
556    }
557}
558