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