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