MapDataExportCSV::handle()   F
last analyzed

Complexity

Conditions 12
Paths 432

Size

Total Lines 110
Code Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 62
nc 432
nop 1
dl 0
loc 110
rs 3.7002
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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