Completed
Push — master ( ed6058...79eb48 )
by André
15:41
created

FieldBlockRenderer::renderContentField()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 14
nc 8
nop 4
dl 0
loc 26
rs 8.5806
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of the eZ Publish Kernel package.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\MVC\Symfony\Templating\Twig;
10
11
use eZ\Publish\API\Repository\Values\Content\Field;
12
use eZ\Publish\API\Repository\Values\ContentType\FieldDefinition;
13
use eZ\Publish\Core\MVC\Symfony\Templating\Exception\MissingFieldBlockException;
14
use eZ\Publish\Core\MVC\Symfony\Templating\FieldBlockRendererInterface;
15
use Twig_Environment;
16
use Twig_Template;
17
18
class FieldBlockRenderer implements FieldBlockRendererInterface
19
{
20
    const VIEW = 1;
21
    const EDIT = 2;
22
23
    const FIELD_VIEW_SUFFIX = '_field';
24
    const FIELD_EDIT_SUFFIX = '_field_edit';
25
    const FIELD_DEFINITION_VIEW_SUFFIX = '_settings';
26
    const FIELD_DEFINITION_EDIT_SUFFIX = '_field_definition_edit';
27
28
    /**
29
     * @var Twig_Environment
30
     */
31
    private $twig;
32
33
    /**
34
     * Array of Twig template resources for field view.
35
     * Either the path to each template and its priority in a hash or its
36
     * \Twig_Template (compiled) counterpart.
37
     *
38
     * @var Twig_Template[]|array
39
     */
40
    private $fieldViewResources = [];
41
42
    /**
43
     * Array of Twig template resources for field edit.
44
     * Either the path to each template and its priority in a hash or its
45
     * \Twig_Template (compiled) counterpart.
46
     *
47
     * @var Twig_Template[]|array
48
     */
49
    private $fieldEditResources = [];
50
51
    /**
52
     * Array of Twig template resources for field definition view.
53
     * Either the path to each template and its priority in a hash or its
54
     * \Twig_Template (compiled) counterpart.
55
     *
56
     * @var Twig_Template[]|array
57
     */
58
    private $fieldDefinitionViewResources = [];
59
60
    /**
61
     * Array of Twig template resources for field definition edit.
62
     * Either the path to each template and its priority in a hash or its
63
     * \Twig_Template (compiled) counterpart.
64
     *
65
     * @var Twig_Template[]|array
66
     */
67
    private $fieldDefinitionEditResources = [];
68
69
    /**
70
     * A \Twig_Template instance used to render template blocks, or path to the template to use.
71
     *
72
     * @var Twig_Template|string
73
     */
74
    private $baseTemplate;
75
76
    /**
77
     * Template blocks.
78
     *
79
     * @var array
80
     */
81
    private $blocks = [];
82
83
    /**
84
     * @param Twig_Environment $twig
85
     */
86
    public function setTwig(Twig_Environment $twig)
87
    {
88
        $this->twig = $twig;
89
    }
90
91
    /**
92
     * @param string|Twig_Template $baseTemplate
93
     */
94
    public function setBaseTemplate($baseTemplate)
95
    {
96
        $this->baseTemplate = $baseTemplate;
97
    }
98
99
    /**
100
     * @param array $fieldViewResources
101
     */
102
    public function setFieldViewResources(array $fieldViewResources = null)
103
    {
104
        $this->fieldViewResources = (array)$fieldViewResources;
105
        usort($this->fieldViewResources, [$this, 'sortResourcesCallback']);
106
    }
107
108
    /**
109
     * @param array $fieldEditResources
110
     */
111
    public function setFieldEditResources(array $fieldEditResources = null)
112
    {
113
        $this->fieldEditResources = (array)$fieldEditResources;
114
        usort($this->fieldEditResources, [$this, 'sortResourcesCallback']);
115
    }
116
117
    /**
118
     * @param array $fieldDefinitionViewResources
119
     */
120
    public function setFieldDefinitionViewResources(array $fieldDefinitionViewResources = null)
121
    {
122
        $this->fieldDefinitionViewResources = (array)$fieldDefinitionViewResources;
123
        usort($this->fieldDefinitionViewResources, [$this, 'sortResourcesCallback']);
124
    }
125
126
    /**
127
     * @param array $fieldDefinitionEditResources
128
     */
129
    public function setFieldDefinitionEditResources(array $fieldDefinitionEditResources = null)
130
    {
131
        $this->fieldDefinitionEditResources = (array)$fieldDefinitionEditResources;
132
        usort($this->fieldDefinitionEditResources, [$this, 'sortResourcesCallback']);
133
    }
134
135
    public function sortResourcesCallback(array $a, array $b)
136
    {
137
        return $b['priority'] - $a['priority'];
138
    }
139
140
    public function renderContentFieldView(Field $field, $fieldTypeIdentifier, array $params = [])
141
    {
142
        return $this->renderContentField($field, $fieldTypeIdentifier, $params, self::VIEW);
143
    }
144
145
    public function renderContentFieldEdit(Field $field, $fieldTypeIdentifier, array $params = [])
146
    {
147
        return $this->renderContentField($field, $fieldTypeIdentifier, $params, self::EDIT);
148
    }
149
150
    /**
151
     * @param Field $field
152
     * @param string $fieldTypeIdentifier
153
     * @param array $params
154
     * @param int $type Either self::VIEW or self::EDIT
155
     *
156
     * @throws MissingFieldBlockException If no template block can be found for $field
157
     *
158
     * @return string
159
     */
160
    private function renderContentField(Field $field, $fieldTypeIdentifier, array $params, $type)
161
    {
162
        $localTemplate = null;
163
        if (isset($params['template'])) {
164
            // local override of the template
165
            // this template is put on the top the templates stack
166
            $localTemplate = $params['template'];
167
            unset($params['template']);
168
        }
169
170
        $params += ['field' => $field];
171
172
        // Getting instance of Twig_Template that will be used to render blocks
173
        if (is_string($this->baseTemplate)) {
174
            $this->baseTemplate = $this->twig->loadTemplate($this->baseTemplate);
175
        }
176
        $blockName = $this->getRenderFieldBlockName($fieldTypeIdentifier, $type);
177
        $context = $this->twig->mergeGlobals($params);
178
        $blocks = $this->getBlocksByField($fieldTypeIdentifier, $type, $localTemplate);
179
180
        if (!$this->baseTemplate->hasBlock($blockName, $context, $blocks)) {
181
            throw new MissingFieldBlockException("Cannot find '$blockName' template block.");
182
        }
183
184
        return $this->baseTemplate->renderBlock($blockName, $context, $blocks);
185
    }
186
187
    public function renderFieldDefinitionView(FieldDefinition $fieldDefinition, array $params = [])
188
    {
189
        return $this->renderFieldDefinition($fieldDefinition, $params, self::VIEW);
190
    }
191
192
    public function renderFieldDefinitionEdit(FieldDefinition $fieldDefinition, array $params = [])
193
    {
194
        return $this->renderFieldDefinition($fieldDefinition, $params, self::EDIT);
195
    }
196
197
    /**
198
     * @param FieldDefinition $fieldDefinition
199
     * @param array $params
200
     * @param int $type Either self::VIEW or self::EDIT
201
     *
202
     * @return string
203
     */
204
    private function renderFieldDefinition(FieldDefinition $fieldDefinition, array $params, $type)
205
    {
206
        if (is_string($this->baseTemplate)) {
207
            $this->baseTemplate = $this->twig->loadTemplate($this->baseTemplate);
208
        }
209
210
        $params += [
211
            'fielddefinition' => $fieldDefinition,
212
            'settings' => $fieldDefinition->getFieldSettings(),
213
        ];
214
        $blockName = $this->getRenderFieldDefinitionBlockName($fieldDefinition->fieldTypeIdentifier, $type);
215
        $context = $this->twig->mergeGlobals($params);
216
        $blocks = $this->getBlocksByFieldDefinition($fieldDefinition, $type);
217
218
        if (!$this->baseTemplate->hasBlock($blockName, $context, $blocks)) {
219
            return '';
220
        }
221
222
        return $this->baseTemplate->renderBlock($blockName, $context, $blocks);
223
    }
224
225
    /**
226
     * Returns the block named $blockName in the given template. If it's not
227
     * found, returns null.
228
     *
229
     * @param string $blockName
230
     * @param Twig_Template $tpl
231
     *
232
     * @return array|null
233
     */
234
    private function searchBlock($blockName, Twig_Template $tpl)
235
    {
236
        // Current template might have parents, so we need to loop against
237
        // them to find a matching block
238
        do {
239
            foreach ($tpl->getBlocks() as $name => $block) {
240
                if ($name === $blockName) {
241
                    return $block;
242
                }
243
            }
244
        } while (($tpl = $tpl->getParent([])) instanceof Twig_Template);
245
246
        return null;
247
    }
248
249
    /**
250
     * Returns template blocks for $fieldTypeIdentifier. First check in the $localTemplate if it's provided.
251
     * Template block convention name is <fieldTypeIdentifier>_field
252
     * Example: 'ezstring_field' will be relevant for a full view of ezstring field type.
253
     *
254
     * @param string $fieldTypeIdentifier
255
     * @param int $type Either self::VIEW or self::EDIT
256
     * @param null|string|Twig_Template $localTemplate a file where to look for the block first
257
     *
258
     * @return array
259
     */
260
    private function getBlocksByField($fieldTypeIdentifier, $type, $localTemplate = null)
261
    {
262
        $fieldBlockName = $this->getRenderFieldBlockName($fieldTypeIdentifier, $type);
263
        if ($localTemplate !== null) {
264
            // $localTemplate might be a Twig_Template instance already (e.g. using _self Twig keyword)
265
            if (!$localTemplate instanceof Twig_Template) {
266
                $localTemplate = $this->twig->loadTemplate($localTemplate);
267
            }
268
269
            $block = $this->searchBlock($fieldBlockName, $localTemplate);
270
            if ($block !== null) {
271
                return [$fieldBlockName => $block];
272
            }
273
        }
274
275
        return $this->getBlockByName($fieldBlockName, $type === self::EDIT ? 'fieldEditResources' : 'fieldViewResources');
276
    }
277
278
    /**
279
     * Returns the template block for the settings of the field definition $definition.
280
     *
281
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition $definition
282
     * @param int $type Either self::VIEW or self::EDIT
283
     *
284
     * @return array
285
     */
286
    private function getBlocksByFieldDefinition(FieldDefinition $definition, $type)
287
    {
288
        return $this->getBlockByName(
289
            $this->getRenderFieldDefinitionBlockName($definition->fieldTypeIdentifier, $type),
290
            $type === self::EDIT ? 'fieldDefinitionEditResources' : 'fieldDefinitionViewResources'
291
        );
292
    }
293
294
    /**
295
     * Returns the template block of the given $name available in the resources
296
     * which name is $resourcesName.
297
     *
298
     * @param string $name
299
     * @param string $resourcesName
300
     *
301
     * @return array
302
     */
303
    private function getBlockByName($name, $resourcesName)
304
    {
305
        if (isset($this->blocks[$name])) {
306
            return [$name => $this->blocks[$name]];
307
        }
308
309
        foreach ($this->{$resourcesName} as &$template) {
310
            if (!$template instanceof Twig_Template) {
311
                $template = $this->twig->loadTemplate($template['template']);
312
            }
313
314
            $tpl = $template;
315
316
            $block = $this->searchBlock($name, $tpl);
317
            if ($block !== null) {
318
                $this->blocks[$name] = $block;
319
320
                return [$name => $block];
321
            }
322
        }
323
324
        return [];
325
    }
326
327
    /**
328
     * Returns expected block name for $fieldTypeIdentifier, attached in $content.
329
     *
330
     * @param string $fieldTypeIdentifier
331
     * @param int $type Either self::VIEW or self::EDIT
332
     *
333
     * @return string
334
     */
335
    private function getRenderFieldBlockName($fieldTypeIdentifier, $type)
336
    {
337
        $suffix = $type === self::EDIT ? self::FIELD_EDIT_SUFFIX : self::FIELD_VIEW_SUFFIX;
338
339
        return $fieldTypeIdentifier . $suffix;
340
    }
341
342
    /**
343
     * Returns the name of the block to render the settings of the field
344
     * definition $definition.
345
     *
346
     * @param string $fieldTypeIdentifier
347
     * @param int $type Either self::VIEW or self::EDIT
348
     *
349
     * @return string
350
     */
351
    private function getRenderFieldDefinitionBlockName($fieldTypeIdentifier, $type)
352
    {
353
        $suffix = $type === self::EDIT ? self::FIELD_DEFINITION_EDIT_SUFFIX : self::FIELD_DEFINITION_VIEW_SUFFIX;
354
355
        return $fieldTypeIdentifier . $suffix;
356
    }
357
}
358