Completed
Branch master (33af51)
by Timo
04:12
created

Template::processConditions()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 40
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5.0042

Importance

Changes 0
Metric Value
dl 0
loc 40
ccs 17
cts 18
cp 0.9444
rs 8.439
c 0
b 0
f 0
cc 5
eloc 21
nc 4
nop 1
crap 5.0042
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\ViewHelper\SubpartViewHelper;
28
use ApacheSolrForTypo3\Solr\ViewHelper\ViewHelper;
29
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
32
33
/**
34
 * A template engine to simplify the work with marker based templates. The
35
 * engine supports easy management of markers, subparts, and even loops.
36
 *
37
 * @author Ingo Renner <[email protected]>
38
 */
39
class Template
40
{
41
    const CLEAN_TEMPLATE_YES = true;
42
    const CLEAN_TEMPLATE_NO = false;
43
44
    protected $prefix;
45
    protected $cObj;
46
    protected $templateFile;
47
    protected $template;
48
    protected $workOnSubpart;
49
    protected $viewHelperIncludePath;
50
    protected $helpers = array();
51
    protected $loadedHelperFiles = array();
52
    protected $variables = array();
53
    protected $markers = array();
54
    protected $subparts = array();
55
    protected $loops = array();
56
57
    protected $debugMode = false;
58
59
    /**
60
     * Constructor for the html marker template engine.
61
     *
62
     * @param ContentObjectRenderer $contentObject content object
63
     * @param string $templateFile path to the template file
64
     * @param string $subpart name of the subpart to work on
65
     */
66 26
    public function __construct(
67
        ContentObjectRenderer $contentObject,
68
        $templateFile,
69
        $subpart
70
    ) {
71 26
        $this->cObj = $contentObject;
72 26
        $this->templateFile = $templateFile;
73
74 26
        $this->loadHtmlFile($templateFile);
75 26
        $this->workOnSubpart($subpart);
76 26
    }
77
78
    /**
79
     * @return \TYPO3\CMS\Core\Service\MarkerBasedTemplateService
80
     */
81 26
    protected function getTemplateService()
82
    {
83 26
        return GeneralUtility::makeInstance('TYPO3\CMS\Core\Service\MarkerBasedTemplateService');
84
    }
85
86
    /**
87
     * Copy constructor, sets the clone object's template content to original
88
     * object's work subpart.
89
     *
90
     */
91 25
    public function __clone()
92
    {
93 25
        $this->setTemplateContent($this->getWorkOnSubpart());
94 25
    }
95
96
    /**
97
     * Loads the content of a html template file. Resolves paths beginning with
98
     * "EXT:".
99
     *
100
     * @param string $htmlFile path to html template file
101
     */
102 26
    public function loadHtmlFile($htmlFile)
103
    {
104 26
        $path = $GLOBALS['TSFE']->tmpl->getFileName($htmlFile);
105 26
        if ($path !== null && file_exists($path)) {
106 26
            $this->template = file_get_contents($path);
107
        }
108 26
        if (empty($this->template)) {
109
            throw new \RuntimeException(
110
                'Could not load template file "' . htmlspecialchars($htmlFile) . '"',
111
                1327490358
112
            );
113
        }
114 26
    }
115
116
    /**
117
     * Sets the content for the template we're working on
118
     *
119
     * @param string $templateContent the template's content - usually HTML
120
     * @return void
121
     */
122 25
    public function setWorkingTemplateContent($templateContent)
123
    {
124 25
        $this->workOnSubpart = $templateContent;
125 25
    }
126
127
    /**
128
     * Finds the view helpers in the template and loads them.
129
     *
130
     * @return void
131
     */
132 26
    protected function initializeViewHelpers($content)
133
    {
134 26
        $viewHelpersFound = $this->findViewHelpers($content);
135
136 26
        foreach ($viewHelpersFound as $helperKey) {
137 24
            if (!isset($this->helpers[strtolower($helperKey)])) {
138 18
                $viewHelperLoaded = $this->loadViewHelper($helperKey);
139
140 18
                if (!$viewHelperLoaded) {
141
                    // skipping processing in case we couldn't find a class
142
                    // to handle the view helper
143
                    continue;
144
                }
145
146 24
                $this->addViewHelper($helperKey);
147
            }
148
        }
149 26
    }
150
151
    /**
152
     * Adds an include path where the template engine should look for template
153
     * view helpers.
154
     *
155
     * @param string $extensionKey Extension key
156
     * @param string $viewHelperPath Path inside the extension to look for view helpers
157
     */
158 26
    public function addViewHelperIncludePath($extensionKey, $viewHelperPath)
159
    {
160 26
        $this->viewHelperIncludePath[$extensionKey] = $viewHelperPath;
161 26
    }
162
163
    /**
164
     * Adds a view helper
165
     *
166
     * @param string $helperName view helper name
167
     * @param array $arguments optional array of arguments
168
     * @return bool
169
     */
170 26
    public function addViewHelper($helperName, array $arguments = array())
171
    {
172 26
        $success = false;
173
174 26
        if (!isset($this->helpers[strtolower($helperName)])) {
175 26
            $viewHelperClassName = $this->loadViewHelper($helperName);
176
177
            // could be FALSE if not matching view helper class was found
178 26
            if ($viewHelperClassName) {
179
                try {
180 26
                    $helperInstance = GeneralUtility::makeInstance($viewHelperClassName,
181
                        $arguments);
182 26
                    $success = $this->addViewHelperObject($helperName,
183
                        $helperInstance);
184
                } catch (\Exception $e) {
185
                    $configuration = Util::getSolrConfiguration();
186
                    if ($configuration->getLoggingExceptions()) {
187
                        GeneralUtility::devLog('exception while adding a viewhelper',
188
                            'solr', 3, array(
189
                                $e->__toString()
190
                            ));
191
                    }
192
                }
193
            }
194
        }
195
196 26
        return $success;
197
    }
198
199 26
    protected function loadViewHelper($helperKey)
200
    {
201 26
        if (isset($this->loadedHelperFiles[strtolower($helperKey)])) {
202 18
            return $this->loadedHelperFiles[strtolower($helperKey)]['class'];
203
        }
204
205 26
        foreach ($this->viewHelperIncludePath as $extensionKey => $viewHelperPath) {
206 26
            $viewHelperRealPath = $viewHelperPath;
207 26
            if (GeneralUtility::isFirstPartOfStr($viewHelperPath, 'Classes/')) {
208 26
                $viewHelperRealPath = substr($viewHelperPath, 8);
209
            }
210 26
            if (substr($viewHelperRealPath, -1) == '/') {
211 26
                $viewHelperRealPath = substr($viewHelperRealPath, 0, -1);
212
            }
213
214 26
            $ucHelperKey = Util::underscoredToUpperCamelCase($helperKey);
215 26
            $vendorNameSpace = 'ApacheSolrForTypo3\\Solr\\';
216 26
            $possibleFilename = $ucHelperKey . '.php';
217 26
            $possibleClassName =  $vendorNameSpace . str_replace('/', '\\',
218 26
                    $viewHelperRealPath) . '\\' . $ucHelperKey;
219
220 26
            $viewHelperIncludePath = ExtensionManagementUtility::extPath($extensionKey)
221 26
                . $viewHelperPath . $possibleFilename;
222
223 26
            if (file_exists($viewHelperIncludePath)) {
224 26
                include_once($viewHelperIncludePath);
225 26
                $this->loadedHelperFiles[strtolower($helperKey)] = array(
226 26
                    'file' => $viewHelperIncludePath,
227 26
                    'class' => $possibleClassName
228
                );
229
230 26
                return $possibleClassName;
231
            }
232
        }
233
234
        // view helper could not be found
235
        return false;
236
    }
237
238
    /**
239
     * adds an already instantiated view helper
240
     *
241
     * @param $helperName
242
     * @param ViewHelper $helperObject
243
     * @return bool
244
     */
245 26
    public function addViewHelperObject($helperName, ViewHelper $helperObject)
246
    {
247 26
        $success = false;
248
249 26
        $helperName = strtolower($helperName);
250
251 26
        if (!isset($this->helpers[$helperName])) {
252 26
            $this->helpers[$helperName] = $helperObject;
253 26
            $success = true;
254
        }
255
256 26
        return $success;
257
    }
258
259
    /**
260
     * Renders the template and fills its markers.
261
     *
262
     * @return string the rendered html template with markers replaced with their content
263
     */
264 26
    public function render($cleanTemplate = false)
265
    {
266
267
        // process loops
268 26
        foreach ($this->loops as $key => $loopVariables) {
269 23
            $this->renderLoop($key);
270
        }
271
272
        // process variables
273 26
        foreach ($this->variables as $variableKey => $variable) {
274 25
            $variableKey = strtoupper($variableKey);
275 25
            $variableMarkers = $this->getVariableMarkers($variableKey,
276 25
                $this->workOnSubpart);
277
278 25
            if (count($variableMarkers)) {
279 25
                $resolvedMarkers = $this->resolveVariableMarkers($variableMarkers,
280
                    $variable);
281
282 25
                $this->workOnSubpart = $this->getTemplateService()->substituteMarkerArray(
283 25
                    $this->workOnSubpart,
284
                    $resolvedMarkers,
285 25
                    '###|###'
286
                );
287
            }
288
        }
289
290
        // process markers
291 26
        $this->workOnSubpart = $this->getTemplateService()->substituteMarkerArray(
292 26
            $this->workOnSubpart,
293 26
            $this->markers
294
        );
295
296
        // process subparts
297 26
        foreach ($this->subparts as $subpart => $content) {
298 25
            $this->workOnSubpart = $this->getTemplateService()->substituteSubpart(
299 25
                $this->workOnSubpart,
300
                $subpart,
301
                $content
302
            );
303
        }
304
305
        // process view helpers, they need to be the last objects processing the template
306 26
        $this->initializeViewHelpers($this->workOnSubpart);
307 26
        $this->workOnSubpart = $this->renderViewHelpers($this->workOnSubpart);
308
309
        // process conditions
310 26
        $this->workOnSubpart = $this->processConditions($this->workOnSubpart);
311
312
        // finally, do a cleanup if not disabled
313 26
        if ($cleanTemplate) {
314 26
            $this->cleanTemplate();
315
        }
316
317 26
        return $this->workOnSubpart;
318
    }
319
320
    /**
321
     * Escapes marker hashes and the pipe symbol so that they will not be
322
     * executed in templates.
323
     *
324
     * @param string $content Content potentially containing markers
325
     * @return string Content with markers escaped
326
     */
327 25
    public static function escapeMarkers($content)
328
    {
329
        // escape marker hashes
330 25
        $content = str_replace('###', '&#35;&#35;&#35;', $content);
331
        // escape pipe character used for parameter separation
332 25
        $content = str_replace('|', '&#124;', $content);
333
334 25
        return $content;
335
    }
336
337
    /**
338
     * cleans the template from non-replaced markers and subparts
339
     *
340
     * @return void
341
     */
342 26
    public function cleanTemplate()
343
    {
344 26
        $remainingMarkers = $this->findMarkers();
345
346 26
        foreach ($remainingMarkers as $remainingMarker) {
347 23
            $isSubpart = preg_match_all(
348 23
                '/(\<\!\-\-[\s]+###' . $remainingMarker . '###.*###'
349 23
                . $remainingMarker . '###.+\-\-\>)/sU',
350 23
                $this->workOnSubpart,
351
                $matches,
352 23
                PREG_SET_ORDER
353
            );
354
355 23
            if ($isSubpart) {
356 23
                $this->workOnSubpart = str_replace(
357 23
                    $matches[0][1],
358 23
                    '',
359 23
                    $this->workOnSubpart
360
                );
361
            } else {
362
                $this->workOnSubpart = str_replace(
363
                    '###' . $remainingMarker . '###',
364
                    '',
365 23
                    $this->workOnSubpart
366
                );
367
            }
368
        }
369
370 26
        $unresolvedConditions = $this->findConditions($this->workOnSubpart);
371 26
        foreach ($unresolvedConditions as $unresolvedCondition) {
372
            // if condition evaluates to FALSE, remove the content from the template
373
            $this->workOnSubpart = $this->getTemplateService()->substituteSubpart(
374
                $this->workOnSubpart,
375
                $unresolvedCondition['marker'],
376
                ''
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...
377
            );
378
        }
379 26
    }
380
381
    /**
382
     * Renders view helpers, detects whether it is a regular marker view helper
383
     * or a subpart view helper and passes rendering on to more specialized
384
     * render methods for each type.
385
     *
386
     * @param string $content The content to process by view helpers
387
     * @return string the view helper processed content
388
     */
389 26
    protected function renderViewHelpers($content)
390
    {
391 26
        $viewHelpersFound = $this->findViewHelpers($content);
392
393 26
        foreach ($viewHelpersFound as $helperKey) {
394 24
            if (array_key_exists(strtolower($helperKey), $this->helpers)) {
395 24
                $helper = $this->helpers[strtolower($helperKey)];
396
397 24
                if ($helper instanceof SubpartViewHelper) {
398
                    $content = $this->renderSubpartViewHelper($helper,
399
                        $helperKey, $content);
400
                } else {
401 24
                    $content = $this->renderMarkerViewHelper($helper,
402
                        $helperKey, $content);
403
                }
404
            }
405
        }
406
407 26
        return $content;
408
    }
409
410
    /**
411
     * Renders single marker view helpers.
412
     *
413
     * @param ViewHelper $viewHelper View helper instance to execute.
414
     * @param string $helperKey The view helper marker key.
415
     * @param string $content Markup that contains the unsubstituted view helper marker.
416
     * @return string Markup with the view helper replaced by the content it returned.
417
     */
418 24
    protected function renderMarkerViewHelper(
419
        ViewHelper $viewHelper,
420
        $helperKey,
421
        $content
422
    ) {
423 24
        $viewHelperArgumentLists = $this->getViewHelperArgumentLists($helperKey,
424
            $content);
425
426 24
        foreach ($viewHelperArgumentLists as $viewHelperArgumentList) {
427 24
            $viewHelperArguments = explode('|', $viewHelperArgumentList);
428
            // TODO check whether one of the parameters is a Helper
429
            // itself, if so resolve it before handing it off to the
430
            // actual helper, this way the order in which viewhelpers
431
            // get added to the template do not matter anymore
432
            // may use findViewHelpers()
433
434
            // checking whether any of the helper arguments should be
435
            // replaced by a variable available to the template
436 24
            foreach ($viewHelperArguments as $i => $helperArgument) {
437 24
                $lowercaseHelperArgument = strtolower($helperArgument);
438 24
                if (array_key_exists($lowercaseHelperArgument,
439 24
                    $this->variables)) {
440 24
                    $viewHelperArguments[$i] = $this->variables[$lowercaseHelperArgument];
441
                }
442
            }
443
444 24
            $viewHelperContent = $viewHelper->execute($viewHelperArguments);
445
446 24
            $content = $this->getTemplateService()->substituteMarker(
447
                $content,
448 24
                '###' . $helperKey . ':' . $viewHelperArgumentList . '###',
449
                $viewHelperContent
450
            );
451
        }
452
453 24
        return $content;
454
    }
455
456
    /**
457
     * Renders subpart view helpers.
458
     *
459
     * @param SubpartViewHelper $viewHelper View helper instance to execute.
460
     * @param string $helperKey The view helper marker key.
461
     * @param string $content Markup that contains the unsubstituted view helper subpart.
462
     * @return string Markup with the view helper replaced by the content it returned.
463
     */
464
    protected function renderSubpartViewHelper(
465
        SubpartViewHelper $viewHelper,
466
        $helperKey,
467
        $content
468
    ) {
469
        $viewHelperArgumentLists = $this->getViewHelperArgumentLists($helperKey,
470
            $content);
471
472
        foreach ($viewHelperArgumentLists as $viewHelperArgumentList) {
473
            $subpartMarker = '###' . $helperKey . ':' . $viewHelperArgumentList . '###';
474
475
            $subpart = $this->getTemplateService()->getSubpart(
476
                $content,
477
                $subpartMarker
478
            );
479
480
            $viewHelperArguments = explode('|', $viewHelperArgumentList);
481
482
            $subpartTemplate = clone $this;
483
            $subpartTemplate->setWorkingTemplateContent($subpart);
484
            $viewHelper->setTemplate($subpartTemplate);
485
486
            try {
487
                $viewHelperContent = $viewHelper->execute($viewHelperArguments);
488
            } catch (\UnexpectedValueException $e) {
489
                $configuration = Util::getSolrConfiguration();
490
                if ($configuration->getLoggingExceptions()) {
491
                    GeneralUtility::devLog('Exception while rendering a viewhelper',
492
                        'solr', 3, array(
493
                            $e->__toString()
494
                        ));
495
                }
496
497
                $viewHelperContent = '';
498
            }
499
500
            $content = $this->getTemplateService()->substituteSubpart(
501
                $content,
502
                $subpartMarker,
503
                $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...
504
                false
505
            );
506
507
            // there might be more occurrences of the same subpart maker with
508
            // the same arguments but different markup to be used...
509
            // that's the case with the facet subpart view helper f.e.
510
            $furtherOccurrences = strpos($content, $subpartMarker);
511
            if ($furtherOccurrences !== false) {
512
                $content = $this->renderSubpartViewHelper($viewHelper,
513
                    $helperKey, $content);
514
            }
515
        }
516
517
        return $content;
518
    }
519
520
    /**
521
     * Renders the loop for a given loop name.
522
     *
523
     * @param string $loopName Key from $this->loops to render
524
     */
525 23
    protected function renderLoop($loopName)
526
    {
527 23
        $loopContent = '';
528 23
        $loopTemplate = $this->getSubpart('LOOP:' . $loopName);
529
530 23
        $loopContentMarker = 'loop_content:' . $loopName;
531 23
        $loopSingleItem = $this->getSubpart($loopContentMarker, $loopTemplate);
532 23
        if (empty($loopSingleItem)) {
533
            // backwards compatible fallback for unnamed loops
534 23
            $loopContentMarker = 'loop_content';
535 23
            $loopSingleItem = $this->getSubpart($loopContentMarker,
536
                $loopTemplate);
537
        }
538
539 23
        $loopMarker = strtoupper($this->loops[$loopName]['marker']);
540 23
        $loopVariables = $this->loops[$loopName]['data'];
541 23
        $foundMarkers = $this->getMarkersFromTemplate($loopSingleItem,
542 23
            $loopMarker . '\.');
543 23
        $loopCount = count($loopVariables);
544
545 23
        if (count($foundMarkers)) {
546 23
            $iterationCount = 0;
547 23
            foreach ($loopVariables as $value) {
548 20
                $resolvedMarkers = $this->resolveVariableMarkers($foundMarkers,
549
                    $value);
550 20
                $resolvedMarkers['LOOP_CURRENT_ITERATION_COUNT'] = ++$iterationCount;
551
552
                // pass the whole object / array / variable as is (serialized though)
553 20
                $resolvedMarkers[$loopMarker] = serialize($value);
554
555 20
                $currentIterationContent = $this->getTemplateService()->substituteMarkerArray(
556
                    $loopSingleItem,
557
                    $resolvedMarkers,
558 20
                    '###|###'
559
                );
560
561 20
                $inLoopMarkers = $this->getMarkersFromTemplate(
562
                    $currentIterationContent,
563 20
                    'LOOP:',
564 20
                    false
565
                );
566
567 20
                $inLoopMarkers = $this->filterProtectedLoops($inLoopMarkers);
568
569 20
                $currentIterationContent = $this->processInLoopMarkers(
570
                    $currentIterationContent,
571
                    $loopName,
572
                    $inLoopMarkers,
573
                    $value
574
                );
575
576 20
                $currentIterationContent = $this->processConditions($currentIterationContent);
577
578 20
                $loopContent .= $currentIterationContent;
579
            }
580
        }
581
582 23
        $loopContent = $this->getTemplateService()->substituteSubpart(
583
            $loopTemplate,
584 23
            '###' . strtoupper($loopContentMarker) . '###',
585
            $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...
586
        );
587
588 23
        $loopContent = $this->getTemplateService()->substituteMarkerArray(
589
            $loopContent,
590 23
            array('LOOP_ELEMENT_COUNT' => $loopCount),
591 23
            '###|###'
592
        );
593
594 23
        $this->workOnSubpart = $this->getTemplateService()->substituteSubpart(
595 23
            $this->workOnSubpart,
596 23
            '###LOOP:' . strtoupper($loopName) . '###',
597
            $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...
598
        );
599 23
    }
600
601
    /**
602
     * Processes marker in a loop that start with LOOP:.
603
     *
604
     * This is useful especially for calling view helpers with the current
605
     * iteration's value as a parameter.
606
     *
607
     * @param string $content
608
     * @param string $loopName
609
     * @param array $markers
610
     * @param string $currentIterationValue
611
     * @return string
612
     */
613 20
    protected function processInLoopMarkers(
614
        $content,
615
        $loopName,
616
        array $markers,
617
        $currentIterationValue
618
    ) {
619 20
        foreach ($markers as $marker) {
620
            list($helperName, $helperArguments) = explode(':', $marker);
621
622
            $helperName = strtolower($helperName);
623
            $helperArguments = explode('|', $helperArguments);
624
625
            // checking whether any of the helper arguments should be
626
            // replaced by the current iteration's value
627
            if (isset($this->loops[$loopName])) {
628
                foreach ($helperArguments as $i => $helperArgument) {
629
                    if (strtoupper($this->loops[$loopName]['marker']) == strtoupper($helperArgument)) {
630
                        $helperArguments[$i] = $currentIterationValue;
631
                    }
632
                }
633
            }
634
635
            if (array_key_exists($helperName, $this->helpers)) {
636
                $markerContent = $this->helpers[$helperName]->execute($helperArguments);
637
            } else {
638
                throw new \RuntimeException(
639
                    'No matching view helper found for marker "' . $marker . '".',
640
                    1311005284
641
                );
642
            }
643
644
            $content = str_replace('###LOOP:' . $marker . '###', $markerContent,
645
                $content);
646
        }
647
648 20
        return $content;
649
    }
650
651
    /**
652
     * Some marker subparts must be protected and only rendered by their
653
     * according commands. This method filters these protected markers from
654
     * others when rendering loops so that they are not replaced and left in
655
     * the template for rendering by the correct command.
656
     *
657
     * @param array $loopMarkers An array of loop markers found during rendering of a loop.
658
     * @return array The array with protected subpart markers removed.
659
     */
660 20
    protected function filterProtectedLoops($loopMarkers)
661
    {
662 20
        $protectedMarkers = array('result_documents');
663
664 20
        foreach ($loopMarkers as $key => $loopMarker) {
665
            if (in_array(strtolower($loopMarker), $protectedMarkers)) {
666
                unset($loopMarkers[$key]);
667
            }
668
        }
669
670 20
        return $loopMarkers;
671
    }
672
673
    /**
674
     * Processes conditions: finds and evaluates them in HTML code.
675
     *
676
     * @param string $content HTML
677
     * @return string
678
     */
679 26
    protected function processConditions($content)
680
    {
681
        // find conditions
682 26
        $conditions = $this->findConditions($content);
683
684
        // evaluate conditions
685 26
        foreach ($conditions as $condition) {
686 20
            if ($this->isVariableMarker($condition['comparand1'])
687 20
                || $this->isVariableMarker($condition['comparand2'])
688
            ) {
689
                // unresolved marker => skip, will be resolved later
690
                continue;
691
            }
692
693 20
            $conditionResult = $this->evaluateCondition(
694 20
                $condition['comparand1'],
695 20
                $condition['comparand2'],
696 20
                $condition['operator']
697
            );
698
699 20
            if ($conditionResult) {
700
                // if condition evaluates to TRUE, simply replace it with
701
                // the original content to have the surrounding markers removed
702 3
                $content = $this->getTemplateService()->substituteSubpart(
703
                    $content,
704 3
                    $condition['marker'],
705 3
                    $condition['content']
706
                );
707
            } else {
708
                // if condition evaluates to FALSE, remove the content from the template
709 20
                $content = $this->getTemplateService()->substituteSubpart(
710
                    $content,
711 20
                    $condition['marker'],
712 20
                    ''
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...
713
                );
714
            }
715
        }
716
717 26
        return $content;
718
    }
719
720
    /**
721
     * Finds conditions in HTML code.
722
     *
723
     * Conditions are subparts with markers in the form of
724
     *
725
     * ###IF:comparand1|operator|comparand2###
726
     * Some content only visible if the condition evaluates as TRUE
727
     * ###IF:comparand1|operator|comparand2###
728
     *
729
     * The returned result is an array of arrays describing a found condition.
730
     * Each conditions is described as follows:
731
     * [marker] the complete marker used to specify the condition
732
     * [content] the content wrapped by the condition
733
     * [operator] the condition operator
734
     * [comparand1] and [comparand2] the comparands.
735
     *
736
     * @param string $content HTML
737
     * @return array An array describing the conditions found
738
     */
739 26
    protected function findConditions($content)
740
    {
741 26
        $conditions = array();
742 26
        $ifMarkers = $this->getViewHelperArgumentLists('IF', $content, false);
743
744 26
        foreach ($ifMarkers as $ifMarker) {
745 20
            list($comparand1, $operator, $comparand2) = explode('|', $ifMarker);
746
747 20
            $ifContent = $this->getTemplateService()->getSubpart(
748
                $content,
749 20
                '###IF:' . $ifMarker . '###'
750
            );
751
752 20
            $conditions[] = array(
753 20
                'marker' => '###IF:' . $ifMarker . '###',
754 20
                'content' => $ifContent,
755 20
                'operator' => trim($operator),
756 20
                'comparand1' => $comparand1,
757 20
                'comparand2' => $comparand2
758
            );
759
        }
760
761 26
        return $conditions;
762
    }
763
764
    /**
765
     * Evaluates conditions.
766
     *
767
     * Supported operators are ==, !=, <, <=, >, >=, %
768
     *
769
     * @param string $comparand1 First comparand
770
     * @param string $comparand2 Second comparand
771
     * @param string $operator Operator
772
     * @return bool Boolean evaluation of the condition.
773
     * @throws \InvalidArgumentException for unknown $operator
774
     */
775 20
    protected function evaluateCondition($comparand1, $comparand2, $operator)
776
    {
777 20
        $conditionResult = false;
0 ignored issues
show
Unused Code introduced by
$conditionResult is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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