Completed
Push — master ( 40a52b...0ced19 )
by André
28:02 queued 13:06
created

RelationListConverter::getIndexColumn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
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
namespace eZ\Publish\Core\Persistence\Legacy\Content\FieldValue\Converter;
10
11
use eZ\Publish\Core\Persistence\Legacy\Content\FieldValue\Converter;
12
use eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue;
13
use eZ\Publish\SPI\Persistence\Content\FieldValue;
14
use eZ\Publish\SPI\Persistence\Content\Type as ContentType;
15
use eZ\Publish\SPI\Persistence\Content\Type\FieldDefinition;
16
use eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldDefinition;
17
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
18
use DOMDocument;
19
use PDO;
20
21
class RelationListConverter implements Converter
22
{
23
    /**
24
     * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler
25
     */
26
    private $db;
27
28
    /**
29
     * Create instance of RelationList converter.
30
     *
31
     * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $db
32
     */
33
    public function __construct(DatabaseHandler $db)
34
    {
35
        $this->db = $db;
36
    }
37
38
    /**
39
     * Converts data from $value to $storageFieldValue.
40
     *
41
     * @param \eZ\Publish\SPI\Persistence\Content\FieldValue $value
42
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $storageFieldValue
43
     */
44
    public function toStorageValue(FieldValue $value, StorageFieldValue $storageFieldValue)
45
    {
46
        $doc = new DOMDocument('1.0', 'utf-8');
47
        $root = $doc->createElement('related-objects');
48
        $doc->appendChild($root);
49
50
        $relationList = $doc->createElement('relation-list');
51
        $data = $this->getRelationXmlHashFromDB($value->data['destinationContentIds']);
52
        $priority = 0;
53
54
        foreach ($value->data['destinationContentIds'] as $id) {
55
            if (!isset($data[$id][0])) {
56
                // Ignore deleted content items (we can't throw as it would block ContentService->createContentDraft())
57
                continue;
58
            }
59
            $row = $data[$id][0];
60
            $row['ezcontentobject_id'] = $id;
61
            $row['priority'] = ($priority += 1);
62
63
            $relationItem = $doc->createElement('relation-item');
64
            foreach (self::dbAttributeMap() as $domAttrKey => $propertyKey) {
65
                if (!isset($row[$propertyKey])) {
66
                    // left join data missing, ignore the given attribute (content in trash missing location)
67
                    continue;
68
                }
69
70
                $relationItem->setAttribute($domAttrKey, $row[$propertyKey]);
71
            }
72
            $relationList->appendChild($relationItem);
73
            unset($relationItem);
74
        }
75
76
        $root->appendChild($relationList);
77
        $doc->appendChild($root);
78
79
        $storageFieldValue->dataText = $doc->saveXML();
80
    }
81
82
    /**
83
     * Converts data from $value to $fieldValue.
84
     *
85
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue $value
86
     * @param \eZ\Publish\SPI\Persistence\Content\FieldValue $fieldValue
87
     */
88
    public function toFieldValue(StorageFieldValue $value, FieldValue $fieldValue)
89
    {
90
        $fieldValue->data = array('destinationContentIds' => array());
91
        if ($value->dataText === null) {
92
            return;
93
        }
94
95
        $priorityByContentId = array();
96
97
        $dom = new DOMDocument('1.0', 'utf-8');
98
        if ($dom->loadXML($value->dataText) === true) {
99
            foreach ($dom->getElementsByTagName('relation-item') as $relationItem) {
100
                /* @var \DOMElement $relationItem */
101
                $priorityByContentId[$relationItem->getAttribute('contentobject-id')] =
102
                    $relationItem->getAttribute('priority');
103
            }
104
        }
105
106
        asort($priorityByContentId, SORT_NUMERIC);
107
108
        $fieldValue->data['destinationContentIds'] = array_keys($priorityByContentId);
109
    }
110
111
    /**
112
     * Converts field definition data in $fieldDef into $storageFieldDef.
113
     *
114
     * @param \eZ\Publish\SPI\Persistence\Content\Type\FieldDefinition $fieldDef
115
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldDefinition $storageDef
116
     */
117
    public function toStorageFieldDefinition(FieldDefinition $fieldDef, StorageFieldDefinition $storageDef)
118
    {
119
        $fieldSettings = $fieldDef->fieldTypeConstraints->fieldSettings;
120
        $doc = new DOMDocument('1.0', 'utf-8');
121
        $root = $doc->createElement('related-objects');
122
        $doc->appendChild($root);
123
124
        $constraints = $doc->createElement('constraints');
125 View Code Duplication
        if (!empty($fieldSettings['selectionContentTypes'])) {
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...
126
            foreach ($fieldSettings['selectionContentTypes'] as $typeIdentifier) {
127
                $allowedClass = $doc->createElement('allowed-class');
128
                $allowedClass->setAttribute('contentclass-identifier', $typeIdentifier);
129
                $constraints->appendChild($allowedClass);
130
                unset($allowedClass);
131
            }
132
        }
133
        $root->appendChild($constraints);
134
135
        $type = $doc->createElement('type');
136
        $type->setAttribute('value', 2); //Deprecated advance object relation list type, set since 4.x does
137
        $root->appendChild($type);
138
139
        $objectClass = $doc->createElement('object_class');
140
        $objectClass->setAttribute('value', ''); //Deprecated advance object relation class type, set since 4.x does
141
        $root->appendChild($objectClass);
142
143
        $selectionType = $doc->createElement('selection_type');
144 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...
145
            $selectionType->setAttribute('value', (int)$fieldSettings['selectionMethod']);
146
        } else {
147
            $selectionType->setAttribute('value', 0);
148
        }
149
        $root->appendChild($selectionType);
150
151
        $defaultLocation = $doc->createElement('contentobject-placement');
152
        if (!empty($fieldSettings['selectionDefaultLocation'])) {
153
            $defaultLocation->setAttribute('node-id', (int)$fieldSettings['selectionDefaultLocation']);
154
        }
155
        $root->appendChild($defaultLocation);
156
157
        $doc->appendChild($root);
158
        $storageDef->dataText5 = $doc->saveXML();
159
    }
160
161
    /**
162
     * Converts field definition data in $storageDef into $fieldDef.
163
     *
164
     * <code>
165
     *   <?xml version="1.0" encoding="utf-8"?>
166
     *   <related-objects>
167
     *     <constraints>
168
     *       <allowed-class contentclass-identifier="blog_post"/>
169
     *     </constraints>
170
     *     <type value="2"/>
171
     *     <selection_type value="1"/>
172
     *     <object_class value=""/>
173
     *     <contentobject-placement node-id="67"/>
174
     *   </related-objects>
175
     *
176
     *   <?xml version="1.0" encoding="utf-8"?>
177
     *   <related-objects>
178
     *     <constraints/>
179
     *     <type value="2"/>
180
     *     <selection_type value="0"/>
181
     *     <object_class value=""/>
182
     *     <contentobject-placement/>
183
     *   </related-objects>
184
     * </code>
185
     *
186
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldDefinition $storageDef
187
     * @param \eZ\Publish\SPI\Persistence\Content\Type\FieldDefinition $fieldDef
188
     */
189
    public function toFieldDefinition(StorageFieldDefinition $storageDef, FieldDefinition $fieldDef)
190
    {
191
        // default settings
192
        $fieldDef->fieldTypeConstraints->fieldSettings = [
193
            'selectionMethod' => 0,
194
            'selectionDefaultLocation' => null,
195
            'selectionContentTypes' => [],
196
        ];
197
198
        // default value
199
        $fieldDef->defaultValue = new FieldValue();
200
        $fieldDef->defaultValue->data = array('destinationContentIds' => array());
201
202
        if ($storageDef->dataText5 === null) {
203
            return;
204
        }
205
206
        // read settings from storage
207
        $fieldSettings = &$fieldDef->fieldTypeConstraints->fieldSettings;
208
        $dom = new DOMDocument('1.0', 'utf-8');
209
        if (empty($storageDef->dataText5) || $dom->loadXML($storageDef->dataText5) !== true) {
210
            return;
211
        }
212
213 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...
214
            $fieldSettings['selectionMethod'] = (int)$selectionType->item(0)->getAttribute('value');
215
        }
216
217 View Code Duplication
        if (
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...
218
            ($defaultLocation = $dom->getElementsByTagName('contentobject-placement')) &&
219
            $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...
220
        ) {
221
            $fieldSettings['selectionDefaultLocation'] = (int)$defaultLocation->item(0)->getAttribute('node-id');
222
        }
223
224
        if (!($constraints = $dom->getElementsByTagName('constraints'))) {
225
            return;
226
        }
227
228
        foreach ($constraints->item(0)->getElementsByTagName('allowed-class') as $allowedClass) {
229
            $fieldSettings['selectionContentTypes'][] = $allowedClass->getAttribute('contentclass-identifier');
230
        }
231
    }
232
233
    /**
234
     * Returns the name of the index column in the attribute table.
235
     *
236
     * Returns the name of the index column the datatype uses, which is either
237
     * "sort_key_int" or "sort_key_string". This column is then used for
238
     * filtering and sorting for this type.
239
     *
240
     * @return string
241
     */
242
    public function getIndexColumn()
243
    {
244
        return 'sort_key_string';
245
    }
246
247
    /**
248
     * @param mixed[] $destinationContentIds
249
     *
250
     * @throws \Exception
251
     *
252
     * @return array
253
     */
254
    protected function getRelationXmlHashFromDB(array $destinationContentIds)
255
    {
256
        if (empty($destinationContentIds)) {
257
            return array();
258
        }
259
260
        $q = $this->db->createSelectQuery();
261
        $q
262
            ->select(
263
                $this->db->aliasedColumn($q, 'id', 'ezcontentobject'),
264
                $this->db->aliasedColumn($q, 'remote_id', 'ezcontentobject'),
265
                $this->db->aliasedColumn($q, 'current_version', 'ezcontentobject'),
266
                $this->db->aliasedColumn($q, 'contentclass_id', 'ezcontentobject'),
267
                $this->db->aliasedColumn($q, 'node_id', 'ezcontentobject_tree'),
268
                $this->db->aliasedColumn($q, 'parent_node_id', 'ezcontentobject_tree'),
269
                $this->db->aliasedColumn($q, 'identifier', 'ezcontentclass')
270
            )
271
            ->from($this->db->quoteTable('ezcontentobject'))
272
            ->leftJoin(
273
                $this->db->quoteTable('ezcontentobject_tree'),
274
                $q->expr->lAnd(
275
                    $q->expr->eq(
276
                        $this->db->quoteColumn('contentobject_id', 'ezcontentobject_tree'),
277
                        $this->db->quoteColumn('id', 'ezcontentobject')
278
                    ),
279
                    $q->expr->eq(
280
                        $this->db->quoteColumn('node_id', 'ezcontentobject_tree'),
281
                        $this->db->quoteColumn('main_node_id', 'ezcontentobject_tree')
282
                    )
283
                )
284
            )
285
            ->leftJoin(
286
                $this->db->quoteTable('ezcontentclass'),
287
                $q->expr->lAnd(
288
                    $q->expr->eq(
289
                        $this->db->quoteColumn('id', 'ezcontentclass'),
290
                        $this->db->quoteColumn('contentclass_id', 'ezcontentobject')
291
                    ),
292
                    $q->expr->eq(
293
                        $this->db->quoteColumn('version', 'ezcontentclass'),
294
                        $q->bindValue(ContentType::STATUS_DEFINED, null, PDO::PARAM_INT)
295
                    )
296
                )
297
            )
298
            ->where(
299
                $q->expr->in(
300
                    $this->db->quoteColumn('id', 'ezcontentobject'),
301
                    $destinationContentIds
302
                )
303
            );
304
        $stmt = $q->prepare();
305
        $stmt->execute();
306
307
        return $stmt->fetchAll(PDO::FETCH_ASSOC | PDO::FETCH_GROUP);
308
    }
309
310
    /**
311
     * @return array
312
     */
313
    private static function dbAttributeMap()
314
    {
315
        return array(
316
            // 'identifier' => 'identifier',// not used
317
            'priority' => 'priority',
318
            // 'in-trash' => 'in_trash',// false by default and implies
319
            'contentobject-id' => 'ezcontentobject_id',
320
            'contentobject-version' => 'ezcontentobject_current_version',
321
            'node-id' => 'ezcontentobject_tree_node_id',
322
            'parent-node-id' => 'ezcontentobject_tree_parent_node_id',
323
            'contentclass-id' => 'ezcontentobject_contentclass_id',
324
            'contentclass-identifier' => 'ezcontentclass_identifier',
325
            // 'is-modified' => 'is_modified',// deprecated and not used
326
            'contentobject-remote-id' => 'ezcontentobject_remote_id',
327
        );
328
    }
329
}
330