Passed
Push — master ( 15ca6f...f67db4 )
by Dante
01:51
created

LayoutHelper::metaConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 21
nc 1
nop 0
dl 0
loc 23
rs 9.584
c 0
b 0
f 0
1
<?php
2
/**
3
 * BEdita, API-first content management framework
4
 * Copyright 2018 ChannelWeb Srl, Chialab Srl
5
 *
6
 * This file is part of BEdita: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published
8
 * by the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
12
 */
13
namespace App\View\Helper;
14
15
use App\Plugin;
16
use App\Utility\CacheTools;
17
use App\Utility\Translate;
18
use Cake\Cache\Cache;
19
use Cake\Core\Configure;
20
use Cake\I18n\I18n;
21
use Cake\Utility\Hash;
22
use Cake\View\Helper;
23
24
/**
25
 * Helper for site layout
26
 *
27
 * @property \App\View\Helper\EditorsHelper $Editors
28
 * @property \Cake\View\Helper\HtmlHelper $Html
29
 * @property \App\View\Helper\LinkHelper $Link
30
 * @property \App\View\Helper\PermsHelper $Perms
31
 * @property \App\View\Helper\PropertyHelper $Property
32
 * @property \App\View\Helper\SystemHelper $System
33
 * @property \Cake\View\Helper\UrlHelper $Url
34
 */
35
class LayoutHelper extends Helper
36
{
37
    /**
38
     * List of helpers used by this helper
39
     *
40
     * @var array
41
     */
42
    public array $helpers = ['Editors', 'Html', 'Link', 'Perms', 'Property', 'System', 'Url'];
43
44
    /**
45
     * Is Dashboard
46
     *
47
     * @return bool True if visible for view
48
     */
49
    public function isDashboard(): bool
50
    {
51
        return in_array($this->_View->getName(), ['Dashboard']);
52
    }
53
54
    /**
55
     * Is Login
56
     *
57
     * @return bool True if visible for view
58
     */
59
    public function isLogin(): bool
60
    {
61
        return in_array($this->_View->getName(), ['Login']);
62
    }
63
64
    /**
65
     * Properties for various publication status
66
     *
67
     * @param array $object The object
68
     * @return string pubstatus
69
     */
70
    public function publishStatus(array $object = []): string
71
    {
72
        if (empty($object)) {
73
            return '';
74
        }
75
76
        $end = (string)Hash::get($object, 'attributes.publish_end');
77
        $start = (string)Hash::get($object, 'attributes.publish_start');
78
79
        if (!empty($end) && strtotime($end) <= time()) {
80
            return 'expired';
81
        }
82
        if (!empty($start) && strtotime($start) > time()) {
83
            return 'future';
84
        }
85
        if (!empty((string)Hash::get($object, 'meta.locked'))) {
86
            return 'locked';
87
        }
88
        if ((string)Hash::get($object, 'attributes.status') === 'draft') {
89
            return 'draft';
90
        }
91
92
        return '';
93
    }
94
95
    /**
96
     * Messages visibility
97
     *
98
     * @return bool True if visible for view
99
     */
100
    public function messages(): bool
101
    {
102
        return $this->_View->getName() != 'Login';
103
    }
104
105
    /**
106
     * Return title with object title and current module name
107
     *
108
     * @return string title with object title and current module name separated by '|'
109
     */
110
    public function title(): string
111
    {
112
        $module = (array)$this->getView()->get('currentModule');
113
        $name = (string)Hash::get($module, 'name');
114
        $object = (array)$this->getView()->get('object');
115
        $title = (string)Hash::get($object, 'attributes.title');
116
        $title = strip_tags($title);
117
118
        return empty($title) ? $name : sprintf('%s | %s', $title, $name);
119
    }
120
121
    /**
122
     * Return dashboard module link
123
     *
124
     * @param string $name The module name
125
     * @param array $module The module data
126
     * @return string
127
     */
128
    public function dashboardModuleLink(string $name, array $module): string
129
    {
130
        if (in_array($name, ['trash', 'users'])) {
131
            return '';
132
        }
133
        $label = $name === 'objects' ? __('All objects') : Hash::get($module, 'label', $name);
134
        $route = (array)Hash::get($module, 'route');
135
        $param = empty($route) ? ['_name' => 'modules:list', 'object_type' => $name, 'plugin' => null] : $route;
136
        $count = !$this->showCounter($name) ? '' : $this->moduleCount($name);
137
138
        return sprintf(
139
            '<a href="%s" class="%s"><span>%s</span>%s%s</a>',
140
            $this->Url->build($param),
141
            sprintf('dashboard-item has-background-module-%s %s', $name, Hash::get($module, 'class', '')),
142
            $this->tr($label),
143
            $this->moduleIcon($name, $module),
144
            $count,
145
        );
146
    }
147
148
    /**
149
     * Show counter for module
150
     *
151
     * @param string $name The module name
152
     * @return bool
153
     */
154
    public function showCounter(string $name): bool
155
    {
156
        $counters = Configure::read('UI.modules.counters', ['trash']);
157
158
        return is_array($counters) ? in_array($name, $counters) : $counters === 'all';
159
    }
160
161
    /**
162
     * Return module count span.
163
     *
164
     * @param string $name The module name
165
     * @param string|null $moduleClass The module class
166
     * @return string
167
     */
168
    public function moduleCount(string $name, ?string $moduleClass = null): string
169
    {
170
        $count = CacheTools::getModuleCount($name);
171
172
        return sprintf('<span class="module-count">%s</span>', $count);
173
    }
174
175
    /**
176
     * Return module icon.
177
     *
178
     * @param string $name The module name
179
     * @param array $module The module data
180
     * @return string
181
     */
182
    public function moduleIcon(string $name, array $module): string
183
    {
184
        if (Hash::get($module, 'hints.multiple_types') && !Hash::get($module, 'class')) {
185
            return '<app-icon icon="carbon:grid" :style="{ width: \'28px\', height: \'28px\' }"></app-icon>';
186
        }
187
        $icon = (string)Configure::read(sprintf('Modules.%s.icon', $name));
188
        if (!empty($icon)) {
189
            return sprintf('<app-icon icon="%s" :style="{ width: \'28px\', height: \'28px\' }"></app-icon>', $icon);
190
        }
191
        $map = [
192
            'audio' => 'carbon:document-audio',
193
            'categories' => 'carbon:collapse-categories',
194
            'documents' => 'carbon:document',
195
            'events' => 'carbon:event',
196
            'files' => 'carbon:document-blank',
197
            'folders' => 'carbon:tree-view',
198
            'images' => 'carbon:image',
199
            'links' => 'carbon:link',
200
            'locations' => 'carbon:location',
201
            'news' => 'carbon:calendar',
202
            'profiles' => 'carbon:person',
203
            'publications' => 'carbon:wikis',
204
            'tags' => 'carbon:tag',
205
            'users' => 'carbon:user',
206
            'videos' => 'carbon:video',
207
        ];
208
        if (!in_array($name, array_keys($map))) {
209
            return '';
210
        }
211
212
        return sprintf('<app-icon icon="%s" :style="{ width: \'28px\', height: \'28px\' }"></app-icon>', $map[$name]);
213
    }
214
215
    /**
216
     * Return module css class(es).
217
     * If object is locked by parents, return base class plus 'locked' class.
218
     * If object is locked by concurrent editors, return 'concurrent-editors' class plus publish status class.
219
     * If object is not locked, return base class plus publish status class.
220
     * Publish status class is 'expired', 'future', 'locked' or 'draft'.
221
     *
222
     * @return string
223
     */
224
    public function moduleClass(): string
225
    {
226
        $moduleClasses = ['app-module-box'];
227
        $object = (array)$this->getView()->get('object');
228
        if (!empty($object) && $this->Perms->isLockedByParents((string)Hash::get($object, 'id'))) {
229
            $moduleClasses[] = 'locked';
230
231
            return trim(implode(' ', $moduleClasses));
232
        }
233
        $editors = $this->Editors->list();
234
        if (count($editors) > 1) {
235
            $moduleClasses = ['app-module-box-concurrent-editors'];
236
        }
237
        $moduleClasses[] = $this->publishStatus($object);
238
239
        return trim(implode(' ', $moduleClasses));
240
    }
241
242
    /**
243
     * Module main link
244
     *
245
     * @return string The link
246
     */
247
    public function moduleLink(): string
248
    {
249
        $currentModule = (array)$this->getView()->get('currentModule');
250
        if (!empty($currentModule) && !empty($currentModule['name'])) {
251
            $name = $currentModule['name'];
252
            $label = Hash::get($currentModule, 'label', $name);
253
            $counters = Configure::read('UI.modules.counters', ['trash']);
254
            $showCounter = is_array($counters) ? in_array($name, $counters) : $counters === 'all';
255
            $count = !$showCounter ? '' : $this->moduleCount($name);
256
257
            return sprintf(
258
                '<a href="%s" class="%s"><span class="mr-05">%s</span>%s%s</a>',
259
                $this->Url->build(['_name' => 'modules:list', 'object_type' => $name]),
260
                sprintf('module-item has-background-module-%s', $name),
261
                $this->tr($label),
262
                $this->moduleIcon($name, $currentModule),
263
                $count,
264
            );
265
        }
266
267
        // if no `currentModule` has been set a `moduleLink` must be set in controller otherwise current link is displayed
268
        return $this->Html->link(
269
            $this->tr($this->getView()->getName()),
270
            (array)$this->getView()->get('moduleLink'),
271
            ['class' => $this->commandLinkClass()],
272
        );
273
    }
274
275
    /**
276
     * Return module index default view type
277
     *
278
     * @return string
279
     */
280
    public function moduleIndexDefaultViewType(): string
281
    {
282
        $module = (array)$this->getView()->get('currentModule');
283
        $name = (string)Hash::get($module, 'name');
284
285
        return $name === 'folders' ? 'tree' : 'list';
286
    }
287
288
    /**
289
     * Return module index view type
290
     *
291
     * @return string
292
     */
293
    public function moduleIndexViewType(): string
294
    {
295
        $query = (array)$this->getView()->getRequest()->getQueryParams();
296
297
        return (string)Hash::get($query, 'view_type', $this->moduleIndexDefaultViewType());
298
    }
299
300
    /**
301
     * Return module index view types
302
     *
303
     * @return array
304
     */
305
    public function moduleIndexViewTypes(): array
306
    {
307
        $defaultType = $this->moduleIndexDefaultViewType();
308
309
        return $defaultType === 'tree' ? ['tree', 'tree-compact', 'list'] : ['list'];
310
    }
311
312
    /**
313
     * Append view type buttons to the sidebar
314
     *
315
     * @return void
316
     */
317
    public function appendViewTypeButtons(): void
318
    {
319
        $indexViewTypes = $this->moduleIndexViewTypes();
320
        if (count($indexViewTypes) > 1) {
321
            $indexViewType = $this->moduleIndexViewType();
322
            foreach ($indexViewTypes as $t) {
323
                if ($t !== $indexViewType) {
324
                    $append = false;
325
                    $icon = '';
326
                    $label = '';
327
                    switch ($t) {
328
                        case 'tree':
329
                            $icon = 'carbon:tree-view';
330
                            $label = __('Tree view');
331
                            $append = true;
332
                            break;
333
                        case 'tree-compact':
334
                            $icon = 'carbon:tree-view';
335
                            $label = __('Tree compact');
336
                            $meta = (array)$this->getView()->get('meta');
337
                            $count = (int)Hash::get($meta, 'pagination.count');
338
                            $append = $count <= Configure::read('UI.tree_compact_view_limit', 100);
339
                            break;
340
                        case 'list':
341
                            $icon = 'carbon:list';
342
                            $label = __('List view');
343
                            $append = true;
344
                            break;
345
                    }
346
                    if ($append) {
347
                        $url = $this->Url->build(
348
                            [
349
                                '_name' => 'modules:list',
350
                                'object_type' => $this->getView()->get('objectType'),
351
                            ],
352
                        );
353
                        $url = sprintf('%s?view_type=%s', $url, $t);
354
                        $anchor = sprintf(
355
                            '<a href="%s" class="button button-outlined button-outlined-module-%s"><app-icon icon="%s"></app-icon><span class="ml-05">%s</span></a>',
356
                            $url,
357
                            $this->getView()->get('currentModule.name'),
358
                            $icon,
359
                            __($label),
360
                        );
361
                        $this->getView()->append('app-module-buttons', $anchor);
362
                    }
363
                }
364
            }
365
        }
366
    }
367
368
    /**
369
     * Return style class for command link
370
     *
371
     * @return string
372
     */
373
    protected function commandLinkClass(): string
374
    {
375
        $moduleClasses = [
376
            'UserProfile' => 'has-background-black icon-user',
377
            'Import' => 'has-background-black icon-download-alt',
378
            'ObjectTypes' => 'has-background-black',
379
            'Relations' => 'has-background-black',
380
            'PropertyTypes' => 'has-background-black',
381
            'Categories' => 'has-background-black',
382
            'Appearance' => 'has-background-black',
383
            'Applications' => 'has-background-black',
384
            'AsyncJobs' => 'has-background-black',
385
            'Config' => 'has-background-black',
386
            'Endpoints' => 'has-background-black',
387
            'Roles' => 'has-background-black',
388
            'RolesModules' => 'has-background-black',
389
            'EndpointPermissions' => 'has-background-black',
390
            'Tags' => 'has-background-module-tags',
391
            'ObjectsHistory' => 'has-background-black',
392
            'SystemInfo' => 'has-background-black',
393
            'UserAccesses' => 'has-background-black',
394
            'Statistics' => 'has-background-black',
395
            'AuthProviders' => 'has-background-black',
396
            'ExternalAuth' => 'has-background-black',
397
        ];
398
399
        return (string)Hash::get($moduleClasses, $this->_View->getName(), 'commands-menu__module');
400
    }
401
402
    /**
403
     * Get translated val by input string, using plugins (if any) translations.
404
     *
405
     * @param string $input The input string
406
     * @return string|null
407
     */
408
    public function tr(string $input): ?string
409
    {
410
        return Translate::get($input);
411
    }
412
413
    /**
414
     * Return configuration items to create JSON BEDITA object
415
     *
416
     * @return array
417
     */
418
    public function metaConfig(): array
419
    {
420
        return [
421
            'base' => $this->Link->baseUrl(),
422
            'currentModule' => $this->getView()->get('currentModule', ['name' => 'home']),
423
            'template' => $this->getView()->getTemplate(),
424
            'modules' => array_keys($this->getView()->get('modules', [])),
425
            'plugins' => Plugin::loadedAppPlugins(),
426
            'uploadable' => $this->getView()->get('uploadable', []),
427
            'locale' => I18n::getLocale(),
428
            'csrfToken' => $this->getCsrfToken(),
429
            'maxFileSize' => $this->System->getMaxFileSize(),
430
            'canReadUsers' => $this->Perms->canRead('users'),
431
            'canSave' => $this->Perms->canSave(),
432
            'cloneConfig' => (array)Configure::read('Clone'),
433
            'placeholdersConfig' => $this->System->placeholdersConfig(),
434
            'uploadConfig' => $this->System->uploadConfig(),
435
            'relationsSchema' => $this->getView()->get('relationsSchema', []),
436
            'richeditorConfig' => (array)Configure::read('Richeditor'),
437
            'richeditorByPropertyConfig' => $this->uiRicheditorConfig(),
438
            'indexLists' => (array)$this->indexLists(),
439
            'fastCreateFields' => (array)$this->Property->fastCreateFieldsMap(),
440
            'concreteTypes' => (array)$this->getView()->get('allConcreteTypes', []),
441
        ];
442
    }
443
444
    /**
445
     * Return configuration for UI rich editor.
446
     * For admin users, add 'code' to the toolbar if not present.
447
     *
448
     * @return array
449
     */
450
    public function uiRicheditorConfig(): array
451
    {
452
        $cfg = (array)Configure::read('UI.richeditor', []);
453
        if (!$this->Perms->userIsAdmin()) {
454
            return $cfg;
455
        }
456
        foreach ($cfg as &$value) {
457
            if (isset($value['toolbar']) && is_array($value['toolbar']) && !in_array('code', $value['toolbar'], true)) {
458
                $value['toolbar'][] = 'code';
459
            }
460
        }
461
462
        return $cfg;
463
    }
464
465
    /**
466
     * Get csrf token, searching in: request params, data, attributes and cookies
467
     *
468
     * @return string|null
469
     */
470
    public function getCsrfToken(): ?string
471
    {
472
        if (!empty($this->getView()->getRequest()->getParam('_csrfToken'))) {
473
            return (string)$this->getView()->getRequest()->getParam('_csrfToken');
474
        }
475
        if (!empty($this->getView()->getRequest()->getData('_csrfToken'))) {
476
            return (string)$this->getView()->getRequest()->getData('_csrfToken');
477
        }
478
        if (!empty($this->getView()->getRequest()->getAttribute('csrfToken'))) {
479
            return (string)$this->getView()->getRequest()->getAttribute('csrfToken');
480
        }
481
        if (!empty($this->getView()->getRequest()->getCookie('csrfToken'))) {
482
            return (string)$this->getView()->getRequest()->getCookie('csrfToken');
483
        }
484
485
        return null;
486
    }
487
488
    /**
489
     * Trash link by type.
490
     *
491
     * @param string|null $type The object type, if any.
492
     * @return string
493
     */
494
    public function trashLink(?string $type): string
495
    {
496
        if (empty($type) || $type === 'trash' || $type === 'translations') {
497
            return '';
498
        }
499
500
        $modules = (array)$this->_View->get('modules');
501
        if (!Hash::check($modules, sprintf('%s.hints.object_type', $type))) {
502
            return '';
503
        }
504
505
        $classes = sprintf('button icon icon-only-icon has-text-module-%s', $type);
506
        $title = $this->tr($type) . __(' in Trashcan');
507
        $filter = ['type' => [$type]];
508
509
        return $this->Html->link(
510
            sprintf('<span class="is-sr-only">%s</span><app-icon icon="carbon:trash-can"></app-icon>', __('Trash')),
511
            ['_name' => 'trash:list', '?' => compact('filter')],
512
            ['class' => $classes, 'title' => $title, 'escape' => false],
513
        );
514
    }
515
516
    /**
517
     * Return properties group for given property
518
     *
519
     * @param string $needle The property to search
520
     * @return ?string
521
     */
522
    public function propertyGroup(string $needle): ?string
523
    {
524
        $properties = (array)$this->getView()->get('properties');
525
        foreach ($properties as $group => $props) {
526
            $keys = array_keys($props);
527
            if (in_array($needle, $keys)) {
528
                return $group;
529
            }
530
        }
531
532
        return null;
533
    }
534
535
    /**
536
     * Return list of properties to display in `index` view per modules
537
     *
538
     * @return array
539
     */
540
    public function indexLists(): array
541
    {
542
        if (!Configure::check('API.apiBaseUrl')) {
543
            return [];
544
        }
545
        $cacheKey = CacheTools::cacheKey('properties.indexLists');
546
        $indexLists = Cache::read($cacheKey, 'default');
547
        if (!empty($indexLists)) {
548
            return $indexLists;
549
        }
550
        Configure::load('properties');
551
        $properties = (array)Configure::read('Properties');
552
        $defaultProperties = (array)Configure::read('DefaultProperties');
553
        $keys = array_keys($properties);
554
        $keysDefault = array_keys($defaultProperties);
555
        $keys = array_unique(array_merge($keys, $keysDefault));
556
        $indexLists = [];
557
        foreach ($keys as $objectType) {
558
            $il = (array)Hash::get($properties, sprintf('%s.index', $objectType));
559
            $il = empty($il) ? (array)Hash::get($defaultProperties, sprintf('%s.index', $objectType)) : $il;
560
            $indexLists[$objectType] = $il;
561
        }
562
        Cache::write($cacheKey, $indexLists);
563
564
        return $indexLists;
565
    }
566
}
567