xref: /webtrees/app/Module/DescendancyModule.php (revision 8fcd0d32e56ee262912bbdb593202cfd1cbc1615)
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\Database;
21use Fisharebest\Webtrees\Family;
22use Fisharebest\Webtrees\FontAwesome;
23use Fisharebest\Webtrees\I18N;
24use Fisharebest\Webtrees\Individual;
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     *
52     * @return Response
53     */
54    public function getSearchAction(Request $request, Tree $tree): Response
55    {
56        $search = $request->get('search', '');
57
58        $html = '';
59
60        if (strlen($search) >= 2) {
61            $rows = Database::prepare(
62                "SELECT i_id AS xref" .
63                " FROM `##individuals`" .
64                " JOIN `##name` ON i_id = n_id AND i_file = n_file" .
65                " WHERE n_sort LIKE CONCAT('%', :query, '%') AND i_file = :tree_id" .
66                " ORDER BY n_sort"
67            )->execute([
68                'query'   => $search,
69                'tree_id' => $tree->id(),
70            ])->fetchAll();
71
72            foreach ($rows as $row) {
73                $individual = Individual::getInstance($row->xref, $tree);
74                if ($individual !== null && $individual->canShow()) {
75                    $html .= $this->getPersonLi($individual);
76                }
77            }
78        }
79
80        if ($html !== '') {
81            $html = '<ul>' . $html . '</ul>';
82        }
83
84        return new Response($html);
85    }
86
87    /**
88     * @param Request $request
89     * @param Tree    $tree
90     *
91     * @return Response
92     */
93    public function getDescendantsAction(Request $request, Tree $tree): Response
94    {
95        $xref = $request->get('xref', '');
96
97        $individual = Individual::getInstance($xref, $tree);
98
99        if ($individual !== null && $individual->canShow()) {
100            $html = $this->loadSpouses($individual, 1);
101        } else {
102            $html = '';
103        }
104
105        return new Response($html);
106    }
107
108    /** {@inheritdoc} */
109    public function defaultSidebarOrder(): int
110    {
111        return 30;
112    }
113
114    /** {@inheritdoc} */
115    public function hasSidebarContent(Individual $individual): bool
116    {
117        return true;
118    }
119
120    /**
121     * Load this sidebar synchronously.
122     *
123     * @param Individual $individual
124     *
125     * @return string
126     */
127    public function getSidebarContent(Individual $individual): string
128    {
129        return view('modules/descendancy/sidebar', [
130            'individual_list' => $this->getPersonLi($individual, 1),
131        ]);
132    }
133
134    /**
135     * Format an individual in a list.
136     *
137     * @param Individual $person
138     * @param int        $generations
139     *
140     * @return string
141     */
142    public function getPersonLi(Individual $person, $generations = 0): string
143    {
144        $icon     = $generations > 0 ? 'icon-minus' : 'icon-plus';
145        $lifespan = $person->canShow() ? '(' . $person->getLifeSpan() . ')' : '';
146        $spouses  = $generations > 0 ? $this->loadSpouses($person, 0) : '';
147
148        return
149            '<li class="sb_desc_indi_li">' .
150            '<a class="sb_desc_indi" href="' . e(route('module', [
151                'module' => 'descendancy',
152                'action' => 'Descendants',
153                'ged'    => $person->tree()->name(),
154                'xref'   => $person->xref(),
155            ])) . '">' .
156            '<i class="plusminus ' . $icon . '"></i>' .
157            $person->getSexImage() . $person->getFullName() . $lifespan .
158            '</a>' .
159            FontAwesome::linkIcon('individual', $person->getFullName(), ['href' => $person->url()]) .
160            '<div>' . $spouses . '</div>' .
161            '</li>';
162    }
163
164    /**
165     * Format a family in a list.
166     *
167     * @param Family     $family
168     * @param Individual $person
169     * @param int        $generations
170     *
171     * @return string
172     */
173    public function getFamilyLi(Family $family, Individual $person, $generations = 0): string
174    {
175        $spouse = $family->getSpouse($person);
176        if ($spouse) {
177            $spouse_name = $spouse->getSexImage() . $spouse->getFullName();
178            $spouse_link = FontAwesome::linkIcon('individual', $spouse->getFullName(), ['href' => $person->url()]);
179        } else {
180            $spouse_name = '';
181            $spouse_link = '';
182        }
183
184        $marryear = $family->getMarriageYear();
185        $marr     = $marryear ? '<i class="icon-rings"></i>' . $marryear : '';
186
187        return
188            '<li class="sb_desc_indi_li">' .
189            '<a class="sb_desc_indi" href="#"><i class="plusminus icon-minus"></i>' . $spouse_name . $marr . '</a>' .
190            $spouse_link .
191            FontAwesome::linkIcon('family', $family->getFullName(), ['href' => $family->url()]) .
192            '<div>' . $this->loadChildren($family, $generations) . '</div>' .
193            '</li>';
194    }
195
196    /**
197     * Display spouses.
198     *
199     * @param Individual $person
200     * @param int        $generations
201     *
202     * @return string
203     */
204    public function loadSpouses(Individual $person, $generations)
205    {
206        $out = '';
207        if ($person && $person->canShow()) {
208            foreach ($person->getSpouseFamilies() as $family) {
209                $out .= $this->getFamilyLi($family, $person, $generations - 1);
210            }
211        }
212        if ($out) {
213            return '<ul>' . $out . '</ul>';
214        }
215
216        return '';
217    }
218
219    /**
220     * Display descendants.
221     *
222     * @param Family $family
223     * @param int    $generations
224     *
225     * @return string
226     */
227    public function loadChildren(Family $family, $generations)
228    {
229        $out = '';
230        if ($family->canShow()) {
231            $children = $family->getChildren();
232            if ($children) {
233                foreach ($children as $child) {
234                    $out .= $this->getPersonLi($child, $generations - 1);
235                }
236            } else {
237                $out .= '<li class="sb_desc_none">' . I18N::translate('No children') . '</li>';
238            }
239        }
240        if ($out) {
241            return '<ul>' . $out . '</ul>';
242        }
243
244        return '';
245    }
246}
247