xref: /webtrees/app/Module/ModuleMapGeoLocationTrait.php (revision 7413816e6dd2d50e569034fb804f3dce7471bb94)
1c9c6f2ecSGreg Roach<?php
2c9c6f2ecSGreg Roach
3c9c6f2ecSGreg Roach/**
4c9c6f2ecSGreg Roach * webtrees: online genealogy
5*d11be702SGreg Roach * Copyright (C) 2023 webtrees development team
6c9c6f2ecSGreg Roach * This program is free software: you can redistribute it and/or modify
7c9c6f2ecSGreg Roach * it under the terms of the GNU General Public License as published by
8c9c6f2ecSGreg Roach * the Free Software Foundation, either version 3 of the License, or
9c9c6f2ecSGreg Roach * (at your option) any later version.
10c9c6f2ecSGreg Roach * This program is distributed in the hope that it will be useful,
11c9c6f2ecSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
12c9c6f2ecSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13c9c6f2ecSGreg Roach * GNU General Public License for more details.
14c9c6f2ecSGreg Roach * You should have received a copy of the GNU General Public License
15c9c6f2ecSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
16c9c6f2ecSGreg Roach */
17c9c6f2ecSGreg Roach
18c9c6f2ecSGreg Roachdeclare(strict_types=1);
19c9c6f2ecSGreg Roach
20c9c6f2ecSGreg Roachnamespace Fisharebest\Webtrees\Module;
21c9c6f2ecSGreg Roach
22c9c6f2ecSGreg Roachuse Fig\Http\Message\StatusCodeInterface;
23c9c6f2ecSGreg Roachuse Fisharebest\Webtrees\Html;
24c9c6f2ecSGreg Roachuse Fisharebest\Webtrees\I18N;
25c9c6f2ecSGreg Roachuse Fisharebest\Webtrees\Registry;
26c9c6f2ecSGreg Roachuse GuzzleHttp\Client;
27c9c6f2ecSGreg Roachuse GuzzleHttp\Psr7\Request;
28c9c6f2ecSGreg Roachuse JsonException;
29c9c6f2ecSGreg Roachuse Psr\Http\Message\RequestInterface;
30c9c6f2ecSGreg Roachuse Psr\Http\Message\ResponseInterface;
31c9c6f2ecSGreg Roach
32c9c6f2ecSGreg Roachuse function json_decode;
3310e06497SGreg Roachuse function strlen;
34c9c6f2ecSGreg Roach
35c9c6f2ecSGreg Roachuse const JSON_THROW_ON_ERROR;
36c9c6f2ecSGreg Roach
37c9c6f2ecSGreg Roach/**
38c9c6f2ecSGreg Roach * Trait ModuleMapGeoLocationTrait - default implementation of ModuleMapGeoLocationInterface
39c9c6f2ecSGreg Roach */
40c9c6f2ecSGreg Roachtrait ModuleMapGeoLocationTrait
41c9c6f2ecSGreg Roach{
42c9c6f2ecSGreg Roach    /**
43bfed30e4SGreg Roach     * A unique internal name for this module (based on the installation folder).
44bfed30e4SGreg Roach     *
45bfed30e4SGreg Roach     * @return string
46bfed30e4SGreg Roach     */
47bfed30e4SGreg Roach    abstract public function name(): string;
48bfed30e4SGreg Roach
49c9c6f2ecSGreg Roach    public function description(): string
50c9c6f2ecSGreg Roach    {
51c9c6f2ecSGreg Roach        return I18N::translate('Use an external service to find locations.');
52c9c6f2ecSGreg Roach    }
53c9c6f2ecSGreg Roach
54c9c6f2ecSGreg Roach    /**
55c9c6f2ecSGreg Roach     * @param string $place
56c9c6f2ecSGreg Roach     *
57c9c6f2ecSGreg Roach     * @return array<string>
58c9c6f2ecSGreg Roach     */
59c9c6f2ecSGreg Roach    public function searchPlaceNames(string $place): array
60c9c6f2ecSGreg Roach    {
61c9c6f2ecSGreg Roach        if (strlen($place) <= 2) {
62c9c6f2ecSGreg Roach            return [];
63c9c6f2ecSGreg Roach        }
64c9c6f2ecSGreg Roach
65c9c6f2ecSGreg Roach        $key   = $this->name() . $place;
66c9c6f2ecSGreg Roach        $cache = Registry::cache()->file();
67c9c6f2ecSGreg Roach        $ttl   = 86400;
68c9c6f2ecSGreg Roach
69c9c6f2ecSGreg Roach        return $cache->remember($key, function () use ($place) {
70c9c6f2ecSGreg Roach            $request = $this->searchLocationsRequest($place);
71c9c6f2ecSGreg Roach
72c9c6f2ecSGreg Roach            $client = new Client([
73c9c6f2ecSGreg Roach                'timeout' => 3,
74c9c6f2ecSGreg Roach            ]);
75c9c6f2ecSGreg Roach
76c9c6f2ecSGreg Roach            $response = $client->send($request);
77c9c6f2ecSGreg Roach
78c9c6f2ecSGreg Roach            if ($response->getStatusCode() === StatusCodeInterface::STATUS_OK) {
79c9c6f2ecSGreg Roach                return $this->extractLocationsFromResponse($response);
80c9c6f2ecSGreg Roach            }
81c9c6f2ecSGreg Roach
82c9c6f2ecSGreg Roach            return [];
83c9c6f2ecSGreg Roach        }, $ttl);
84c9c6f2ecSGreg Roach    }
85c9c6f2ecSGreg Roach
86c9c6f2ecSGreg Roach    /**
87c9c6f2ecSGreg Roach     * @param string $place
88c9c6f2ecSGreg Roach     *
89c9c6f2ecSGreg Roach     * @return RequestInterface
90c9c6f2ecSGreg Roach     */
91c9c6f2ecSGreg Roach    protected function searchLocationsRequest(string $place): RequestInterface
92c9c6f2ecSGreg Roach    {
93c9c6f2ecSGreg Roach        $uri = Html::url('https://nominatim.openstreetmap.org/search', [
94c9c6f2ecSGreg Roach            'accept-language' => I18N::languageTag(),
95c9c6f2ecSGreg Roach            'format'          => 'jsonv2',
96c9c6f2ecSGreg Roach            'limit'           => 50,
97c9c6f2ecSGreg Roach            'q'               => $place,
98c9c6f2ecSGreg Roach        ]);
99c9c6f2ecSGreg Roach
100c9c6f2ecSGreg Roach        return new Request('GET', $uri);
101c9c6f2ecSGreg Roach    }
102c9c6f2ecSGreg Roach
103c9c6f2ecSGreg Roach    /**
104c9c6f2ecSGreg Roach     * @param ResponseInterface $response
105c9c6f2ecSGreg Roach     *
106c9c6f2ecSGreg Roach     * @return array<string>
107c9c6f2ecSGreg Roach     */
108c9c6f2ecSGreg Roach    protected function extractLocationsFromResponse(ResponseInterface $response): array
109c9c6f2ecSGreg Roach    {
110c9c6f2ecSGreg Roach        $body = $response->getBody()->getContents();
111c9c6f2ecSGreg Roach
112c9c6f2ecSGreg Roach        try {
113d85de00fSGreg Roach            return json_decode($body, false, 512, JSON_THROW_ON_ERROR);
11428d026adSGreg Roach        } catch (JsonException) {
115c9c6f2ecSGreg Roach            return [];
116c9c6f2ecSGreg Roach        }
117c9c6f2ecSGreg Roach    }
118c9c6f2ecSGreg Roach}
119