Issues (9)

src/View/Helper/LinkHelper.php (2 issues)

Labels
Severity
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 Cake\Core\Configure;
16
use Cake\Core\Plugin;
17
use Cake\Routing\Router;
18
use Cake\Utility\Hash;
19
use Cake\View\Helper;
20
21
/**
22
 * Helper class to generate links or link tags
23
 *
24
 * @property \Cake\View\Helper\HtmlHelper $Html
25
 */
26
class LinkHelper extends Helper
27
{
28
    /**
29
     * List of helpers used by this helper
30
     *
31
     * @var array
32
     */
33
    public $helpers = ['Html'];
34
35
    /**
36
     * {@inheritDoc}
37
     *
38
     * Default configuration
39
     *
40
     *  - 'apiBaseUrl': API base URL
41
     *  - 'webBaseUrl': WebApp base URL
42
     *  - 'query': Request Query params
43
     *  - 'manifestPath': Manifest file path
44
     *  - 'manifest': Manifest content (array)
45
     */
46
    protected $_defaultConfig = [
47
        'apiBaseUrl' => '',
48
        'webBaseUrl' => '',
49
        'query' => [],
50
        'manifestPath' => WWW_ROOT . 'manifest.json',
51
        'manifest' => [],
52
    ];
53
54
    /**
55
     * {@inheritDoc}
56
     *
57
     * Init API and WebAPP base URL
58
     *
59
     * @codeCoverageIgnore
60
     */
61
    public function initialize(array $config): void
62
    {
63
        $default = [
64
            'apiBaseUrl' => Configure::read('API.apiBaseUrl'),
65
            'webBaseUrl' => Router::fullBaseUrl(),
66
            'query' => $this->getView()->getRequest()->getQueryParams(),
67
        ];
68
        $this->setConfig(array_merge($default, $config));
69
        if (empty($this->getConfig('manifest')) && file_exists($this->getConfig('manifestPath'))) {
0 ignored issues
show
It seems like $this->getConfig('manifestPath') can also be of type null; however, parameter $filename of file_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

69
        if (empty($this->getConfig('manifest')) && file_exists(/** @scrutinizer ignore-type */ $this->getConfig('manifestPath'))) {
Loading history...
70
            $content = (string)file_get_contents($this->getConfig('manifestPath'));
0 ignored issues
show
It seems like $this->getConfig('manifestPath') can also be of type null; however, parameter $filename of file_get_contents() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

70
            $content = (string)file_get_contents(/** @scrutinizer ignore-type */ $this->getConfig('manifestPath'));
Loading history...
71
            $this->setConfig('manifest', json_decode($content, true));
72
        }
73
    }
74
75
    /**
76
     * Return base url.
77
     * If it ends with ':80' (proxy case), remove it.
78
     *
79
     * @return string
80
     */
81
    public function baseUrl(): string
82
    {
83
        $url = (string)$this->getConfig('webBaseUrl');
84
        if (substr_compare($url, ':80', -strlen(':80')) === 0) {
85
            return substr($url, 0, strpos($url, ':80'));
86
        }
87
88
        return $url;
89
    }
90
91
    /**
92
     * Transform API url in web app URL, preserving path part.
93
     * Extremely simple for now
94
     *
95
     * @param string $apiUrl Api url
96
     * @return void
97
     */
98
    public function fromAPI($apiUrl): void
99
    {
100
        echo str_replace($this->getConfig('apiBaseUrl'), $this->getConfig('webBaseUrl'), $apiUrl);
101
    }
102
103
    /**
104
     * Returns sort url by field direction
105
     *
106
     * @param string $field the Field.
107
     * @param bool $resetPage flag to reset pagination.
108
     * @return string
109
     */
110
    public function sortUrl($field, $resetPage = true): string
111
    {
112
        $sort = (string)Hash::get($this->getConfig('query'), 'sort');
113
        $sort = $this->sortValue($field, $sort);
114
        $replace = compact('sort');
115
        $currentPage = Hash::get($this->getConfig('query'), 'page');
116
        if (isset($currentPage) && $resetPage) {
117
            $replace['page'] = 1;
118
        }
119
120
        return $this->replaceQueryParams($replace);
121
    }
122
123
    /**
124
     * Define sort query string value using sort string and current
125
     * 'sort' value
126
     *
127
     * @param string $field Field to sort.
128
     * @param string $currentSort Current sort value
129
     * @return string
130
     */
131
    protected function sortValue(string $field, string $currentSort): string
132
    {
133
        $sort = $this->sortField($field); // <= ascendant order
134
        if ($currentSort === $sort) { // it was ascendant sort
135
            $sort = '-' . $sort; // <= descendant order
136
        }
137
138
        return $sort;
139
    }
140
141
    /**
142
     * Retrieve 'sort' field from field name.
143
     *
144
     * @param string $field Field name.
145
     * @return string
146
     */
147
    protected function sortField(string $field): string
148
    {
149
        if ($field === 'date_ranges') {
150
            return 'date_ranges_min_start_date';
151
        }
152
153
        return $field;
154
    }
155
156
    /**
157
     * Returns sort class by field direction
158
     *
159
     * @param string $field the Field.
160
     * @return string
161
     */
162
    public function sortClass(string $field): string
163
    {
164
        $sort = (string)Hash::get($this->getConfig('query'), 'sort');
165
        if (empty($sort)) {
166
            return '';
167
        }
168
        $sortField = $this->sortField($field);
169
        if ($sort === $sortField) { // it was ascendant sort
170
            return 'sort down';
171
        }
172
        if ($sort === '-' . $sortField) { // it was descendant sort
173
            return 'sort up';
174
        }
175
176
        return '';
177
    }
178
179
    /**
180
     * Pagination link for page
181
     *
182
     * @param int $page destination page.
183
     * @return void
184
     */
185
    public function page($page): void
186
    {
187
        echo $this->replaceQueryParams(['page' => $page]);
188
    }
189
190
    /**
191
     * Pagination link for page size
192
     *
193
     * @param int $pageSize new page size.
194
     * @return void
195
     */
196
    public function pageSize($pageSize): void
197
    {
198
        echo $this->replaceQueryParams(['page_size' => $pageSize]);
199
    }
200
201
    /**
202
     * Return url for current page
203
     *
204
     * @param array $options options for query
205
     * @return string url
206
     */
207
    public function here($options = []): string
208
    {
209
        $url = (string)$this->getConfig('webBaseUrl');
210
        $here = sprintf(
211
            '%s%s',
212
            $url,
213
            $this->getView()->getRequest()->getAttribute('here')
214
        );
215
        $query = (array)$this->getConfig('query');
216
        if (empty($query) || !empty($options['no-query'])) {
217
            return $here;
218
        }
219
        if (isset($options['exclude'])) {
220
            $key = sprintf('query.%s', $options['exclude']);
221
            $this->setConfig($key, null);
222
            $query = (array)$this->getConfig('query');
223
        }
224
        $q = http_build_query($query);
225
        if (!empty($q)) {
226
            return $here . '?' . $q;
227
        }
228
229
        return $here;
230
    }
231
232
    /**
233
     * Replace query parameters on current request.
234
     *
235
     * @param array $queryParams list of query params to replace.
236
     * @return string new uri
237
     */
238
    private function replaceQueryParams(array $queryParams): string
239
    {
240
        $request = $this->getView()->getRequest();
241
        $query = array_merge((array)$this->getConfig('query'), $queryParams);
242
243
        return (string)$request->getUri()->withQuery(http_build_query($query));
244
    }
245
246
    /**
247
     * Include plugin js and css bundles.
248
     *
249
     * @return void
250
     */
251
    public function pluginsBundle(): void
252
    {
253
        $plugins = Configure::read('Plugins', []);
254
        foreach (['js', 'css'] as $extension) {
255
            foreach ($plugins as $plugin => $v) {
256
                echo $this->pluginAsset($plugin, $extension);
257
            }
258
        }
259
    }
260
261
    /**
262
     * Return js/css plugin asset by plugin and extension
263
     *
264
     * @param string $plugin The plugin
265
     * @param string $extension the extension
266
     * @return string
267
     */
268
    public function pluginAsset(string $plugin, string $extension): string
269
    {
270
        $path = sprintf('%swebroot%s%s%s%s.plugin.%s', Plugin::path($plugin), DS, $extension, DS, $plugin, $extension);
271
        if (!file_exists($path)) {
272
            return '';
273
        }
274
        $method = $extension === 'js' ? 'script' : 'css';
275
276
        return $this->Html->{$method}(sprintf('%s.%s.plugin.%s', $plugin, $plugin, $extension));
277
    }
278
279
    /**
280
     * Include statically imported JS splitted vendors.
281
     *
282
     * @param array $filter list of file name filters
283
     * @return void
284
     */
285
    public function jsBundle(array $filter = []): void
286
    {
287
        $jsFiles = $this->findFiles($filter, 'js');
288
        foreach ($jsFiles as $jsFile) {
289
            echo $this->Html->script(sprintf('%s', $jsFile));
290
        }
291
    }
292
293
    /**
294
     * Include statically imported CSS splitted vendors.
295
     *
296
     * @param array $filter list of file name filters
297
     * @return void
298
     */
299
    public function cssBundle(array $filter = []): void
300
    {
301
        $cssFiles = $this->findFiles($filter, 'css');
302
        foreach ($cssFiles as $cssFile) {
303
            echo $this->Html->css(sprintf('%s', $cssFile));
304
        }
305
    }
306
307
    /**
308
     * find files under webroot directory specifing a ordered list of filters and the file type
309
     * to search for
310
     *
311
     * @param array $filter list of file name filters
312
     * @param string $type file type (js/css)
313
     * @return array files found
314
     */
315
    public function findFiles(array $filter, string $type): array
316
    {
317
        $ext = '.' . $type;
318
        $len = strlen($ext);
319
        $prefixLen = strlen(sprintf('/%s/', $type));
320
        $files = [];
321
        $manifest = (array)$this->getConfig('manifest');
322
        foreach ($manifest as $key => $value) {
323
            // see if file name ends with extension
324
            if (substr_compare($key, $ext, -$len) !== 0) {
325
                continue;
326
            }
327
            foreach ($filter as $filterName) {
328
                if (strpos($key, $filterName) !== false) {
329
                    // add file without prefix
330
                    $files[] = substr($value, $prefixLen);
331
                }
332
            }
333
        }
334
335
        return $files;
336
    }
337
}
338