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