xref: /webtrees/app/Http/RequestHandlers/MapDataExportCSV.php (revision 449b311ecf65f677a2595e1e29f712d11ef22f34)
190949315SGreg Roach<?php
290949315SGreg Roach
390949315SGreg Roach/**
490949315SGreg Roach * webtrees: online genealogy
5d11be702SGreg Roach * Copyright (C) 2023 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
226f4ec3caSGreg Roachuse Fisharebest\Webtrees\DB;
2390949315SGreg Roachuse Fisharebest\Webtrees\PlaceLocation;
2490949315SGreg Roachuse Fisharebest\Webtrees\Services\MapDataService;
2590949315SGreg Roachuse Psr\Http\Message\ResponseInterface;
2690949315SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
2790949315SGreg Roachuse Psr\Http\Server\RequestHandlerInterface;
2890949315SGreg Roachuse RuntimeException;
2990949315SGreg Roach
3090949315SGreg Roachuse function addcslashes;
3190949315SGreg Roachuse function array_map;
3290949315SGreg Roachuse function array_merge;
3390949315SGreg Roachuse function array_pad;
3490949315SGreg Roachuse function array_reverse;
3590949315SGreg Roachuse function array_shift;
3690949315SGreg Roachuse function array_unshift;
3790949315SGreg Roachuse function count;
3890949315SGreg Roachuse function fopen;
3990949315SGreg Roachuse function fputcsv;
4090949315SGreg Roachuse function max;
4190949315SGreg Roachuse function preg_replace;
4290949315SGreg Roachuse function response;
4390949315SGreg Roachuse function rewind;
4490949315SGreg Roachuse function stream_get_contents;
4590949315SGreg Roach
4690949315SGreg Roach/**
4790949315SGreg Roach * Export geographic data.
4890949315SGreg Roach */
4990949315SGreg Roachclass MapDataExportCSV implements RequestHandlerInterface
5090949315SGreg Roach{
51c4943cffSGreg Roach    private MapDataService $map_data_service;
5290949315SGreg Roach
5390949315SGreg Roach    /**
5490949315SGreg Roach     * Dependency injection.
5590949315SGreg Roach     *
5690949315SGreg Roach     * @param MapDataService $map_data_service
5790949315SGreg Roach     */
5890949315SGreg Roach    public function __construct(MapDataService $map_data_service)
5990949315SGreg Roach    {
6090949315SGreg Roach        $this->map_data_service = $map_data_service;
6190949315SGreg Roach    }
6290949315SGreg Roach
6390949315SGreg Roach    /**
6490949315SGreg Roach     * @param ServerRequestInterface $request
6590949315SGreg Roach     *
6690949315SGreg Roach     * @return ResponseInterface
6790949315SGreg Roach     */
6890949315SGreg Roach    public function handle(ServerRequestInterface $request): ResponseInterface
6990949315SGreg Roach    {
7090949315SGreg Roach        $parent_id = $request->getAttribute('parent_id');
7190949315SGreg Roach
7290949315SGreg Roach        if ($parent_id === null) {
7390949315SGreg Roach            $parent = new PlaceLocation('');
7490949315SGreg Roach        } else {
7590949315SGreg Roach            $parent = $this->map_data_service->findById((int) $parent_id);
7690949315SGreg Roach        }
7790949315SGreg Roach
7890949315SGreg Roach        for ($tmp = $parent, $hierarchy = []; $tmp->id() !== null; $tmp = $tmp->parent()) {
7990949315SGreg Roach            $hierarchy[] = $tmp->locationName();
8090949315SGreg Roach        }
8190949315SGreg Roach
8290949315SGreg Roach        // Create the file name
8390949315SGreg Roach        $filename = preg_replace('/[^\p{L}]+/u', '-', $hierarchy[0] ?? 'Global') . '.csv';
8490949315SGreg Roach
8590949315SGreg Roach        // Recursively search for child places
8690949315SGreg Roach        $places = [];
8790949315SGreg Roach        $queue  = [[
8890949315SGreg Roach            $parent->id(), array_reverse($hierarchy), $parent->latitude(), $parent->longitude()
8990949315SGreg Roach        ]];
9090949315SGreg Roach
9190949315SGreg Roach        while ($queue !== []) {
9290949315SGreg Roach            [$id, $hierarchy, $latitude, $longitude] = array_shift($queue);
9390949315SGreg Roach
949d188ea6SGreg Roach            if ($latitude !== null && $longitude !== null) {
9590949315SGreg Roach                $places[] = (object) [
9690949315SGreg Roach                    'hierarchy' => $hierarchy,
9790949315SGreg Roach                    'latitude'  => $latitude,
9890949315SGreg Roach                    'longitude' => $longitude,
9990949315SGreg Roach                ];
10090949315SGreg Roach            }
10190949315SGreg Roach
10290949315SGreg Roach            $query = DB::table('place_location');
10390949315SGreg Roach            // Data for the next level.
10490949315SGreg Roach
10590949315SGreg Roach            if ($id === null) {
10690949315SGreg Roach                $query->whereNull('parent_id');
10790949315SGreg Roach            } else {
10890949315SGreg Roach                $query->where('parent_id', '=', $id);
10990949315SGreg Roach            }
11090949315SGreg Roach
11190949315SGreg Roach            $rows = $query
11290949315SGreg Roach                ->orderBy('place', 'DESC')
11390949315SGreg Roach                ->select(['id', 'place', 'latitude', 'longitude'])
11490949315SGreg Roach                ->get();
11590949315SGreg Roach
11690949315SGreg Roach            $next_level = count($hierarchy);
11790949315SGreg Roach
11890949315SGreg Roach            foreach ($rows as $row) {
11990949315SGreg Roach                $hierarchy[$next_level] = $row->place;
12090949315SGreg Roach                array_unshift($queue, [$row->id, $hierarchy, $row->latitude, $row->longitude]);
12190949315SGreg Roach            }
12290949315SGreg Roach        }
12390949315SGreg Roach
12490949315SGreg Roach        // Pad all locations to the length of the longest.
12590949315SGreg Roach        $max_level = 0;
12690949315SGreg Roach        foreach ($places as $place) {
12790949315SGreg Roach            $max_level = max($max_level, count($place->hierarchy));
12890949315SGreg Roach        }
12990949315SGreg Roach
130*f25fc0f9SGreg Roach        $places = array_map(fn (object $place): array => array_merge(
13190949315SGreg Roach            [
13290949315SGreg Roach                count($place->hierarchy) - 1,
13390949315SGreg Roach            ],
13490949315SGreg Roach            array_pad($place->hierarchy, $max_level, ''),
13590949315SGreg Roach            [
13690949315SGreg Roach                $this->map_data_service->writeLongitude((float) $place->longitude),
13790949315SGreg Roach                $this->map_data_service->writeLatitude((float) $place->latitude),
13890949315SGreg Roach                '',
13990949315SGreg Roach                '',
14090949315SGreg Roach            ]
141*f25fc0f9SGreg Roach        ), $places);
14290949315SGreg Roach
14390949315SGreg Roach        // Create the header line for the output file (always English)
14490949315SGreg Roach        $header = [
14590949315SGreg Roach            'Level',
14690949315SGreg Roach        ];
14790949315SGreg Roach
14890949315SGreg Roach        for ($i = 0; $i < $max_level; $i++) {
14990949315SGreg Roach            $header[] = 'Place' . $i;
15090949315SGreg Roach        }
15190949315SGreg Roach
15290949315SGreg Roach        $header[] = 'Longitude';
15390949315SGreg Roach        $header[] = 'Latitude';
15490949315SGreg Roach        $header[] = 'Zoom';
15590949315SGreg Roach        $header[] = 'Icon';
15690949315SGreg Roach
157ea517a3bSGreg Roach        $resource = fopen('php://memory', 'wb+');
15890949315SGreg Roach
15990949315SGreg Roach        if ($resource === false) {
16090949315SGreg Roach            throw new RuntimeException('Failed to create temporary stream');
16190949315SGreg Roach        }
16290949315SGreg Roach
16390949315SGreg Roach        fputcsv($resource, $header, MapDataService::CSV_SEPARATOR);
16490949315SGreg Roach
16590949315SGreg Roach        foreach ($places as $place) {
16690949315SGreg Roach            fputcsv($resource, $place, MapDataService::CSV_SEPARATOR);
16790949315SGreg Roach        }
16890949315SGreg Roach
16990949315SGreg Roach        rewind($resource);
17090949315SGreg Roach
17190949315SGreg Roach        $filename = addcslashes($filename, '"');
17290949315SGreg Roach
17390949315SGreg Roach        return response(stream_get_contents($resource))
1746172e7f6SGreg Roach            ->withHeader('content-type', 'text/csv; charset=UTF-8')
1756172e7f6SGreg Roach            ->withHeader('content-disposition', 'attachment; filename="' . $filename . '"');
17690949315SGreg Roach    }
17790949315SGreg Roach}
178