xref: /webtrees/app/Module/GeonamesAutocomplete.php (revision 28d026ad36e53e5af3ffb5b483ee815e04b18ecf)
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 Fisharebest\Webtrees\FlashMessages;
23use Fisharebest\Webtrees\Gedcom;
24use Fisharebest\Webtrees\Html;
25use Fisharebest\Webtrees\I18N;
26use Fisharebest\Webtrees\Site;
27use Fisharebest\Webtrees\Validator;
28use GuzzleHttp\Psr7\Request;
29use Psr\Http\Message\RequestInterface;
30use Psr\Http\Message\ResponseInterface;
31use Psr\Http\Message\ServerRequestInterface;
32
33use function array_filter;
34use function implode;
35use function json_decode;
36use function redirect;
37use function usort;
38
39use const JSON_THROW_ON_ERROR;
40
41/**
42 * Class GeonamesAutocomplete - use geonames.org to search for place names
43 */
44class GeonamesAutocomplete extends AbstractModule implements ModuleConfigInterface, ModuleMapAutocompleteInterface
45{
46    use ModuleConfigTrait;
47    use ModuleMapAutocompleteTrait;
48
49    /**
50     * Name of the map provider.
51     *
52     * @return string
53     */
54    public function title(): string
55    {
56        // I18N: https://www.geonames.org
57        return I18N::translate('GeoNames');
58    }
59
60    /**
61     * Name of the map provider.
62     *
63     * @return string
64     */
65    public function description(): string
66    {
67        $link = '<a href="https://geonames.org">geonames.org</a>';
68
69        return I18N::translate('Search for place names using %s.', $link);
70    }
71
72    /**
73     * Should this module be enabled when it is first installed?
74     *
75     * @return bool
76     */
77    public function isEnabledByDefault(): bool
78    {
79        return false;
80    }
81
82    /**
83     * @return ResponseInterface
84     */
85    public function getAdminAction(): ResponseInterface
86    {
87        $this->layout = 'layouts/administration';
88
89        // This was a global setting before it became a module setting...
90        $default  = Site::getPreference('geonames');
91        $username = $this->getPreference('username', $default);
92
93        return $this->viewResponse('modules/geonames/config', [
94            'username' => $username,
95            'title'    => $this->title(),
96        ]);
97    }
98
99    /**
100     * @param ServerRequestInterface $request
101     *
102     * @return ResponseInterface
103     */
104    public function postAdminAction(ServerRequestInterface $request): ResponseInterface
105    {
106        $username = Validator::parsedBody($request)->string('username');
107
108        $this->setPreference('username', $username);
109
110        FlashMessages::addMessage(I18N::translate('The preferences for the module “%s” have been updated.', $this->title()), 'success');
111
112        return redirect($this->getConfigLink());
113    }
114
115    /**
116     * @param string $place
117     *
118     * @return RequestInterface
119     */
120    protected function createPlaceNameSearchRequest(string $place): RequestInterface
121    {
122        // This was a global setting before it became a module setting...
123        $default  = Site::getPreference('geonames');
124        $username = $this->getPreference('username', $default);
125
126        $uri = Html::url('https://secure.geonames.org/searchJSON', [
127            'name_startsWith' => $place,
128            'featureClass'    => 'P',
129            'lang'            => I18N::languageTag(),
130            'username'        => $username,
131        ]);
132
133        return new Request('GET', $uri);
134    }
135
136    /**
137     * @param ResponseInterface $response
138     *
139     * @return array<string>
140     */
141    protected function parsePlaceNameSearchResponse(ResponseInterface $response): array
142    {
143        $body    = $response->getBody()->getContents();
144        $places  = [];
145        $results = json_decode($body, false, 512, JSON_THROW_ON_ERROR);
146
147        foreach ($results->geonames as $result) {
148            if (($result->countryName ?? null) === 'United Kingdom') {
149                // adminName1 will be England, Scotland, etc.
150                $result->countryName = null;
151            }
152
153            $parts = [
154                $result->name,
155                $result->adminName2 ?? null,
156                $result->adminName1 ?? null,
157                $result->countryName ?? null,
158            ];
159
160            $places[] = implode(Gedcom::PLACE_SEPARATOR, array_filter($parts));
161        }
162
163        usort($places, I18N::comparator());
164
165        return $places;
166    }
167}
168