xref: /webtrees/app/Http/RequestHandlers/IndividualPage.php (revision abf376262fd2cfd2ca905e083b32a7d104e05d5b)
1852ede8cSGreg Roach<?php
2852ede8cSGreg Roach
3852ede8cSGreg Roach/**
4852ede8cSGreg Roach * webtrees: online genealogy
5a091ac74SGreg Roach * Copyright (C) 2020 webtrees development team
6852ede8cSGreg Roach * This program is free software: you can redistribute it and/or modify
7852ede8cSGreg Roach * it under the terms of the GNU General Public License as published by
8852ede8cSGreg Roach * the Free Software Foundation, either version 3 of the License, or
9852ede8cSGreg Roach * (at your option) any later version.
10852ede8cSGreg Roach * This program is distributed in the hope that it will be useful,
11852ede8cSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
12852ede8cSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13852ede8cSGreg Roach * GNU General Public License for more details.
14852ede8cSGreg Roach * You should have received a copy of the GNU General Public License
15852ede8cSGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>.
16852ede8cSGreg Roach */
17852ede8cSGreg Roach
18852ede8cSGreg Roachdeclare(strict_types=1);
19852ede8cSGreg Roach
20852ede8cSGreg Roachnamespace Fisharebest\Webtrees\Http\RequestHandlers;
21852ede8cSGreg Roach
22e5d858f5SGreg Roachuse Fig\Http\Message\StatusCodeInterface;
23054771e9SGreg Roachuse Fisharebest\Webtrees\Age;
24852ede8cSGreg Roachuse Fisharebest\Webtrees\Auth;
25852ede8cSGreg Roachuse Fisharebest\Webtrees\Date;
26852ede8cSGreg Roachuse Fisharebest\Webtrees\Fact;
27a091ac74SGreg Roachuse Fisharebest\Webtrees\Factory;
28852ede8cSGreg Roachuse Fisharebest\Webtrees\Http\ViewResponseTrait;
29852ede8cSGreg Roachuse Fisharebest\Webtrees\I18N;
30852ede8cSGreg Roachuse Fisharebest\Webtrees\Individual;
31852ede8cSGreg Roachuse Fisharebest\Webtrees\Media;
32852ede8cSGreg Roachuse Fisharebest\Webtrees\MediaFile;
33852ede8cSGreg Roachuse Fisharebest\Webtrees\Module\ModuleSidebarInterface;
34852ede8cSGreg Roachuse Fisharebest\Webtrees\Module\ModuleTabInterface;
35852ede8cSGreg Roachuse Fisharebest\Webtrees\Services\ClipboardService;
36852ede8cSGreg Roachuse Fisharebest\Webtrees\Services\ModuleService;
37852ede8cSGreg Roachuse Fisharebest\Webtrees\Services\UserService;
38852ede8cSGreg Roachuse Fisharebest\Webtrees\Tree;
39852ede8cSGreg Roachuse Illuminate\Support\Collection;
40852ede8cSGreg Roachuse Psr\Http\Message\ResponseInterface;
41852ede8cSGreg Roachuse Psr\Http\Message\ServerRequestInterface;
42852ede8cSGreg Roachuse Psr\Http\Server\RequestHandlerInterface;
43852ede8cSGreg Roachuse stdClass;
44852ede8cSGreg Roach
452406e0e0SGreg Roachuse function array_map;
46852ede8cSGreg Roachuse function assert;
472406e0e0SGreg Roachuse function date;
48852ede8cSGreg Roachuse function e;
49852ede8cSGreg Roachuse function explode;
502406e0e0SGreg Roachuse function implode;
51852ede8cSGreg Roachuse function is_string;
52852ede8cSGreg Roachuse function redirect;
53852ede8cSGreg Roachuse function route;
542406e0e0SGreg Roachuse function strtoupper;
552406e0e0SGreg Roachuse function view;
562406e0e0SGreg Roach
57852ede8cSGreg Roach/**
58852ede8cSGreg Roach * Show an individual's page.
59852ede8cSGreg Roach */
60852ede8cSGreg Roachclass IndividualPage implements RequestHandlerInterface
61852ede8cSGreg Roach{
62852ede8cSGreg Roach    use ViewResponseTrait;
63852ede8cSGreg Roach
64852ede8cSGreg Roach    /** @var ClipboardService */
65852ede8cSGreg Roach    private $clipboard_service;
66852ede8cSGreg Roach
67852ede8cSGreg Roach    /** @var ModuleService */
68852ede8cSGreg Roach    private $module_service;
69852ede8cSGreg Roach
70852ede8cSGreg Roach    /** @var UserService */
71852ede8cSGreg Roach    private $user_service;
72852ede8cSGreg Roach
73852ede8cSGreg Roach    /**
74aba707cdSGreg Roach     * IndividualPage constructor.
75852ede8cSGreg Roach     *
76852ede8cSGreg Roach     * @param ClipboardService $clipboard_service
77852ede8cSGreg Roach     * @param ModuleService    $module_service
78852ede8cSGreg Roach     * @param UserService      $user_service
79852ede8cSGreg Roach     */
80852ede8cSGreg Roach    public function __construct(ClipboardService $clipboard_service, ModuleService $module_service, UserService $user_service)
81852ede8cSGreg Roach    {
82852ede8cSGreg Roach        $this->clipboard_service = $clipboard_service;
83852ede8cSGreg Roach        $this->module_service    = $module_service;
84852ede8cSGreg Roach        $this->user_service      = $user_service;
85852ede8cSGreg Roach    }
86852ede8cSGreg Roach
87852ede8cSGreg Roach    /**
88852ede8cSGreg Roach     * @param ServerRequestInterface $request
89852ede8cSGreg Roach     *
90852ede8cSGreg Roach     * @return ResponseInterface
91852ede8cSGreg Roach     */
92852ede8cSGreg Roach    public function handle(ServerRequestInterface $request): ResponseInterface
93852ede8cSGreg Roach    {
94852ede8cSGreg Roach        $tree = $request->getAttribute('tree');
95852ede8cSGreg Roach        assert($tree instanceof Tree);
96852ede8cSGreg Roach
97852ede8cSGreg Roach        $xref = $request->getAttribute('xref');
98852ede8cSGreg Roach        assert(is_string($xref));
99852ede8cSGreg Roach
100a091ac74SGreg Roach        $individual = Factory::individual()->make($xref, $tree);
101852ede8cSGreg Roach        $individual = Auth::checkIndividualAccess($individual);
102852ede8cSGreg Roach
103852ede8cSGreg Roach        // Redirect to correct xref/slug
104852ede8cSGreg Roach        if ($individual->xref() !== $xref || $request->getAttribute('slug') !== $individual->slug()) {
105e5d858f5SGreg Roach            return redirect($individual->url(), StatusCodeInterface::STATUS_MOVED_PERMANENTLY);
106852ede8cSGreg Roach        }
107852ede8cSGreg Roach
108852ede8cSGreg Roach        // What is (was) the age of the individual
109852ede8cSGreg Roach        $bdate = $individual->getBirthDate();
110852ede8cSGreg Roach        $ddate = $individual->getDeathDate();
111054771e9SGreg Roach
112054771e9SGreg Roach        if ($individual->isDead()) {
113852ede8cSGreg Roach            // If dead, show age at death
114054771e9SGreg Roach            $age = (new Age($bdate, $ddate))->ageAtEvent(false);
115852ede8cSGreg Roach        } else {
116054771e9SGreg Roach            // If living, show age today
11753432476SGreg Roach            $today = strtoupper(date('d M Y'));
11853432476SGreg Roach            $age   = (new Age($bdate, new Date($today)))->ageAtEvent(true);
119852ede8cSGreg Roach        }
120852ede8cSGreg Roach
121852ede8cSGreg Roach        // What images are linked to this individual
122852ede8cSGreg Roach        $individual_media = new Collection();
123852ede8cSGreg Roach        foreach ($individual->facts(['OBJE']) as $fact) {
124852ede8cSGreg Roach            $media_object = $fact->target();
125852ede8cSGreg Roach            if ($media_object instanceof Media) {
126852ede8cSGreg Roach                $media_file = $media_object->firstImageFile();
127852ede8cSGreg Roach                if ($media_file instanceof MediaFile) {
128852ede8cSGreg Roach                    $individual_media->add($media_file);
129852ede8cSGreg Roach                }
130852ede8cSGreg Roach            }
131852ede8cSGreg Roach        }
132852ede8cSGreg Roach
133*abf37626SGreg Roach        $name_records = $individual->facts(['NAME'])->map(static function (Fact $fact): string {
134*abf37626SGreg Roach            return view('individual-name', ['fact' => $fact]);
135*abf37626SGreg Roach        });
136852ede8cSGreg Roach
137*abf37626SGreg Roach        $sex_records = $individual->facts(['SEX'])->map(static function (Fact $fact): string {
138*abf37626SGreg Roach            return view('individual-sex', ['fact' => $fact]);
139*abf37626SGreg Roach        });
140852ede8cSGreg Roach
141852ede8cSGreg Roach        // If this individual is linked to a user account, show the link
142852ede8cSGreg Roach        $user_link = '';
143852ede8cSGreg Roach        if (Auth::isAdmin()) {
144852ede8cSGreg Roach            $users = $this->user_service->findByIndividual($individual);
145852ede8cSGreg Roach            foreach ($users as $user) {
146852ede8cSGreg Roach                $user_link = ' —  <a href="' . e(route('admin-users', ['filter' => $user->email()])) . '">' . e($user->userName()) . '</a>';
147852ede8cSGreg Roach            }
148852ede8cSGreg Roach        }
149852ede8cSGreg Roach
150852ede8cSGreg Roach        return $this->viewResponse('individual-page', [
151852ede8cSGreg Roach            'age'              => $age,
152852ede8cSGreg Roach            'clipboard_facts'  => $this->clipboard_service->pastableFacts($individual, new Collection()),
153852ede8cSGreg Roach            'individual'       => $individual,
154852ede8cSGreg Roach            'individual_media' => $individual_media,
1552406e0e0SGreg Roach            'meta_description' => $this->metaDescription($individual),
156852ede8cSGreg Roach            'meta_robots'      => 'index,follow',
157852ede8cSGreg Roach            'name_records'     => $name_records,
158852ede8cSGreg Roach            'sex_records'      => $sex_records,
159852ede8cSGreg Roach            'sidebars'         => $this->getSidebars($individual),
160852ede8cSGreg Roach            'tabs'             => $this->getTabs($individual),
161852ede8cSGreg Roach            'significant'      => $this->significant($individual),
1625e6816beSGreg Roach            'title'            => $individual->fullName() . ' ' . $individual->lifespan(),
163852ede8cSGreg Roach            'tree'             => $tree,
164852ede8cSGreg Roach            'user_link'        => $user_link,
165852ede8cSGreg Roach        ]);
166852ede8cSGreg Roach    }
167852ede8cSGreg Roach
168852ede8cSGreg Roach    /**
1692406e0e0SGreg Roach     * @param Individual $individual
1702406e0e0SGreg Roach     *
1712406e0e0SGreg Roach     * @return string
1722406e0e0SGreg Roach     */
1732406e0e0SGreg Roach    private function metaDescription(Individual $individual): string
1742406e0e0SGreg Roach    {
1752406e0e0SGreg Roach        $meta_facts = [];
1762406e0e0SGreg Roach
1772406e0e0SGreg Roach        $birth_date  = $individual->getBirthDate();
1782406e0e0SGreg Roach        $birth_place = $individual->getBirthPlace();
1792406e0e0SGreg Roach
1802406e0e0SGreg Roach        if ($birth_date->isOK() || $birth_place->id() !== 0) {
1812406e0e0SGreg Roach            $meta_facts[] = I18N::translate('Birth') . ' ' .
1822406e0e0SGreg Roach                $birth_date->display(false, null, false) . ' ' .
1832406e0e0SGreg Roach                $birth_place->placeName();
1842406e0e0SGreg Roach        }
1852406e0e0SGreg Roach
1862406e0e0SGreg Roach        $death_date  = $individual->getDeathDate();
1872406e0e0SGreg Roach        $death_place = $individual->getDeathPlace();
1882406e0e0SGreg Roach
1892406e0e0SGreg Roach        if ($death_date->isOK() || $death_place->id() !== 0) {
1902406e0e0SGreg Roach            $meta_facts[] = I18N::translate('Death') . ' ' .
1912406e0e0SGreg Roach                $death_date->display(false, null, false) . ' ' .
1922406e0e0SGreg Roach                $death_place->placeName();
1932406e0e0SGreg Roach        }
1942406e0e0SGreg Roach
1952406e0e0SGreg Roach        foreach ($individual->childFamilies() as $family) {
1962406e0e0SGreg Roach            $meta_facts[] = I18N::translate('Parents') . ' ' . $family->fullName();
1972406e0e0SGreg Roach        }
1982406e0e0SGreg Roach
1992406e0e0SGreg Roach        foreach ($individual->spouseFamilies() as $family) {
2002406e0e0SGreg Roach            $spouse = $family->spouse($individual);
2012406e0e0SGreg Roach            if ($spouse instanceof Individual) {
2022406e0e0SGreg Roach                $meta_facts[] = I18N::translate('Spouse') . ' ' . $spouse->fullName();
2032406e0e0SGreg Roach            }
2042406e0e0SGreg Roach
2052406e0e0SGreg Roach            $child_names = $family->children()->map(static function (Individual $individual): string {
2062406e0e0SGreg Roach                return e($individual->getAllNames()[0]['givn']);
2072406e0e0SGreg Roach            })->implode(', ');
2082406e0e0SGreg Roach
2092406e0e0SGreg Roach
2102406e0e0SGreg Roach            if ($child_names !== '') {
2112406e0e0SGreg Roach                $meta_facts[] = I18N::translate('Children') . ' ' . $child_names;
2122406e0e0SGreg Roach            }
2132406e0e0SGreg Roach        }
2142406e0e0SGreg Roach
2152406e0e0SGreg Roach        $meta_facts = array_map('strip_tags', $meta_facts);
2162406e0e0SGreg Roach        $meta_facts = array_map('trim', $meta_facts);
2172406e0e0SGreg Roach
2182406e0e0SGreg Roach        return implode(', ', $meta_facts);
2192406e0e0SGreg Roach    }
2202406e0e0SGreg Roach
2212406e0e0SGreg Roach    /**
222852ede8cSGreg Roach     * Which tabs should we show on this individual's page.
223852ede8cSGreg Roach     * We don't show empty tabs.
224852ede8cSGreg Roach     *
225852ede8cSGreg Roach     * @param Individual $individual
226852ede8cSGreg Roach     *
227b5c8fd7eSGreg Roach     * @return Collection<ModuleSidebarInterface>
228852ede8cSGreg Roach     */
229852ede8cSGreg Roach    public function getSidebars(Individual $individual): Collection
230852ede8cSGreg Roach    {
231852ede8cSGreg Roach        return $this->module_service->findByComponent(ModuleSidebarInterface::class, $individual->tree(), Auth::user())
232852ede8cSGreg Roach            ->filter(static function (ModuleSidebarInterface $sidebar) use ($individual): bool {
233852ede8cSGreg Roach                return $sidebar->hasSidebarContent($individual);
234852ede8cSGreg Roach            });
235852ede8cSGreg Roach    }
236852ede8cSGreg Roach
237852ede8cSGreg Roach    /**
238852ede8cSGreg Roach     * Which tabs should we show on this individual's page.
239852ede8cSGreg Roach     * We don't show empty tabs.
240852ede8cSGreg Roach     *
241852ede8cSGreg Roach     * @param Individual $individual
242852ede8cSGreg Roach     *
243b5c8fd7eSGreg Roach     * @return Collection<ModuleTabInterface>
244852ede8cSGreg Roach     */
245852ede8cSGreg Roach    public function getTabs(Individual $individual): Collection
246852ede8cSGreg Roach    {
247852ede8cSGreg Roach        return $this->module_service->findByComponent(ModuleTabInterface::class, $individual->tree(), Auth::user())
248852ede8cSGreg Roach            ->filter(static function (ModuleTabInterface $tab) use ($individual): bool {
249852ede8cSGreg Roach                return $tab->hasTabContent($individual);
250852ede8cSGreg Roach            });
251852ede8cSGreg Roach    }
252852ede8cSGreg Roach
253852ede8cSGreg Roach    /**
254852ede8cSGreg Roach     * What are the significant elements of this page?
255852ede8cSGreg Roach     * The layout will need them to generate URLs for charts and reports.
256852ede8cSGreg Roach     *
257852ede8cSGreg Roach     * @param Individual $individual
258852ede8cSGreg Roach     *
259852ede8cSGreg Roach     * @return stdClass
260852ede8cSGreg Roach     */
261852ede8cSGreg Roach    private function significant(Individual $individual): stdClass
262852ede8cSGreg Roach    {
263852ede8cSGreg Roach        [$surname] = explode(',', $individual->sortName());
264852ede8cSGreg Roach
265852ede8cSGreg Roach        $family = $individual->childFamilies()->merge($individual->spouseFamilies())->first();
266852ede8cSGreg Roach
267852ede8cSGreg Roach        return (object) [
268852ede8cSGreg Roach            'family'     => $family,
269852ede8cSGreg Roach            'individual' => $individual,
270852ede8cSGreg Roach            'surname'    => $surname,
271852ede8cSGreg Roach        ];
272852ede8cSGreg Roach    }
273852ede8cSGreg Roach}
274