-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
IncludeRenderer.php
178 lines (152 loc) · 6.28 KB
/
IncludeRenderer.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
<?php
declare(strict_types=1);
/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/
namespace Pimcore\Templating\Renderer;
use Pimcore\Cache;
use Pimcore\Model;
use Pimcore\Model\Document\PageSnippet;
use Pimcore\Model\Document\Targeting\TargetingDocumentInterface;
use Pimcore\Model\Element;
use Pimcore\Targeting\Document\DocumentTargetingConfigurator;
use Pimcore\Tool\DeviceDetector;
use Pimcore\Tool\DomCrawler;
use Pimcore\Tool\Frontend;
/**
* @internal
*/
class IncludeRenderer
{
protected ActionRenderer $actionRenderer;
private DocumentTargetingConfigurator $targetingConfigurator;
public function __construct(
ActionRenderer $actionRenderer,
DocumentTargetingConfigurator $targetingConfigurator
) {
$this->actionRenderer = $actionRenderer;
$this->targetingConfigurator = $targetingConfigurator;
}
/**
* Renders a document include
*
* @param mixed $include
* @param array $params
* @param bool $editmode
* @param bool $cacheEnabled
*
* @return string
*/
public function render(mixed $include, array $params = [], bool $editmode = false, bool $cacheEnabled = true): string
{
if (!is_array($params)) {
$params = [];
}
$originalInclude = $include;
// this is if $this->inc is called eg. with $this->relation() as argument
if (!$include instanceof PageSnippet && is_object($include) && method_exists($include, '__toString')) {
$include = (string)$include;
}
if (is_numeric($include)) {
try {
$include = Model\Document::getById($include);
} catch (\Exception $e) {
$include = $originalInclude;
}
} elseif (is_string($include)) {
try {
$include = Model\Document::getByPath($include);
} catch (\Exception $e) {
$include = $originalInclude;
}
}
if ($include instanceof PageSnippet && $include->isPublished()) {
// apply best matching target group (if any)
$this->targetingConfigurator->configureTargetGroup($include);
}
// check if output-cache is enabled, if so, we're also using the cache here
$cacheKey = null;
$cacheConfig = false;
if ($cacheEnabled && !$editmode && $cacheConfig = Frontend::isOutputCacheEnabled()) {
// cleanup params to avoid serializing Element\ElementInterface objects
$cacheParams = $params;
$cacheParams['~~include-document'] = $originalInclude;
array_walk($cacheParams, function (&$value, $key) {
if ($value instanceof Element\ElementInterface) {
$value = $value->getId();
} elseif (is_object($value) && method_exists($value, '__toString')) {
$value = (string)$value;
}
});
// TODO is this enough for cache or should we disable caching completely?
if ($include instanceof TargetingDocumentInterface && $include->getUseTargetGroup()) {
$cacheParams['target_group'] = $include->getUseTargetGroup();
}
$cacheKey = 'tag_inc__' . md5(serialize($cacheParams));
if ($content = Cache::load($cacheKey)) {
return $content;
}
}
$params = array_merge($params, ['document' => $include]);
$content = '';
if ($include instanceof PageSnippet && $include->isPublished()) {
$content = $this->renderAction($include, $params);
if ($editmode) {
$content = $this->modifyEditmodeContent($include, $content);
}
}
// write contents to the cache, if output-cache is enabled & not in editmode
if ($cacheConfig && !$editmode && !DeviceDetector::getInstance()->wasUsed()) {
$cacheTags = ['output_inline'];
$cacheTags[] = $cacheConfig['lifetime'] ? 'output_lifetime' : 'output';
Cache::save($content, $cacheKey, $cacheTags, $cacheConfig['lifetime']);
}
return $content;
}
protected function renderAction(PageSnippet $include, array $params): string
{
return $this->actionRenderer->render($include, $params);
}
/**
* in editmode, we need to parse the returned html from the document include
* add a class and the pimcore id / type so that it can be opened in editmode using the context menu
* if there's no first level HTML container => add one (wrapper)
*
* @param PageSnippet $include
* @param string $content
*
* @return string
*/
protected function modifyEditmodeContent(PageSnippet $include, string $content): string
{
$editmodeClass = ' pimcore_editable pimcore_editable_inc ';
// this is if the content that is included does already contain markup/html
// this is needed by the editmode to highlight included documents
try {
$html = new DomCrawler($content);
$children = $html->filterXPath('//' . DomCrawler::FRAGMENT_WRAPPER_TAG . '/*'); // FRAGMENT_WRAPPER_TAG is added by DomCrawler for fragments
/** @var \DOMElement $child */
foreach ($children as $child) {
$child->setAttribute('class', $child->getAttribute('class') . $editmodeClass);
$child->setAttribute('pimcore_type', $include->getType());
$child->setAttribute('pimcore_id', (string) $include->getId());
}
$content = $html->html();
$html->clear();
unset($html);
} catch (\Exception $e) {
// add a div container if the include doesn't contain markup/html
$content = '<div class="' . $editmodeClass . '" pimcore_id="' . $include->getId() . '" pimcore_type="' . $include->getType() . '">' . $content . '</div>';
}
return $content;
}
}