xref: /webtrees/app/Module/ModuleMapGeoLocationTrait.php (revision 2978efea0c934a7f830579d20c3e8661e837b748)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2023 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\Module;
21
22use Fig\Http\Message\StatusCodeInterface;
23use Fisharebest\Webtrees\Html;
24use Fisharebest\Webtrees\I18N;
25use Fisharebest\Webtrees\Registry;
26use GuzzleHttp\Client;
27use GuzzleHttp\Psr7\Request;
28use JsonException;
29use Psr\Http\Message\RequestInterface;
30use Psr\Http\Message\ResponseInterface;
31
32use function json_decode;
33use function strlen;
34
35use const JSON_THROW_ON_ERROR;
36
37/**
38 * Trait ModuleMapGeoLocationTrait - default implementation of ModuleMapGeoLocationInterface
39 */
40trait ModuleMapGeoLocationTrait
41{
42    /**
43     * A unique internal name for this module (based on the installation folder).
44     *
45     * @return string
46     */
47    abstract public function name(): string;
48
49    /**
50     * A sentence describing what this module does.
51     *
52     * @return string
53     */
54    public function description(): string
55    {
56        return I18N::translate('Use an external service to find locations.');
57    }
58
59    /**
60     * @param string $place
61     *
62     * @return array<string>
63     */
64    public function searchPlaceNames(string $place): array
65    {
66        if (strlen($place) <= 2) {
67            return [];
68        }
69
70        $key   = $this->name() . $place;
71        $cache = Registry::cache()->file();
72        $ttl   = 86400;
73
74        return $cache->remember($key, function () use ($place) {
75            $request = $this->searchLocationsRequest($place);
76
77            $client = new Client([
78                'timeout' => 3,
79            ]);
80
81            $response = $client->send($request);
82
83            if ($response->getStatusCode() === StatusCodeInterface::STATUS_OK) {
84                return $this->extractLocationsFromResponse($response);
85            }
86
87            return [];
88        }, $ttl);
89    }
90
91    /**
92     * @param string $place
93     *
94     * @return RequestInterface
95     */
96    protected function searchLocationsRequest(string $place): RequestInterface
97    {
98        $uri = Html::url('https://nominatim.openstreetmap.org/search', [
99            'accept-language' => I18N::languageTag(),
100            'format'          => 'jsonv2',
101            'limit'           => 50,
102            'q'               => $place,
103        ]);
104
105        return new Request('GET', $uri);
106    }
107
108    /**
109     * @param ResponseInterface $response
110     *
111     * @return array<string>
112     */
113    protected function extractLocationsFromResponse(ResponseInterface $response): array
114    {
115        $body = $response->getBody()->getContents();
116
117        try {
118            return json_decode($body, false, 512, JSON_THROW_ON_ERROR);
119        } catch (JsonException) {
120            return [];
121        }
122    }
123}
124