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