Passed
Pull Request — master (#1275)
by Dante
01:42
created

LayoutHelper::appendViewTypeButtons()   B

Complexity

Conditions 8
Paths 11

Size

Total Lines 45
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

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