Passed
Push — master ( 20e220...1cc721 )
by Dante
02:02
created

LayoutHelper::uiRicheditorConfig()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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