xref: /webtrees/app/Module/ModuleMapGeoLocationTrait.php (revision 7413816e6dd2d50e569034fb804f3dce7471bb94)
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    public function description(): string
50    {
51        return I18N::translate('Use an external service to find locations.');
52    }
53
54    /**
55     * @param string $place
56     *
57     * @return array<string>
58     */
59    public function searchPlaceNames(string $place): array
60    {
61        if (strlen($place) <= 2) {
62            return [];
63        }
64
65        $key   = $this->name() . $place;
66        $cache = Registry::cache()->file();
67        $ttl   = 86400;
68
69        return $cache->remember($key, function () use ($place) {
70            $request = $this->searchLocationsRequest($place);
71
72            $client = new Client([
73                'timeout' => 3,
74            ]);
75
76            $response = $client->send($request);
77
78            if ($response->getStatusCode() === StatusCodeInterface::STATUS_OK) {
79                return $this->extractLocationsFromResponse($response);
80            }
81
82            return [];
83        }, $ttl);
84    }
85
86    /**
87     * @param string $place
88     *
89     * @return RequestInterface
90     */
91    protected function searchLocationsRequest(string $place): RequestInterface
92    {
93        $uri = Html::url('https://nominatim.openstreetmap.org/search', [
94            'accept-language' => I18N::languageTag(),
95            'format'          => 'jsonv2',
96            'limit'           => 50,
97            'q'               => $place,
98        ]);
99
100        return new Request('GET', $uri);
101    }
102
103    /**
104     * @param ResponseInterface $response
105     *
106     * @return array<string>
107     */
108    protected function extractLocationsFromResponse(ResponseInterface $response): array
109    {
110        $body = $response->getBody()->getContents();
111
112        try {
113            return json_decode($body, false, 512, JSON_THROW_ON_ERROR);
114        } catch (JsonException) {
115            return [];
116        }
117    }
118}
119