Completed
Push — EZP-26342 ( f736d8...3812b1 )
by André
22:44
created

RelationListConverter::dbAttributeMap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 0
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the Relation converter.
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
 * @version //autogentag//
10
 */
11
namespace eZ\Publish\Core\Persistence\Legacy\Content\FieldValue\Converter;
12
13
use eZ\Publish\Core\Persistence\Legacy\Content\FieldValue\Converter;
14
use eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue;
15
use eZ\Publish\SPI\Persistence\Content\ContentInfo;
16
use eZ\Publish\SPI\Persistence\Content\FieldValue;
17
use eZ\Publish\SPI\Persistence\Content\Type as ContentType;
18
use eZ\Publish\SPI\Persistence\Content\Type\FieldDefinition;
19
use eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldDefinition;
20
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
21
use DOMDocument;
22
use DOMElement;
23
use PDO;
24
25
class RelationListConverter implements Converter
26
{
27
    /**
28
     * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler
29
     */
30
    private $db;
31
32
    /**
33
     * Create instance of RelationList converter.
34
     *
35
     * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $db
36
     */
37
    public function __construct(DatabaseHandler $db)
38
    {
39
        $this->db = $db;
40
    }
41
42
    /**
43
     * Converts data from $value to $storageFieldValue.
44
     *
45
     * @param \eZ\Publish\SPI\Persistence\Content\FieldValue $value
46
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $storageFieldValue
47
     */
48
    public function toStorageValue(FieldValue $value, StorageFieldValue $storageFieldValue)
49
    {
50
        $doc = new DOMDocument('1.0', 'utf-8');
51
        $root = $doc->createElement('related-objects');
52
        $doc->appendChild($root);
53
54
        $relationList = $doc->createElement('relation-list');
55
        $data = $this->getRelationXmlHashFromDB($value->data['destinationContentIds']);
56
        $priority = 0;
57
58
        foreach ($value->data['destinationContentIds'] as $id) {
59
            $relationItem = $doc->createElement('relation-item');
60
            if (isset($data[$id][0])) {
61
                $this->setRelationItem($relationItem, $id, $data[$id][0], $priority += 1);
62
            } else {
63
                $this->setDeletedRelationItem($relationItem, $id, $priority += 1);
64
            }
65
66
            $relationList->appendChild($relationItem);
67
            unset($relationItem);
68
        }
69
70
        $root->appendChild($relationList);
71
        $doc->appendChild($root);
72
73
        $storageFieldValue->dataText = $doc->saveXML();
74
    }
75
76
    /**
77
     * Set RelationItem for Content.
78
     *
79
     * Handles existing Content, with and without location data, for deleted Content {@see setDeletedRelationItem()}.
80
     *
81
     * @param \DOMElement $relationItem
82
     * @param mixed $id
83
     * @param array $row
84
     * @param int $priority
85
     */
86
    private function setRelationItem(DOMElement $relationItem, $id, array $row, $priority)
87
    {
88
        $row['ezcontentobject_id'] = $id;
89
        $row['priority'] = $priority;
90
        $row['in_trash'] = ($row['ezcontentobject_status'] == ContentInfo::STATUS_ARCHIVED);
91
        foreach (self::dbAttributeMap() as $domAttrKey => $propertyKey) {
92
            if (!isset($row[$propertyKey])) {
93
                // left join data missing, ignore the given attribute (content most likely in trash, or missing location)
94
                continue;
95
            }
96
97
            $relationItem->setAttribute($domAttrKey, $row[$propertyKey]);
98
        }
99
    }
100
101
    /**
102
     * Set RelationItem for deleted Content.
103
     *
104
     * This is most likely draft creation (version copy) so we just need enough data for toFieldValue() so user is made
105
     * aware of this when saving/publishing draft via API.
106
     *
107
     * @param \DOMElement $relationItem
108
     * @param mixed $id
109
     * @param int $priority
110
     */
111
    private function setDeletedRelationItem(DOMElement $relationItem, $id, $priority)
112
    {
113
        $relationItem->setAttribute('contentobject-id', $id);
114
        $relationItem->setAttribute('priority', $priority);
115
    }
116
117
    /**
118
     * Converts data from $value to $fieldValue.
119
     *
120
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $value
121
     * @param \eZ\Publish\SPI\Persistence\Content\FieldValue $fieldValue
122
     */
123
    public function toFieldValue(StorageFieldValue $value, FieldValue $fieldValue)
124
    {
125
        $fieldValue->data = array('destinationContentIds' => array());
126
        if ($value->dataText === null) {
127
            return;
128
        }
129
130
        $priorityByContentId = array();
131
132
        $dom = new DOMDocument('1.0', 'utf-8');
133
        if ($dom->loadXML($value->dataText) === true) {
134
            foreach ($dom->getElementsByTagName('relation-item') as $relationItem) {
135
                /* @var \DOMElement $relationItem */
136
                $priorityByContentId[$relationItem->getAttribute('contentobject-id')] =
137
                    $relationItem->getAttribute('priority');
138
            }
139
        }
140
141
        asort($priorityByContentId, SORT_NUMERIC);
142
143
        $fieldValue->data['destinationContentIds'] = array_keys($priorityByContentId);
144
    }
145
146
    /**
147
     * Converts field definition data in $fieldDef into $storageFieldDef.
148
     *
149
     * @param \eZ\Publish\SPI\Persistence\Content\Type\FieldDefinition $fieldDef
150
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldDefinition $storageDef
151
     */
152
    public function toStorageFieldDefinition(FieldDefinition $fieldDef, StorageFieldDefinition $storageDef)
153
    {
154
        $fieldSettings = $fieldDef->fieldTypeConstraints->fieldSettings;
155
        $doc = new DOMDocument('1.0', 'utf-8');
156
        $root = $doc->createElement('related-objects');
157
        $doc->appendChild($root);
158
159
        $constraints = $doc->createElement('constraints');
160
        if (!empty($fieldSettings['selectionContentTypes'])) {
161
            foreach ($fieldSettings['selectionContentTypes'] as $typeIdentifier) {
162
                $allowedClass = $doc->createElement('allowed-class');
163
                $allowedClass->setAttribute('contentclass-identifier', $typeIdentifier);
164
                $constraints->appendChild($allowedClass);
165
                unset($allowedClass);
166
            }
167
        }
168
        $root->appendChild($constraints);
169
170
        $type = $doc->createElement('type');
171
        $type->setAttribute('value', 2);//Deprecated advance object relation list type, set since 4.x does
172
        $root->appendChild($type);
173
174
        $objectClass = $doc->createElement('object_class');
175
        $objectClass->setAttribute('value', '');//Deprecated advance object relation class type, set since 4.x does
176
        $root->appendChild($objectClass);
177
178
        $selectionType = $doc->createElement('selection_type');
179 View Code Duplication
        if (isset($fieldSettings['selectionMethod'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
180
            $selectionType->setAttribute('value', (int)$fieldSettings['selectionMethod']);
181
        } else {
182
            $selectionType->setAttribute('value', 0);
183
        }
184
        $root->appendChild($selectionType);
185
186
        $defaultLocation = $doc->createElement('contentobject-placement');
187
        if (!empty($fieldSettings['selectionDefaultLocation'])) {
188
            $defaultLocation->setAttribute('node-id', (int)$fieldSettings['selectionDefaultLocation']);
189
        }
190
        $root->appendChild($defaultLocation);
191
192
        $doc->appendChild($root);
193
        $storageDef->dataText5 = $doc->saveXML();
194
    }
195
196
    /**
197
     * Converts field definition data in $storageDef into $fieldDef.
198
     *
199
     * <?xml version="1.0" encoding="utf-8"?>
200
     * <related-objects>
201
     *   <constraints>
202
     *     <allowed-class contentclass-identifier="blog_post"/>
203
     *   </constraints>
204
     *   <type value="2"/>
205
     *   <selection_type value="1"/>
206
     *   <object_class value=""/>
207
     *   <contentobject-placement node-id="67"/>
208
     * </related-objects>
209
     *
210
     * <?xml version="1.0" encoding="utf-8"?>
211
     * <related-objects>
212
     *   <constraints/>
213
     *   <type value="2"/>
214
     *   <selection_type value="0"/>
215
     *   <object_class value=""/>
216
     *   <contentobject-placement/>
217
     * </related-objects>
218
     *
219
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldDefinition $storageDef
220
     * @param \eZ\Publish\SPI\Persistence\Content\Type\FieldDefinition $fieldDef
221
     */
222
    public function toFieldDefinition(StorageFieldDefinition $storageDef, FieldDefinition $fieldDef)
223
    {
224
        // default settings
225
        $fieldDef->fieldTypeConstraints->fieldSettings = array(
226
            'selectionMethod' => 0,
227
            'selectionDefaultLocation' => null,
228
            'selectionContentTypes' => array(),
229
        );
230
231
        // default value
232
        $fieldDef->defaultValue = new FieldValue();
233
        $fieldDef->defaultValue->data = array('destinationContentIds' => array());
234
235
        if ($storageDef->dataText5 === null) {
236
            return;
237
        }
238
239
        // read settings from storage
240
        $fieldSettings = &$fieldDef->fieldTypeConstraints->fieldSettings;
241
        $dom = new DOMDocument('1.0', 'utf-8');
242
        if ($dom->loadXML($storageDef->dataText5) !== true) {
243
            return;
244
        }
245
246 View Code Duplication
        if ($selectionType = $dom->getElementsByTagName('selection_type')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
247
            $fieldSettings['selectionMethod'] = (int)$selectionType->item(0)->getAttribute('value');
248
        }
249
250
        if (
251
            ($defaultLocation = $dom->getElementsByTagName('contentobject-placement')) &&
252
            $defaultLocation->item(0)->hasAttribute('node-id')
0 ignored issues
show
Bug introduced by
The method hasAttribute() does not exist on DOMNode. Did you maybe mean hasAttributes()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
253
        ) {
254
            $fieldSettings['selectionDefaultLocation'] = (int)$defaultLocation->item(0)->getAttribute('node-id');
255
        }
256
257
        if (!($constraints = $dom->getElementsByTagName('constraints'))) {
258
            return;
259
        }
260
261
        foreach ($constraints->item(0)->getElementsByTagName('allowed-class') as $allowedClass) {
262
            $fieldSettings['selectionContentTypes'][] = $allowedClass->getAttribute('contentclass-identifier');
263
        }
264
    }
265
266
    /**
267
     * Returns the name of the index column in the attribute table.
268
     *
269
     * Returns the name of the index column the datatype uses, which is either
270
     * "sort_key_int" or "sort_key_string". This column is then used for
271
     * filtering and sorting for this type.
272
     *
273
     * @return bool
274
     */
275
    public function getIndexColumn()
276
    {
277
        return 'sort_key_string';
278
    }
279
280
    /**
281
     * @param mixed[] $destinationContentIds
282
     *
283
     * @throws \Exception
284
     *
285
     * @return array
286
     */
287
    protected function getRelationXmlHashFromDB(array $destinationContentIds)
288
    {
289
        if (empty($destinationContentIds)) {
290
            return array();
291
        }
292
293
        $q = $this->db->createSelectQuery();
294
        $q
295
            ->select(
296
                $this->db->aliasedColumn($q, 'id', 'ezcontentobject'),
297
                $this->db->aliasedColumn($q, 'remote_id', 'ezcontentobject'),
298
                $this->db->aliasedColumn($q, 'current_version', 'ezcontentobject'),
299
                $this->db->aliasedColumn($q, 'contentclass_id', 'ezcontentobject'),
300
                $this->db->aliasedColumn($q, 'status', 'ezcontentobject'),
301
                $this->db->aliasedColumn($q, 'node_id', 'ezcontentobject_tree'),
302
                $this->db->aliasedColumn($q, 'parent_node_id', 'ezcontentobject_tree'),
303
                $this->db->aliasedColumn($q, 'identifier', 'ezcontentclass')
304
            )
305
            ->from($this->db->quoteTable('ezcontentobject'))
306
            ->leftJoin(
307
                $this->db->quoteTable('ezcontentobject_tree'),
308
                $q->expr->lAnd(
309
                    $q->expr->eq(
310
                        $this->db->quoteColumn('contentobject_id', 'ezcontentobject_tree'),
311
                        $this->db->quoteColumn('id', 'ezcontentobject')
312
                    ),
313
                    $q->expr->eq(
314
                        $this->db->quoteColumn('node_id', 'ezcontentobject_tree'),
315
                        $this->db->quoteColumn('main_node_id', 'ezcontentobject_tree')
316
                    )
317
                )
318
            )
319
            ->leftJoin(
320
                $this->db->quoteTable('ezcontentclass'),
321
                $q->expr->lAnd(
322
                    $q->expr->eq(
323
                        $this->db->quoteColumn('id', 'ezcontentclass'),
324
                        $this->db->quoteColumn('contentclass_id', 'ezcontentobject')
325
                    ),
326
                    $q->expr->eq(
327
                        $this->db->quoteColumn('version', 'ezcontentclass'),
328
                        $q->bindValue(ContentType::STATUS_DEFINED, null, PDO::PARAM_INT)
329
                    )
330
                )
331
            )
332
            ->where(
333
                $q->expr->in(
334
                    $this->db->quoteColumn('id', 'ezcontentobject'),
335
                    $destinationContentIds
336
                )
337
            );
338
        $stmt = $q->prepare();
339
        $stmt->execute();
340
341
        return $stmt->fetchAll(PDO::FETCH_ASSOC | PDO::FETCH_GROUP);
342
    }
343
344
    /**
345
     * Legacy relevant properties.
346
     *
347
     * These properties are only relevant for legacy database, for improved database engine in 7.0+ they are not.
348
     *
349
     * @return array
350
     */
351
    private static function dbAttributeMap()
352
    {
353
        return array(
354
            // 'identifier' => 'identifier',// not used
355
            'priority' => 'priority',
356
            'in-trash' => 'in_trash',// false by default and implies
357
            'contentobject-id' => 'ezcontentobject_id',
358
            'contentobject-version' => 'ezcontentobject_current_version',
359
            'node-id' => 'ezcontentobject_tree_node_id',
360
            'parent-node-id' => 'ezcontentobject_tree_parent_node_id',
361
            'contentclass-id' => 'ezcontentobject_contentclass_id',
362
            'contentclass-identifier' => 'ezcontentclass_identifier',
363
            // 'is-modified' => 'is_modified',// deprecated and not used
364
            'contentobject-remote-id' => 'ezcontentobject_remote_id',
365
        );
366
    }
367
}
368