Completed
Push — develop ( 8b0b98...d9b800 )
by Greg
26:49 queued 16:13
created

PlaceRepository::commonMarriagePlacesList()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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