xref: /webtrees/app/Module/AncestorsChartModule.php (revision 6b1381a306f9b3ddbe167a71b6a607d5ab7a609d)
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\Auth;
21use Fisharebest\Webtrees\Contracts\UserInterface;
22use Fisharebest\Webtrees\I18N;
23use Fisharebest\Webtrees\Individual;
24use Fisharebest\Webtrees\Menu;
25use Fisharebest\Webtrees\Services\ChartService;
26use Fisharebest\Webtrees\Tree;
27use Illuminate\Support\Collection;
28use Psr\Http\Message\ResponseInterface;
29use Psr\Http\Message\ServerRequestInterface;
30use function view;
31
32/**
33 * Class AncestorsChartModule
34 */
35class AncestorsChartModule extends AbstractModule implements ModuleChartInterface
36{
37    use ModuleChartTrait;
38
39    // Chart styles
40    protected const CHART_STYLE_TREE        = 'tree';
41    protected const CHART_STYLE_INDIVIDUALS = 'individuals';
42    protected const CHART_STYLE_FAMILIES    = 'families';
43
44    // Defaults
45    protected const DEFAULT_STYLE               = self::CHART_STYLE_TREE;
46    protected const DEFAULT_GENERATIONS         = '4';
47
48    // Limits
49    protected const MINIMUM_GENERATIONS = 2;
50    protected const MAXIMUM_GENERATIONS = 10;
51
52    /**
53     * How should this module be identified in the control panel, etc.?
54     *
55     * @return string
56     */
57    public function title(): string
58    {
59        /* I18N: Name of a module/chart */
60        return I18N::translate('Ancestors');
61    }
62
63    /**
64     * A sentence describing what this module does.
65     *
66     * @return string
67     */
68    public function description(): string
69    {
70        /* I18N: Description of the “AncestorsChart” module */
71        return I18N::translate('A chart of an individual’s ancestors.');
72    }
73
74    /**
75     * CSS class for the URL.
76     *
77     * @return string
78     */
79    public function chartMenuClass(): string
80    {
81        return 'menu-chart-ancestry';
82    }
83
84    /**
85     * Return a menu item for this chart - for use in individual boxes.
86     *
87     * @param Individual $individual
88     *
89     * @return Menu|null
90     */
91    public function chartBoxMenu(Individual $individual): ?Menu
92    {
93        return $this->chartMenu($individual);
94    }
95
96    /**
97     * The title for a specific instance of this chart.
98     *
99     * @param Individual $individual
100     *
101     * @return string
102     */
103    public function chartTitle(Individual $individual): string
104    {
105        /* I18N: %s is an individual’s name */
106        return I18N::translate('Ancestors of %s', $individual->fullName());
107    }
108
109    /**
110     * A form to request the chart parameters.
111     *
112     * @param ServerRequestInterface $request
113     * @param Tree                   $tree
114     * @param UserInterface          $user
115     * @param ChartService           $chart_service
116     *
117     * @return ResponseInterface
118     */
119    public function getChartAction(ServerRequestInterface $request, Tree $tree, UserInterface $user, ChartService $chart_service): ResponseInterface
120    {
121        $ajax       = $request->getQueryParams()['ajax'] ?? '';
122        $xref       = $request->getQueryParams()['xref'] ?? '';
123        $individual = Individual::getInstance($xref, $tree);
124
125        Auth::checkIndividualAccess($individual);
126        Auth::checkComponentAccess($this, 'chart', $tree, $user);
127
128        $chart_style  = $request->getQueryParams()['chart_style'] ?? self::DEFAULT_STYLE;
129        $generations  = (int) ($request->getQueryParams()['generations'] ?? self::DEFAULT_GENERATIONS);
130
131        $generations = min($generations, self::MAXIMUM_GENERATIONS);
132        $generations = max($generations, self::MINIMUM_GENERATIONS);
133
134        if ($ajax === '1') {
135            $ancestors = $chart_service->sosaStradonitzAncestors($individual, $generations);
136
137            switch ($chart_style) {
138                default:
139                case self::CHART_STYLE_TREE:
140                    return response(view('modules/ancestors-chart/tree', ['individual' => $individual, 'parents' => $individual->primaryChildFamily(), 'generations' => $generations, 'sosa' => 1]));
141
142                case self::CHART_STYLE_INDIVIDUALS:
143                    return $this->ancestorsIndividuals($tree, $ancestors);
144
145                case self::CHART_STYLE_FAMILIES:
146                    return $this->ancestorsFamilies($tree, $ancestors);
147            }
148        }
149
150        $ajax_url = $this->chartUrl($individual, [
151            'generations'  => $generations,
152            'chart_style'  => $chart_style,
153            'ajax'         => true,
154        ]);
155
156        return $this->viewResponse('modules/ancestors-chart/page', [
157            'ajax_url'            => $ajax_url,
158            'chart_style'         => $chart_style,
159            'chart_styles'        => $this->chartStyles(),
160            'default_generations' => self::DEFAULT_GENERATIONS,
161            'generations'         => $generations,
162            'individual'          => $individual,
163            'maximum_generations' => self::MAXIMUM_GENERATIONS,
164            'minimum_generations' => self::MINIMUM_GENERATIONS,
165            'module_name'         => $this->name(),
166            'title'               => $this->chartTitle($individual),
167        ]);
168    }
169
170    /**
171     * Show a tabular list of individual ancestors.
172     *
173     * @param Tree       $tree
174     * @param Collection $ancestors
175     *
176     * @return ResponseInterface
177     */
178    protected function ancestorsIndividuals(Tree $tree, Collection $ancestors): ResponseInterface
179    {
180        $this->layout = 'layouts/ajax';
181
182        return $this->viewResponse('lists/individuals-table', [
183            'individuals' => $ancestors,
184            'sosa'        => true,
185            'tree'        => $tree,
186        ]);
187    }
188
189    /**
190     * Show a tabular list of individual ancestors.
191     *
192     * @param Tree       $tree
193     * @param Collection $ancestors
194     *
195     * @return ResponseInterface
196     */
197    protected function ancestorsFamilies(Tree $tree, Collection $ancestors): ResponseInterface
198    {
199        $this->layout = 'layouts/ajax';
200
201        $families = [];
202        foreach ($ancestors as $individual) {
203            foreach ($individual->childFamilies() as $family) {
204                $families[$family->xref()] = $family;
205            }
206        }
207
208        return $this->viewResponse('lists/families-table', [
209            'families' => $families,
210            'tree'     => $tree,
211        ]);
212    }
213
214    /**
215     * This chart can display its output in a number of styles
216     *
217     * @return array
218     */
219    protected function chartStyles(): array
220    {
221        return [
222            self::CHART_STYLE_TREE        => I18N::translate('Tree'),
223            self::CHART_STYLE_INDIVIDUALS => I18N::translate('Individuals'),
224            self::CHART_STYLE_FAMILIES    => I18N::translate('Families'),
225        ];
226    }
227}
228