xref: /webtrees/app/Http/RequestHandlers/MapDataImportAction.php (revision 08b5db2af7b56fafdef6563369bf89966bc0e451)
190949315SGreg Roach<?php
290949315SGreg Roach
390949315SGreg Roach/**
490949315SGreg Roach * webtrees: online genealogy
590949315SGreg Roach * Copyright (C) 2021 webtrees development team
690949315SGreg Roach * This program is free software: you can redistribute it and/or modify
790949315SGreg Roach * it under the terms of the GNU General Public License as published by
890949315SGreg Roach * the Free Software Foundation, either version 3 of the License, or
990949315SGreg Roach * (at your option) any later version.
1090949315SGreg Roach * This program is distributed in the hope that it will be useful,
1190949315SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
1290949315SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1390949315SGreg Roach * GNU General Public License for more details.
1490949315SGreg Roach * You should have received a copy of the GNU General Public License
1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
1690949315SGreg Roach */
1790949315SGreg Roach
1890949315SGreg Roachdeclare(strict_types=1);
1990949315SGreg Roach
2090949315SGreg Roachnamespace Fisharebest\Webtrees\Http\RequestHandlers;
2190949315SGreg Roach
2290949315SGreg Roachuse Exception;
2390949315SGreg Roachuse Fisharebest\Webtrees\FlashMessages;
2490949315SGreg Roachuse Fisharebest\Webtrees\Gedcom;
2590949315SGreg Roachuse Fisharebest\Webtrees\I18N;
2690949315SGreg Roachuse Fisharebest\Webtrees\PlaceLocation;
2790949315SGreg Roachuse Fisharebest\Webtrees\Registry;
2890949315SGreg Roachuse Fisharebest\Webtrees\Services\MapDataService;
2990949315SGreg Roachuse Illuminate\Database\Capsule\Manager as DB;
3090949315SGreg Roachuse Psr\Http\Message\ResponseInterface;
3190949315SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
3290949315SGreg Roachuse Psr\Http\Message\UploadedFileInterface;
3390949315SGreg Roachuse Psr\Http\Server\RequestHandlerInterface;
3490949315SGreg Roach
3590949315SGreg Roachuse function array_filter;
3690949315SGreg Roachuse function array_reverse;
3790949315SGreg Roachuse function array_slice;
3890949315SGreg Roachuse function count;
3990949315SGreg Roachuse function fclose;
4090949315SGreg Roachuse function fgetcsv;
4190949315SGreg Roachuse function implode;
4290949315SGreg Roachuse function is_numeric;
4390949315SGreg Roachuse function json_decode;
4490949315SGreg Roachuse function redirect;
4590949315SGreg Roachuse function rewind;
4690949315SGreg Roachuse function route;
4790949315SGreg Roachuse function str_contains;
4890949315SGreg Roachuse function stream_get_contents;
4990949315SGreg Roach
50*08b5db2aSGreg Roachuse const JSON_THROW_ON_ERROR;
5190949315SGreg Roachuse const UPLOAD_ERR_OK;
5290949315SGreg Roach
5390949315SGreg Roach/**
5490949315SGreg Roach * Import geographic data.
5590949315SGreg Roach */
5690949315SGreg Roachclass MapDataImportAction implements RequestHandlerInterface
5790949315SGreg Roach{
5890949315SGreg Roach    /**
5990949315SGreg Roach     * This function assumes the input file layout is
6090949315SGreg Roach     * level followed by a variable number of placename fields
6190949315SGreg Roach     * followed by Longitude, Latitude, Zoom & Icon
6290949315SGreg Roach     *
6390949315SGreg Roach     * @param ServerRequestInterface $request
6490949315SGreg Roach     *
6590949315SGreg Roach     * @return ResponseInterface
6690949315SGreg Roach     * @throws Exception
6790949315SGreg Roach     */
6890949315SGreg Roach    public function handle(ServerRequestInterface $request): ResponseInterface
6990949315SGreg Roach    {
7090949315SGreg Roach        $data_filesystem = Registry::filesystem()->data();
7190949315SGreg Roach
7290949315SGreg Roach        $params = (array) $request->getParsedBody();
7390949315SGreg Roach
7490949315SGreg Roach        $serverfile     = $params['serverfile'] ?? '';
7590949315SGreg Roach        $options        = $params['import-options'] ?? '';
7690949315SGreg Roach        $clear_database = (bool) ($params['cleardatabase'] ?? false);
7790949315SGreg Roach        $local_file     = $request->getUploadedFiles()['localfile'] ?? null;
7890949315SGreg Roach
7990949315SGreg Roach        $places = [];
8090949315SGreg Roach
8190949315SGreg Roach        $url = route(MapDataList::class, ['parent_id' => 0]);
8290949315SGreg Roach
8390949315SGreg Roach        $fp = false;
8490949315SGreg Roach
85f7cf8a15SGreg Roach        if ($serverfile !== '' && $data_filesystem->fileExists(MapDataService::PLACES_FOLDER . $serverfile)) {
8690949315SGreg Roach            // first choice is file on server
8790949315SGreg Roach            $fp = $data_filesystem->readStream(MapDataService::PLACES_FOLDER . $serverfile);
8890949315SGreg Roach        } elseif ($local_file instanceof UploadedFileInterface && $local_file->getError() === UPLOAD_ERR_OK) {
8990949315SGreg Roach            // 2nd choice is local file
9090949315SGreg Roach            $fp = $local_file->getStream()->detach();
9190949315SGreg Roach        }
9290949315SGreg Roach
9390949315SGreg Roach        if ($fp === false || $fp === null) {
9490949315SGreg Roach            return redirect($url);
9590949315SGreg Roach        }
9690949315SGreg Roach
9790949315SGreg Roach        $string = stream_get_contents($fp);
9890949315SGreg Roach
9990949315SGreg Roach        // Check the file type
10090949315SGreg Roach        if (str_contains($string, 'FeatureCollection')) {
101*08b5db2aSGreg Roach            $input_array = json_decode($string, false, 512, JSON_THROW_ON_ERROR);
10290949315SGreg Roach
10390949315SGreg Roach            foreach ($input_array->features as $feature) {
10490949315SGreg Roach                $places[] = [
10590949315SGreg Roach                    'latitude'  => $feature->geometry->coordinates[1],
10690949315SGreg Roach                    'longitude' => $feature->geometry->coordinates[0],
10790949315SGreg Roach                    'name'      => $feature->properties->name,
10890949315SGreg Roach                ];
10990949315SGreg Roach            }
11090949315SGreg Roach        } else {
11190949315SGreg Roach            rewind($fp);
11290949315SGreg Roach            while (($row = fgetcsv($fp, 0, MapDataService::CSV_SEPARATOR)) !== false) {
11390949315SGreg Roach                // Skip the header
11490949315SGreg Roach                if (!is_numeric($row[0])) {
11590949315SGreg Roach                    continue;
11690949315SGreg Roach                }
11790949315SGreg Roach
11890949315SGreg Roach                $level = (int) $row[0];
11990949315SGreg Roach                $count = count($row);
12090949315SGreg Roach                $name  = implode(Gedcom::PLACE_SEPARATOR, array_reverse(array_slice($row, 1, 1 + $level)));
12190949315SGreg Roach
12290949315SGreg Roach                $places[] = [
12390949315SGreg Roach                    'latitude'  => (float) strtr($row[$count - 3], ['N' => '', 'S' => '-', ',' => '.']),
12490949315SGreg Roach                    'longitude' => (float) strtr($row[$count - 4], ['E' => '', 'W' => '-', ',' => '.']),
12590949315SGreg Roach                    'name'      => $name
12690949315SGreg Roach                ];
12790949315SGreg Roach            }
12890949315SGreg Roach        }
12990949315SGreg Roach
13090949315SGreg Roach        fclose($fp);
13190949315SGreg Roach
13290949315SGreg Roach        if ($clear_database) {
13390949315SGreg Roach            // Child places are deleted via on-delete-cascade...
13490949315SGreg Roach            DB::table('place_location')
13590949315SGreg Roach                ->whereNull('parent_id')
13690949315SGreg Roach                ->delete();
13790949315SGreg Roach        }
13890949315SGreg Roach
13990949315SGreg Roach        $added   = 0;
14090949315SGreg Roach        $updated = 0;
14190949315SGreg Roach
14290949315SGreg Roach        // Remove places with 0,0 coordinates at lower levels.
14390949315SGreg Roach        $places = array_filter($places, static function ($place) {
14490949315SGreg Roach            return !str_contains($place['name'], ',') || $place['longitude'] !== 0.0 || $place['latitude'] !== 0.0;
14590949315SGreg Roach        });
14690949315SGreg Roach
14790949315SGreg Roach        foreach ($places as $place) {
14890949315SGreg Roach            $location = new PlaceLocation($place['name']);
14990949315SGreg Roach            $exists   = $location->exists();
15090949315SGreg Roach
15190949315SGreg Roach            // Only update existing records
15290949315SGreg Roach            if ($options === 'update' && !$exists) {
15390949315SGreg Roach                continue;
15490949315SGreg Roach            }
15590949315SGreg Roach
15690949315SGreg Roach            // Only add new records
15790949315SGreg Roach            if ($options === 'add' && $exists) {
15890949315SGreg Roach                continue;
15990949315SGreg Roach            }
16090949315SGreg Roach
16190949315SGreg Roach            if (!$exists) {
16290949315SGreg Roach                $added++;
16390949315SGreg Roach            }
16490949315SGreg Roach
16590949315SGreg Roach            $updated += DB::table('place_location')
16690949315SGreg Roach                ->where('id', '=', $location->id())
16790949315SGreg Roach                ->update([
16890949315SGreg Roach                    'latitude'  => $place['latitude'],
16990949315SGreg Roach                    'longitude' => $place['longitude'],
17090949315SGreg Roach                ]);
17190949315SGreg Roach        }
17290949315SGreg Roach
17390949315SGreg Roach        FlashMessages::addMessage(
17490949315SGreg Roach            I18N::translate('locations updated: %s, locations added: %s', I18N::number($updated), I18N::number($added)),
17590949315SGreg Roach            'info'
17690949315SGreg Roach        );
17790949315SGreg Roach
17890949315SGreg Roach        return redirect($url);
17990949315SGreg Roach    }
18090949315SGreg Roach}
181