Issues (281)

Branch: master

src/Frontend/Core/Engine/TemplateModifiers.php (2 issues)

1
<?php
2
3
namespace Frontend\Core\Engine;
4
5
use Common\Exception\RedirectException;
6
use DateTime;
7
use Frontend\Core\Engine\Model as FrontendModel;
8
use Frontend\Core\Engine\Block\Widget as FrontendBlockWidget;
9
use Frontend\Core\Language\Language;
10
use Frontend\Core\Language\Locale;
11
use Frontend\Modules\Profiles\Engine\Model as FrontendProfilesModel;
12
use Common\Core\Twig\Extensions\BaseTwigModifiers;
13
use SpoonDate;
14
use Twig\Error\Error;
15
16
/**
17
 * Contains all Frontend-related custom modifiers
18
 */
19
class TemplateModifiers extends BaseTwigModifiers
20
{
21
    /**
22
     * Format a UNIX-timestamp as a date
23
     * syntax: {{ $var|formatdate }}
24
     *
25
     * @param int|DateTime $var The UNIX-timestamp to format or \DateTime
26
     *
27
     * @return string
28
     */
29
    public static function formatDate($var): string
30
    {
31
        // get setting
32
        $format = FrontendModel::get('fork.settings')->get('Core', 'date_format_short');
33
34
        if ($var instanceof DateTime) {
35
            $var = $var->getTimestamp();
36
        }
37
38
        // format the date
39
        return SpoonDate::getDate($format, (int) $var, Locale::frontendLanguage());
40
    }
41
42
    /**
43
     * Format a UNIX-timestamp as a date
44
     * syntax: {{ $var|formatdatetime }}
45
     *
46
     * @param int|DateTime $var The UNIX-timestamp to format or \DateTime
47
     *
48
     * @return string
49
     */
50
    public static function formatDateTime($var): string
51
    {
52
        // get setting
53
        $format = FrontendModel::get('fork.settings')->get('Core', 'date_format_long');
54
55
        if ($var instanceof DateTime) {
56
            $var = $var->getTimestamp();
57
        }
58
59
        // format the date
60
        return SpoonDate::getDate($format, (int) $var, Locale::frontendLanguage());
61
    }
62
63
    /**
64
     * Format a number as a float
65
     *    syntax: {{ $number|formatfloat($decimals) }}
66
     *
67
     * @param float $number The number to format.
68
     * @param int $decimals The number of decimals.
69
     *
70
     * @return string
71 1
     */
72
    public static function formatFloat(float $number, int $decimals = 2): string
73 1
    {
74
        return number_format($number, $decimals, '.', ' ');
75
    }
76
77
    /**
78
     * Format a number
79
     *    syntax: {{ $string|formatnumber($decimals) }}
80
     *
81
     * @param float $number The number to format.
82
     * @param int $decimals The number of decimals
83
     *
84
     * @return string
85
     */
86
    public static function formatNumber(float $number, int $decimals = null): string
87
    {
88
        // get setting
89
        $format = FrontendModel::get('fork.settings')->get('Core', 'number_format');
90
91
        // get amount of decimals
92
        if ($decimals === null) {
93
            $decimals = (mb_strpos($number, '.') ? mb_strlen(mb_substr($number, mb_strpos($number, '.') + 1)) : 0);
94
        }
95
96
        // get separators
97
        $separators = explode('_', $format);
98
        $separatorSymbols = ['comma' => ',', 'dot' => '.', 'space' => ' ', 'nothing' => ''];
99
        $decimalSeparator = isset($separators[0], $separatorSymbols[$separators[0]])
100
            ? $separatorSymbols[$separators[0]] : null;
101
        $thousandsSeparator = isset($separators[1], $separatorSymbols[$separators[1]])
102
            ? $separatorSymbols[$separators[1]] : null;
103
104
        // format the number
105
        return number_format($number, $decimals, $decimalSeparator, $thousandsSeparator);
106
    }
107
108
    /**
109
     * Format a UNIX-timestamp as a date
110
     * syntax: {{ $var|formatdate }}
111
     *
112
     * @param int|DateTime $var The UNIX-timestamp to format or \DateTime
113
     *
114
     * @return string
115
     */
116
    public static function formatTime($var): string
117
    {
118
        // get setting
119
        $format = FrontendModel::get('fork.settings')->get('Core', 'time_format');
120
121
        if ($var instanceof DateTime) {
122
            $var = $var->getTimestamp();
123
        }
124
125
        // format the date
126
        return SpoonDate::getDate($format, (int) $var, Locale::frontendLanguage());
127
    }
128
129
    /**
130
     * Get the navigation html
131
     *    syntax: {{ getnavigation($type, $parentId, $depth, $excludeIds-splitted-by-dash, $template) }}
132
     *
133
     * @param string $type The type of navigation, possible values are: page, footer.
134
     * @param int $parentId The parent wherefore the navigation should be build.
135
     * @param int $depth The maximum depth that has to be build.
136
     * @param string $excludeIds Which pageIds should be excluded (split them by -).
137
     * @param string $template The template that will be used.
138
     *
139
     * @return string
140 26
     */
141
    public static function getNavigation(
142
        string $type = 'page',
143
        int $parentId = 0,
144
        int $depth = null,
145
        string $excludeIds = null,
146
        string $template = 'Core/Layout/Templates/Navigation.html.twig'
147
    ): string {
148 26
        // build excludeIds
149
        if ($excludeIds !== null) {
150
            $excludeIds = explode('-', $excludeIds);
151
        }
152
153
        // get HTML
154 26
        try {
155
            $return = (string) Navigation::getNavigationHTML($type, $parentId, $depth, (array) $excludeIds, $template);
156
        } catch (Exception $e) {
157
            // if something goes wrong just return as fallback
158
            return '';
159
        }
160
161 26
        // return the var
162 26
        if ($return !== '') {
163
            return $return;
164
        }
165
166 9
        // fallback
167
        return '';
168
    }
169
170
    /**
171
     * Formats a timestamp as a string that indicates the time ago
172
     *    syntax: {{ $$timestamp|timeago }}.
173
     *
174
     * @param int|DateTime $timestamp A UNIX-timestamp that will be formatted as a time-ago-string.
175
     *
176
     * @return string
177
     */
178
    public static function timeAgo($timestamp = null): string
179
    {
180
        if ($timestamp instanceof DateTime) {
181
            $timestamp = $timestamp->getTimestamp();
182
        }
183
184
        // invalid timestamp
185
        if ((int) $timestamp === 0) {
186
            return '';
187
        }
188
189
        // return
190
        return '<abbr title="'.\SpoonDate::getDate(
191
            FrontendModel::get('fork.settings')->get('Core', 'date_format_long') .', '
192
            . FrontendModel::get('fork.settings')->get('Core', 'time_format'),
193
            $timestamp,
194
            Locale::frontendLanguage()
195
        ).'">'.\SpoonDate::getTimeAgo($timestamp, Locale::frontendLanguage()).'</abbr>';
196
    }
197
198
    /**
199
     * Get a given field for a page-record
200
     *    syntax: {{ $pageId|getpageinfo($field) }}
201
     *
202
     * @param int $pageId The id of the page to build the URL for.
203
     * @param string $field The field to get.
204
     *
205
     * @return string
206
     */
207
    public static function getPageInfo(int $pageId, string $field = 'title'): string
208
    {
209
        // get page
210
        $page = Navigation::getPageInfo($pageId);
211
212
        // validate
213
        if (empty($page)) {
214
            return '';
215
        }
216
217
        if (!isset($page[$field])) {
218
            return '';
219
        }
220
221
        // return page info
222
        return $page[$field];
223
    }
224
225
    /**
226
     * Fetch the path for an include (theme file if available, core file otherwise)
227
     *    syntax: {{ getpath($file) }}
228
     *
229
     * @param string $file The base path.
230
     *
231
     * @return string
232
     */
233
    public static function getPath(string $file): string
234
    {
235
        return Theme::getPath($file);
236
    }
237
238
    /**
239
     * Get the subnavigation html
240
     *   syntax: {{ getsubnavigation($type, $parentId, $startdepth, $enddepth, $excludeIds-splitted-by-dash, $template) }}
241
     *
242
     *   NOTE: When supplying more than 1 ID to exclude, the single quotes around the dash-separated list are mandatory.
243
     *
244
     * @param string $type The type of navigation, possible values are: page, footer.
245
     * @param int $pageId The parent wherefore the navigation should be build.
246
     * @param int $startDepth The depth to start from.
247
     * @param int $endDepth The maximum depth that has to be build.
248
     * @param string $excludeIds Which pageIds should be excluded (split them by -).
249
     * @param string $template The template that will be used.
250
     *
251
     * @return string
252
     */
253
    public static function getSubNavigation(
254
        string $type = 'page',
255
        int $pageId = 0,
256
        int $startDepth = 1,
257
        int $endDepth = null,
258
        string $excludeIds = null,
259
        string $template = 'Core/Layout/Templates/Navigation.html.twig'
260
    ): string {
261
        // build excludeIds
262
        if ($excludeIds !== null) {
263
            $excludeIds = explode('-', $excludeIds);
264
        }
265
266
        // get info about the given page
267
        $pageInfo = Navigation::getPageInfo($pageId);
268
269
        // validate page info
270
        if ($pageInfo === false) {
271
            return '';
272
        }
273
274
        // split URL into chunks
275
        $chunks = (array) explode('/', $pageInfo['full_url']);
276
277
        // remove language chunk
278
        $hasMultiLanguages = (bool) FrontendModel::getContainer()->getParameter('site.multilanguage');
279
        $chunks = $hasMultiLanguages ? (array) array_slice($chunks, 2) : (array) array_slice($chunks, 1);
280
        if (count($chunks) === 0) {
281
            $chunks[0] = '';
282
        }
283
284
        // init var
285
        $parentUrl = '';
286
287
        // build url
288
        for ($i = 0; $i < $startDepth - 1; ++$i) {
289
            $parentUrl .= $chunks[$i] . '/';
290
        }
291
292
        // get parent ID
293
        $parentID = Navigation::getPageId($parentUrl);
294
295
        try {
296
            // get HTML
297
            $return = (string) Navigation::getNavigationHTML(
298
                $type,
299
                $parentID,
300
                $endDepth,
301
                (array) $excludeIds,
302
                (string) $template
303
            );
304
        } catch (Exception $e) {
305
            return '';
306
        }
307
308
        return $return;
309
    }
310
311
    /**
312
     * Get the URL for a given pageId & language
313
     *    syntax: {{ geturl($pageId, $language) }}
314
     *
315
     * @param int $pageId The id of the page to build the URL for.
316
     * @param string $language The language to use, if not provided we will use the loaded language.
317
     *
318
     * @return string
319
     */
320
    public static function getUrl(int $pageId, string $language = null): string
321
    {
322
        return Navigation::getUrl($pageId, $language);
323
    }
324
325
    /**
326
     * Get the URL for a give module & action combination
327
     *    syntax: {{ geturlforblock($module, $action, $language, $data) }}
328
     *
329
     * @param string $module The module wherefore the URL should be build.
330
     * @param string $action A specific action wherefore the URL should be build, otherwise the default will be used.
331
     * @param string $language The language to use, if not provided we will use the loaded language.
332
     * @param array $data An array with keys and values that partially or fully match the data of the block.
333
     *                         If it matches multiple versions of that block it will just return the first match.
334
     *
335
     * @return string
336 5
     */
337
    public static function getUrlForBlock(
338
        string $module,
339
        string $action = null,
340
        string $language = null,
341
        array $data = null
342 5
    ): string {
343
        return Navigation::getUrlForBlock($module, $action, $language, $data);
344
    }
345
346
    /**
347
     * Fetch an URL based on an extraId
348
     *    syntax: {{ geturlforextraid($extraId, $language) }}
349
     *
350
     * @param int $extraId The id of the extra.
351
     * @param string $language The language to use, if not provided we will use the loaded language.
352
     *
353
     * @return string
354
     */
355
    public static function getUrlForExtraId(int $extraId, string $language = null): string
356
    {
357
        return Navigation::getUrlForExtraId($extraId, $language);
358
    }
359
360
    /**
361
     * Parse a widget straight from the template, rather than adding it through pages.
362
     *    syntax: {{ parsewidget($module, $action, $id) }}
363
     *
364
     * @internal if your widget outputs random data you should cache it inside the widget
365
     * Fork checks the output and if the output of the widget is random it will loop until the random data
366
     * is the same as in the previous iteration
367
     *
368
     * @param string $module The module whose module we want to execute.
369
     * @param string $action The action to execute.
370
     * @param string $id The widget id (saved in data-column).
371
     *
372
     * @throws Exception
373
     *
374
     * @return string
375
     */
376
    public static function parseWidget(string $module, string $action, string $id = null): string
377
    {
378
        // create new widget instance and return parsed content
379
        $extra = FrontendBlockWidget::getForId(
380
            FrontendModel::get('kernel'),
0 ignored issues
show
It seems like Frontend\Core\Engine\Model::get('kernel') can also be of type null; however, parameter $kernel of Frontend\Core\Engine\Block\Widget::getForId() does only seem to accept Symfony\Component\HttpKernel\KernelInterface, 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

380
            /** @scrutinizer ignore-type */ FrontendModel::get('kernel'),
Loading history...
381
            $module,
382
            $action,
383
            $id
384
        );
385
386
        // set parseWidget because we will need it to skip setting headers in the display
387
        FrontendModel::getContainer()->set('parseWidget', true);
0 ignored issues
show
true of type true is incompatible with the type null|object expected by parameter $service of Symfony\Component\Depend...ntainerInterface::set(). ( Ignorable by Annotation )

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

387
        FrontendModel::getContainer()->set('parseWidget', /** @scrutinizer ignore-type */ true);
Loading history...
388
389
        try {
390
            $extra->execute();
391
            $content = $extra->getContent();
392
            FrontendModel::getContainer()->set('parseWidget', null);
393
394
            return (string) $content;
395
        } catch (RedirectException $redirectException) {
396
            throw new Error('redirect fix from template modifier', -1, null, $redirectException);
397
        } catch (Exception $e) {
398
            // if we are debugging, we want to see the exception
399
            if (FrontendModel::getContainer()->getParameter('kernel.debug')) {
400
                throw $e;
401
            }
402
403
            return '';
404
        }
405
    }
406
407
    /**
408
     * Output a profile setting
409
     *    syntax: {{ profilesetting($profileId, $name) }}
410
     *
411
     * @param int $profileId The variable
412
     * @param string $name The name of the setting
413
     *
414
     * @return mixed
415
     */
416
    public static function profileSetting(int $profileId, string $name)
417
    {
418
        $profile = FrontendProfilesModel::get($profileId);
419
420
        // convert into array
421
        $profile = $profile->toArray();
422
423
        // @remark I know this is dirty, but I couldn't find a better way.
424
        if (in_array($name, ['display_name', 'registered_on', 'full_url']) && isset($profile[$name])) {
425
            return $profile[$name];
426
        }
427
        if (isset($profile['settings'][$name])) {
428
            return $profile['settings'][$name];
429
        }
430
431
        return '';
432
    }
433
434
    /**
435
     * Get the value for a user-setting
436
     *    syntax {{ usersetting($setting, $userId) }}
437
     *
438
     * @param string|null $string  The string passed from the template.
439
     * @param string $setting The name of the setting you want.
440
     * @param int $userId  The userId, if not set by $string.
441
     *
442
     * @throws Exception
443
     *
444
     * @return string
445 5
     */
446
    public static function userSetting($string, string $setting, int $userId = null)
447 5
    {
448
        $userId = ($string !== null) ? (int) $string : $userId;
449
450 5
        // validate
451
        if ($userId === 0) {
452
            throw new Exception('Invalid user id');
453
        }
454
455 5
        // get user
456
        $user = User::getBackendUser($userId);
457
458 5
        // return
459
        return (string) $user->getSetting($setting);
460
    }
461
462
    /**
463
     * Formats plain text as HTML, links will be detected, paragraphs will be inserted
464
     *    syntax: {{ $string|cleanupPlainText }}.
465
     *
466
     * @param string $string The text to cleanup.
467
     *
468
     * @return string
469
     */
470
    public static function cleanupPlainText(string $string): string
471
    {
472
        // detect links
473
        $string = \SpoonFilter::replaceURLsWithAnchors(
474
            $string,
475
            FrontendModel::get('fork.settings')->get('Core', 'seo_nofollow_in_comments', false)
476
        );
477
478
        // replace newlines
479
        $string = str_replace("\r", '', $string);
480
        $string = preg_replace('/(?<!.)(\r\n|\r|\n){3,}$/m', '', $string);
481
482
        // replace br's into p's
483
        $string = '<p>' . str_replace("\n", '</p><p>', $string) . '</p>';
484
485
        // cleanup
486
        $string = str_replace("\n", '', $string);
487
        $string = str_replace('<p></p>', '', $string);
488
489
        // return
490
        return $string;
491
    }
492
493
    /**
494
     * Returns the count of the count of the array.
495
     *
496
     * @param array $data
497
     *
498
     * @return int
499
     */
500
    public static function count(array $data): int
501
    {
502
        return count($data);
503
    }
504
505
    /**
506
     * Convert this string into a well formed label.
507
     *  syntax: {{ var|tolabel }}.
508
     *
509
     * @param string $value The value to convert to a label.
510
     *
511
     * @return string
512
     */
513
    public static function toLabel(string $value): string
514
    {
515
        return \SpoonFilter::ucfirst(Language::lbl(\SpoonFilter::toCamelCase($value, '_', false)));
516
    }
517
}
518