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