MapDataExportCSV::handle()   F
last analyzed

Complexity

Conditions 12
Paths 432

Size

Total Lines 108
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 61
nc 432
nop 1
dl 0
loc 108
rs 3.722
c 0
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) 2023 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(fn (object $place): array => array_merge(
131
            [
132
                count($place->hierarchy) - 1,
133
            ],
134
            array_pad($place->hierarchy, $max_level, ''),
135
            [
136
                $this->map_data_service->writeLongitude((float) $place->longitude),
137
                $this->map_data_service->writeLatitude((float) $place->latitude),
138
                '',
139
                '',
140
            ]
141
        ), $places);
142
143
        // Create the header line for the output file (always English)
144
        $header = [
145
            'Level',
146
        ];
147
148
        for ($i = 0; $i < $max_level; $i++) {
149
            $header[] = 'Place' . $i;
150
        }
151
152
        $header[] = 'Longitude';
153
        $header[] = 'Latitude';
154
        $header[] = 'Zoom';
155
        $header[] = 'Icon';
156
157
        $resource = fopen('php://memory', 'wb+');
158
159
        if ($resource === false) {
160
            throw new RuntimeException('Failed to create temporary stream');
161
        }
162
163
        fputcsv($resource, $header, MapDataService::CSV_SEPARATOR);
164
165
        foreach ($places as $place) {
166
            fputcsv($resource, $place, MapDataService::CSV_SEPARATOR);
167
        }
168
169
        rewind($resource);
170
171
        $filename = addcslashes($filename, '"');
172
173
        return response(stream_get_contents($resource))
174
            ->withHeader('content-type', 'text/csv; charset=UTF-8')
175
            ->withHeader('content-disposition', 'attachment; filename="' . $filename . '"');
176
    }
177
}
178