_load_datamanagers()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.25

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 1
dl 0
loc 12
ccs 6
cts 8
cp 0.75
crap 4.25
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package midcom.baseclasses
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
use midcom\datamanager\datamanager;
10
use Symfony\Component\HttpFoundation\Request;
11
use Symfony\Component\HttpFoundation\StreamedResponse;
12
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
13
14
/**
15
 * Generic CSV export handler baseclass
16
 *
17
 * @package midcom.baseclasses
18
 */
19
abstract class midcom_baseclasses_components_handler_dataexport extends midcom_baseclasses_components_handler
20
{
21
    /**
22
     * @var datamanager[]
23
     */
24
    private array $_datamanagers = [];
25
26
    /**
27
     * Flag indicating whether or not the GUID of the first type should be included in exports.
28
     */
29
    public bool $include_guid = true;
30
31
    /**
32
     * Flag indicating whether or not totals for number fields should be generated
33
     */
34
    public bool $include_totals = false;
35
36
    public array $csv = [];
37
38
    protected string $_schema = '';
39
40
    private array $_rows = [];
41
42
    private array $_totals = [];
43
44
    private array $schemas = [];
45
46
    /**
47
     * @return midcom\datamanager\schemadb[]
48
     */
49
    abstract public function _load_schemadbs(string $handler_id, array &$args, array &$data) : array;
50
51
    abstract public function _load_data(string $handler_id, array &$args, array &$data) : array;
52
53 1
    public function _handler_csv(Request $request, string $handler_id, array $args, array &$data)
54
    {
55 1
        midcom::get()->auth->require_valid_user();
56 1
        $this->_load_datamanagers($this->_load_schemadbs($handler_id, $args, $data));
57
58 1
        if (empty($args[0])) {
59
            //We do not have filename in URL, generate one and redirect
60
            if (empty($data['filename'])) {
61
                $data['filename'] = preg_replace('/[^a-z0-9-]/i', '_', strtolower($this->_topic->extra)) . '_' . date('Y-m-d') . '.csv';
62
            }
63
            if (!str_ends_with(midcom_connection::get_url('uri'), '/')) {
0 ignored issues
show
Bug introduced by
It seems like midcom_connection::get_url('uri') can also be of type null; however, parameter $haystack of str_ends_with() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

63
            if (!str_ends_with(/** @scrutinizer ignore-type */ midcom_connection::get_url('uri'), '/')) {
Loading history...
64
                $data['filename'] = '/' . $data['filename'];
65
            }
66
            return new midcom_response_relocate(midcom_connection::get_url('uri') . $data['filename']);
67
        }
68
69 1
        midcom::get()->disable_limits();
70
71 1
        $rows = $this->_load_data($handler_id, $args, $data);
72 1
        if (count($this->_datamanagers) == 1) {
73
            foreach ($rows as $row) {
74
                $this->_rows[] = [$row];
75
            }
76
        } else {
77 1
            $this->_rows = $rows;
78
        }
79
80 1
        if (empty($data['filename'])) {
81
            $data['filename'] = str_replace('.csv', '', $args[0]);
82
        }
83
84 1
        $this->_init_csv_variables($request);
85
86 1
        $response = new StreamedResponse($this->render_csv(...));
87 1
        $response->headers->set('Content-Disposition', $response->headers->makeDisposition(
88 1
            ResponseHeaderBag::DISPOSITION_ATTACHMENT,
89 1
            $data['filename'])
90 1
        );
91 1
        $response->headers->set('Content-Type', $this->csv['mimetype']);
92 1
        return $response;
93
    }
94
95
    public function render_csv()
96
    {
97
        // Make real sure we're dumping data live
98
        midcom::get()->cache->content->enable_live_mode();
99
100
        // Dump headers
101
        $first_type = array_key_first($this->_datamanagers);
102
        $multiple_types = count($this->_datamanagers) > 1;
103
        $row = [];
104
        if ($this->include_guid) {
105
            $row[] = $first_type . ' GUID';
106
        }
107
108
        foreach ($this->_datamanagers as $type => $datamanager) {
109
            foreach ($datamanager->get_schema($this->schemas[$type])->get('fields') as $name => $config) {
110
                $title = $config['title'];
111
                if (   $this->include_totals
112
                    && $config['type'] == 'number') {
113
                    $this->_totals[$type . '-' . $name] = 0;
114
                }
115
                $title = $this->_l10n->get($title);
116
                if ($multiple_types) {
117
                    $title = $this->_l10n->get($type) . ': ' . $title;
118
                }
119
                $row[] = $title;
120
            }
121
        }
122
        $output = fopen("php://output", 'w');
123
        $this->_print_row($row, $output);
124
125
        $this->_dump_rows($output);
126
127
        if ($this->include_totals) {
128
            $row = [];
129
            foreach ($this->_datamanagers as $type => $datamanager) {
130
                foreach ($datamanager->get_schema()->get('fields') as $name => $config) {
131
                    $value = "";
132
                    if ($config['type'] == 'number') {
133
                        $value = $this->_totals[$type . '-' . $name];
134
                    }
135
                    $row[] = $value;
136
                }
137
            }
138
            $this->_print_row($row, $output);
139
        }
140
        fclose($output);
141
    }
142
143
    /**
144
     * Internal helper, loads the datamanagers for the given types. Any error triggers a 500.
145
     */
146 1
    private function _load_datamanagers(array $schemadbs)
147
    {
148 1
        if (empty($this->_schema)) {
149
            throw new midcom_error('Export schema ($this->_schema) must be defined');
150
        }
151 1
        foreach ($schemadbs as $type => $schemadb) {
152 1
            $this->_datamanagers[$type] = new datamanager($schemadb);
153
154 1
            if ($schemadb->has($this->_schema)) {
155
                $this->schemas[$type] = $this->_schema;
156
            } else {
157 1
                $this->schemas[$type] = $schemadb->get_first()->get_name();
158
            }
159
        }
160
    }
161
162
    private function _dump_rows($output)
163
    {
164
        $first_type = array_key_first($this->_datamanagers);
165
        // Output each row
166
        foreach ($this->_rows as $num => $row) {
167
            $data = [];
168
            foreach ($this->_datamanagers as $type => $datamanager) {
169
                if (!array_key_exists($type, $row)) {
170
                    debug_add("row #{$num} does not have {$type} set", MIDCOM_LOG_INFO);
171
                    $target_size = count($datamanager->get_schema($this->schemas[$type])->get('fields')) + count($data);
172
                    $data = array_pad($data, $target_size, '');
173
                    continue;
174
                }
175
                $object =& $row[$type];
176
177
                $datamanager->set_storage($object, $this->schemas[$type]);
178
179
                if (   $this->include_guid
180
                    && $type == $first_type) {
181
                    $data[] = $object->guid;
182
                }
183
184
                $csvdata = $datamanager->get_content_csv();
185
                foreach ($datamanager->get_schema()->get('fields') as $fieldname => $config) {
186
                    if (   $this->include_totals
187
                        && $config['type'] == 'number') {
188
                        $this->_totals[$type . '-' . $fieldname] += $csvdata[$fieldname];
189
                    }
190
                    $data[] = $csvdata[$fieldname];
191
                }
192
            }
193
            $this->_print_row($data, $output);
194
        }
195
    }
196
197
    private function _print_row(array $row, $output)
198
    {
199
        $row = array_map($this->encode_csv(...), $row);
200
        fputcsv($output, $row, $this->csv['s'], $this->csv['q']);
201
    }
202
203 1
    private function _init_csv_variables(Request $request)
204
    {
205
        // FIXME: Use global configuration
206 1
        $this->csv['s'] = $this->_config->get('csv_export_separator') ?: ';';
207 1
        $this->csv['q'] = $this->_config->get('csv_export_quote') ?: '"';
208 1
        $this->csv['mimetype'] = $this->_config->get('csv_export_content_type') ?: 'application/csv';
209 1
        $this->csv['charset'] = $this->_config->get('csv_export_charset');
210
211 1
        if (empty($this->csv['charset'])) {
212
            // Default to ISO-LATIN-15 (Latin-1 with EURO sign etc)
213 1
            $this->csv['charset'] = 'ISO-8859-15';
214 1
            if (   $request->server->has('HTTP_USER_AGENT')
215 1
                && !preg_match('/Windows/i', $request->server->get('HTTP_USER_AGENT'))) {
216
                // Except when not on windows, then default to UTF-8
217 1
                $this->csv['charset'] = 'UTF-8';
218
            }
219
        }
220
    }
221
222
    private function encode_csv(string $data) : string
0 ignored issues
show
Unused Code introduced by
The method encode_csv() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
223
    {
224
        /* START: Quick'n'Dirty on-the-fly charset conversion */
225
        if ($this->csv['charset'] !== 'UTF-8') {
226
            $to_charset = "{$this->csv['charset']}//TRANSLIT";
227
            $stat = @iconv('UTF-8', $to_charset, $data);
228
            if (!empty($stat)) {
229
                $data = $stat;
230
            }
231
        }
232
        /* END: Quick'n'Dirty on-the-fly charset conversion */
233
234
        return $data;
235
    }
236
}
237