xref: /webtrees/app/Module/DescendancyModule.php (revision c1010eda29c0909ed4d5d463f32d32bfefdd4dfe)
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        return /* I18N: Name of a module/sidebar */
36            I18N::translate('Descendants');
37    }
38
39    /** {@inheritdoc} */
40    public function getDescription()
41    {
42        return /* I18N: Description of the “Descendants” module */
43            I18N::translate('A sidebar showing the descendants of an individual.');
44    }
45
46    /**
47     * @param Request $request
48     *
49     * @return Response
50     */
51    public function getSearchAction(Request $request): Response
52    {
53        /** @var Tree $tree */
54        $tree = $request->attributes->get('tree');
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->getTreeId(),
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     *
90     * @return Response
91     */
92    public function getDescendantsAction(Request $request): Response
93    {
94        /** @var Tree $tree */
95        $tree = $request->attributes->get('tree');
96
97        $xref = $request->get('xref');
98
99        $individual = Individual::getInstance($xref, $tree);
100
101        if ($individual !== null && $individual->canShow()) {
102            $html = $this->loadSpouses($individual, 1);
103        } else {
104            $html = '';
105        }
106
107        return new Response($html);
108    }
109
110    /** {@inheritdoc} */
111    public function defaultSidebarOrder()
112    {
113        return 30;
114    }
115
116    /** {@inheritdoc} */
117    public function hasSidebarContent(Individual $individual)
118    {
119        return true;
120    }
121
122    /**
123     * Load this sidebar synchronously.
124     *
125     * @param Individual $individual
126     *
127     * @return string
128     */
129    public function getSidebarContent(Individual $individual)
130    {
131        return view('modules/descendancy/sidebar', [
132            'individual_list' => $this->getPersonLi($individual, 1),
133        ]);
134    }
135
136    /**
137     * Format an individual in a list.
138     *
139     * @param Individual $person
140     * @param int        $generations
141     *
142     * @return string
143     */
144    public function getPersonLi(Individual $person, $generations = 0)
145    {
146        $icon     = $generations > 0 ? 'icon-minus' : 'icon-plus';
147        $lifespan = $person->canShow() ? '(' . $person->getLifeSpan() . ')' : '';
148        $spouses  = $generations > 0 ? $this->loadSpouses($person, 0) : '';
149
150        return
151            '<li class="sb_desc_indi_li">' .
152            '<a class="sb_desc_indi" href="' . e(route('module', [
153                'module' => 'descendancy',
154                'action' => 'Descendants',
155                'ged'    => $person->getTree()->getName(),
156                'xref'   => $person->getXref(),
157            ])) . '">' .
158            '<i class="plusminus ' . $icon . '"></i>' .
159            $person->getSexImage() . $person->getFullName() . $lifespan .
160            '</a>' .
161            FontAwesome::linkIcon('individual', $person->getFullName(), ['href' => $person->url()]) .
162            '<div>' . $spouses . '</div>' .
163            '</li>';
164    }
165
166    /**
167     * Format a family in a list.
168     *
169     * @param Family     $family
170     * @param Individual $person
171     * @param int        $generations
172     *
173     * @return string
174     */
175    public function getFamilyLi(Family $family, Individual $person, $generations = 0)
176    {
177        $spouse = $family->getSpouse($person);
178        if ($spouse) {
179            $spouse_name = $spouse->getSexImage() . $spouse->getFullName();
180            $spouse_link = FontAwesome::linkIcon('individual', $spouse->getFullName(), ['href' => $person->url()]);
181        } else {
182            $spouse_name = '';
183            $spouse_link = '';
184        }
185
186        $marryear = $family->getMarriageYear();
187        $marr     = $marryear ? '<i class="icon-rings"></i>' . $marryear : '';
188
189        return
190            '<li class="sb_desc_indi_li">' .
191            '<a class="sb_desc_indi" href="#"><i class="plusminus icon-minus"></i>' . $spouse_name . $marr . '</a>' .
192            $spouse_link .
193            FontAwesome::linkIcon('family', $family->getFullName(), ['href' => $family->url()]) .
194            '<div>' . $this->loadChildren($family, $generations) . '</div>' .
195            '</li>';
196    }
197
198    /**
199     * Display spouses.
200     *
201     * @param Individual $person
202     * @param int        $generations
203     *
204     * @return string
205     */
206    public function loadSpouses(Individual $person, $generations)
207    {
208        $out = '';
209        if ($person && $person->canShow()) {
210            foreach ($person->getSpouseFamilies() as $family) {
211                $out .= $this->getFamilyLi($family, $person, $generations - 1);
212            }
213        }
214        if ($out) {
215            return '<ul>' . $out . '</ul>';
216        } else {
217            return '';
218        }
219    }
220
221    /**
222     * Display descendants.
223     *
224     * @param Family $family
225     * @param int    $generations
226     *
227     * @return string
228     */
229    public function loadChildren(Family $family, $generations)
230    {
231        $out = '';
232        if ($family->canShow()) {
233            $children = $family->getChildren();
234            if ($children) {
235                foreach ($children as $child) {
236                    $out .= $this->getPersonLi($child, $generations - 1);
237                }
238            } else {
239                $out .= '<li class="sb_desc_none">' . I18N::translate('No children') . '</li>';
240            }
241        }
242        if ($out) {
243            return '<ul>' . $out . '</ul>';
244        } else {
245            return '';
246        }
247    }
248}
249