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