Passed
Pull Request — master (#1249)
by
unknown
19:13
created

Template::processConditions()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 40
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 5.0016

Importance

Changes 0
Metric Value
dl 0
loc 40
ccs 24
cts 25
cp 0.96
rs 8.439
c 0
b 0
f 0
cc 5
eloc 21
nc 4
nop 1
crap 5.0016
1
<?php
2
namespace ApacheSolrForTypo3\Solr;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2008-2015 Ingo Renner <[email protected]>
8
 *  All rights reserved
9
 *
10
 *  This script is part of the TYPO3 project. The TYPO3 project is
11
 *  free software; you can redistribute it and/or modify
12
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 2 of the License, or
14
 *  (at your option) any later version.
15
 *
16
 *  The GNU General Public License can be found at
17
 *  http://www.gnu.org/copyleft/gpl.html.
18
 *
19
 *  This script is distributed in the hope that it will be useful,
20
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 *  GNU General Public License for more details.
23
 *
24
 *  This copyright notice MUST APPEAR in all copies of the script!
25
 ***************************************************************/
26
27
use ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager;
28
use ApacheSolrForTypo3\Solr\ViewHelper\SubpartViewHelper;
29
use ApacheSolrForTypo3\Solr\ViewHelper\ViewHelper;
30
use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
31
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
32
use TYPO3\CMS\Core\Utility\GeneralUtility;
33
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
34
35
/**
36
 * A template engine to simplify the work with marker based templates. The
37
 * engine supports easy management of markers, subparts, and even loops.
38
 *
39
 * @author Ingo Renner <[email protected]>
40
 */
41
class Template
42
{
43
    const CLEAN_TEMPLATE_YES = true;
44
    const CLEAN_TEMPLATE_NO = false;
45
46
    protected $prefix;
47
    protected $cObj;
48
    protected $templateFile;
49
    protected $template;
50
    protected $workOnSubpart;
51
    protected $viewHelperIncludePath;
52
    protected $helpers = [];
53
    protected $loadedHelperFiles = [];
54
    protected $variables = [];
55
    protected $markers = [];
56
    protected $subparts = [];
57
    protected $loops = [];
58
59
    protected $debugMode = false;
60
61
    /**
62
     * @var \ApacheSolrForTypo3\Solr\System\Logging\SolrLogManager
63
     */
64
    protected $logger = null;
65
66
    /**
67
     * Constructor for the html marker template engine.
68
     *
69
     * @param ContentObjectRenderer $contentObject content object
70
     * @param string $templateFile path to the template file
71
     * @param string $subpart name of the subpart to work on
72
     */
73 26
    public function __construct(
74
        ContentObjectRenderer $contentObject,
75
        $templateFile,
76
        $subpart
77
    ) {
78 26
        $this->logger = GeneralUtility::makeInstance(SolrLogManager::class, __CLASS__);
79
80 26
        $this->cObj = $contentObject;
81 26
        $this->templateFile = $templateFile;
82
83 26
        $this->loadHtmlFile($templateFile);
84 26
        $this->workOnSubpart($subpart);
85 26
    }
86
87
    /**
88
     * @return \TYPO3\CMS\Core\Service\MarkerBasedTemplateService
89
     */
90 26
    protected function getTemplateService()
91
    {
92 26
        return GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
93
    }
94
95
    /**
96
     * Copy constructor, sets the clone object's template content to original
97
     * object's work subpart.
98
     *
99
     */
100 25
    public function __clone()
101
    {
102 25
        $this->setTemplateContent($this->getWorkOnSubpart());
103 25
    }
104
105
    /**
106
     * Loads the content of a html template file. Resolves paths beginning with
107
     * "EXT:".
108
     *
109
     * @param string $htmlFile path to html template file
110
     */
111 26
    public function loadHtmlFile($htmlFile)
112
    {
113 26
        $path = $GLOBALS['TSFE']->tmpl->getFileName($htmlFile);
114 26
        if ($path !== null && file_exists($path)) {
115 26
            $this->template = file_get_contents($path);
116 26
        }
117 26
        if (empty($this->template)) {
118
            throw new \RuntimeException(
119
                'Could not load template file "' . htmlspecialchars($htmlFile) . '"',
120
                1327490358
121
            );
122
        }
123 26
    }
124
125
    /**
126
     * Sets the content for the template we're working on
127
     *
128
     * @param string $templateContent the template's content - usually HTML
129
     * @return void
130
     */
131 25
    public function setWorkingTemplateContent($templateContent)
132
    {
133 25
        $this->workOnSubpart = $templateContent;
134 25
    }
135
136
    /**
137
     * Finds the view helpers in the template and loads them.
138
     *
139
     * @param string $content
140
     */
141 26
    protected function initializeViewHelpers($content)
142
    {
143 26
        $viewHelpersFound = $this->findViewHelpers($content);
144
145 26
        foreach ($viewHelpersFound as $helperKey) {
146 24
            if (!isset($this->helpers[strtolower($helperKey)])) {
147 18
                $viewHelperLoaded = $this->loadViewHelper($helperKey);
148
149 18
                if (!$viewHelperLoaded) {
150
                    // skipping processing in case we couldn't find a class
151
                    // to handle the view helper
152
                    continue;
153
                }
154
155 18
                $this->addViewHelper($helperKey);
156 18
            }
157 26
        }
158 26
    }
159
160
    /**
161
     * Adds an include path where the template engine should look for template
162
     * view helpers.
163
     *
164
     * @param string $extensionKey Extension key
165
     * @param string $viewHelperPath Path inside the extension to look for view helpers
166
     */
167 26
    public function addViewHelperIncludePath($extensionKey, $viewHelperPath)
168
    {
169 26
        $this->viewHelperIncludePath[$extensionKey] = $viewHelperPath;
170 26
    }
171
172
    /**
173
     * Adds a view helper
174
     *
175
     * @param string $helperName view helper name
176
     * @param array $arguments optional array of arguments
177
     * @return bool
178
     */
179 26
    public function addViewHelper($helperName, array $arguments = [])
180
    {
181 26
        $success = false;
182
183 26
        if (!isset($this->helpers[strtolower($helperName)])) {
184 26
            $viewHelperClassName = $this->loadViewHelper($helperName);
185
186
            // could be FALSE if not matching view helper class was found
187 26
            if ($viewHelperClassName) {
188
                try {
189 26
                    $helperInstance = GeneralUtility::makeInstance($viewHelperClassName,
190 26
                        $arguments);
191 26
                    $success = $this->addViewHelperObject($helperName,
192 26
                        $helperInstance);
193 26
                } catch (\Exception $e) {
194
                    $configuration = Util::getSolrConfiguration();
195
                    if ($configuration->getLoggingExceptions()) {
196
                        $this->logger->log(
197
                            SolrLogManager::ERROR,
198
                            'Exception while adding a viewhelper',
199
                            [
200
                                $e->__toString()
201
                            ]
202
                        );
203
                    }
204
                }
205 26
            }
206 26
        }
207
208 26
        return $success;
209
    }
210
211 26
    protected function loadViewHelper($helperKey)
212
    {
213 26
        if (isset($this->loadedHelperFiles[strtolower($helperKey)])) {
214 18
            return $this->loadedHelperFiles[strtolower($helperKey)]['class'];
215
        }
216
217 26
        foreach ($this->viewHelperIncludePath as $extensionKey => $viewHelperPath) {
218 26
            $viewHelperRealPath = $viewHelperPath;
219 26
            if (GeneralUtility::isFirstPartOfStr($viewHelperPath, 'Classes/')) {
220 26
                $viewHelperRealPath = substr($viewHelperPath, 8);
221 26
            }
222 26
            if (substr($viewHelperRealPath, -1) == '/') {
223 26
                $viewHelperRealPath = substr($viewHelperRealPath, 0, -1);
224 26
            }
225
226 26
            $ucHelperKey = Util::underscoredToUpperCamelCase($helperKey);
227 26
            $vendorNameSpace = 'ApacheSolrForTypo3\\Solr\\';
228 26
            $possibleFilename = $ucHelperKey . '.php';
229 26
            $possibleClassName = $vendorNameSpace . str_replace('/', '\\',
230 26
                    $viewHelperRealPath) . '\\' . $ucHelperKey;
231
232 26
            $viewHelperIncludePath = ExtensionManagementUtility::extPath($extensionKey)
233 26
                . $viewHelperPath . $possibleFilename;
234
235 26
            if (file_exists($viewHelperIncludePath)) {
236 26
                include_once($viewHelperIncludePath);
237 26
                $this->loadedHelperFiles[strtolower($helperKey)] = [
238 26
                    'file' => $viewHelperIncludePath,
239
                    'class' => $possibleClassName
240 26
                ];
241
242 26
                return $possibleClassName;
243
            }
244
        }
245
246
        // view helper could not be found
247
        return false;
248
    }
249
250
    /**
251
     * adds an already instantiated view helper
252
     *
253
     * @param $helperName
254
     * @param ViewHelper $helperObject
255
     * @return bool
256
     */
257 26
    public function addViewHelperObject($helperName, ViewHelper $helperObject)
258
    {
259 26
        $success = false;
260
261 26
        $helperName = strtolower($helperName);
262
263 26
        if (!isset($this->helpers[$helperName])) {
264 26
            $this->helpers[$helperName] = $helperObject;
265 26
            $success = true;
266 26
        }
267
268 26
        return $success;
269
    }
270
271
    /**
272
     * Renders the template and fills its markers.
273
     *
274
     * @param bool $cleanTemplate
275
     * @return string the rendered html template with markers replaced with their content
276
     */
277 26
    public function render($cleanTemplate = false)
278
    {
279
280
        // process loops
281 26
        foreach ($this->loops as $key => $loopVariables) {
282 23
            $this->renderLoop($key);
283 26
        }
284
285
        // process variables
286 26
        foreach ($this->variables as $variableKey => $variable) {
287 25
            $variableKey = strtoupper($variableKey);
288 25
            $variableMarkers = $this->getVariableMarkers($variableKey,
289 25
                $this->workOnSubpart);
290
291 25
            if (count($variableMarkers)) {
292 25
                $resolvedMarkers = $this->resolveVariableMarkers($variableMarkers,
293 25
                    $variable);
294
295 25
                $this->workOnSubpart = $this->getTemplateService()->substituteMarkerArray(
296 25
                    $this->workOnSubpart,
297 25
                    $resolvedMarkers,
298
                    '###|###'
299 25
                );
300 25
            }
301 26
        }
302
303
        // process markers
304 26
        $this->workOnSubpart = $this->getTemplateService()->substituteMarkerArray(
305 26
            $this->workOnSubpart,
306 26
            $this->markers
307 26
        );
308
309
        // process subparts
310 26
        foreach ($this->subparts as $subpart => $content) {
311 25
            $this->workOnSubpart = $this->getTemplateService()->substituteSubpart(
312 25
                $this->workOnSubpart,
313 25
                $subpart,
314
                $content
315 25
            );
316 26
        }
317
318
        // process view helpers, they need to be the last objects processing the template
319 26
        $this->initializeViewHelpers($this->workOnSubpart);
320 26
        $this->workOnSubpart = $this->renderViewHelpers($this->workOnSubpart);
321
322
        // process conditions
323 26
        $this->workOnSubpart = $this->processConditions($this->workOnSubpart);
324
325
        // finally, do a cleanup if not disabled
326 26
        if ($cleanTemplate) {
327 26
            $this->cleanTemplate();
328 26
        }
329
330 26
        return $this->workOnSubpart;
331
    }
332
333
    /**
334
     * Escapes marker hashes and the pipe symbol so that they will not be
335
     * executed in templates.
336
     *
337
     * @param string $content Content potentially containing markers
338
     * @return string Content with markers escaped
339
     */
340 25
    public static function escapeMarkers($content)
341
    {
342
        // escape marker hashes
343 25
        $content = str_replace('###', '&#35;&#35;&#35;', $content);
344
        // escape pipe character used for parameter separation
345 25
        $content = str_replace('|', '&#124;', $content);
346
347 25
        return $content;
348
    }
349
350
    /**
351
     * cleans the template from non-replaced markers and subparts
352
     *
353
     * @return void
354
     */
355 26
    public function cleanTemplate()
356
    {
357 26
        $remainingMarkers = $this->findMarkers();
358
359 26
        foreach ($remainingMarkers as $remainingMarker) {
360 23
            $isSubpart = preg_match_all(
361 23
                '/(\<\!\-\-[\s]+###' . $remainingMarker . '###.*###'
362 23
                . $remainingMarker . '###.+\-\-\>)/sU',
363 23
                $this->workOnSubpart,
364 23
                $matches,
365
                PREG_SET_ORDER
366 23
            );
367
368 23
            if ($isSubpart) {
369 23
                $this->workOnSubpart = str_replace(
370 23
                    $matches[0][1],
371 23
                    '',
372 23
                    $this->workOnSubpart
373 23
                );
374 23
            } else {
375
                $this->workOnSubpart = str_replace(
376
                    '###' . $remainingMarker . '###',
377
                    '',
378
                    $this->workOnSubpart
379
                );
380
            }
381 26
        }
382
383 26
        $unresolvedConditions = $this->findConditions($this->workOnSubpart);
384 26
        foreach ($unresolvedConditions as $unresolvedCondition) {
385
            // if condition evaluates to FALSE, remove the content from the template
386
            $this->workOnSubpart = $this->getTemplateService()->substituteSubpart(
387
                $this->workOnSubpart,
388
                $unresolvedCondition['marker'],
389
                ''
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
390
            );
391 26
        }
392 26
    }
393
394
    /**
395
     * Renders view helpers, detects whether it is a regular marker view helper
396
     * or a subpart view helper and passes rendering on to more specialized
397
     * render methods for each type.
398
     *
399
     * @param string $content The content to process by view helpers
400
     * @return string the view helper processed content
401
     */
402 26
    protected function renderViewHelpers($content)
403
    {
404 26
        $viewHelpersFound = $this->findViewHelpers($content);
405
406 26
        foreach ($viewHelpersFound as $helperKey) {
407 24
            if (array_key_exists(strtolower($helperKey), $this->helpers)) {
408 24
                $helper = $this->helpers[strtolower($helperKey)];
409
410 24
                if ($helper instanceof SubpartViewHelper) {
411
                    $content = $this->renderSubpartViewHelper($helper,
412
                        $helperKey, $content);
413
                } else {
414 24
                    $content = $this->renderMarkerViewHelper($helper,
415 24
                        $helperKey, $content);
416
                }
417 24
            }
418 26
        }
419
420 26
        return $content;
421
    }
422
423
    /**
424
     * Renders single marker view helpers.
425
     *
426
     * @param ViewHelper $viewHelper View helper instance to execute.
427
     * @param string $helperKey The view helper marker key.
428
     * @param string $content Markup that contains the unsubstituted view helper marker.
429
     * @return string Markup with the view helper replaced by the content it returned.
430
     */
431 24
    protected function renderMarkerViewHelper(
432
        ViewHelper $viewHelper,
433
        $helperKey,
434
        $content
435
    ) {
436 24
        $viewHelperArgumentLists = $this->getViewHelperArgumentLists($helperKey,
437 24
            $content);
438
439 24
        foreach ($viewHelperArgumentLists as $viewHelperArgumentList) {
440 24
            $viewHelperArguments = explode('|', $viewHelperArgumentList);
441
            // TODO check whether one of the parameters is a Helper
442
            // itself, if so resolve it before handing it off to the
443
            // actual helper, this way the order in which viewhelpers
444
            // get added to the template do not matter anymore
445
            // may use findViewHelpers()
446
447
            // checking whether any of the helper arguments should be
448
            // replaced by a variable available to the template
449 24
            foreach ($viewHelperArguments as $i => $helperArgument) {
450 24
                $lowercaseHelperArgument = strtolower($helperArgument);
451 24
                if (array_key_exists($lowercaseHelperArgument,
452 24
                    $this->variables)) {
453
                    $viewHelperArguments[$i] = $this->variables[$lowercaseHelperArgument];
454
                }
455 24
            }
456
457 24
            $viewHelperContent = $viewHelper->execute($viewHelperArguments);
458
459 24
            $content = $this->getTemplateService()->substituteMarker(
460 24
                $content,
461 24
                '###' . $helperKey . ':' . $viewHelperArgumentList . '###',
462
                $viewHelperContent
463 24
            );
464 24
        }
465
466 24
        return $content;
467
    }
468
469
    /**
470
     * Renders subpart view helpers.
471
     *
472
     * @param SubpartViewHelper $viewHelper View helper instance to execute.
473
     * @param string $helperKey The view helper marker key.
474
     * @param string $content Markup that contains the unsubstituted view helper subpart.
475
     * @return string Markup with the view helper replaced by the content it returned.
476
     */
477
    protected function renderSubpartViewHelper(
478
        SubpartViewHelper $viewHelper,
479
        $helperKey,
480
        $content
481
    ) {
482
        $viewHelperArgumentLists = $this->getViewHelperArgumentLists($helperKey,
483
            $content);
484
485
        foreach ($viewHelperArgumentLists as $viewHelperArgumentList) {
486
            $subpartMarker = '###' . $helperKey . ':' . $viewHelperArgumentList . '###';
487
488
            $subpart = $this->getTemplateService()->getSubpart(
489
                $content,
490
                $subpartMarker
491
            );
492
493
            $viewHelperArguments = explode('|', $viewHelperArgumentList);
494
495
            $subpartTemplate = clone $this;
496
            $subpartTemplate->setWorkingTemplateContent($subpart);
497
            $viewHelper->setTemplate($subpartTemplate);
498
499
            try {
500
                $viewHelperContent = $viewHelper->execute($viewHelperArguments);
501
            } catch (\UnexpectedValueException $e) {
502
                $configuration = Util::getSolrConfiguration();
503
                if ($configuration->getLoggingExceptions()) {
504
                    $this->logger->log(
505
                        SolrLogManager::ERROR,
506
                        'Exception while rendering a viewhelper',
507
                        [
508
                            $e->__toString()
509
                        ]
510
                    );
511
                }
512
513
                $viewHelperContent = '';
514
            }
515
516
            $content = $this->getTemplateService()->substituteSubpart(
517
                $content,
518
                $subpartMarker,
519
                $viewHelperContent,
0 ignored issues
show
Documentation introduced by
$viewHelperContent is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
520
                false
521
            );
522
523
            // there might be more occurrences of the same subpart maker with
524
            // the same arguments but different markup to be used...
525
            // that's the case with the facet subpart view helper f.e.
526
            $furtherOccurrences = strpos($content, $subpartMarker);
527
            if ($furtherOccurrences !== false) {
528
                $content = $this->renderSubpartViewHelper($viewHelper,
529
                    $helperKey, $content);
530
            }
531
        }
532
533
        return $content;
534
    }
535
536
    /**
537
     * Renders the loop for a given loop name.
538
     *
539
     * @param string $loopName Key from $this->loops to render
540
     */
541 23
    protected function renderLoop($loopName)
542
    {
543 23
        $loopContent = '';
544 23
        $loopTemplate = $this->getSubpart('LOOP:' . $loopName);
545
546 23
        $loopContentMarker = 'loop_content:' . $loopName;
547 23
        $loopSingleItem = $this->getSubpart($loopContentMarker, $loopTemplate);
548 23
        if (empty($loopSingleItem)) {
549
            // backwards compatible fallback for unnamed loops
550 23
            $loopContentMarker = 'loop_content';
551 23
            $loopSingleItem = $this->getSubpart($loopContentMarker,
552 23
                $loopTemplate);
553 23
        }
554
555 23
        $loopMarker = strtoupper($this->loops[$loopName]['marker']);
556 23
        $loopVariables = $this->loops[$loopName]['data'];
557 23
        $foundMarkers = $this->getMarkersFromTemplate($loopSingleItem,
558 23
            $loopMarker . '\.');
559 23
        $loopCount = count($loopVariables);
560
561 23
        if (count($foundMarkers)) {
562 23
            $iterationCount = 0;
563 23
            foreach ($loopVariables as $value) {
564 20
                $resolvedMarkers = $this->resolveVariableMarkers($foundMarkers,
565 20
                    $value);
566 20
                $resolvedMarkers['LOOP_CURRENT_ITERATION_COUNT'] = ++$iterationCount;
567
568
                // pass the whole object / array / variable as is (serialized though)
569 20
                $resolvedMarkers[$loopMarker] = serialize($value);
570
571 20
                $currentIterationContent = $this->getTemplateService()->substituteMarkerArray(
572 20
                    $loopSingleItem,
573 20
                    $resolvedMarkers,
574
                    '###|###'
575 20
                );
576
577 20
                $inLoopMarkers = $this->getMarkersFromTemplate(
578 20
                    $currentIterationContent,
579 20
                    'LOOP:',
580
                    false
581 20
                );
582
583 20
                $inLoopMarkers = $this->filterProtectedLoops($inLoopMarkers);
584
585 20
                $currentIterationContent = $this->processInLoopMarkers(
586 20
                    $currentIterationContent,
587 20
                    $loopName,
588 20
                    $inLoopMarkers,
589
                    $value
590 20
                );
591
592 20
                $currentIterationContent = $this->processConditions($currentIterationContent);
593
594 20
                $loopContent .= $currentIterationContent;
595 23
            }
596 23
        }
597
598 23
        $loopContent = $this->getTemplateService()->substituteSubpart(
599 23
            $loopTemplate,
600 23
            '###' . strtoupper($loopContentMarker) . '###',
601
            $loopContent
0 ignored issues
show
Documentation introduced by
$loopContent is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
602 23
        );
603
604 23
        $loopContent = $this->getTemplateService()->substituteMarkerArray(
605 23
            $loopContent,
606 23
            ['LOOP_ELEMENT_COUNT' => $loopCount],
607
            '###|###'
608 23
        );
609
610 23
        $this->workOnSubpart = $this->getTemplateService()->substituteSubpart(
611 23
            $this->workOnSubpart,
612 23
            '###LOOP:' . strtoupper($loopName) . '###',
613
            $loopContent
0 ignored issues
show
Documentation introduced by
$loopContent is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
614 23
        );
615 23
    }
616
617
    /**
618
     * Processes marker in a loop that start with LOOP:.
619
     *
620
     * This is useful especially for calling view helpers with the current
621
     * iteration's value as a parameter.
622
     *
623
     * @param string $content
624
     * @param string $loopName
625
     * @param array $markers
626
     * @param string $currentIterationValue
627
     * @return string
628
     */
629 20
    protected function processInLoopMarkers(
630
        $content,
631
        $loopName,
632
        array $markers,
633
        $currentIterationValue
634
    ) {
635 20
        foreach ($markers as $marker) {
636
            list($helperName, $helperArguments) = explode(':', $marker);
637
638
            $helperName = strtolower($helperName);
639
            $helperArguments = explode('|', $helperArguments);
640
641
            // checking whether any of the helper arguments should be
642
            // replaced by the current iteration's value
643
            if (isset($this->loops[$loopName])) {
644
                foreach ($helperArguments as $i => $helperArgument) {
645
                    if (strtoupper($this->loops[$loopName]['marker']) == strtoupper($helperArgument)) {
646
                        $helperArguments[$i] = $currentIterationValue;
647
                    }
648
                }
649
            }
650
651
            if (array_key_exists($helperName, $this->helpers)) {
652
                $markerContent = $this->helpers[$helperName]->execute($helperArguments);
653
            } else {
654
                throw new \RuntimeException(
655
                    'No matching view helper found for marker "' . $marker . '".',
656
                    1311005284
657
                );
658
            }
659
660
            $content = str_replace('###LOOP:' . $marker . '###', $markerContent,
661
                $content);
662 20
        }
663
664 20
        return $content;
665
    }
666
667
    /**
668
     * Some marker subparts must be protected and only rendered by their
669
     * according commands. This method filters these protected markers from
670
     * others when rendering loops so that they are not replaced and left in
671
     * the template for rendering by the correct command.
672
     *
673
     * @param array $loopMarkers An array of loop markers found during rendering of a loop.
674
     * @return array The array with protected subpart markers removed.
675
     */
676 20
    protected function filterProtectedLoops($loopMarkers)
677
    {
678 20
        $protectedMarkers = ['result_documents'];
679
680 20
        foreach ($loopMarkers as $key => $loopMarker) {
681
            if (in_array(strtolower($loopMarker), $protectedMarkers)) {
682
                unset($loopMarkers[$key]);
683
            }
684 20
        }
685
686 20
        return $loopMarkers;
687
    }
688
689
    /**
690
     * Processes conditions: finds and evaluates them in HTML code.
691
     *
692
     * @param string $content HTML
693
     * @return string
694
     */
695 26
    protected function processConditions($content)
696
    {
697
        // find conditions
698 26
        $conditions = $this->findConditions($content);
699
700
        // evaluate conditions
701 26
        foreach ($conditions as $condition) {
702 20
            if ($this->isVariableMarker($condition['comparand1'])
703 20
                || $this->isVariableMarker($condition['comparand2'])
704 20
            ) {
705
                // unresolved marker => skip, will be resolved later
706
                continue;
707
            }
708
709 20
            $conditionResult = $this->evaluateCondition(
710 20
                $condition['comparand1'],
711 20
                $condition['comparand2'],
712 20
                $condition['operator']
713 20
            );
714
715 20
            if ($conditionResult) {
716
                // if condition evaluates to TRUE, simply replace it with
717
                // the original content to have the surrounding markers removed
718 3
                $content = $this->getTemplateService()->substituteSubpart(
719 3
                    $content,
720 3
                    $condition['marker'],
721 3
                    $condition['content']
722 3
                );
723 3
            } else {
724
                // if condition evaluates to FALSE, remove the content from the template
725 20
                $content = $this->getTemplateService()->substituteSubpart(
726 20
                    $content,
727 20
                    $condition['marker'],
728
                    ''
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
729 20
                );
730
            }
731 26
        }
732
733 26
        return $content;
734
    }
735
736
    /**
737
     * Finds conditions in HTML code.
738
     *
739
     * Conditions are subparts with markers in the form of
740
     *
741
     * ###IF:comparand1|operator|comparand2###
742
     * Some content only visible if the condition evaluates as TRUE
743
     * ###IF:comparand1|operator|comparand2###
744
     *
745
     * The returned result is an array of arrays describing a found condition.
746
     * Each conditions is described as follows:
747
     * [marker] the complete marker used to specify the condition
748
     * [content] the content wrapped by the condition
749
     * [operator] the condition operator
750
     * [comparand1] and [comparand2] the comparands.
751
     *
752
     * @param string $content HTML
753
     * @return array An array describing the conditions found
754
     */
755 26
    protected function findConditions($content)
756
    {
757 26
        $conditions = [];
758 26
        $ifMarkers = $this->getViewHelperArgumentLists('IF', $content, false);
759
760 26
        foreach ($ifMarkers as $ifMarker) {
761 20
            list($comparand1, $operator, $comparand2) = explode('|', $ifMarker);
762
763 20
            $ifContent = $this->getTemplateService()->getSubpart(
764 20
                $content,
765 20
                '###IF:' . $ifMarker . '###'
766 20
            );
767
768 20
            $conditions[] = [
769 20
                'marker' => '###IF:' . $ifMarker . '###',
770 20
                'content' => $ifContent,
771 20
                'operator' => trim($operator),
772 20
                'comparand1' => $comparand1,
773
                'comparand2' => $comparand2
774 20
            ];
775 26
        }
776
777 26
        return $conditions;
778
    }
779
780
    /**
781
     * Evaluates conditions.
782
     *
783
     * Supported operators are ==, !=, <, <=, >, >=, %
784
     *
785
     * @param string $comparand1 First comparand
786
     * @param string $comparand2 Second comparand
787
     * @param string $operator Operator
788
     * @return bool Boolean evaluation of the condition.
789
     * @throws \InvalidArgumentException for unknown $operator
790
     */
791 20
    protected function evaluateCondition($comparand1, $comparand2, $operator)
792
    {
793
        switch ($operator) {
794 20
            case '==':
795 20
                $conditionResult = ($comparand1 == $comparand2);
796 20
                break;
797 20
            case '!=':
798 20
                $conditionResult = ($comparand1 != $comparand2);
799 20
                break;
800
            case '<':
801
                $conditionResult = ($comparand1 < $comparand2);
802
                break;
803
            case '<=':
804
                $conditionResult = ($comparand1 <= $comparand2);
805
                break;
806
            case '>':
807
                $conditionResult = ($comparand1 > $comparand2);
808
                break;
809
            case '>=':
810
                $conditionResult = ($comparand1 >= $comparand2);
811
                break;
812
            case '%':
813
                $conditionResult = ($comparand1 % $comparand2);
814
                break;
815
            default:
816
                throw new \InvalidArgumentException(
817
                    'Unknown condition operator "' . htmlspecialchars($operator) . '"',
818
                    1344340207
819
                );
820
        }
821
822
        // explicit casting, just in case
823 20
        $conditionResult = (boolean)$conditionResult;
824
825 20
        return $conditionResult;
826
    }
827
828
    /**
829
     * Resolves variables to marker. Markers can be simple markers like
830
     * ###MY_MARKER## or "nested" markers which divide their sub values by a
831
     * dot: ###MY_MARKER.MY_VALUE### ###MY_MARKER.MY_OTHER_VALUE###.
832
     *
833
     * @param array $markers array with markers to resolve
834
     * @param mixed $variableValue the marker's value, which can be an array of values, an object with certain getter methods or a simple string
835
     * @return array with marker as index and value for it
836
     */
837 25
    protected function resolveVariableMarkers(array $markers, $variableValue)
838
    {
839 25
        $resolvedMarkers = [];
840
841 25
        $normalizedKeysArray = [];
842 25
        foreach ($variableValue as $key => $value) {
843 25
            $key = $this->normalizeString($key);
844 25
            $normalizedKeysArray[$key] = $value;
845 25
        }
846
847 25
        foreach ($markers as $marker) {
848 25
            $dotPosition = strpos($marker, '.');
849
850 25
            if ($dotPosition !== false) {
851 25
                $resolvedValue = null;
852
853
                // the marker contains a dot, thus we have to resolve the
854
                // second part of the marker
855 25
                $valueSelector = substr($marker, $dotPosition + 1);
856 25
                $valueSelector = $this->normalizeString($valueSelector);
857
858 25
                if (is_array($variableValue) && array_key_exists($valueSelector,
859 25
                        $normalizedKeysArray)
860 25
                ) {
861 25
                    $resolvedValue = $normalizedKeysArray[$valueSelector];
862 25
                } elseif (is_object($variableValue)) {
863
                    $resolveMethod = 'get' . Util::camelize($valueSelector);
864
                    $resolvedValue = $variableValue->$resolveMethod();
865
                }
866 25
            } else {
867
                $resolvedValue = $variableValue[strtolower($marker)];
868
            }
869
870 25
            if (is_null($resolvedValue)) {
871 23
                if ($this->debugMode) {
872
                    $resolvedValue = '!!! Marker &quot;' . $marker . '&quot; could not be resolved.';
873
                } else {
874 23
                    $resolvedValue = '';
875
                }
876 23
            }
877
878 25
            if (is_array($resolvedValue)) {
879
                // handling multivalue fields, @see ApacheSolrForTypo3\Solr\ViewHelper\Multivalue
880
                $resolvedValue = serialize($resolvedValue);
881
            }
882
883 25
            $resolvedMarkers[$marker] = $resolvedValue;
884 25
        }
885
886 25
        return $resolvedMarkers;
887
    }
888
889
    /**
890
     * Normalizes the various input formats of the markers to a common format.
891
     *
892
     * Example:
893
     *
894
     * FILE_MIME_TYPE_STRING_S => file_mime_type_string_s
895
     * file_mime_type_string_s => file_mime_type_string_s
896
     * fileMimeType_stringS    => file_mime_type_string_s
897
     *
898
     * @param string $selector A string in upper case with underscores, lowercase with underscores, camel case, or a mix.
899
     * @return string A lowercased, underscorized version of the given string
900
     */
901 25
    protected function normalizeString($selector)
902
    {
903 25
        static $normalizeCache = [];
904
905 25
        if (!isset($normalizeCache[$selector])) {
906 25
            $originalSelector = $selector;
907 25
            $selector = str_replace('-', '_', $selector);
908
909
            // when switching from lowercase to Uppercase in camel cased
910
            // strings, insert an underscore
911 25
            $underscorized = preg_replace('/([a-z])([A-Z])/', '\\1_\\2',
912 25
                $selector);
913
914
            // for all other cases - all upper or all lower case
915
            // we simply lowercase the complete string
916 25
            $normalizeCache[$originalSelector] = strtolower($underscorized);
917 25
        }
918
919 25
        return $normalizeCache[$selector];
920
    }
921
922
    /**
923
     * Selects a subpart to work on / to apply all operations to.
924
     *
925
     * @param string $subpartName subpart name
926
     */
927 26
    public function workOnSubpart($subpartName)
928
    {
929 26
        $this->workOnSubpart = $this->getSubpart($subpartName, $this->template);
930 26
    }
931
932
    /**
933
     * Retrieves a subpart from the given html template.
934
     *
935
     * @param string $subpartName subpart marker name, can be lowercase, doesn't need the ### delimiters
936
     * @param string $alternativeTemplate
937
     * @return string the html subpart
938
     */
939 26
    public function getSubpart($subpartName, $alternativeTemplate = '')
940
    {
941 26
        $template = $this->workOnSubpart;
942
943
        // set alternative template to work on
944 26
        if (!empty($alternativeTemplate)) {
945 26
            $template = $alternativeTemplate;
946 26
        }
947
948 26
        $subpart = $this->getTemplateService()->getSubpart(
949 26
            $template,
950 26
            '###' . strtoupper($subpartName) . '###'
951 26
        );
952
953 26
        return $subpart;
954
    }
955
956
    /**
957
     * Sets a marker's value.
958
     *
959
     * @param string $marker marker name, can be lower case, doesn't need the ### delimiters
960
     * @param string $content the marker's value
961
     */
962
    public function addMarker($marker, $content)
963
    {
964
        $this->markers['###' . strtoupper($marker) . '###'] = $content;
965
    }
966
967
    /**
968
     * Sets an array of markers with their values.
969
     *
970
     * @param array $markers array of markers
971
     */
972
    public function addMarkerArray(array $markers)
973
    {
974
        foreach ($markers as $marker => $content) {
975
            $this->addMarker($marker, $content);
976
        }
977
    }
978
979
    /**
980
     * Sets a subpart's value.
981
     *
982
     * @param string $subpartMarker subpart name, can be lower case, doesn't need the ### delimiters
983
     * @param string $content the subpart's value
984
     */
985 25
    public function addSubpart($subpartMarker, $content)
986
    {
987 25
        $this->subparts['###' . strtoupper($subpartMarker) . '###'] = $content;
988 25
    }
989
990
    /**
991
     * Assigns a variable to the html template.
992
     * Simple variables can be used like regular markers or in the form
993
     * VAR:"VARIABLE_NAME" (without the quotes). Objects can be used in the
994
     * form VAR:"OBJECT_NAME"."PROPERTY_NAME" (without the quotes).
995
     *
996
     * @param string $key variable key
997
     * @param mixed $value variable value
998
     */
999 25
    public function addVariable($key, $value)
1000
    {
1001 25
        $key = strtolower($key);
1002
1003 25
        $this->variables[$key] = $value;
1004 25
    }
1005
1006
    /**
1007
     * Adds a named loop. The given array is looped over in the template.
1008
     *
1009
     * @param string $loopName loop name
1010
     * @param string $markerName
1011
     * @param array $variables variables array
1012
     */
1013 23
    public function addLoop($loopName, $markerName, array $variables)
1014
    {
1015 23
        $this->loops[$loopName] = [
1016 23
            'marker' => $markerName,
1017
            'data' => $variables
1018 23
        ];
1019 23
    }
1020
1021
    /**
1022
     * Gets a list of Markers from the selected subpart.
1023
     *
1024
     * @param string $template marker name
1025
     * @param string $markerPrefix
1026
     * @param bool $capturePrefix
1027
     * @return array Array of markers
1028
     */
1029 26
    public function getMarkersFromTemplate(
1030
        $template,
1031
        $markerPrefix = '',
1032
        $capturePrefix = true
1033
    ) {
1034 26
        $regex = '!###([A-Z0-9_-|:.]*)\###!is';
1035
1036 26
        if (!empty($markerPrefix)) {
1037 23
            if ($capturePrefix) {
1038 23
                $regex = '!###(' . strtoupper($markerPrefix) . '[A-Z0-9_-|:.]*)\###!is';
1039 23
            } else {
1040 20
                $regex = '!###' . strtoupper($markerPrefix) . '([A-Z0-9_-|:.]*)\###!is';
1041
            }
1042 23
        }
1043
1044 26
        preg_match_all($regex, $template, $match);
1045 26
        $markers = array_unique($match[1]);
1046
1047 26
        return $markers;
1048
    }
1049
1050
    /**
1051
     * returns the markers found in the template
1052
     *
1053
     * @param string $markerPrefix a prefix to limit the result to markers beginning with the specified prefix
1054
     * @return array Array of markers names
1055
     */
1056 26
    public function findMarkers($markerPrefix = '')
1057
    {
1058 26
        return $this->getMarkersFromTemplate($this->workOnSubpart,
1059 26
            $markerPrefix);
1060
    }
1061
1062
    /**
1063
     * Gets a list of view helper marker arguments for a given view helper from
1064
     * the selected subpart.
1065
     *
1066
     * @param string $helperMarker marker name, can be lower case, doesn't need the ### delimiters
1067
     * @param string $subpart subpart markup to search in
1068
     * @param bool $removeDuplicates Optionally determines whether duplicate view helpers are removed. Defaults to TRUE.
1069
     * @return array Array of markers
1070
     */
1071 26
    protected function getViewHelperArgumentLists($helperMarker, $subpart, $removeDuplicates = true)
1072
    {
1073 26
        $markers = self::extractViewHelperArguments($helperMarker, $subpart);
1074
1075 26
        if ($removeDuplicates) {
1076 24
            $markers = array_unique($markers);
1077 24
        }
1078
1079 26
        return $markers;
1080
    }
1081
1082
    /**
1083
     * This helper function is used to extract all ViewHelper arguments by a name of the ViewHelper.
1084
     *
1085
     * Arguments can be: strings or even other markers.
1086
     *
1087
     * @param string $helperMarker
1088
     * @param string $subpart
1089
     * @return array
1090
     */
1091 36
    public static function extractViewHelperArguments($helperMarker, $subpart)
1092
    {
1093
        // already tried (and failed) variants:
1094
        // '!###' . $helperMarker . ':([A-Z0-9_-|.]*)\###!is'
1095
        // '!###' . $helperMarker . ':(.*?)\###!is',
1096
        // '!###' . $helperMarker . ':((.*?)+?(\###(.*?)\###(|.*?)?)?)?\###!is'
1097
        // '!###' . $helperMarker . ':((?:###(?:.+?)###)(?:\|.+?)*|(?:.+?)+)###!is'
1098
        // '/###' . $helperMarker . ':((?:###.+?###(?:\|.+?)*)|(?:.+?)?)###/si'
1099
1100 36
        preg_match_all('/###' . $helperMarker . ':(((###.+?(?=###)###)|(.+?(?=###))|([\w]+|))(?:\|((###.+?(?=###)###)|(.+?(?=###))|([\w]+|)))*)###/si', $subpart, $match, PREG_PATTERN_ORDER);
1101 36
        $markers = $match[1];
1102 36
        return $markers;
1103
    }
1104
1105
    /**
1106
     * Finds view helpers used in the current subpart being worked on.
1107
     *
1108
     * @param string $content A string that should be searched for view helpers.
1109
     * @return array A list of view helper names used in the template.
1110
     */
1111 26
    public function findViewHelpers($content)
1112
    {
1113 26
        preg_match_all('!###([\w]+):.*?\###!is', $content, $match);
1114 26
        $viewHelpers = array_unique($match[1]);
1115
1116
        // remove / protect LOOP, LOOP_CONTENT subparts
1117 26
        $loopIndex = array_search('LOOP', $viewHelpers);
1118 26
        if ($loopIndex !== false) {
1119 1
            unset($viewHelpers[$loopIndex]);
1120 1
        }
1121 26
        $loopContentIndex = array_search('LOOP_CONTENT', $viewHelpers);
1122 26
        if ($loopContentIndex !== false) {
1123
            unset($viewHelpers[$loopContentIndex]);
1124
        }
1125
1126
        // remove / protect IF subparts
1127 26
        $ifIndex = array_search('IF', $viewHelpers);
1128 26
        if ($ifIndex !== false) {
1129 3
            unset($viewHelpers[$ifIndex]);
1130 3
        }
1131
1132 26
        return $viewHelpers;
1133
    }
1134
1135
    /**
1136
     * Gets a list of given markers from the selected subpart.
1137
     *
1138
     * @param string $variableMarker marker name, can be lower case, doesn't need the ### delimiters
1139
     * @param string $subpart subpart name
1140
     * @return array array of markers
1141
     */
1142 25
    public function getVariableMarkers($variableMarker, $subpart)
1143
    {
1144 25
        preg_match_all(
1145 25
            '!###(' . $variableMarker . '\.[A-Z0-9_-]*)\###!is',
1146 25
            $subpart,
1147
            $match
1148 25
        );
1149 25
        $markers = array_unique($match[1]);
1150
1151 25
        return $markers;
1152
    }
1153
1154
    /**
1155
     * Checks whether a given string is a variable marker
1156
     *
1157
     * @param string $potentialVariableMarker String to check whether it is a variable marker
1158
     * @return bool TRUE if the string is identified being a variable marker, FALSE otherwise
1159
     */
1160 20
    public function isVariableMarker($potentialVariableMarker)
1161
    {
1162 20
        $regex = '!###[A-Z0-9_-]*\.[A-Z0-9_-|:.]*\###!is';
1163 20
        $isVariableMarker = preg_match($regex, $potentialVariableMarker);
1164
1165 20
        return (boolean)$isVariableMarker;
1166
    }
1167
1168
    /**
1169
     * @param $templateContent
1170
     * @return mixed
1171
     */
1172 25
    public function setTemplateContent($templateContent)
1173
    {
1174 25
        return $this->template = $templateContent;
1175
    }
1176
1177
    public function getTemplateContent()
1178
    {
1179
        return $this->template;
1180
    }
1181
1182 25
    public function getWorkOnSubpart()
1183
    {
1184 25
        return $this->workOnSubpart;
1185
    }
1186
1187
    /**
1188
     * Sets the debug mode on or off.
1189
     *
1190
     * @param bool $mode debug mode, TRUE to enable debug mode, FALSE to turn off again, off by default
1191
     */
1192
    public function setDebugMode($mode)
1193
    {
1194
        $this->debugMode = (boolean)$mode;
1195
    }
1196
}
1197