Passed
Push — master ( a50fee...785e55 )
by Greg
05:43
created

IndividualPage::formatNameRecord()   F

Complexity

Conditions 15
Paths 336

Size

Total Lines 94
Code Lines 70

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 70
nc 336
nop 3
dl 0
loc 94
rs 3.3833
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A IndividualPage::getTabs() 0 5 1
A IndividualPage::significant() 0 10 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2020 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Http\RequestHandlers;
21
22
use Fig\Http\Message\StatusCodeInterface;
23
use Fisharebest\Webtrees\Age;
24
use Fisharebest\Webtrees\Auth;
25
use Fisharebest\Webtrees\Date;
26
use Fisharebest\Webtrees\Fact;
27
use Fisharebest\Webtrees\Factory;
28
use Fisharebest\Webtrees\Http\ViewResponseTrait;
29
use Fisharebest\Webtrees\I18N;
30
use Fisharebest\Webtrees\Individual;
31
use Fisharebest\Webtrees\Media;
32
use Fisharebest\Webtrees\MediaFile;
33
use Fisharebest\Webtrees\Module\ModuleSidebarInterface;
34
use Fisharebest\Webtrees\Module\ModuleTabInterface;
35
use Fisharebest\Webtrees\Services\ClipboardService;
36
use Fisharebest\Webtrees\Services\ModuleService;
37
use Fisharebest\Webtrees\Services\UserService;
38
use Fisharebest\Webtrees\Tree;
39
use Illuminate\Support\Collection;
40
use Psr\Http\Message\ResponseInterface;
41
use Psr\Http\Message\ServerRequestInterface;
42
use Psr\Http\Server\RequestHandlerInterface;
43
use stdClass;
44
45
use function array_map;
46
use function assert;
47
use function date;
48
use function e;
49
use function explode;
50
use function implode;
51
use function is_string;
52
use function redirect;
53
use function route;
54
use function strtoupper;
55
use function view;
56
57
/**
58
 * Show an individual's page.
59
 */
60
class IndividualPage implements RequestHandlerInterface
61
{
62
    use ViewResponseTrait;
63
64
    /** @var ClipboardService */
65
    private $clipboard_service;
66
67
    /** @var ModuleService */
68
    private $module_service;
69
70
    /** @var UserService */
71
    private $user_service;
72
73
    /**
74
     * IndividualPage constructor.
75
     *
76
     * @param ClipboardService $clipboard_service
77
     * @param ModuleService    $module_service
78
     * @param UserService      $user_service
79
     */
80
    public function __construct(ClipboardService $clipboard_service, ModuleService $module_service, UserService $user_service)
81
    {
82
        $this->clipboard_service = $clipboard_service;
83
        $this->module_service    = $module_service;
84
        $this->user_service      = $user_service;
85
    }
86
87
    /**
88
     * @param ServerRequestInterface $request
89
     *
90
     * @return ResponseInterface
91
     */
92
    public function handle(ServerRequestInterface $request): ResponseInterface
93
    {
94
        $tree = $request->getAttribute('tree');
95
        assert($tree instanceof Tree);
96
97
        $xref = $request->getAttribute('xref');
98
        assert(is_string($xref));
99
100
        $individual = Factory::individual()->make($xref, $tree);
101
        $individual = Auth::checkIndividualAccess($individual);
102
103
        // Redirect to correct xref/slug
104
        if ($individual->xref() !== $xref || $request->getAttribute('slug') !== $individual->slug()) {
105
            return redirect($individual->url(), StatusCodeInterface::STATUS_MOVED_PERMANENTLY);
106
        }
107
108
        // What is (was) the age of the individual
109
        $bdate = $individual->getBirthDate();
110
        $ddate = $individual->getDeathDate();
111
112
        if ($individual->isDead()) {
113
            // If dead, show age at death
114
            $age = (new Age($bdate, $ddate))->ageAtEvent(false);
115
        } else {
116
            // If living, show age today
117
            $today = strtoupper(date('d M Y'));
118
            $age   = (new Age($bdate, new Date($today)))->ageAtEvent(true);
119
        }
120
121
        // What images are linked to this individual
122
        $individual_media = new Collection();
123
        foreach ($individual->facts(['OBJE']) as $fact) {
124
            $media_object = $fact->target();
125
            if ($media_object instanceof Media) {
126
                $media_file = $media_object->firstImageFile();
127
                if ($media_file instanceof MediaFile) {
128
                    $individual_media->add($media_file);
129
                }
130
            }
131
        }
132
133
        $name_records = new Collection();
134
        foreach ($individual->facts(['NAME']) as $n => $name_fact) {
135
            $name_records->add(view('individual-name', ['fact' => $name_fact, 'n' => $n]));
136
        }
137
138
        $sex_records = new Collection();
139
        foreach ($individual->facts(['SEX']) as $n => $sex_fact) {
140
            $sex_records->add(view('individual-sex', ['fact' => $sex_fact]));
141
        }
142
143
        // If this individual is linked to a user account, show the link
144
        $user_link = '';
145
        if (Auth::isAdmin()) {
146
            $users = $this->user_service->findByIndividual($individual);
147
            foreach ($users as $user) {
148
                $user_link = ' —  <a href="' . e(route('admin-users', ['filter' => $user->email()])) . '">' . e($user->userName()) . '</a>';
149
            }
150
        }
151
152
        return $this->viewResponse('individual-page', [
153
            'age'              => $age,
154
            'clipboard_facts'  => $this->clipboard_service->pastableFacts($individual, new Collection()),
155
            'individual'       => $individual,
156
            'individual_media' => $individual_media,
157
            'meta_description' => $this->metaDescription($individual),
158
            'meta_robots'      => 'index,follow',
159
            'name_records'     => $name_records,
160
            'sex_records'      => $sex_records,
161
            'sidebars'         => $this->getSidebars($individual),
162
            'tabs'             => $this->getTabs($individual),
163
            'significant'      => $this->significant($individual),
164
            'title'            => $individual->fullName() . ' ' . $individual->lifespan(),
165
            'tree'             => $tree,
166
            'user_link'        => $user_link,
167
        ]);
168
    }
169
170
    /**
171
     * @param Individual $individual
172
     *
173
     * @return string
174
     */
175
    private function metaDescription(Individual $individual): string
176
    {
177
        $meta_facts = [];
178
179
        $birth_date  = $individual->getBirthDate();
180
        $birth_place = $individual->getBirthPlace();
181
182
        if ($birth_date->isOK() || $birth_place->id() !== 0) {
183
            $meta_facts[] = I18N::translate('Birth') . ' ' .
184
                $birth_date->display(false, null, false) . ' ' .
185
                $birth_place->placeName();
186
        }
187
188
        $death_date  = $individual->getDeathDate();
189
        $death_place = $individual->getDeathPlace();
190
191
        if ($death_date->isOK() || $death_place->id() !== 0) {
192
            $meta_facts[] = I18N::translate('Death') . ' ' .
193
                $death_date->display(false, null, false) . ' ' .
194
                $death_place->placeName();
195
        }
196
197
        foreach ($individual->childFamilies() as $family) {
198
            $meta_facts[] = I18N::translate('Parents') . ' ' . $family->fullName();
199
        }
200
201
        foreach ($individual->spouseFamilies() as $family) {
202
            $spouse = $family->spouse($individual);
203
            if ($spouse instanceof Individual) {
204
                $meta_facts[] = I18N::translate('Spouse') . ' ' . $spouse->fullName();
205
            }
206
207
            $child_names = $family->children()->map(static function (Individual $individual): string {
208
                return e($individual->getAllNames()[0]['givn']);
209
            })->implode(', ');
210
211
212
            if ($child_names !== '') {
213
                $meta_facts[] = I18N::translate('Children') . ' ' . $child_names;
214
            }
215
        }
216
217
        $meta_facts = array_map('strip_tags', $meta_facts);
218
        $meta_facts = array_map('trim', $meta_facts);
219
220
        return implode(', ', $meta_facts);
221
    }
222
223
    /**
224
     * Which tabs should we show on this individual's page.
225
     * We don't show empty tabs.
226
     *
227
     * @param Individual $individual
228
     *
229
     * @return Collection<ModuleSidebarInterface>
230
     */
231
    public function getSidebars(Individual $individual): Collection
232
    {
233
        return $this->module_service->findByComponent(ModuleSidebarInterface::class, $individual->tree(), Auth::user())
234
            ->filter(static function (ModuleSidebarInterface $sidebar) use ($individual): bool {
235
                return $sidebar->hasSidebarContent($individual);
236
            });
237
    }
238
239
    /**
240
     * Which tabs should we show on this individual's page.
241
     * We don't show empty tabs.
242
     *
243
     * @param Individual $individual
244
     *
245
     * @return Collection<ModuleTabInterface>
246
     */
247
    public function getTabs(Individual $individual): Collection
248
    {
249
        return $this->module_service->findByComponent(ModuleTabInterface::class, $individual->tree(), Auth::user())
250
            ->filter(static function (ModuleTabInterface $tab) use ($individual): bool {
251
                return $tab->hasTabContent($individual);
252
            });
253
    }
254
255
    /**
256
     * What are the significant elements of this page?
257
     * The layout will need them to generate URLs for charts and reports.
258
     *
259
     * @param Individual $individual
260
     *
261
     * @return stdClass
262
     */
263
    private function significant(Individual $individual): stdClass
264
    {
265
        [$surname] = explode(',', $individual->sortName());
266
267
        $family = $individual->childFamilies()->merge($individual->spouseFamilies())->first();
268
269
        return (object) [
270
            'family'     => $family,
271
            'individual' => $individual,
272
            'surname'    => $surname,
273
        ];
274
    }
275
}
276