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