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