Passed
Push — 2.0 ( 401112...fa4b31 )
by Greg
13:39
created

PlaceRepository::statsPlaces()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 55
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

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

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) 2021 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\PlaceRepositoryInterface;
30
use Fisharebest\Webtrees\Statistics\Service\CountryService;
31
use Fisharebest\Webtrees\Tree;
32
use Illuminate\Database\Capsule\Manager as DB;
33
use Illuminate\Database\Query\JoinClause;
34
use stdClass;
35
36
use function array_key_exists;
37
use function arsort;
38
use function end;
39
use function explode;
40
use function preg_match;
41
use function trim;
42
use function view;
43
44
/**
45
 * A repository providing methods for place related statistics.
46
 */
47
class PlaceRepository implements PlaceRepositoryInterface
48
{
49
    /**
50
     * @var Tree
51
     */
52
    private $tree;
53
54
    /**
55
     * @var CountryService
56
     */
57
    private $country_service;
58
59
    /**
60
     * @param Tree $tree
61
     */
62
    public function __construct(Tree $tree)
63
    {
64
        $this->tree          = $tree;
65
        $this->country_service = new CountryService();
66
    }
67
68
    /**
69
     * Places
70
     *
71
     * @param string $fact
72
     * @param string $what
73
     * @param bool   $country
74
     *
75
     * @return array<int|string,int>
76
     */
77
    private function queryFactPlaces(string $fact, string $what = 'ALL', bool $country = false): array
78
    {
79
        $rows = [];
80
81
        if ($what === 'INDI') {
82
            $rows = DB::table('individuals')
83
                ->select(['i_gedcom as tree'])
84
                ->where('i_file', '=', $this->tree->id())
85
                ->where('i_gedcom', 'LIKE', "%\n2 PLAC %")
86
                ->get()
87
                ->all();
88
        } elseif ($what === 'FAM') {
89
            $rows = DB::table('families')->select(['f_gedcom as tree'])
90
                ->where('f_file', '=', $this->tree->id())
91
                ->where('f_gedcom', 'LIKE', "%\n2 PLAC %")
92
                ->get()
93
                ->all();
94
        }
95
96
        $placelist = [];
97
98
        foreach ($rows as $row) {
99
            if (preg_match('/\n1 ' . $fact . '(?:\n[2-9].*)*\n2 PLAC (.+)/', $row->tree, $match)) {
100
                if ($country) {
101
                    $tmp   = explode(Gedcom::PLACE_SEPARATOR, $match[1]);
102
                    $place = end($tmp);
103
                } else {
104
                    $place = $match[1];
105
                }
106
107
                if (isset($placelist[$place])) {
108
                    ++$placelist[$place];
109
                } else {
110
                    $placelist[$place] = 1;
111
                }
112
            }
113
        }
114
115
        return $placelist;
116
    }
117
118
    /**
119
     * Query places.
120
     *
121
     * @param string $what
122
     * @param string $fact
123
     * @param int    $parent
124
     * @param bool   $country
125
     *
126
     * @return int[]|stdClass[]
127
     */
128
    public function statsPlaces(string $what = 'ALL', string $fact = '', int $parent = 0, bool $country = false): array
129
    {
130
        if ($fact) {
131
            return $this->queryFactPlaces($fact, $what, $country);
132
        }
133
134
        $query = DB::table('places')
135
            ->join('placelinks', static function (JoinClause $join): void {
136
                $join->on('pl_file', '=', 'p_file')
137
                    ->on('pl_p_id', '=', 'p_id');
138
            })
139
            ->where('p_file', '=', $this->tree->id());
140
141
        if ($parent > 0) {
142
            // Used by placehierarchy map modules
143
            $query->select(['p_place AS place'])
144
                ->selectRaw('COUNT(*) AS tot')
145
                ->where('p_id', '=', $parent)
146
                ->groupBy(['place']);
147
        } else {
148
            $query->select(['p_place AS country'])
149
                ->selectRaw('COUNT(*) AS tot')
150
                ->where('p_parent_id', '=', 0)
151
                ->groupBy(['country'])
152
                ->orderByDesc('tot')
153
                ->orderBy('country');
154
        }
155
156
        if ($what === Individual::RECORD_TYPE) {
157
            $query->join('individuals', static function (JoinClause $join): void {
158
                $join->on('pl_file', '=', 'i_file')
159
                    ->on('pl_gid', '=', 'i_id');
160
            });
161
        } elseif ($what === Family::RECORD_TYPE) {
162
            $query->join('families', static function (JoinClause $join): void {
163
                $join->on('pl_file', '=', 'f_file')
164
                    ->on('pl_gid', '=', 'f_id');
165
            });
166
        } elseif ($what === Location::RECORD_TYPE) {
167
            $query->join('other', static function (JoinClause $join): void {
168
                $join->on('pl_file', '=', 'o_file')
169
                    ->on('pl_gid', '=', 'o_id');
170
            })
171
                ->where('o_type', '=', Location::RECORD_TYPE);
172
        }
173
174
        return $query
175
            ->get()
176
            ->map(static function (stdClass $entry) {
177
                // Map total value to integer
178
                $entry->tot = (int) $entry->tot;
179
180
                return $entry;
181
            })
182
            ->all();
183
    }
184
185
    /**
186
     * Get the top 10 places list.
187
     *
188
     * @param array<int> $places
189
     *
190
     * @return array<array<string,int|Place>>
191
     */
192
    private function getTop10Places(array $places): array
193
    {
194
        $top10 = [];
195
        $i     = 0;
196
197
        arsort($places);
198
199
        foreach ($places as $place => $count) {
200
            $tmp     = new Place((string) $place, $this->tree);
201
            $top10[] = [
202
                'place' => $tmp,
203
                'count' => $count,
204
            ];
205
206
            ++$i;
207
208
            if ($i === 10) {
209
                break;
210
            }
211
        }
212
213
        return $top10;
214
    }
215
216
    /**
217
     * Renders the top 10 places list.
218
     *
219
     * @param array<int|string,int> $places
220
     *
221
     * @return string
222
     */
223
    private function renderTop10(array $places): string
224
    {
225
        $top10Records = $this->getTop10Places($places);
226
227
        return view(
228
            'statistics/other/top10-list',
229
            [
230
                'records' => $top10Records,
231
            ]
232
        );
233
    }
234
235
    /**
236
     * A list of common birth places.
237
     *
238
     * @return string
239
     */
240
    public function commonBirthPlacesList(): string
241
    {
242
        $places = $this->queryFactPlaces('BIRT', 'INDI');
243
        return $this->renderTop10($places);
244
    }
245
246
    /**
247
     * A list of common death places.
248
     *
249
     * @return string
250
     */
251
    public function commonDeathPlacesList(): string
252
    {
253
        $places = $this->queryFactPlaces('DEAT', 'INDI');
254
        return $this->renderTop10($places);
255
    }
256
257
    /**
258
     * A list of common marriage places.
259
     *
260
     * @return string
261
     */
262
    public function commonMarriagePlacesList(): string
263
    {
264
        $places = $this->queryFactPlaces('MARR', 'FAM');
265
        return $this->renderTop10($places);
266
    }
267
268
    /**
269
     * A list of common countries.
270
     *
271
     * @return string
272
     */
273
    public function commonCountriesList(): string
274
    {
275
        $countries = $this->statsPlaces();
276
277
        if ($countries === []) {
278
            return '';
279
        }
280
281
        $top10 = [];
282
        $i     = 1;
283
284
        // Get the country names for each language
285
        $country_names = [];
286
        $old_language = I18N::languageTag();
287
288
        foreach (I18N::activeLocales() as $locale) {
289
            I18N::init($locale->languageTag());
290
            $all_countries = $this->country_service->getAllCountries();
291
            foreach ($all_countries as $country_code => $country_name) {
292
                $country_names[$country_name] = $country_code;
293
            }
294
        }
295
296
        I18N::init($old_language);
297
298
        $all_db_countries = [];
299
        foreach ($countries as $place) {
300
            $country = trim($place->country);
301
            if (array_key_exists($country, $country_names)) {
302
                if (isset($all_db_countries[$country_names[$country]][$country])) {
303
                    $all_db_countries[$country_names[$country]][$country] += (int) $place->tot;
304
                } else {
305
                    $all_db_countries[$country_names[$country]][$country] = (int) $place->tot;
306
                }
307
            }
308
        }
309
310
        // get all the user’s countries names
311
        $all_countries = $this->country_service->getAllCountries();
312
313
        foreach ($all_db_countries as $country_code => $country) {
314
            foreach ($country as $country_name => $tot) {
315
                $tmp     = new Place($country_name, $this->tree);
316
317
                $top10[] = [
318
                    'place' => $tmp,
319
                    'count' => $tot,
320
                    'name'  => $all_countries[$country_code],
321
                ];
322
            }
323
324
            if ($i++ === 10) {
325
                break;
326
            }
327
        }
328
329
        return view(
330
            'statistics/other/top10-list',
331
            [
332
                'records' => $top10,
333
            ]
334
        );
335
    }
336
337
    /**
338
     * Count total places.
339
     *
340
     * @return int
341
     */
342
    private function totalPlacesQuery(): int
343
    {
344
        return DB::table('places')
345
            ->where('p_file', '=', $this->tree->id())
346
            ->count();
347
    }
348
349
    /**
350
     * Count total places.
351
     *
352
     * @return string
353
     */
354
    public function totalPlaces(): string
355
    {
356
        return I18N::number($this->totalPlacesQuery());
357
    }
358
359
    /**
360
     * Create a chart showing where events occurred.
361
     *
362
     * @param string $chart_shows
363
     * @param string $chart_type
364
     * @param string $surname
365
     *
366
     * @return string
367
     */
368
    public function chartDistribution(
369
        string $chart_shows = 'world',
370
        string $chart_type = '',
371
        string $surname = ''
372
    ): string {
373
        return (new ChartDistribution($this->tree))
374
            ->chartDistribution($chart_shows, $chart_type, $surname);
375
    }
376
}
377