Completed
Push — master ( 8849ee...a1b70f )
by Andreas
24:35
created

save_snippet()   B

Complexity

Conditions 6
Paths 11

Size

Total Lines 41
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 27
nc 11
nop 1
dl 0
loc 41
ccs 0
cts 28
cp 0
crap 42
rs 8.8657
c 1
b 0
f 0
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
13
/**
14
 * Component configuration handler
15
 *
16
 * @package midgard.admin.asgard
17
 */
18
class midgard_admin_asgard_handler_component_configuration extends midcom_baseclasses_components_handler
19
{
20
    use midgard_admin_asgard_handler;
21
22
    private $_controller;
23
24 3
    public function _on_initialize()
25
    {
26 3
        $this->add_stylesheet(MIDCOM_STATIC_URL . '/midgard.admin.asgard/libconfig.css');
27 3
    }
28
29 2
    private function _prepare_toolbar($handler_id)
30
    {
31 2
        $view_url = $this->router->generate('components_configuration', ['component' => $this->_request_data['name']]);
32 2
        $edit_url = $this->router->generate('components_configuration_edit', ['component' => $this->_request_data['name']]);
33
        $buttons = [
34
            [
35 2
                MIDCOM_TOOLBAR_URL => $view_url,
36 2
                MIDCOM_TOOLBAR_LABEL => $this->_l10n_midcom->get('view'),
37 2
                MIDCOM_TOOLBAR_GLYPHICON => 'eye',
38
            ],
39
            [
40 2
                MIDCOM_TOOLBAR_URL => $edit_url,
41 2
                MIDCOM_TOOLBAR_LABEL => $this->_l10n_midcom->get('edit'),
42 2
                MIDCOM_TOOLBAR_GLYPHICON => 'pencil',
43
            ]
44
        ];
45 2
        $this->_request_data['asgard_toolbar']->add_items($buttons);
46
47
        switch ($handler_id) {
48 2
            case 'components_configuration_edit':
49 1
                $this->_request_data['asgard_toolbar']->disable_item($edit_url);
50 1
                break;
51 1
            case 'components_configuration':
52 1
                $this->_request_data['asgard_toolbar']->disable_item($view_url);
53 1
                break;
54
        }
55 2
    }
56
57
    /**
58
     * Set the breadcrumb data
59
     */
60 2
    private function _prepare_breadcrumbs($handler_id)
61
    {
62 2
        $this->add_breadcrumb($this->router->generate('welcome'), $this->_l10n->get($this->_component));
63 2
        $this->add_breadcrumb($this->router->generate('components'), $this->_l10n->get('components'));
64
65 2
        $this->add_breadcrumb(
66 2
            $this->router->generate('components_component', ['component' => $this->_request_data['name']]),
67 2
            midcom::get()->i18n->get_string($this->_request_data['name'], $this->_request_data['name'])
68
        );
69 2
        $this->add_breadcrumb(
70 2
            $this->router->generate('components_configuration', ['component' => $this->_request_data['name']]),
71 2
            $this->_l10n_midcom->get('component configuration')
72
        );
73
74 2
        if ($handler_id == 'components_configuration_edit') {
75 1
            $this->add_breadcrumb(
76 1
                $this->router->generate('components_configuration_edit', ['component' => $this->_request_data['name']]),
77 1
                $this->_l10n_midcom->get('edit')
78
            );
79
        }
80 2
    }
81
82 3
    private function _load_configs($component, $object = null)
83
    {
84 3
        $config = midcom_baseclasses_components_configuration::get($component, 'config');
85
86 3
        if ($object) {
87 1
            $topic_config = new midcom_helper_configuration($object, $component);
88 1
            $config->store($topic_config->_local, false);
89
        }
90
91 3
        return $config;
92
    }
93
94
    /**
95
     * @return \midcom\datamanager\controller
96
     */
97 2
    private function load_controller()
98
    {
99
        // Load SchemaDb
100 2
        $schemadb_config_path = midcom::get()->componentloader->path_to_snippetpath($this->_request_data['name']) . '/config/config_schemadb.inc';
101 2
        $schemaname = 'default';
102
103 2
        if (file_exists($schemadb_config_path)) {
104
            $schemadb = schemadb::from_path('file:/' . str_replace('.', '/', $this->_request_data['name']) . '/config/config_schemadb.inc');
105
            if ($schemadb->has('config')) {
106
                $schemaname = 'config';
107
            }
108
            // TODO: Log error on deprecated config schema?
109
        } else {
110
            // Create dummy schema. Naughty component would not provide config schema.
111 2
            $schemadb = schemadb::from_path("file:/midgard/admin/asgard/config/schemadb_libconfig.inc");
112
        }
113 2
        $schema = $schemadb->get($schemaname);
114 2
        $schema->set('l10n_db', $this->_request_data['name']);
115 2
        $fields = $schema->get('fields');
116
117 2
        foreach ($this->_request_data['config']->_global as $key => $value) {
118
            // try to sniff what fields are missing in schema
119 2
            if (!array_key_exists($key, $fields)) {
120 2
                $fields[$key] = $this->_detect_schema($key, $value);
121
            }
122
123 2
            if (   !isset($this->_request_data['config']->_local[$key])
124 2
                || $this->_request_data['config']->_local[$key] == $this->_request_data['config']->_global[$key]) {
125
                // No local configuration setting, note to user that this is the global value
126 2
                $fields[$key]['title'] = $schema->get_l10n()->get($fields[$key]['title']);
127 2
                $fields[$key]['title'] .= " <span class=\"global\">(" . $this->_l10n->get('global value') .")</span>";
128
            }
129
        }
130
131
        // Prepare defaults
132 2
        $config = array_intersect_key($this->_request_data['config']->get_all(), $fields);
133 2
        foreach ($config as $key => $value) {
134 2
            if (is_array($value)) {
135 2
                $fields[$key]['default'] = var_export($value, true);
136
            } else {
137 2
                if ($fields[$key]['widget'] == 'checkbox') {
138 2
                    $value = (boolean) $value;
139
                }
140 2
                $fields[$key]['default'] = $value;
141
            }
142
        }
143 2
        $schema->set('fields', $fields);
144 2
        $validation = $schema->get('validation') ?: [];
145 2
        $validation[] = [
146 2
            'callback' => [$this, 'check_config'],
147
        ];
148 2
        $schema->set('validation', $validation);
149
150 2
        $dm = new datamanager($schemadb);
151 2
        return $dm->get_controller();
152
    }
153
154
    /**
155
     * @param mixed $handler_id The ID of the handler.
156
     * @param string $component The component name
157
     * @param array $data The local request data.
158
     */
159 1
    public function _handler_view($handler_id, $component, array &$data)
160
    {
161 1
        $data['name'] = $component;
162 1
        if (!midcom::get()->componentloader->is_installed($data['name'])) {
163
            throw new midcom_error_notfound("Component {$data['name']} was not found.");
164
        }
165
166 1
        $data['config'] = $this->_load_configs($data['name']);
167
168 1
        $data['view_title'] = sprintf($this->_l10n->get('configuration for %s'), $data['name']);
169 1
        $this->_prepare_toolbar($handler_id);
170 1
        $this->_prepare_breadcrumbs($handler_id);
171 1
        return $this->get_response();
172
    }
173
174
    /**
175
     * @param string $handler_id Name of the used handler
176
     * @param array $data Data passed to the show method
177
     */
178 1
    public function _show_view($handler_id, array &$data)
179
    {
180 1
        midcom_show_style('midgard_admin_asgard_component_configuration_header');
181
182 1
        foreach ($data['config']->_global as $key => $value) {
183 1
            $data['key'] = $this->_i18n->get_string($key, $data['name']);
184 1
            $data['global'] = $this->_detect($value);
185
186 1
            if (isset($data['config']->_local[$key])) {
187
                $data['local'] = $this->_detect($data['config']->_local[$key]);
188
            } else {
189 1
                $data['local'] = $this->_detect(null);
190
            }
191
192 1
            midcom_show_style('midgard_admin_asgard_component_configuration_item');
193
        }
194 1
        midcom_show_style('midgard_admin_asgard_component_configuration_footer');
195 1
    }
196
197 1
    private function _detect($value)
198
    {
199 1
        $type = gettype($value);
200
201
        switch ($type) {
202 1
            case 'boolean':
203 1
                $result = '<i class="fa fa-' . ($value === true ? 'check' : 'times') . '"></i>';
204 1
                break;
205 1
            case 'array':
206 1
                $content = '<ul>';
207 1
                foreach ($value as $key => $val) {
208 1
                    $content .= "<li>{$key} => " . $this->_detect($val) . ",</li>\n";
209
                }
210 1
                $content .= '</ul>';
211 1
                $result = "<ul>\n<li>array</li>\n<li>(\n{$content}\n)</li>\n</ul>\n";
212 1
                break;
213 1
            case 'object':
214
                $result = '<strong>Object</strong>';
215
                break;
216 1
            case 'NULL':
217 1
                $result = '<strong>N/A</strong>';
218 1
                break;
219
            default:
220 1
                $result = $value;
221
        }
222
223 1
        return $result;
224
    }
225
226
    /**
227
     * Ensure the configuration is valid
228
     *
229
     * @throws midcom_error
230
     */
231
    public function check_config($values)
232
    {
233
        $current = $this->_request_data['config']->get_all();
234
        $result = [];
235
        foreach ($values as $key => $newval) {
236
            if ($newval === '' || !isset($current[$key])) {
237
                continue;
238
            }
239
            $val = $current[$key];
240
241
            if (is_array($val)) {
242
                $tmpfile = tempnam(midcom::get()->config->get('midcom_tempdir'), 'midgard_admin_asgard_handler_component_configuration_');
243
                file_put_contents($tmpfile, "<?php\n\$data = array({$newval}\n);\n?>");
244
245
                exec("php -l {$tmpfile} 2>&1", $parse_results, $retval);
246
                debug_print_r("'php -l {$tmpfile}' returned:", $parse_results);
247
                unlink($tmpfile);
248
249
                if ($retval !== 0) {
250
                    $parse_results = array_shift($parse_results);
251
252
                    if (strstr($parse_results, 'Parse error')) {
253
                        $line = preg_replace('/^.+?on line (\d+?)$/', '\1', $parse_results);
254
                        $result[$key] = sprintf($this->_i18n->get_string('type php: parse error in line %s', 'midcom.datamanager'), $line);
255
                    }
256
                }
257
            }
258
        }
259
        if (empty($result)) {
260
            return true;
261
        }
262
        return $result;
263
    }
264
265
    private function convert_to_config(array $values) : array
266
    {
267
        $config_array = [];
268
269
        foreach ($this->_request_data['config']->get_all() as $key => $val) {
270
            if (isset($values[$key])) {
271
                $newval = $values[$key];
272
            } else {
273
                continue;
274
            }
275
276
            if ($newval === '') {
277
                continue;
278
            }
279
280
            if (is_array($val)) {
281
                //try make sure entries have the same format before deciding if there was a change
282
                eval("\$newval = $newval;");
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
283
            }
284
285
            if ($newval != $val) {
286
                $config_array[$key] = $newval;
287
            }
288
        }
289
290
        return $config_array;
291
    }
292
293
    /**
294
     * @param Request $request The request object
295
     * @param mixed $handler_id The ID of the handler.
296
     * @param array $data The local request data.
297
     * @param string $component The component name
298
     * @param string $folder The topic GUID
299
     */
300 2
    public function _handler_edit(Request $request, $handler_id, array &$data, $component, $folder = null)
301
    {
302 2
        $data['name'] = $component;
303 2
        if (!midcom::get()->componentloader->is_installed($data['name'])) {
304
            throw new midcom_error_notfound("Component {$data['name']} was not found.");
305
        }
306
307 2
        if ($handler_id == 'components_configuration_edit_folder') {
308 1
            $data['folder'] = new midcom_db_topic($folder);
309 1
            if ($data['folder']->component != $data['name']) {
310
                throw new midcom_error_notfound("Folder {$folder} not found for configuration.");
311
            }
312
313 1
            $data['folder']->require_do('midgard:update');
314
315 1
            $data['config'] = $this->_load_configs($data['name'], $data['folder']);
316
        } else {
317 1
            $data['config'] = $this->_load_configs($data['name']);
318
        }
319
320 2
        $this->_controller = $this->load_controller();
321
322 2
        switch ($this->_controller->handle($request)) {
323 2
            case 'save':
324
                if (!$this->save_configuration($data)) {
325
                    midcom::get()->uimessages->add(
326
                        $this->_l10n_midcom->get('component configuration'),
327
                        sprintf($this->_l10n->get('configuration save failed: %s'), midcom_connection::get_error_string()),
328
                        'error'
329
                    );
330
                    // back to edit
331
                    break;
332
                }
333
                midcom::get()->uimessages->add(
334
                    $this->_l10n_midcom->get('component configuration'),
335
                    $this->_l10n->get('configuration saved successfully')
336
                );
337
338
                // FALL-THROUGH (i.e. relocate to view)
339
340 2
            case 'cancel':
341
                if ($handler_id == 'components_configuration_edit_folder') {
342
                    return new midcom_response_relocate($this->router->generate('object_view', ['guid' => $data['folder']->guid]));
343
                }
344
                return new midcom_response_relocate($this->router->generate('components_configuration', ['component' => $data['name']]));
345
        }
346
347 2
        $data['controller'] = $this->_controller;
348
349 2
        if ($handler_id == 'components_configuration_edit_folder') {
350 1
            midgard_admin_asgard_plugin::bind_to_object($data['folder'], $handler_id, $data);
351 1
            $data['view_title'] = sprintf($this->_l10n->get('edit configuration for %s folder %s'), $data['name'], $data['folder']->extra);
352
        } else {
353 1
            $this->_prepare_toolbar($handler_id);
354 1
            $data['view_title'] = sprintf($this->_l10n->get('edit configuration for %s'), $data['name']);
355 1
            $this->_prepare_breadcrumbs($handler_id);
356
        }
357
358 2
        return $this->get_response();
359
    }
360
361
    private function save_configuration(array $data) : bool
362
    {
363
        $values = $this->convert_to_config($this->_controller->get_datamanager()->get_content_raw());
364
365
        if ($data['handler_id'] == 'components_configuration_edit_folder') {
366
            // Editing folder configuration
367
            return $this->save_topic($data['folder'], $values);
368
        }
369
        return $this->save_snippet($values);
370
    }
371
372
373
    /**
374
     * Save configuration values to a topic as "serialized" array
375
     */
376
    private function save_snippet(array $values) : bool
377
    {
378
        $config = var_export($values, true);
379
        // Remove opening and closing array( ) lines, because that's the way midcom likes it
380
        $config = preg_replace('/^.*?\n/', '', $config);
381
        $config = preg_replace('/(\n.*?|\))$/', '', $config);
382
383
        $basedir = midcom::get()->config->get('midcom_sgconfig_basedir');
384
        $sg_snippetdir = new midcom_db_snippetdir();
385
        if (!$sg_snippetdir->get_by_path($basedir)) {
386
            // Create config snippetdir
387
            $sg_snippetdir = new midcom_db_snippetdir();
388
            $sg_snippetdir->name = $basedir;
389
            // remove leading slash from name
390
            $sg_snippetdir->name = preg_replace("/^\//", "", $sg_snippetdir->name);
391
            if (!$sg_snippetdir->create()) {
392
                throw new midcom_error("Failed to create snippetdir {$basedir}: " . midcom_connection::get_error_string());
393
            }
394
        }
395
396
        $lib_snippetdir = new midcom_db_snippetdir();
397
        if (!$lib_snippetdir->get_by_path("{$basedir}/{$this->_request_data['name']}")) {
398
            $lib_snippetdir = new midcom_db_snippetdir();
399
            $lib_snippetdir->up = $sg_snippetdir->id;
400
            $lib_snippetdir->name = $this->_request_data['name'];
401
            if (!$lib_snippetdir->create()) {
402
                throw new midcom_error("Failed to create snippetdir {$basedir}/{$lib_snippetdir->name}: " . midcom_connection::get_error_string());
403
            }
404
        }
405
406
        $snippet = new midcom_db_snippet();
407
        if (!$snippet->get_by_path("{$basedir}/{$this->_request_data['name']}/config")) {
408
            $sn = new midcom_db_snippet();
409
            $sn->snippetdir = $lib_snippetdir->id;
410
            $sn->name = 'config';
411
            $sn->code = $config;
412
            return $sn->create();
413
        }
414
415
        $snippet->code = $config;
416
        return $snippet->update();
417
    }
418
419
    /**
420
     * Save configuration values to a topic as parameters
421
     */
422
    private function save_topic(midcom_db_topic $topic, $config) : bool
423
    {
424
        $success = true;
425
        foreach ($this->_request_data['config']->_global as $global_key => $global_value) {
426
            if (   isset($config[$global_key])
427
                && $config[$global_key] != $global_value) {
428
                continue;
429
                // Skip the ones we will set next
430
            }
431
432
            // Clear unset params
433
            if ($topic->get_parameter($this->_request_data['name'], $global_key)) {
434
                $success = $topic->delete_parameter($this->_request_data['name'], $global_key) && $success;
435
            }
436
        }
437
438
        foreach ($config as $key => $value) {
439
            if (   is_array($value)
440
                || is_object($value)) {
441
                /**
442
                 * See http://trac.midgard-project.org/ticket/1442
443
                 $topic->set_parameter($this->_request_data['name'], var_export($value, true));
444
                 */
445
                continue;
446
            }
447
448
            if ($value === false) {
449
                $value = '0';
450
            }
451
            $success = $topic->set_parameter($this->_request_data['name'], $key, $value) && $success;
452
        }
453
        return $success;
454
    }
455
456
    /**
457
     * @param string $handler_id Name of the used handler
458
     * @param array $data Data passed to the show method
459
     */
460 2
    public function _show_edit($handler_id, array &$data)
461
    {
462 2
        midcom_show_style('midgard_admin_asgard_component_configuration_edit');
463 2
    }
464
465 2
    private function _detect_schema($key, $value)
466
    {
467
        $result = [
468 2
            'title'       => $key,
469 2
            'type'        => 'text',
470 2
            'widget'      => 'text',
471
        ];
472
473 2
        $type = gettype($value);
474
        switch ($type) {
475 2
            case "boolean":
476 2
                $result['type'] = 'boolean';
477 2
                $result['widget'] = 'checkbox';
478 2
                break;
479 2
            case "array":
480 2
                $result['widget'] = 'textarea';
481
482 2
                if (isset($this->_request_data['folder'])) {
483
                    // Complex Array fields should be readonly for topics as we cannot store and read them properly with parameters
484 1
                    $result['readonly'] = true;
485
                }
486
487 2
                break;
488
            default:
489 2
                if (preg_match("/\n/", $value)) {
490
                    $result['widget'] = 'textarea';
491
                }
492
        }
493
494 2
        return $result;
495
    }
496
}
497