xref: /webtrees/app/Module/DescendancyModule.php (revision 269403b38b318054ee1e1d2591b41b540b581296)
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 */
16declare(strict_types=1);
17
18namespace Fisharebest\Webtrees\Module;
19
20use Fisharebest\Webtrees\Family;
21use Fisharebest\Webtrees\FontAwesome;
22use Fisharebest\Webtrees\I18N;
23use Fisharebest\Webtrees\Individual;
24use Fisharebest\Webtrees\Services\SearchService;
25use Fisharebest\Webtrees\Tree;
26use Symfony\Component\HttpFoundation\Request;
27use Symfony\Component\HttpFoundation\Response;
28
29/**
30 * Class DescendancyModule
31 */
32class DescendancyModule extends AbstractModule implements ModuleSidebarInterface
33{
34    /** {@inheritdoc} */
35    public function getTitle(): string
36    {
37        /* I18N: Name of a module/sidebar */
38        return I18N::translate('Descendants');
39    }
40
41    /** {@inheritdoc} */
42    public function getDescription(): string
43    {
44        /* I18N: Description of the “Descendants” module */
45        return I18N::translate('A sidebar showing the descendants of an individual.');
46    }
47
48    /**
49     * @param Request       $request
50     * @param Tree          $tree
51     * @param SearchService $search_service
52     *
53     * @return Response
54     */
55    public function getSearchAction(Request $request, Tree $tree, SearchService $search_service): Response
56    {
57        $search = $request->get('search', '');
58
59        $html = '';
60
61        if (strlen($search) >= 2) {
62            $html = $search_service
63                ->searchIndividualNames([$tree], [$search])
64                ->map(function (Individual $individual): string {
65                    return $this->getPersonLi($individual);
66                })
67                ->implode('');
68        }
69
70        if ($html !== '') {
71            $html = '<ul>' . $html . '</ul>';
72        }
73
74        return new Response($html);
75    }
76
77    /**
78     * @param Request $request
79     * @param Tree    $tree
80     *
81     * @return Response
82     */
83    public function getDescendantsAction(Request $request, Tree $tree): Response
84    {
85        $xref = $request->get('xref', '');
86
87        $individual = Individual::getInstance($xref, $tree);
88
89        if ($individual !== null && $individual->canShow()) {
90            $html = $this->loadSpouses($individual, 1);
91        } else {
92            $html = '';
93        }
94
95        return new Response($html);
96    }
97
98    /** {@inheritdoc} */
99    public function defaultSidebarOrder(): int
100    {
101        return 30;
102    }
103
104    /** {@inheritdoc} */
105    public function hasSidebarContent(Individual $individual): bool
106    {
107        return true;
108    }
109
110    /**
111     * Load this sidebar synchronously.
112     *
113     * @param Individual $individual
114     *
115     * @return string
116     */
117    public function getSidebarContent(Individual $individual): string
118    {
119        return view('modules/descendancy/sidebar', [
120            'individual_list' => $this->getPersonLi($individual, 1),
121        ]);
122    }
123
124    /**
125     * Format an individual in a list.
126     *
127     * @param Individual $person
128     * @param int        $generations
129     *
130     * @return string
131     */
132    public function getPersonLi(Individual $person, $generations = 0): string
133    {
134        $icon     = $generations > 0 ? 'icon-minus' : 'icon-plus';
135        $lifespan = $person->canShow() ? '(' . $person->getLifeSpan() . ')' : '';
136        $spouses  = $generations > 0 ? $this->loadSpouses($person, 0) : '';
137
138        return
139            '<li class="sb_desc_indi_li">' .
140            '<a class="sb_desc_indi" href="' . e(route('module', [
141                'module' => 'descendancy',
142                'action' => 'Descendants',
143                'ged'    => $person->tree()->name(),
144                'xref'   => $person->xref(),
145            ])) . '">' .
146            '<i class="plusminus ' . $icon . '"></i>' .
147            $person->getSexImage() . $person->getFullName() . $lifespan .
148            '</a>' .
149            FontAwesome::linkIcon('individual', $person->getFullName(), ['href' => $person->url()]) .
150            '<div>' . $spouses . '</div>' .
151            '</li>';
152    }
153
154    /**
155     * Format a family in a list.
156     *
157     * @param Family     $family
158     * @param Individual $person
159     * @param int        $generations
160     *
161     * @return string
162     */
163    public function getFamilyLi(Family $family, Individual $person, $generations = 0): string
164    {
165        $spouse = $family->getSpouse($person);
166        if ($spouse) {
167            $spouse_name = $spouse->getSexImage() . $spouse->getFullName();
168            $spouse_link = FontAwesome::linkIcon('individual', $spouse->getFullName(), ['href' => $person->url()]);
169        } else {
170            $spouse_name = '';
171            $spouse_link = '';
172        }
173
174        $marryear = $family->getMarriageYear();
175        $marr     = $marryear ? '<i class="icon-rings"></i>' . $marryear : '';
176
177        return
178            '<li class="sb_desc_indi_li">' .
179            '<a class="sb_desc_indi" href="#"><i class="plusminus icon-minus"></i>' . $spouse_name . $marr . '</a>' .
180            $spouse_link .
181            FontAwesome::linkIcon('family', $family->getFullName(), ['href' => $family->url()]) .
182            '<div>' . $this->loadChildren($family, $generations) . '</div>' .
183            '</li>';
184    }
185
186    /**
187     * Display spouses.
188     *
189     * @param Individual $person
190     * @param int        $generations
191     *
192     * @return string
193     */
194    public function loadSpouses(Individual $person, $generations)
195    {
196        $out = '';
197        if ($person && $person->canShow()) {
198            foreach ($person->getSpouseFamilies() as $family) {
199                $out .= $this->getFamilyLi($family, $person, $generations - 1);
200            }
201        }
202        if ($out) {
203            return '<ul>' . $out . '</ul>';
204        }
205
206        return '';
207    }
208
209    /**
210     * Display descendants.
211     *
212     * @param Family $family
213     * @param int    $generations
214     *
215     * @return string
216     */
217    public function loadChildren(Family $family, $generations)
218    {
219        $out = '';
220        if ($family->canShow()) {
221            $children = $family->getChildren();
222            if ($children) {
223                foreach ($children as $child) {
224                    $out .= $this->getPersonLi($child, $generations - 1);
225                }
226            } else {
227                $out .= '<li class="sb_desc_none">' . I18N::translate('No children') . '</li>';
228            }
229        }
230        if ($out) {
231            return '<ul>' . $out . '</ul>';
232        }
233
234        return '';
235    }
236}
237