xref: /webtrees/app/Http/RequestHandlers/MapDataExportCSV.php (revision 354a9dba5390121e4a1e00e10c580c5313773f45)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2021 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\Http\RequestHandlers;
21
22use Fisharebest\Webtrees\PlaceLocation;
23use Fisharebest\Webtrees\Services\MapDataService;
24use Illuminate\Database\Capsule\Manager as DB;
25use Psr\Http\Message\ResponseInterface;
26use Psr\Http\Message\ServerRequestInterface;
27use Psr\Http\Server\RequestHandlerInterface;
28use RuntimeException;
29use stdClass;
30
31use function addcslashes;
32use function array_map;
33use function array_merge;
34use function array_pad;
35use function array_reverse;
36use function array_shift;
37use function array_unshift;
38use function count;
39use function fopen;
40use function fputcsv;
41use function max;
42use function preg_replace;
43use function response;
44use function rewind;
45use function stream_get_contents;
46
47/**
48 * Export geographic data.
49 */
50class MapDataExportCSV implements RequestHandlerInterface
51{
52    /** @var MapDataService */
53    private $map_data_service;
54
55    /**
56     * Dependency injection.
57     *
58     * @param MapDataService $map_data_service
59     */
60    public function __construct(MapDataService $map_data_service)
61    {
62        $this->map_data_service = $map_data_service;
63    }
64
65    /**
66     * @param ServerRequestInterface $request
67     *
68     * @return ResponseInterface
69     */
70    public function handle(ServerRequestInterface $request): ResponseInterface
71    {
72        $parent_id = $request->getAttribute('parent_id');
73
74        if ($parent_id === null) {
75            $parent = new PlaceLocation('');
76        } else {
77            $parent = $this->map_data_service->findById((int) $parent_id);
78        }
79
80        for ($tmp = $parent, $hierarchy = []; $tmp->id() !== null; $tmp = $tmp->parent()) {
81            $hierarchy[] = $tmp->locationName();
82        }
83
84        // Create the file name
85        $filename = preg_replace('/[^\p{L}]+/u', '-', $hierarchy[0] ?? 'Global') . '.csv';
86
87        // Recursively search for child places
88        $places = [];
89        $queue  = [[
90            $parent->id(), array_reverse($hierarchy), $parent->latitude(), $parent->longitude()
91        ]];
92
93        while ($queue !== []) {
94            [$id, $hierarchy, $latitude, $longitude] = array_shift($queue);
95
96            if ($latitude !== null && !$longitude !== null) {
97                $places[] = (object) [
98                    'hierarchy' => $hierarchy,
99                    'latitude'  => $latitude,
100                    'longitude' => $longitude,
101                ];
102            }
103
104            $query = DB::table('place_location');
105            // Data for the next level.
106
107            if ($id === null) {
108                $query->whereNull('parent_id');
109            } else {
110                $query->where('parent_id', '=', $id);
111            }
112
113            $rows = $query
114                ->orderBy('place', 'DESC')
115                ->select(['id', 'place', 'latitude', 'longitude'])
116                ->get();
117
118            $next_level = count($hierarchy);
119
120            foreach ($rows as $row) {
121                $hierarchy[$next_level] = $row->place;
122                array_unshift($queue, [$row->id, $hierarchy, $row->latitude, $row->longitude]);
123            }
124        }
125
126        // Pad all locations to the length of the longest.
127        $max_level = 0;
128        foreach ($places as $place) {
129            $max_level = max($max_level, count($place->hierarchy));
130        }
131
132        $places = array_map(function (stdClass $place) use ($max_level): array {
133            return array_merge(
134                [
135                    count($place->hierarchy) - 1,
136                ],
137                array_pad($place->hierarchy, $max_level, ''),
138                [
139                    $this->map_data_service->writeLongitude((float) $place->longitude),
140                    $this->map_data_service->writeLatitude((float) $place->latitude),
141                    '',
142                    '',
143                ]
144            );
145        }, $places);
146
147        // Create the header line for the output file (always English)
148        $header = [
149            'Level',
150        ];
151
152        for ($i = 0; $i < $max_level; $i++) {
153            $header[] = 'Place' . $i;
154        }
155
156        $header[] = 'Longitude';
157        $header[] = 'Latitude';
158        $header[] = 'Zoom';
159        $header[] = 'Icon';
160
161        $resource = fopen('php://temp', 'wb+');
162
163        if ($resource === false) {
164            throw new RuntimeException('Failed to create temporary stream');
165        }
166
167        fputcsv($resource, $header, MapDataService::CSV_SEPARATOR);
168
169        foreach ($places as $place) {
170            fputcsv($resource, $place, MapDataService::CSV_SEPARATOR);
171        }
172
173        rewind($resource);
174
175        $filename = addcslashes($filename, '"');
176
177        return response(stream_get_contents($resource))
178            ->withHeader('Content-Type', 'text/csv; charset=UTF-8')
179            ->withHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
180    }
181}
182