Passed
Push — develop ( 1125ee...8bca93 )
by Greg
20:39 queued 07:22
created

PlaceRepository::statsPlaces()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 55
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 38
nc 9
nop 4
dl 0
loc 55
rs 8.6897
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A PlaceRepository::commonBirthPlacesList() 0 4 1
A PlaceRepository::commonDeathPlacesList() 0 4 1
A PlaceRepository::renderTop10() 0 8 1

How to fix   Long Method   

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) 2022 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 <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Statistics\Repository;
21
22
use Fisharebest\Webtrees\Family;
23
use Fisharebest\Webtrees\Gedcom;
24
use Fisharebest\Webtrees\I18N;
25
use Fisharebest\Webtrees\Individual;
26
use Fisharebest\Webtrees\Location;
27
use Fisharebest\Webtrees\Place;
28
use Fisharebest\Webtrees\Statistics\Google\ChartDistribution;
29
use Fisharebest\Webtrees\Statistics\Repository\Interfaces\IndividualRepositoryInterface;
30
use Fisharebest\Webtrees\Statistics\Repository\Interfaces\PlaceRepositoryInterface;
31
use Fisharebest\Webtrees\Statistics\Service\CountryService;
32
use Fisharebest\Webtrees\Tree;
33
use Illuminate\Database\Capsule\Manager as DB;
34
use Illuminate\Database\Query\Expression;
35
use Illuminate\Database\Query\JoinClause;
36
37
use function array_key_exists;
38
use function arsort;
39
use function end;
40
use function explode;
41
use function preg_match;
42
use function trim;
43
use function view;
44
45
/**
46
 * A repository providing methods for place related statistics.
47
 */
48
class PlaceRepository implements PlaceRepositoryInterface
49
{
50
    private Tree $tree;
51
52
    private CountryService $country_service;
53
54
    private IndividualRepositoryInterface $individual_repository;
55
56
    /**
57
     * @param Tree                          $tree
58
     * @param CountryService                $country_service
59
     * @param IndividualRepositoryInterface $individual_repository
60
     */
61
    public function __construct(
62
        Tree $tree,
63
        CountryService $country_service,
64
        IndividualRepositoryInterface $individual_repository
65
    ) {
66
        $this->tree                  = $tree;
67
        $this->country_service       = $country_service;
68
        $this->individual_repository = $individual_repository;
69
    }
70
71
    /**
72
     * Places
73
     *
74
     * @param string $fact
75
     * @param string $what
76
     *
77
     * @return array<int>
78
     */
79
    private function queryFactPlaces(string $fact, string $what): array
80
    {
81
        $rows = [];
82
83
        if ($what === 'INDI') {
84
            $rows = DB::table('individuals')
85
                ->select(['i_gedcom as tree'])
86
                ->where('i_file', '=', $this->tree->id())
87
                ->where('i_gedcom', 'LIKE', "%\n2 PLAC %")
88
                ->get()
89
                ->all();
90
        } elseif ($what === 'FAM') {
91
            $rows = DB::table('families')->select(['f_gedcom as tree'])
92
                ->where('f_file', '=', $this->tree->id())
93
                ->where('f_gedcom', 'LIKE', "%\n2 PLAC %")
94
                ->get()
95
                ->all();
96
        }
97
98
        $placelist = [];
99
100
        foreach ($rows as $row) {
101
            if (preg_match('/\n1 ' . $fact . '(?:\n[2-9].*)*\n2 PLAC (.+)/', $row->tree, $match)) {
102
                $place = $match[1];
103
104
                $placelist[$place] = ($placelist[$place] ?? 0) + 1;
105
            }
106
        }
107
108
        return $placelist;
109
    }
110
111
    /**
112
     * Get the top 10 places list.
113
     *
114
     * @param array<int> $places
115
     *
116
     * @return array<array<string,int|Place>>
117
     */
118
    private function getTop10Places(array $places): array
119
    {
120
        $top10 = [];
121
        $i     = 0;
122
123
        arsort($places);
124
125
        foreach ($places as $place => $count) {
126
            $tmp     = new Place((string) $place, $this->tree);
127
            $top10[] = [
128
                'place' => $tmp,
129
                'count' => $count,
130
            ];
131
132
            ++$i;
133
134
            if ($i === 10) {
135
                break;
136
            }
137
        }
138
139
        return $top10;
140
    }
141
142
    /**
143
     * Renders the top 10 places list.
144
     *
145
     * @param array<int|string,int> $places
146
     *
147
     * @return string
148
     */
149
    private function renderTop10(array $places): string
150
    {
151
        $top10Records = $this->getTop10Places($places);
152
153
        return view(
154
            'statistics/other/top10-list',
155
            [
156
                'records' => $top10Records,
157
            ]
158
        );
159
    }
160
161
    /**
162
     * A list of common birth places.
163
     *
164
     * @return string
165
     */
166
    public function commonBirthPlacesList(): string
167
    {
168
        $places = $this->queryFactPlaces('BIRT', 'INDI');
169
        return $this->renderTop10($places);
170
    }
171
172
    /**
173
     * A list of common death places.
174
     *
175
     * @return string
176
     */
177
    public function commonDeathPlacesList(): string
178
    {
179
        $places = $this->queryFactPlaces('DEAT', 'INDI');
180
        return $this->renderTop10($places);
181
    }
182
183
    /**
184
     * A list of common marriage places.
185
     *
186
     * @return string
187
     */
188
    public function commonMarriagePlacesList(): string
189
    {
190
        $places = $this->queryFactPlaces('MARR', 'FAM');
191
        return $this->renderTop10($places);
192
    }
193
194
    /**
195
     * A list of common countries.
196
     *
197
     * @return string
198
     */
199
    public function commonCountriesList(): string
200
    {
201
        $countries = DB::table('places')
202
            ->join('placelinks', static function (JoinClause $join): void {
203
                $join
204
                    ->on('pl_file', '=', 'p_file')
205
                    ->on('pl_p_id', '=', 'p_id');
206
            })
207
            ->where('p_file', '=', $this->tree->id())
208
            ->where('p_parent_id', '=', 0)
209
            ->groupBy(['p_place'])
210
            ->orderByDesc(new Expression('COUNT(*)'))
211
            ->orderBy('p_place')
212
            ->pluck(new Expression('COUNT(*)'), 'p_place')
213
            ->map(static fn(string $col): int => (int) $col)
214
            ->all();
215
216
        if ($countries === []) {
217
            return I18N::translate('This information is not available.');
218
        }
219
220
        $top10 = [];
221
        $i     = 1;
222
223
        // Get the country names for each language
224
        $country_names = [];
225
        $old_language = I18N::languageTag();
226
227
        foreach (I18N::activeLocales() as $locale) {
228
            I18N::init($locale->languageTag());
229
            $all_countries = $this->country_service->getAllCountries();
230
            foreach ($all_countries as $country_code => $country_name) {
231
                $country_names[$country_name] = $country_code;
232
            }
233
        }
234
235
        I18N::init($old_language);
236
237
        $all_db_countries = [];
238
239
        foreach ($countries as $country => $count) {
240
            if (array_key_exists($country, $country_names)) {
241
                if (isset($all_db_countries[$country_names[$country]][$country])) {
242
                    $all_db_countries[$country_names[$country]][$country] += (int) $count;
243
                } else {
244
                    $all_db_countries[$country_names[$country]][$country] = (int) $count;
245
                }
246
            }
247
        }
248
249
        // get all the user’s countries names
250
        $all_countries = $this->country_service->getAllCountries();
251
252
        foreach ($all_db_countries as $country_code => $country) {
253
            foreach ($country as $country_name => $tot) {
254
                $tmp = new Place($country_name, $this->tree);
255
256
                $top10[] = [
257
                    'place' => $tmp,
258
                    'count' => $tot,
259
                    'name'  => $all_countries[$country_code],
260
                ];
261
            }
262
263
            if ($i++ === 10) {
264
                break;
265
            }
266
        }
267
268
        return view(
269
            'statistics/other/top10-list',
270
            [
271
                'records' => $top10,
272
            ]
273
        );
274
    }
275
276
    /**
277
     * Count total places.
278
     *
279
     * @return int
280
     */
281
    private function totalPlacesQuery(): int
282
    {
283
        return DB::table('places')
284
            ->where('p_file', '=', $this->tree->id())
285
            ->count();
286
    }
287
288
    /**
289
     * Count total places.
290
     *
291
     * @return string
292
     */
293
    public function totalPlaces(): string
294
    {
295
        return I18N::number($this->totalPlacesQuery());
296
    }
297
298
    /**
299
     * Create a chart showing where events occurred.
300
     *
301
     * @param string $chart_shows
302
     * @param string $chart_type
303
     * @param string $surname
304
     *
305
     * @return string
306
     */
307
    public function chartDistribution(
308
        string $chart_shows = 'world',
309
        string $chart_type = '',
310
        string $surname = ''
311
    ): string {
312
        return (new ChartDistribution($this->tree, $this->country_service, $this->individual_repository))
313
            ->chartDistribution($chart_shows, $chart_type, $surname);
314
    }
315
}
316