Passed
Push — master ( d995fb...48f5cf )
by Andreas
28:37
created

render()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 27
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7.0422

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 21
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 27
ccs 19
cts 21
cp 0.9048
crap 7.0422
rs 8.6506
1
<?php
2
/**
3
 * @package midgard.admin.asgard
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\schemadb;
10
use midcom\datamanager\datamanager;
11
use Symfony\Component\HttpFoundation\Request;
12
use midcom\datamanager\controller;
13
use midcom\datamanager\validation\phpValidator;
14
15
/**
16
 * Component configuration handler
17
 *
18
 * @package midgard.admin.asgard
19
 */
20
class midgard_admin_asgard_handler_component_configuration extends midcom_baseclasses_components_handler
21
{
22
    use midgard_admin_asgard_handler;
23
24
    private $_controller;
25
26 3
    public function _on_initialize()
27
    {
28 3
        $this->add_stylesheet(MIDCOM_STATIC_URL . '/midgard.admin.asgard/libconfig.css');
29 3
    }
30
31 2
    private function _prepare_toolbar(bool $is_view)
32
    {
33 2
        $view_url = $this->router->generate('components_configuration', ['component' => $this->_request_data['name']]);
34 2
        $edit_url = $this->router->generate('components_configuration_edit', ['component' => $this->_request_data['name']]);
35
        $buttons = [[
36 2
            MIDCOM_TOOLBAR_URL => $view_url,
37 2
            MIDCOM_TOOLBAR_LABEL => $this->_l10n_midcom->get('view'),
38 2
            MIDCOM_TOOLBAR_GLYPHICON => 'eye',
39 2
            MIDCOM_TOOLBAR_ENABLED => !$is_view
40
        ], [
41 2
            MIDCOM_TOOLBAR_URL => $edit_url,
42 2
            MIDCOM_TOOLBAR_LABEL => $this->_l10n_midcom->get('edit'),
43 2
            MIDCOM_TOOLBAR_GLYPHICON => 'pencil',
44 2
            MIDCOM_TOOLBAR_ENABLED => $is_view
45
        ]];
46 2
        $this->_request_data['asgard_toolbar']->add_items($buttons);
47 2
    }
48
49
    /**
50
     * Set the breadcrumb data
51
     */
52 2
    private function _prepare_breadcrumbs()
53
    {
54 2
        $this->add_breadcrumb($this->router->generate('welcome'), $this->_l10n->get($this->_component));
55 2
        $this->add_breadcrumb($this->router->generate('components'), $this->_l10n->get('components'));
56
57 2
        $this->add_breadcrumb(
58 2
            $this->router->generate('components_component', ['component' => $this->_request_data['name']]),
59 2
            $this->_i18n->get_string($this->_request_data['name'], $this->_request_data['name'])
60
        );
61 2
        $this->add_breadcrumb(
62 2
            $this->router->generate('components_configuration', ['component' => $this->_request_data['name']]),
63 2
            $this->_l10n_midcom->get('component configuration')
64
        );
65 2
    }
66
67 3
    private function _load_configs(string $component, midcom_db_topic $topic = null) : midcom_helper_configuration
68
    {
69 3
        $config = midcom_baseclasses_components_configuration::get($component, 'config');
70
71 3
        if ($topic) {
72 1
            $topic_config = new midcom_helper_configuration($topic, $component);
73 1
            $config->store($topic_config->_local, false);
74
        }
75
76 3
        return $config;
77
    }
78
79 2
    private function load_controller() : controller
80
    {
81
        // Load SchemaDb
82 2
        $schemadb_config_path = midcom::get()->componentloader->path_to_snippetpath($this->_request_data['name']) . '/config/config_schemadb.inc';
83 2
        $schemaname = 'default';
84
85 2
        if (file_exists($schemadb_config_path)) {
86
            $schemadb = schemadb::from_path('file:/' . str_replace('.', '/', $this->_request_data['name']) . '/config/config_schemadb.inc');
87
            if ($schemadb->has('config')) {
88
                $schemaname = 'config';
89
            }
90
            // TODO: Log error on deprecated config schema?
91
        } else {
92
            // Create dummy schema. Naughty component would not provide config schema.
93 2
            $schemadb = new schemadb(['default' => [
94 2
                'description' => 'configuration for ' . $this->_request_data['name'],
95
                'fields'      => []
96
            ]]);
97
        }
98 2
        $schema = $schemadb->get($schemaname);
99 2
        $schema->set('l10n_db', $this->_request_data['name']);
100 2
        $fields = $schema->get('fields');
101
102 2
        foreach ($this->_request_data['config']->_global as $key => $value) {
103
            // try to sniff what fields are missing in schema
104 2
            if (!array_key_exists($key, $fields)) {
105 2
                $fields[$key] = $this->_detect_schema($key, $value);
106
            }
107
108 2
            if (   !isset($this->_request_data['config']->_local[$key])
109 2
                || $this->_request_data['config']->_local[$key] == $this->_request_data['config']->_global[$key]) {
110
                // No local configuration setting, note to user that this is the global value
111 2
                $fields[$key]['title'] = $schema->get_l10n()->get($fields[$key]['title']);
112 2
                $fields[$key]['title'] .= " <span class=\"global\">(" . $this->_l10n->get('global value') .")</span>";
113
            }
114
        }
115
116
        // Prepare defaults
117 2
        $config = array_intersect_key($this->_request_data['config']->get_all(), $fields);
118 2
        foreach ($config as $key => $value) {
119 2
            if (is_array($value)) {
120 2
                $fields[$key]['default'] = var_export($value, true);
121
            } else {
122 2
                if ($fields[$key]['widget'] == 'checkbox') {
123 2
                    $value = (boolean) $value;
124
                }
125 2
                $fields[$key]['default'] = $value;
126
            }
127
        }
128 2
        $schema->set('fields', $fields);
129 2
        $validation = $schema->get('validation') ?: [];
130 2
        $validation[] = [
131 2
            'callback' => [$this, 'check_config'],
132
        ];
133 2
        $schema->set('validation', $validation);
134
135 2
        $dm = new datamanager($schemadb);
136 2
        return $dm->get_controller();
137
    }
138
139 1
    public function _handler_view(string $component, array &$data)
140
    {
141 1
        $data['name'] = $component;
142 1
        $data['config'] = $this->_load_configs($data['name']);
143
144 1
        $data['view_title'] = sprintf($this->_l10n->get('configuration for %s'), $data['name']);
145 1
        $this->_prepare_toolbar(true);
146 1
        $this->_prepare_breadcrumbs();
147 1
        return $this->get_response();
148
    }
149
150
    /**
151
     * @param array $data Data passed to the show method
152
     */
153 1
    public function _show_view(string $handler_id, array &$data)
154
    {
155 1
        midcom_show_style('midgard_admin_asgard_component_configuration_header');
156
157 1
        foreach ($data['config']->_global as $key => $value) {
158 1
            $data['key'] = $this->_i18n->get_string($key, $data['name']);
159 1
            $data['global'] = $this->render($value);
160
161 1
            if (isset($data['config']->_local[$key])) {
162
                $data['local'] = $this->render($data['config']->_local[$key]);
163
            } else {
164 1
                $data['local'] = $this->render(null);
165
            }
166
167 1
            midcom_show_style('midgard_admin_asgard_component_configuration_item');
168
        }
169 1
        midcom_show_style('midgard_admin_asgard_component_configuration_footer');
170 1
    }
171
172 1
    private function render($value)
173
    {
174 1
        $type = gettype($value);
175
176 1
        switch ($type) {
177 1
            case 'boolean':
178 1
                $result = '<i class="fa fa-' . ($value === true ? 'check' : 'times') . '"></i>';
179 1
                break;
180 1
            case 'array':
181 1
                $content = '<ul>';
182 1
                foreach ($value as $key => $val) {
183 1
                    $content .= "<li>{$key} => " . $this->render($val) . ",</li>\n";
184
                }
185 1
                $content .= '</ul>';
186 1
                $result = "<ul>\n<li>array</li>\n<li>(\n{$content}\n)</li>\n</ul>\n";
187 1
                break;
188 1
            case 'object':
189
                $result = '<strong>Object</strong>';
190
                break;
191 1
            case 'NULL':
192 1
                $result = '<strong>N/A</strong>';
193 1
                break;
194
            default:
195 1
                $result = $value;
196
        }
197
198 1
        return $result;
199
    }
200
201
    /**
202
     * Ensure the configuration is valid
203
     *
204
     * @throws midcom_error
205
     */
206
    public function check_config(array $values)
207
    {
208
        $current = $this->_request_data['config']->get_all();
209
        $result = [];
210
        foreach ($values as $key => $newval) {
211
            if ($newval === '' || !isset($current[$key])) {
212
                continue;
213
            }
214
            $val = $current[$key];
215
216
            if (is_array($val)) {
217
                $code = "<?php\n\$data = array({$newval}\n);\n?>";
218
                if ($error = phpValidator::lint($code)) {
219
                    $result[$key] = $error;
220
                }
221
            }
222
        }
223
        if (empty($result)) {
224
            return true;
225
        }
226
        return $result;
227
    }
228
229
    private function convert_to_config(array $values) : array
230
    {
231
        $config_array = [];
232
233
        foreach ($this->_request_data['config']->get_all() as $key => $val) {
234
            if (!isset($values[$key])) {
235
                continue;
236
            }
237
            $newval = $values[$key];
238
239
            if ($newval === '') {
240
                continue;
241
            }
242
243
            if (is_array($val)) {
244
                //try make sure entries have the same format before deciding if there was a change
245
                eval("\$newval = $newval;");
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
246
            }
247
248
            if ($newval != $val) {
249
                $config_array[$key] = $newval;
250
            }
251
        }
252
253
        return $config_array;
254
    }
255
256 2
    public function _handler_edit(Request $request, string $handler_id, array &$data, string $component, string $folder = null)
257
    {
258 2
        $data['name'] = $component;
259 2
        if ($handler_id == 'components_configuration_edit_folder') {
260 1
            $data['folder'] = new midcom_db_topic($folder);
261 1
            if ($data['folder']->component != $data['name']) {
262
                throw new midcom_error_notfound("Folder {$folder} not found for configuration.");
263
            }
264
265 1
            $data['folder']->require_do('midgard:update');
266
267 1
            $data['config'] = $this->_load_configs($data['name'], $data['folder']);
268
        } else {
269 1
            $data['config'] = $this->_load_configs($data['name']);
270
        }
271
272 2
        $this->_controller = $this->load_controller();
273
274 2
        switch ($this->_controller->handle($request)) {
275 2
            case 'save':
276
                if (!$this->save_configuration($data)) {
277
                    midcom::get()->uimessages->add(
278
                        $this->_l10n_midcom->get('component configuration'),
279
                        sprintf($this->_l10n->get('configuration save failed: %s'), midcom_connection::get_error_string()),
280
                        'error'
281
                    );
282
                    // back to edit
283
                    break;
284
                }
285
                midcom::get()->uimessages->add(
286
                    $this->_l10n_midcom->get('component configuration'),
287
                    $this->_l10n->get('configuration saved successfully')
288
                );
289
290
                // FALL-THROUGH (i.e. relocate to view)
291
292 2
            case 'cancel':
293
                if ($handler_id == 'components_configuration_edit_folder') {
294
                    return new midcom_response_relocate($this->router->generate('object_view', ['guid' => $data['folder']->guid]));
295
                }
296
                return new midcom_response_relocate($this->router->generate('components_configuration', ['component' => $data['name']]));
297
        }
298
299 2
        $data['controller'] = $this->_controller;
300
301 2
        if ($handler_id == 'components_configuration_edit_folder') {
302 1
            midgard_admin_asgard_plugin::bind_to_object($data['folder'], $handler_id, $data);
303 1
            $data['view_title'] = sprintf($this->_l10n->get('edit configuration for %s folder %s'), $data['name'], $data['folder']->extra);
304
        } else {
305 1
            $this->_prepare_toolbar(false);
306 1
            $data['view_title'] = sprintf($this->_l10n->get('edit configuration for %s'), $data['name']);
307 1
            $this->_prepare_breadcrumbs();
308 1
            $this->add_breadcrumb(
309 1
                $this->router->generate('components_configuration_edit', ['component' => $this->_request_data['name']]),
310 1
                $this->_l10n_midcom->get('edit')
311
            );
312
        }
313
314 2
        return $this->get_response('midgard_admin_asgard_component_configuration_edit');
315
    }
316
317
    private function save_configuration(array $data) : bool
318
    {
319
        $values = $this->convert_to_config($this->_controller->get_datamanager()->get_content_raw());
320
321
        if ($data['handler_id'] == 'components_configuration_edit_folder') {
322
            // Editing folder configuration
323
            return $this->save_topic($data['folder'], $values);
324
        }
325
        return $this->save_snippet($values);
326
    }
327
328
329
    /**
330
     * Save configuration values to a topic as "serialized" array
331
     */
332
    private function save_snippet(array $values) : bool
333
    {
334
        $config = var_export($values, true);
335
        // Remove opening and closing array( ) lines, because that's the way midcom likes it
336
        $config = preg_replace('/^.*?\n/', '', $config);
337
        $config = preg_replace('/(\n.*?|\))$/', '', $config);
338
339
        $basedir = midcom::get()->config->get('midcom_sgconfig_basedir');
340
        $sg_snippetdir = new midcom_db_snippetdir();
341
        if (!$sg_snippetdir->get_by_path($basedir)) {
0 ignored issues
show
Bug introduced by
It seems like $basedir can also be of type null; however, parameter $path of midcom_core_dbaobject::get_by_path() 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

341
        if (!$sg_snippetdir->get_by_path(/** @scrutinizer ignore-type */ $basedir)) {
Loading history...
342
            // Create config snippetdir
343
            $sg_snippetdir = new midcom_db_snippetdir();
344
            $sg_snippetdir->name = $basedir;
345
            // remove leading slash from name
346
            $sg_snippetdir->name = preg_replace("/^\//", "", $sg_snippetdir->name);
347
            if (!$sg_snippetdir->create()) {
348
                throw new midcom_error("Failed to create snippetdir {$basedir}: " . midcom_connection::get_error_string());
349
            }
350
        }
351
352
        $lib_snippetdir = new midcom_db_snippetdir();
353
        if (!$lib_snippetdir->get_by_path("{$basedir}/{$this->_request_data['name']}")) {
354
            $lib_snippetdir = new midcom_db_snippetdir();
355
            $lib_snippetdir->up = $sg_snippetdir->id;
356
            $lib_snippetdir->name = $this->_request_data['name'];
357
            if (!$lib_snippetdir->create()) {
358
                throw new midcom_error("Failed to create snippetdir {$basedir}/{$lib_snippetdir->name}: " . midcom_connection::get_error_string());
359
            }
360
        }
361
362
        $snippet = new midcom_db_snippet();
363
        if (!$snippet->get_by_path("{$basedir}/{$this->_request_data['name']}/config")) {
364
            $sn = new midcom_db_snippet();
365
            $sn->snippetdir = $lib_snippetdir->id;
366
            $sn->name = 'config';
367
            $sn->code = $config;
368
            return $sn->create();
369
        }
370
371
        $snippet->code = $config;
372
        return $snippet->update();
373
    }
374
375
    /**
376
     * Save configuration values to a topic as parameters
377
     */
378
    private function save_topic(midcom_db_topic $topic, array $config) : bool
379
    {
380
        $success = true;
381
        foreach ($this->_request_data['config']->_global as $global_key => $global_value) {
382
            if (   isset($config[$global_key])
383
                && $config[$global_key] != $global_value) {
384
                continue;
385
                // Skip the ones we will set next
386
            }
387
388
            // Clear unset params
389
            if ($topic->get_parameter($this->_request_data['name'], $global_key)) {
390
                $success = $topic->delete_parameter($this->_request_data['name'], $global_key) && $success;
391
            }
392
        }
393
394
        foreach ($config as $key => $value) {
395
            if (   is_array($value)
396
                || is_object($value)) {
397
                /**
398
                 * See http://trac.midgard-project.org/ticket/1442
399
                 $topic->set_parameter($this->_request_data['name'], var_export($value, true));
400
                 */
401
                continue;
402
            }
403
404
            if ($value === false) {
405
                $value = '0';
406
            }
407
            $success = $topic->set_parameter($this->_request_data['name'], $key, $value) && $success;
408
        }
409
        return $success;
410
    }
411
412 2
    private function _detect_schema(string $key, $value) : array
413
    {
414
        $result = [
415 2
            'title'       => $key,
416 2
            'type'        => 'text',
417 2
            'widget'      => 'text',
418
        ];
419
420 2
        $type = gettype($value);
421 2
        switch ($type) {
422 2
            case "boolean":
423 2
                $result['type'] = 'boolean';
424 2
                $result['widget'] = 'checkbox';
425 2
                break;
426 2
            case "array":
427 2
                $result['widget'] = 'textarea';
428
429 2
                if (isset($this->_request_data['folder'])) {
430
                    // Complex Array fields should be readonly for topics as we cannot store and read them properly with parameters
431 1
                    $result['readonly'] = true;
432
                }
433
434 2
                break;
435
            default:
436 2
                if (preg_match("/\n/", $value)) {
437
                    $result['widget'] = 'textarea';
438
                }
439
        }
440
441 2
        return $result;
442
    }
443
}
444