LayoutHelper::getCsrfToken()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 16
rs 9.6111
cc 5
nc 5
nop 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
        $schema = (array)$this->_View->get('schema');
285
        $defaultType = in_array('DateRanges', (array)Hash::get($schema, 'associations')) ? 'calendar' : 'list';
286
        $defaultType = $name === 'folders' ? 'tree' : $defaultType;
287
288
        return $defaultType;
289
    }
290
291
    /**
292
     * Return module index view type
293
     *
294
     * @return string
295
     */
296
    public function moduleIndexViewType(): string
297
    {
298
        $query = (array)$this->getView()->getRequest()->getQueryParams();
299
300
        return (string)Hash::get($query, 'view_type', $this->moduleIndexDefaultViewType());
301
    }
302
303
    /**
304
     * Return module index view types
305
     *
306
     * @return array
307
     */
308
    public function moduleIndexViewTypes(): array
309
    {
310
        $defaultType = $this->moduleIndexDefaultViewType();
311
        $defaultList = $defaultType === 'calendar' ? ['calendar', 'list'] : ['list'];
312
        $defaultList = $defaultType === 'tree' ? ['tree', 'tree-compact', 'list'] : $defaultList;
313
314
        return $defaultList;
315
    }
316
317
    /**
318
     * Append view type buttons to the sidebar
319
     *
320
     * @return void
321
     */
322
    public function appendViewTypeButtons(): void
323
    {
324
        $indexViewTypes = $this->moduleIndexViewTypes();
325
        if (count($indexViewTypes) > 1) {
326
            $indexViewType = $this->moduleIndexViewType();
327
            foreach ($indexViewTypes as $t) {
328
                if ($t !== $indexViewType) {
329
                    $append = false;
330
                    $icon = '';
331
                    $label = '';
332
                    switch ($t) {
333
                        case 'tree':
334
                            $icon = 'carbon:tree-view';
335
                            $label = __('Tree view');
336
                            $append = true;
337
                            break;
338
                        case 'tree-compact':
339
                            $icon = 'carbon:tree-view';
340
                            $label = __('Tree compact');
341
                            $meta = (array)$this->getView()->get('meta');
342
                            $count = (int)Hash::get($meta, 'pagination.count');
343
                            $append = $count <= Configure::read('UI.tree_compact_view_limit', 100);
344
                            break;
345
                        case 'list':
346
                            $icon = 'carbon:list';
347
                            $label = __('List view');
348
                            $append = true;
349
                            break;
350
                    }
351
                    if ($append) {
352
                        $url = $this->Url->build(
353
                            [
354
                                '_name' => 'modules:list',
355
                                'object_type' => $this->getView()->get('objectType'),
356
                            ],
357
                        );
358
                        $url = sprintf('%s?view_type=%s', $url, $t);
359
                        $anchor = sprintf(
360
                            '<a href="%s" class="button button-outlined button-outlined-module-%s"><app-icon icon="%s"></app-icon><span class="ml-05">%s</span></a>',
361
                            $url,
362
                            $this->getView()->get('currentModule.name'),
363
                            $icon,
364
                            __($label),
365
                        );
366
                        $this->getView()->append('app-module-buttons', $anchor);
367
                    }
368
                }
369
            }
370
        }
371
    }
372
373
    /**
374
     * Return style class for command link
375
     *
376
     * @return string
377
     */
378
    protected function commandLinkClass(): string
379
    {
380
        $moduleClasses = [
381
            'UserProfile' => 'has-background-black icon-user',
382
            'Import' => 'has-background-black icon-download-alt',
383
            'ObjectTypes' => 'has-background-black',
384
            'Relations' => 'has-background-black',
385
            'PropertyTypes' => 'has-background-black',
386
            'Categories' => 'has-background-black',
387
            'Appearance' => 'has-background-black',
388
            'Applications' => 'has-background-black',
389
            'AsyncJobs' => 'has-background-black',
390
            'Config' => 'has-background-black',
391
            'Endpoints' => 'has-background-black',
392
            'Roles' => 'has-background-black',
393
            'RolesModules' => 'has-background-black',
394
            'EndpointPermissions' => 'has-background-black',
395
            'Tags' => 'has-background-module-tags',
396
            'ObjectsHistory' => 'has-background-black',
397
            'SystemInfo' => 'has-background-black',
398
            'UserAccesses' => 'has-background-black',
399
            'Statistics' => 'has-background-black',
400
            'AuthProviders' => 'has-background-black',
401
            'ExternalAuth' => 'has-background-black',
402
        ];
403
404
        return (string)Hash::get($moduleClasses, $this->_View->getName(), 'commands-menu__module');
405
    }
406
407
    /**
408
     * Get translated val by input string, using plugins (if any) translations.
409
     *
410
     * @param string $input The input string
411
     * @return string|null
412
     */
413
    public function tr(string $input): ?string
414
    {
415
        return Translate::get($input);
416
    }
417
418
    /**
419
     * Return configuration items to create JSON BEDITA object
420
     *
421
     * @return array
422
     */
423
    public function metaConfig(): array
424
    {
425
        return [
426
            'base' => $this->Link->baseUrl(),
427
            'currentModule' => $this->getView()->get('currentModule', ['name' => 'home']),
428
            'template' => $this->getView()->getTemplate(),
429
            'modules' => array_keys($this->getView()->get('modules', [])),
430
            'plugins' => Plugin::loadedAppPlugins(),
431
            'uploadable' => $this->getView()->get('uploadable', []),
432
            'locale' => I18n::getLocale(),
433
            'csrfToken' => $this->getCsrfToken(),
434
            'maxFileSize' => $this->System->getMaxFileSize(),
435
            'canReadUsers' => $this->Perms->canRead('users'),
436
            'canSave' => $this->Perms->canSave(),
437
            'cloneConfig' => (array)Configure::read('Clone'),
438
            'placeholdersConfig' => $this->System->placeholdersConfig(),
439
            'uploadConfig' => $this->System->uploadConfig(),
440
            'relationsSchema' => $this->getView()->get('relationsSchema', []),
441
            'richeditorConfig' => (array)Configure::read('Richeditor'),
442
            'richeditorByPropertyConfig' => $this->uiRicheditorConfig(),
443
            'indexLists' => (array)$this->indexLists(),
444
            'fastCreateFields' => (array)$this->Property->fastCreateFieldsMap(),
445
            'concreteTypes' => (array)$this->getView()->get('allConcreteTypes', []),
446
        ];
447
    }
448
449
    /**
450
     * Return configuration for UI rich editor.
451
     * For admin users, add 'code' to the toolbar if not present.
452
     *
453
     * @return array
454
     */
455
    public function uiRicheditorConfig(): array
456
    {
457
        $cfg = (array)Configure::read('UI.richeditor', []);
458
        if (!$this->Perms->userIsAdmin()) {
459
            return $cfg;
460
        }
461
        foreach ($cfg as &$value) {
462
            if (isset($value['toolbar']) && is_array($value['toolbar']) && !in_array('code', $value['toolbar'], true)) {
463
                $value['toolbar'][] = 'code';
464
            }
465
        }
466
467
        return $cfg;
468
    }
469
470
    /**
471
     * Get csrf token, searching in: request params, data, attributes and cookies
472
     *
473
     * @return string|null
474
     */
475
    public function getCsrfToken(): ?string
476
    {
477
        if (!empty($this->getView()->getRequest()->getParam('_csrfToken'))) {
478
            return (string)$this->getView()->getRequest()->getParam('_csrfToken');
479
        }
480
        if (!empty($this->getView()->getRequest()->getData('_csrfToken'))) {
481
            return (string)$this->getView()->getRequest()->getData('_csrfToken');
482
        }
483
        if (!empty($this->getView()->getRequest()->getAttribute('csrfToken'))) {
484
            return (string)$this->getView()->getRequest()->getAttribute('csrfToken');
485
        }
486
        if (!empty($this->getView()->getRequest()->getCookie('csrfToken'))) {
487
            return (string)$this->getView()->getRequest()->getCookie('csrfToken');
488
        }
489
490
        return null;
491
    }
492
493
    /**
494
     * Trash link by type.
495
     *
496
     * @param string|null $type The object type, if any.
497
     * @return string
498
     */
499
    public function trashLink(?string $type): string
500
    {
501
        if (empty($type) || $type === 'trash' || $type === 'translations') {
502
            return '';
503
        }
504
505
        $modules = (array)$this->_View->get('modules');
506
        if (!Hash::check($modules, sprintf('%s.hints.object_type', $type))) {
507
            return '';
508
        }
509
510
        $classes = sprintf('button icon icon-only-icon has-text-module-%s', $type);
511
        $title = $this->tr($type) . __(' in Trashcan');
512
        $filter = ['type' => [$type]];
513
514
        return $this->Html->link(
515
            sprintf('<span class="is-sr-only">%s</span><app-icon icon="carbon:trash-can"></app-icon>', __('Trash')),
516
            ['_name' => 'trash:list', '?' => compact('filter')],
517
            ['class' => $classes, 'title' => $title, 'escape' => false],
518
        );
519
    }
520
521
    /**
522
     * Return properties group for given property
523
     *
524
     * @param string $needle The property to search
525
     * @return ?string
526
     */
527
    public function propertyGroup(string $needle): ?string
528
    {
529
        $properties = (array)$this->getView()->get('properties');
530
        foreach ($properties as $group => $props) {
531
            $keys = array_keys($props);
532
            if (in_array($needle, $keys)) {
533
                return $group;
534
            }
535
        }
536
537
        return null;
538
    }
539
540
    /**
541
     * Return list of properties to display in `index` view per modules
542
     *
543
     * @return array
544
     */
545
    public function indexLists(): array
546
    {
547
        if (!Configure::check('API.apiBaseUrl')) {
548
            return [];
549
        }
550
        $cacheKey = CacheTools::cacheKey('properties.indexLists');
551
        $indexLists = Cache::read($cacheKey, 'default');
552
        if (!empty($indexLists)) {
553
            return $indexLists;
554
        }
555
        Configure::load('properties');
556
        $properties = (array)Configure::read('Properties');
557
        $defaultProperties = (array)Configure::read('DefaultProperties');
558
        $keys = array_keys($properties);
559
        $keysDefault = array_keys($defaultProperties);
560
        $keys = array_unique(array_merge($keys, $keysDefault));
561
        $indexLists = [];
562
        foreach ($keys as $objectType) {
563
            $il = (array)Hash::get($properties, sprintf('%s.index', $objectType));
564
            $il = empty($il) ? (array)Hash::get($defaultProperties, sprintf('%s.index', $objectType)) : $il;
565
            $indexLists[$objectType] = $il;
566
        }
567
        Cache::write($cacheKey, $indexLists);
568
569
        return $indexLists;
570
    }
571
}
572