Completed
Branch master (b22b71)
by Timo
13:13 queued 21s
created

Template   D

Complexity

Total Complexity 116

Size/Duplication

Total Lines 1143
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 75.72%

Importance

Changes 0
Metric Value
wmc 116
lcom 1
cbo 7
dl 0
loc 1143
ccs 393
cts 519
cp 0.7572
rs 4.4102
c 0
b 0
f 0

42 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A getTemplateService() 0 4 1
A __clone() 0 4 1
A setWorkingTemplateContent() 0 4 1
A addViewHelperIncludePath() 0 4 1
A loadHtmlFile() 0 13 4
A initializeViewHelpers() 0 18 4
B addViewHelper() 0 28 5
B loadViewHelper() 0 38 6
A addViewHelperObject() 0 13 2
A escapeMarkers() 0 9 1
A workOnSubpart() 0 4 1
A addMarker() 0 4 1
A addSubpart() 0 4 1
A addVariable() 0 6 1
A addLoop() 0 7 1
A extractViewHelperArguments() 0 13 1
A isVariableMarker() 0 7 1
A setTemplateContent() 0 4 1
A getTemplateContent() 0 4 1
A getWorkOnSubpart() 0 4 1
A setDebugMode() 0 4 1
B render() 0 55 6
B cleanTemplate() 0 38 4
A renderViewHelpers() 0 20 4
B renderMarkerViewHelper() 0 37 4
B renderSubpartViewHelper() 0 55 5
B renderLoop() 0 75 4
B processInLoopMarkers() 0 37 6
A filterProtectedLoops() 0 12 3
B processConditions() 0 40 5
B findConditions() 0 24 2
C evaluateCondition() 0 36 8
C resolveVariableMarkers() 0 51 10
A normalizeString() 0 20 2
A getSubpart() 0 16 2
A addMarkerArray() 0 6 2
A getMarkersFromTemplate() 0 20 3
A findMarkers() 0 5 1
A getViewHelperArgumentLists() 0 10 2
B findViewHelpers() 0 23 4
A getVariableMarkers() 0 11 1

How to fix   Complexity   

Complex Class

Complex classes like Template often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Template, and based on these observations, apply Extract Interface, too.

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 26
        }
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
     * @param string $content
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 18
                $this->addViewHelper($helperKey);
147 18
            }
148 26
        }
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 26
                        $arguments);
182 26
                    $success = $this->addViewHelperObject($helperName,
183 26
                        $helperInstance);
184 26
                } 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 26
            }
194 26
        }
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 26
            }
210 26
            if (substr($viewHelperRealPath, -1) == '/') {
211 26
                $viewHelperRealPath = substr($viewHelperRealPath, 0, -1);
212 26
            }
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
                    'class' => $possibleClassName
228 26
                );
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 26
        }
255
256 26
        return $success;
257
    }
258
259
    /**
260
     * Renders the template and fills its markers.
261
     *
262
     * @param bool $cleanTemplate
263
     * @return string the rendered html template with markers replaced with their content
264
     */
265 26
    public function render($cleanTemplate = false)
266
    {
267
268
        // process loops
269 26
        foreach ($this->loops as $key => $loopVariables) {
270 23
            $this->renderLoop($key);
271 26
        }
272
273
        // process variables
274 26
        foreach ($this->variables as $variableKey => $variable) {
275 25
            $variableKey = strtoupper($variableKey);
276 25
            $variableMarkers = $this->getVariableMarkers($variableKey,
277 25
                $this->workOnSubpart);
278
279 25
            if (count($variableMarkers)) {
280 25
                $resolvedMarkers = $this->resolveVariableMarkers($variableMarkers,
281 25
                    $variable);
282
283 25
                $this->workOnSubpart = $this->getTemplateService()->substituteMarkerArray(
284 25
                    $this->workOnSubpart,
285 25
                    $resolvedMarkers,
286
                    '###|###'
287 25
                );
288 25
            }
289 26
        }
290
291
        // process markers
292 26
        $this->workOnSubpart = $this->getTemplateService()->substituteMarkerArray(
293 26
            $this->workOnSubpart,
294 26
            $this->markers
295 26
        );
296
297
        // process subparts
298 26
        foreach ($this->subparts as $subpart => $content) {
299 25
            $this->workOnSubpart = $this->getTemplateService()->substituteSubpart(
300 25
                $this->workOnSubpart,
301 25
                $subpart,
302
                $content
303 25
            );
304 26
        }
305
306
        // process view helpers, they need to be the last objects processing the template
307 26
        $this->initializeViewHelpers($this->workOnSubpart);
308 26
        $this->workOnSubpart = $this->renderViewHelpers($this->workOnSubpart);
309
310
        // process conditions
311 26
        $this->workOnSubpart = $this->processConditions($this->workOnSubpart);
312
313
        // finally, do a cleanup if not disabled
314 26
        if ($cleanTemplate) {
315 26
            $this->cleanTemplate();
316 26
        }
317
318 26
        return $this->workOnSubpart;
319
    }
320
321
    /**
322
     * Escapes marker hashes and the pipe symbol so that they will not be
323
     * executed in templates.
324
     *
325
     * @param string $content Content potentially containing markers
326
     * @return string Content with markers escaped
327
     */
328 25
    public static function escapeMarkers($content)
329
    {
330
        // escape marker hashes
331 25
        $content = str_replace('###', '&#35;&#35;&#35;', $content);
332
        // escape pipe character used for parameter separation
333 25
        $content = str_replace('|', '&#124;', $content);
334
335 25
        return $content;
336
    }
337
338
    /**
339
     * cleans the template from non-replaced markers and subparts
340
     *
341
     * @return void
342
     */
343 26
    public function cleanTemplate()
344
    {
345 26
        $remainingMarkers = $this->findMarkers();
346
347 26
        foreach ($remainingMarkers as $remainingMarker) {
348 23
            $isSubpart = preg_match_all(
349 23
                '/(\<\!\-\-[\s]+###' . $remainingMarker . '###.*###'
350 23
                . $remainingMarker . '###.+\-\-\>)/sU',
351 23
                $this->workOnSubpart,
352 23
                $matches,
353
                PREG_SET_ORDER
354 23
            );
355
356 23
            if ($isSubpart) {
357 23
                $this->workOnSubpart = str_replace(
358 23
                    $matches[0][1],
359 23
                    '',
360 23
                    $this->workOnSubpart
361 23
                );
362 23
            } else {
363
                $this->workOnSubpart = str_replace(
364
                    '###' . $remainingMarker . '###',
365
                    '',
366
                    $this->workOnSubpart
367
                );
368
            }
369 26
        }
370
371 26
        $unresolvedConditions = $this->findConditions($this->workOnSubpart);
372 26
        foreach ($unresolvedConditions as $unresolvedCondition) {
373
            // if condition evaluates to FALSE, remove the content from the template
374
            $this->workOnSubpart = $this->getTemplateService()->substituteSubpart(
375
                $this->workOnSubpart,
376
                $unresolvedCondition['marker'],
377
                ''
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...
378
            );
379 26
        }
380 26
    }
381
382
    /**
383
     * Renders view helpers, detects whether it is a regular marker view helper
384
     * or a subpart view helper and passes rendering on to more specialized
385
     * render methods for each type.
386
     *
387
     * @param string $content The content to process by view helpers
388
     * @return string the view helper processed content
389
     */
390 26
    protected function renderViewHelpers($content)
391
    {
392 26
        $viewHelpersFound = $this->findViewHelpers($content);
393
394 26
        foreach ($viewHelpersFound as $helperKey) {
395 24
            if (array_key_exists(strtolower($helperKey), $this->helpers)) {
396 24
                $helper = $this->helpers[strtolower($helperKey)];
397
398 24
                if ($helper instanceof SubpartViewHelper) {
399
                    $content = $this->renderSubpartViewHelper($helper,
400
                        $helperKey, $content);
401
                } else {
402 24
                    $content = $this->renderMarkerViewHelper($helper,
403 24
                        $helperKey, $content);
404
                }
405 24
            }
406 26
        }
407
408 26
        return $content;
409
    }
410
411
    /**
412
     * Renders single marker view helpers.
413
     *
414
     * @param ViewHelper $viewHelper View helper instance to execute.
415
     * @param string $helperKey The view helper marker key.
416
     * @param string $content Markup that contains the unsubstituted view helper marker.
417
     * @return string Markup with the view helper replaced by the content it returned.
418
     */
419 24
    protected function renderMarkerViewHelper(
420
        ViewHelper $viewHelper,
421
        $helperKey,
422
        $content
423
    ) {
424 24
        $viewHelperArgumentLists = $this->getViewHelperArgumentLists($helperKey,
425 24
            $content);
426
427 24
        foreach ($viewHelperArgumentLists as $viewHelperArgumentList) {
428 24
            $viewHelperArguments = explode('|', $viewHelperArgumentList);
429
            // TODO check whether one of the parameters is a Helper
430
            // itself, if so resolve it before handing it off to the
431
            // actual helper, this way the order in which viewhelpers
432
            // get added to the template do not matter anymore
433
            // may use findViewHelpers()
434
435
            // checking whether any of the helper arguments should be
436
            // replaced by a variable available to the template
437 24
            foreach ($viewHelperArguments as $i => $helperArgument) {
438 24
                $lowercaseHelperArgument = strtolower($helperArgument);
439 24
                if (array_key_exists($lowercaseHelperArgument,
440 24
                    $this->variables)) {
441
                    $viewHelperArguments[$i] = $this->variables[$lowercaseHelperArgument];
442
                }
443 24
            }
444
445 24
            $viewHelperContent = $viewHelper->execute($viewHelperArguments);
446
447 24
            $content = $this->getTemplateService()->substituteMarker(
448 24
                $content,
449 24
                '###' . $helperKey . ':' . $viewHelperArgumentList . '###',
450
                $viewHelperContent
451 24
            );
452 24
        }
453
454 24
        return $content;
455
    }
456
457
    /**
458
     * Renders subpart view helpers.
459
     *
460
     * @param SubpartViewHelper $viewHelper View helper instance to execute.
461
     * @param string $helperKey The view helper marker key.
462
     * @param string $content Markup that contains the unsubstituted view helper subpart.
463
     * @return string Markup with the view helper replaced by the content it returned.
464
     */
465
    protected function renderSubpartViewHelper(
466
        SubpartViewHelper $viewHelper,
467
        $helperKey,
468
        $content
469
    ) {
470
        $viewHelperArgumentLists = $this->getViewHelperArgumentLists($helperKey,
471
            $content);
472
473
        foreach ($viewHelperArgumentLists as $viewHelperArgumentList) {
474
            $subpartMarker = '###' . $helperKey . ':' . $viewHelperArgumentList . '###';
475
476
            $subpart = $this->getTemplateService()->getSubpart(
477
                $content,
478
                $subpartMarker
479
            );
480
481
            $viewHelperArguments = explode('|', $viewHelperArgumentList);
482
483
            $subpartTemplate = clone $this;
484
            $subpartTemplate->setWorkingTemplateContent($subpart);
485
            $viewHelper->setTemplate($subpartTemplate);
486
487
            try {
488
                $viewHelperContent = $viewHelper->execute($viewHelperArguments);
489
            } catch (\UnexpectedValueException $e) {
490
                $configuration = Util::getSolrConfiguration();
491
                if ($configuration->getLoggingExceptions()) {
492
                    GeneralUtility::devLog('Exception while rendering a viewhelper',
493
                        'solr', 3, array(
494
                            $e->__toString()
495
                        ));
496
                }
497
498
                $viewHelperContent = '';
499
            }
500
501
            $content = $this->getTemplateService()->substituteSubpart(
502
                $content,
503
                $subpartMarker,
504
                $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...
505
                false
506
            );
507
508
            // there might be more occurrences of the same subpart maker with
509
            // the same arguments but different markup to be used...
510
            // that's the case with the facet subpart view helper f.e.
511
            $furtherOccurrences = strpos($content, $subpartMarker);
512
            if ($furtherOccurrences !== false) {
513
                $content = $this->renderSubpartViewHelper($viewHelper,
514
                    $helperKey, $content);
515
            }
516
        }
517
518
        return $content;
519
    }
520
521
    /**
522
     * Renders the loop for a given loop name.
523
     *
524
     * @param string $loopName Key from $this->loops to render
525
     */
526 23
    protected function renderLoop($loopName)
527
    {
528 23
        $loopContent = '';
529 23
        $loopTemplate = $this->getSubpart('LOOP:' . $loopName);
530
531 23
        $loopContentMarker = 'loop_content:' . $loopName;
532 23
        $loopSingleItem = $this->getSubpart($loopContentMarker, $loopTemplate);
533 23
        if (empty($loopSingleItem)) {
534
            // backwards compatible fallback for unnamed loops
535 23
            $loopContentMarker = 'loop_content';
536 23
            $loopSingleItem = $this->getSubpart($loopContentMarker,
537 23
                $loopTemplate);
538 23
        }
539
540 23
        $loopMarker = strtoupper($this->loops[$loopName]['marker']);
541 23
        $loopVariables = $this->loops[$loopName]['data'];
542 23
        $foundMarkers = $this->getMarkersFromTemplate($loopSingleItem,
543 23
            $loopMarker . '\.');
544 23
        $loopCount = count($loopVariables);
545
546 23
        if (count($foundMarkers)) {
547 23
            $iterationCount = 0;
548 23
            foreach ($loopVariables as $value) {
549 20
                $resolvedMarkers = $this->resolveVariableMarkers($foundMarkers,
550 20
                    $value);
551 20
                $resolvedMarkers['LOOP_CURRENT_ITERATION_COUNT'] = ++$iterationCount;
552
553
                // pass the whole object / array / variable as is (serialized though)
554 20
                $resolvedMarkers[$loopMarker] = serialize($value);
555
556 20
                $currentIterationContent = $this->getTemplateService()->substituteMarkerArray(
557 20
                    $loopSingleItem,
558 20
                    $resolvedMarkers,
559
                    '###|###'
560 20
                );
561
562 20
                $inLoopMarkers = $this->getMarkersFromTemplate(
563 20
                    $currentIterationContent,
564 20
                    'LOOP:',
565
                    false
566 20
                );
567
568 20
                $inLoopMarkers = $this->filterProtectedLoops($inLoopMarkers);
569
570 20
                $currentIterationContent = $this->processInLoopMarkers(
571 20
                    $currentIterationContent,
572 20
                    $loopName,
573 20
                    $inLoopMarkers,
574
                    $value
575 20
                );
576
577 20
                $currentIterationContent = $this->processConditions($currentIterationContent);
578
579 20
                $loopContent .= $currentIterationContent;
580 23
            }
581 23
        }
582
583 23
        $loopContent = $this->getTemplateService()->substituteSubpart(
584 23
            $loopTemplate,
585 23
            '###' . strtoupper($loopContentMarker) . '###',
586
            $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...
587 23
        );
588
589 23
        $loopContent = $this->getTemplateService()->substituteMarkerArray(
590 23
            $loopContent,
591 23
            array('LOOP_ELEMENT_COUNT' => $loopCount),
592
            '###|###'
593 23
        );
594
595 23
        $this->workOnSubpart = $this->getTemplateService()->substituteSubpart(
596 23
            $this->workOnSubpart,
597 23
            '###LOOP:' . strtoupper($loopName) . '###',
598
            $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...
599 23
        );
600 23
    }
601
602
    /**
603
     * Processes marker in a loop that start with LOOP:.
604
     *
605
     * This is useful especially for calling view helpers with the current
606
     * iteration's value as a parameter.
607
     *
608
     * @param string $content
609
     * @param string $loopName
610
     * @param array $markers
611
     * @param string $currentIterationValue
612
     * @return string
613
     */
614 20
    protected function processInLoopMarkers(
615
        $content,
616
        $loopName,
617
        array $markers,
618
        $currentIterationValue
619
    ) {
620 20
        foreach ($markers as $marker) {
621
            list($helperName, $helperArguments) = explode(':', $marker);
622
623
            $helperName = strtolower($helperName);
624
            $helperArguments = explode('|', $helperArguments);
625
626
            // checking whether any of the helper arguments should be
627
            // replaced by the current iteration's value
628
            if (isset($this->loops[$loopName])) {
629
                foreach ($helperArguments as $i => $helperArgument) {
630
                    if (strtoupper($this->loops[$loopName]['marker']) == strtoupper($helperArgument)) {
631
                        $helperArguments[$i] = $currentIterationValue;
632
                    }
633
                }
634
            }
635
636
            if (array_key_exists($helperName, $this->helpers)) {
637
                $markerContent = $this->helpers[$helperName]->execute($helperArguments);
638
            } else {
639
                throw new \RuntimeException(
640
                    'No matching view helper found for marker "' . $marker . '".',
641
                    1311005284
642
                );
643
            }
644
645
            $content = str_replace('###LOOP:' . $marker . '###', $markerContent,
646
                $content);
647 20
        }
648
649 20
        return $content;
650
    }
651
652
    /**
653
     * Some marker subparts must be protected and only rendered by their
654
     * according commands. This method filters these protected markers from
655
     * others when rendering loops so that they are not replaced and left in
656
     * the template for rendering by the correct command.
657
     *
658
     * @param array $loopMarkers An array of loop markers found during rendering of a loop.
659
     * @return array The array with protected subpart markers removed.
660
     */
661 20
    protected function filterProtectedLoops($loopMarkers)
662
    {
663 20
        $protectedMarkers = array('result_documents');
664
665 20
        foreach ($loopMarkers as $key => $loopMarker) {
666
            if (in_array(strtolower($loopMarker), $protectedMarkers)) {
667
                unset($loopMarkers[$key]);
668
            }
669 20
        }
670
671 20
        return $loopMarkers;
672
    }
673
674
    /**
675
     * Processes conditions: finds and evaluates them in HTML code.
676
     *
677
     * @param string $content HTML
678
     * @return string
679
     */
680 26
    protected function processConditions($content)
681
    {
682
        // find conditions
683 26
        $conditions = $this->findConditions($content);
684
685
        // evaluate conditions
686 26
        foreach ($conditions as $condition) {
687 20
            if ($this->isVariableMarker($condition['comparand1'])
688 20
                || $this->isVariableMarker($condition['comparand2'])
689 20
            ) {
690
                // unresolved marker => skip, will be resolved later
691
                continue;
692
            }
693
694 20
            $conditionResult = $this->evaluateCondition(
695 20
                $condition['comparand1'],
696 20
                $condition['comparand2'],
697 20
                $condition['operator']
698 20
            );
699
700 20
            if ($conditionResult) {
701
                // if condition evaluates to TRUE, simply replace it with
702
                // the original content to have the surrounding markers removed
703 3
                $content = $this->getTemplateService()->substituteSubpart(
704 3
                    $content,
705 3
                    $condition['marker'],
706 3
                    $condition['content']
707 3
                );
708 3
            } else {
709
                // if condition evaluates to FALSE, remove the content from the template
710 20
                $content = $this->getTemplateService()->substituteSubpart(
711 20
                    $content,
712 20
                    $condition['marker'],
713
                    ''
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...
714 20
                );
715
            }
716 26
        }
717
718 26
        return $content;
719
    }
720
721
    /**
722
     * Finds conditions in HTML code.
723
     *
724
     * Conditions are subparts with markers in the form of
725
     *
726
     * ###IF:comparand1|operator|comparand2###
727
     * Some content only visible if the condition evaluates as TRUE
728
     * ###IF:comparand1|operator|comparand2###
729
     *
730
     * The returned result is an array of arrays describing a found condition.
731
     * Each conditions is described as follows:
732
     * [marker] the complete marker used to specify the condition
733
     * [content] the content wrapped by the condition
734
     * [operator] the condition operator
735
     * [comparand1] and [comparand2] the comparands.
736
     *
737
     * @param string $content HTML
738
     * @return array An array describing the conditions found
739
     */
740 26
    protected function findConditions($content)
741
    {
742 26
        $conditions = array();
743 26
        $ifMarkers = $this->getViewHelperArgumentLists('IF', $content, false);
744
745 26
        foreach ($ifMarkers as $ifMarker) {
746 20
            list($comparand1, $operator, $comparand2) = explode('|', $ifMarker);
747
748 20
            $ifContent = $this->getTemplateService()->getSubpart(
749 20
                $content,
750 20
                '###IF:' . $ifMarker . '###'
751 20
            );
752
753 20
            $conditions[] = array(
754 20
                'marker' => '###IF:' . $ifMarker . '###',
755 20
                'content' => $ifContent,
756 20
                'operator' => trim($operator),
757 20
                'comparand1' => $comparand1,
758
                'comparand2' => $comparand2
759 20
            );
760 26
        }
761
762 26
        return $conditions;
763
    }
764
765
    /**
766
     * Evaluates conditions.
767
     *
768
     * Supported operators are ==, !=, <, <=, >, >=, %
769
     *
770
     * @param string $comparand1 First comparand
771
     * @param string $comparand2 Second comparand
772
     * @param string $operator Operator
773
     * @return bool Boolean evaluation of the condition.
774
     * @throws \InvalidArgumentException for unknown $operator
775
     */
776 20
    protected function evaluateCondition($comparand1, $comparand2, $operator)
777
    {
778
        switch ($operator) {
779 20
            case '==':
780 20
                $conditionResult = ($comparand1 == $comparand2);
781 20
                break;
782 20
            case '!=':
783 20
                $conditionResult = ($comparand1 != $comparand2);
784 20
                break;
785
            case '<':
786
                $conditionResult = ($comparand1 < $comparand2);
787
                break;
788
            case '<=':
789
                $conditionResult = ($comparand1 <= $comparand2);
790
                break;
791
            case '>':
792
                $conditionResult = ($comparand1 > $comparand2);
793
                break;
794
            case '>=':
795
                $conditionResult = ($comparand1 >= $comparand2);
796
                break;
797
            case '%':
798
                $conditionResult = ($comparand1 % $comparand2);
799
                break;
800
            default:
801
                throw new \InvalidArgumentException(
802
                    'Unknown condition operator "' . htmlspecialchars($operator) . '"',
803
                    1344340207
804
                );
805
        }
806
807
        // explicit casting, just in case
808 20
        $conditionResult = (boolean)$conditionResult;
809
810 20
        return $conditionResult;
811
    }
812
813
    /**
814
     * Resolves variables to marker. Markers can be simple markers like
815
     * ###MY_MARKER## or "nested" markers which divide their sub values by a
816
     * dot: ###MY_MARKER.MY_VALUE### ###MY_MARKER.MY_OTHER_VALUE###.
817
     *
818
     * @param array $markers array with markers to resolve
819
     * @param mixed $variableValue the marker's value, which can be an array of values, an object with certain getter methods or a simple string
820
     * @return array with marker as index and value for it
821
     */
822 25
    protected function resolveVariableMarkers(array $markers, $variableValue)
823
    {
824 25
        $resolvedMarkers = array();
825
826 25
        $normalizedKeysArray = array();
827 25
        foreach ($variableValue as $key => $value) {
828 25
            $key = $this->normalizeString($key);
829 25
            $normalizedKeysArray[$key] = $value;
830 25
        }
831
832 25
        foreach ($markers as $marker) {
833 25
            $dotPosition = strpos($marker, '.');
834
835 25
            if ($dotPosition !== false) {
836 25
                $resolvedValue = null;
837
838
                // the marker contains a dot, thus we have to resolve the
839
                // second part of the marker
840 25
                $valueSelector = substr($marker, $dotPosition + 1);
841 25
                $valueSelector = $this->normalizeString($valueSelector);
842
843 25
                if (is_array($variableValue) && array_key_exists($valueSelector,
844 25
                        $normalizedKeysArray)
845 25
                ) {
846 25
                    $resolvedValue = $normalizedKeysArray[$valueSelector];
847 25
                } elseif (is_object($variableValue)) {
848
                    $resolveMethod = 'get' . Util::camelize($valueSelector);
849
                    $resolvedValue = $variableValue->$resolveMethod();
850
                }
851 25
            } else {
852
                $resolvedValue = $variableValue[strtolower($marker)];
853
            }
854
855 25
            if (is_null($resolvedValue)) {
856 23
                if ($this->debugMode) {
857
                    $resolvedValue = '!!! Marker &quot;' . $marker . '&quot; could not be resolved.';
858
                } else {
859 23
                    $resolvedValue = '';
860
                }
861 23
            }
862
863 25
            if (is_array($resolvedValue)) {
864
                // handling multivalue fields, @see ApacheSolrForTypo3\Solr\ViewHelper\Multivalue
865
                $resolvedValue = serialize($resolvedValue);
866
            }
867
868 25
            $resolvedMarkers[$marker] = $resolvedValue;
869 25
        }
870
871 25
        return $resolvedMarkers;
872
    }
873
874
    /**
875
     * Normalizes the various input formats of the markers to a common format.
876
     *
877
     * Example:
878
     *
879
     * FILE_MIME_TYPE_STRING_S => file_mime_type_string_s
880
     * file_mime_type_string_s => file_mime_type_string_s
881
     * fileMimeType_stringS    => file_mime_type_string_s
882
     *
883
     * @param string $selector A string in upper case with underscores, lowercase with underscores, camel case, or a mix.
884
     * @return string A lowercased, underscorized version of the given string
885
     */
886 25
    protected function normalizeString($selector)
887
    {
888 25
        static $normalizeCache = array();
889
890 25
        if (!isset($normalizeCache[$selector])) {
891 25
            $originalSelector = $selector;
892 25
            $selector = str_replace('-', '_', $selector);
893
894
            // when switching from lowercase to Uppercase in camel cased
895
            // strings, insert an underscore
896 25
            $underscorized = preg_replace('/([a-z])([A-Z])/', '\\1_\\2',
897 25
                $selector);
898
899
            // for all other cases - all upper or all lower case
900
            // we simply lowercase the complete string
901 25
            $normalizeCache[$originalSelector] = strtolower($underscorized);
902 25
        }
903
904 25
        return $normalizeCache[$selector];
905
    }
906
907
    /**
908
     * Selects a subpart to work on / to apply all operations to.
909
     *
910
     * @param string $subpartName subpart name
911
     */
912 26
    public function workOnSubpart($subpartName)
913
    {
914 26
        $this->workOnSubpart = $this->getSubpart($subpartName, $this->template);
915 26
    }
916
917
    /**
918
     * Retrieves a subpart from the given html template.
919
     *
920
     * @param string $subpartName subpart marker name, can be lowercase, doesn't need the ### delimiters
921
     * @param string $alternativeTemplate
922
     * @return string the html subpart
923
     */
924 26
    public function getSubpart($subpartName, $alternativeTemplate = '')
925
    {
926 26
        $template = $this->workOnSubpart;
927
928
        // set alternative template to work on
929 26
        if (!empty($alternativeTemplate)) {
930 26
            $template = $alternativeTemplate;
931 26
        }
932
933 26
        $subpart = $this->getTemplateService()->getSubpart(
934 26
            $template,
935 26
            '###' . strtoupper($subpartName) . '###'
936 26
        );
937
938 26
        return $subpart;
939
    }
940
941
    /**
942
     * Sets a marker's value.
943
     *
944
     * @param string $marker marker name, can be lower case, doesn't need the ### delimiters
945
     * @param string $content the marker's value
946
     */
947
    public function addMarker($marker, $content)
948
    {
949
        $this->markers['###' . strtoupper($marker) . '###'] = $content;
950
    }
951
952
    /**
953
     * Sets an array of markers with their values.
954
     *
955
     * @param array $markers array of markers
956
     */
957
    public function addMarkerArray(array $markers)
958
    {
959
        foreach ($markers as $marker => $content) {
960
            $this->addMarker($marker, $content);
961
        }
962
    }
963
964
    /**
965
     * Sets a subpart's value.
966
     *
967
     * @param string $subpartMarker subpart name, can be lower case, doesn't need the ### delimiters
968
     * @param string $content the subpart's value
969
     */
970 25
    public function addSubpart($subpartMarker, $content)
971
    {
972 25
        $this->subparts['###' . strtoupper($subpartMarker) . '###'] = $content;
973 25
    }
974
975
    /**
976
     * Assigns a variable to the html template.
977
     * Simple variables can be used like regular markers or in the form
978
     * VAR:"VARIABLE_NAME" (without the quotes). Objects can be used in the
979
     * form VAR:"OBJECT_NAME"."PROPERTY_NAME" (without the quotes).
980
     *
981
     * @param string $key variable key
982
     * @param mixed $value variable value
983
     */
984 25
    public function addVariable($key, $value)
985
    {
986 25
        $key = strtolower($key);
987
988 25
        $this->variables[$key] = $value;
989 25
    }
990
991
    /**
992
     * Adds a named loop. The given array is looped over in the template.
993
     *
994
     * @param string $loopName loop name
995
     * @param string $markerName
996
     * @param array $variables variables array
997
     */
998 23
    public function addLoop($loopName, $markerName, array $variables)
999
    {
1000 23
        $this->loops[$loopName] = array(
1001 23
            'marker' => $markerName,
1002
            'data' => $variables
1003 23
        );
1004 23
    }
1005
1006
    /**
1007
     * Gets a list of Markers from the selected subpart.
1008
     *
1009
     * @param string $template marker name
1010
     * @param string $markerPrefix
1011
     * @param bool $capturePrefix
1012
     * @return array Array of markers
1013
     */
1014 26
    public function getMarkersFromTemplate(
1015
        $template,
1016
        $markerPrefix = '',
1017
        $capturePrefix = true
1018
    ) {
1019 26
        $regex = '!###([A-Z0-9_-|:.]*)\###!is';
1020
1021 26
        if (!empty($markerPrefix)) {
1022 23
            if ($capturePrefix) {
1023 23
                $regex = '!###(' . strtoupper($markerPrefix) . '[A-Z0-9_-|:.]*)\###!is';
1024 23
            } else {
1025 20
                $regex = '!###' . strtoupper($markerPrefix) . '([A-Z0-9_-|:.]*)\###!is';
1026
            }
1027 23
        }
1028
1029 26
        preg_match_all($regex, $template, $match);
1030 26
        $markers = array_unique($match[1]);
1031
1032 26
        return $markers;
1033
    }
1034
1035
    /**
1036
     * returns the markers found in the template
1037
     *
1038
     * @param string $markerPrefix a prefix to limit the result to markers beginning with the specified prefix
1039
     * @return array Array of markers names
1040
     */
1041 26
    public function findMarkers($markerPrefix = '')
1042
    {
1043 26
        return $this->getMarkersFromTemplate($this->workOnSubpart,
1044 26
            $markerPrefix);
1045
    }
1046
1047
    /**
1048
     * Gets a list of view helper marker arguments for a given view helper from
1049
     * the selected subpart.
1050
     *
1051
     * @param string $helperMarker marker name, can be lower case, doesn't need the ### delimiters
1052
     * @param string $subpart subpart markup to search in
1053
     * @param bool $removeDuplicates Optionally determines whether duplicate view helpers are removed. Defaults to TRUE.
1054
     * @return array Array of markers
1055
     */
1056 26
    protected function getViewHelperArgumentLists($helperMarker, $subpart, $removeDuplicates = true)
1057
    {
1058 26
        $markers = self::extractViewHelperArguments($helperMarker, $subpart);
1059
1060 26
        if ($removeDuplicates) {
1061 24
            $markers = array_unique($markers);
1062 24
        }
1063
1064 26
        return $markers;
1065
    }
1066
1067
    /**
1068
     * This helper function is used to extract all ViewHelper arguments by a name of the ViewHelper.
1069
     *
1070
     * Arguments can be: strings or even other markers.
1071
     *
1072
     * @param string $helperMarker
1073
     * @param string $subpart
1074
     * @return array
1075
     */
1076 36
    public static function extractViewHelperArguments($helperMarker, $subpart)
1077
    {
1078
        // already tried (and failed) variants:
1079
        // '!###' . $helperMarker . ':([A-Z0-9_-|.]*)\###!is'
1080
        // '!###' . $helperMarker . ':(.*?)\###!is',
1081
        // '!###' . $helperMarker . ':((.*?)+?(\###(.*?)\###(|.*?)?)?)?\###!is'
1082
        // '!###' . $helperMarker . ':((?:###(?:.+?)###)(?:\|.+?)*|(?:.+?)+)###!is'
1083
        // '/###' . $helperMarker . ':((?:###.+?###(?:\|.+?)*)|(?:.+?)?)###/si'
1084
1085 36
        preg_match_all('/###' . $helperMarker . ':(((###.+?(?=###)###)|(.+?(?=###))|([\w]+|))(?:\|((###.+?(?=###)###)|(.+?(?=###))|([\w]+|)))*)###/si', $subpart, $match, PREG_PATTERN_ORDER);
1086 36
        $markers = $match[1];
1087 36
        return $markers;
1088
    }
1089
1090
    /**
1091
     * Finds view helpers used in the current subpart being worked on.
1092
     *
1093
     * @param string $content A string that should be searched for view helpers.
1094
     * @return array A list of view helper names used in the template.
1095
     */
1096 26
    public function findViewHelpers($content)
1097
    {
1098 26
        preg_match_all('!###([\w]+):.*?\###!is', $content, $match);
1099 26
        $viewHelpers = array_unique($match[1]);
1100
1101
        // remove / protect LOOP, LOOP_CONTENT subparts
1102 26
        $loopIndex = array_search('LOOP', $viewHelpers);
1103 26
        if ($loopIndex !== false) {
1104 1
            unset($viewHelpers[$loopIndex]);
1105 1
        }
1106 26
        $loopContentIndex = array_search('LOOP_CONTENT', $viewHelpers);
1107 26
        if ($loopContentIndex !== false) {
1108
            unset($viewHelpers[$loopContentIndex]);
1109
        }
1110
1111
        // remove / protect IF subparts
1112 26
        $ifIndex = array_search('IF', $viewHelpers);
1113 26
        if ($ifIndex !== false) {
1114 3
            unset($viewHelpers[$ifIndex]);
1115 3
        }
1116
1117 26
        return $viewHelpers;
1118
    }
1119
1120
    /**
1121
     * Gets a list of given markers from the selected subpart.
1122
     *
1123
     * @param string $variableMarker marker name, can be lower case, doesn't need the ### delimiters
1124
     * @param string $subpart subpart name
1125
     * @return array array of markers
1126
     */
1127 25
    public function getVariableMarkers($variableMarker, $subpart)
1128
    {
1129 25
        preg_match_all(
1130 25
            '!###(' . $variableMarker . '\.[A-Z0-9_-]*)\###!is',
1131 25
            $subpart,
1132
            $match
1133 25
        );
1134 25
        $markers = array_unique($match[1]);
1135
1136 25
        return $markers;
1137
    }
1138
1139
    /**
1140
     * Checks whether a given string is a variable marker
1141
     *
1142
     * @param string $potentialVariableMarker String to check whether it is a variable marker
1143
     * @return bool TRUE if the string is identified being a variable marker, FALSE otherwise
1144
     */
1145 20
    public function isVariableMarker($potentialVariableMarker)
1146
    {
1147 20
        $regex = '!###[A-Z0-9_-]*\.[A-Z0-9_-|:.]*\###!is';
1148 20
        $isVariableMarker = preg_match($regex, $potentialVariableMarker);
1149
1150 20
        return (boolean)$isVariableMarker;
1151
    }
1152
1153
    /**
1154
     * @param $templateContent
1155
     * @return mixed
1156
     */
1157 25
    public function setTemplateContent($templateContent)
1158
    {
1159 25
        return $this->template = $templateContent;
1160
    }
1161
1162
    public function getTemplateContent()
1163
    {
1164
        return $this->template;
1165
    }
1166
1167 25
    public function getWorkOnSubpart()
1168
    {
1169 25
        return $this->workOnSubpart;
1170
    }
1171
1172
    /**
1173
     * Sets the debug mode on or off.
1174
     *
1175
     * @param bool $mode debug mode, TRUE to enable debug mode, FALSE to turn off again, off by default
1176
     */
1177
    public function setDebugMode($mode)
1178
    {
1179
        $this->debugMode = (boolean)$mode;
1180
    }
1181
}
1182