Completed
Push — 7.0 ( dcb9ac...8de687 )
by Łukasz
13:07
created

RelationListConverter   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 337
Duplicated Lines 5.93 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
dl 20
loc 337
rs 9.6
c 0
b 0
f 0
wmc 32
lcom 1
cbo 7

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B toStorageValue() 0 37 5
B toFieldValue() 0 22 4
B toStorageFieldDefinition() 8 52 6
C toFieldDefinition() 12 61 12
A getIndexColumn() 0 4 1
A getRelationXmlHashFromDB() 0 55 2
A dbAttributeMap() 0 16 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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
        $validators = $fieldDef->fieldTypeConstraints->validators;
121
        $doc = new DOMDocument('1.0', 'utf-8');
122
        $root = $doc->createElement('related-objects');
123
        $doc->appendChild($root);
124
125
        $constraints = $doc->createElement('constraints');
126 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...
127
            foreach ($fieldSettings['selectionContentTypes'] as $typeIdentifier) {
128
                $allowedClass = $doc->createElement('allowed-class');
129
                $allowedClass->setAttribute('contentclass-identifier', $typeIdentifier);
130
                $constraints->appendChild($allowedClass);
131
                unset($allowedClass);
132
            }
133
        }
134
        $root->appendChild($constraints);
135
136
        $type = $doc->createElement('type');
137
        $type->setAttribute('value', 2); //Deprecated advance object relation list type, set since 4.x does
138
        $root->appendChild($type);
139
140
        $objectClass = $doc->createElement('object_class');
141
        $objectClass->setAttribute('value', ''); //Deprecated advance object relation class type, set since 4.x does
142
        $root->appendChild($objectClass);
143
144
        $selectionType = $doc->createElement('selection_type');
145
        if (isset($fieldSettings['selectionMethod'])) {
146
            $selectionType->setAttribute('value', (int)$fieldSettings['selectionMethod']);
147
        } else {
148
            $selectionType->setAttribute('value', 0);
149
        }
150
        $root->appendChild($selectionType);
151
152
        $defaultLocation = $doc->createElement('contentobject-placement');
153
        if (!empty($fieldSettings['selectionDefaultLocation'])) {
154
            $defaultLocation->setAttribute('node-id', (int)$fieldSettings['selectionDefaultLocation']);
155
        }
156
        $root->appendChild($defaultLocation);
157
158
        $selectionLimit = $doc->createElement('selection_limit');
159
        if (isset($validators['RelationListValueValidator']['selectionLimit'])) {
160
            $selectionLimit->setAttribute('value', (int)$validators['RelationListValueValidator']['selectionLimit']);
161
        } else {
162
            $selectionLimit->setAttribute('value', 0);
163
        }
164
        $root->appendChild($selectionLimit);
165
166
        $doc->appendChild($root);
167
        $storageDef->dataText5 = $doc->saveXML();
168
    }
169
170
    /**
171
     * Converts field definition data in $storageDef into $fieldDef.
172
     *
173
     * <code>
174
     *   <?xml version="1.0" encoding="utf-8"?>
175
     *   <related-objects>
176
     *     <constraints>
177
     *       <allowed-class contentclass-identifier="blog_post"/>
178
     *     </constraints>
179
     *     <type value="2"/>
180
     *     <selection_type value="1"/>
181
     *     <selection_limit value="5"/>
182
     *     <object_class value=""/>
183
     *     <contentobject-placement node-id="67"/>
184
     *   </related-objects>
185
     *
186
     *   <?xml version="1.0" encoding="utf-8"?>
187
     *   <related-objects>
188
     *     <constraints/>
189
     *     <type value="2"/>
190
     *     <selection_type value="0"/>
191
     *     <object_class value=""/>
192
     *     <contentobject-placement/>
193
     *   </related-objects>
194
     * </code>
195
     *
196
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldDefinition $storageDef
197
     * @param \eZ\Publish\SPI\Persistence\Content\Type\FieldDefinition $fieldDef
198
     */
199
    public function toFieldDefinition(StorageFieldDefinition $storageDef, FieldDefinition $fieldDef)
200
    {
201
        // default settings
202
        $fieldDef->fieldTypeConstraints->fieldSettings = [
203
            'selectionMethod' => 0,
204
            'selectionDefaultLocation' => null,
205
            'selectionContentTypes' => [],
206
        ];
207
208
        $fieldDef->fieldTypeConstraints->validators = [
209
            'RelationListValueValidator' => [
210
                'selectionLimit' => 0,
211
            ],
212
        ];
213
214
        // default value
215
        $fieldDef->defaultValue = new FieldValue();
216
        $fieldDef->defaultValue->data = array('destinationContentIds' => array());
217
218
        if ($storageDef->dataText5 === null) {
219
            return;
220
        }
221
222
        $dom = new DOMDocument('1.0', 'utf-8');
223
        if (empty($storageDef->dataText5) || $dom->loadXML($storageDef->dataText5) !== true) {
224
            return;
225
        }
226
227
        // read settings from storage
228
        $fieldSettings = &$fieldDef->fieldTypeConstraints->fieldSettings;
229 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...
230
            ($selectionType = $dom->getElementsByTagName('selection_type')->item(0)) &&
231
            $selectionType->hasAttribute('value')
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...
232
        ) {
233
            $fieldSettings['selectionMethod'] = (int)$selectionType->getAttribute('value');
234
        }
235
236 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...
237
            ($defaultLocation = $dom->getElementsByTagName('contentobject-placement')->item(0)) &&
238
            $defaultLocation->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...
239
        ) {
240
            $fieldSettings['selectionDefaultLocation'] = (int)$defaultLocation->getAttribute('node-id');
241
        }
242
243
        if (!($constraints = $dom->getElementsByTagName('constraints'))) {
244
            return;
245
        }
246
247
        foreach ($constraints->item(0)->getElementsByTagName('allowed-class') as $allowedClass) {
248
            $fieldSettings['selectionContentTypes'][] = $allowedClass->getAttribute('contentclass-identifier');
249
        }
250
251
        // read validators configuration from storage
252
        $validators = &$fieldDef->fieldTypeConstraints->validators;
253
        if (
254
            ($selectionLimit = $dom->getElementsByTagName('selection_limit')->item(0)) &&
255
            $selectionLimit->hasAttribute('value')
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...
256
        ) {
257
            $validators['RelationListValueValidator']['selectionLimit'] = (int)$selectionLimit->getAttribute('value');
258
        }
259
    }
260
261
    /**
262
     * Returns the name of the index column in the attribute table.
263
     *
264
     * Returns the name of the index column the datatype uses, which is either
265
     * "sort_key_int" or "sort_key_string". This column is then used for
266
     * filtering and sorting for this type.
267
     *
268
     * @return string
269
     */
270
    public function getIndexColumn()
271
    {
272
        return 'sort_key_string';
273
    }
274
275
    /**
276
     * @param mixed[] $destinationContentIds
277
     *
278
     * @throws \Exception
279
     *
280
     * @return array
281
     */
282
    protected function getRelationXmlHashFromDB(array $destinationContentIds)
283
    {
284
        if (empty($destinationContentIds)) {
285
            return array();
286
        }
287
288
        $q = $this->db->createSelectQuery();
289
        $q
290
            ->select(
291
                $this->db->aliasedColumn($q, 'id', 'ezcontentobject'),
292
                $this->db->aliasedColumn($q, 'remote_id', 'ezcontentobject'),
293
                $this->db->aliasedColumn($q, 'current_version', 'ezcontentobject'),
294
                $this->db->aliasedColumn($q, 'contentclass_id', 'ezcontentobject'),
295
                $this->db->aliasedColumn($q, 'node_id', 'ezcontentobject_tree'),
296
                $this->db->aliasedColumn($q, 'parent_node_id', 'ezcontentobject_tree'),
297
                $this->db->aliasedColumn($q, 'identifier', 'ezcontentclass')
298
            )
299
            ->from($this->db->quoteTable('ezcontentobject'))
300
            ->leftJoin(
301
                $this->db->quoteTable('ezcontentobject_tree'),
302
                $q->expr->lAnd(
303
                    $q->expr->eq(
304
                        $this->db->quoteColumn('contentobject_id', 'ezcontentobject_tree'),
305
                        $this->db->quoteColumn('id', 'ezcontentobject')
306
                    ),
307
                    $q->expr->eq(
308
                        $this->db->quoteColumn('node_id', 'ezcontentobject_tree'),
309
                        $this->db->quoteColumn('main_node_id', 'ezcontentobject_tree')
310
                    )
311
                )
312
            )
313
            ->leftJoin(
314
                $this->db->quoteTable('ezcontentclass'),
315
                $q->expr->lAnd(
316
                    $q->expr->eq(
317
                        $this->db->quoteColumn('id', 'ezcontentclass'),
318
                        $this->db->quoteColumn('contentclass_id', 'ezcontentobject')
319
                    ),
320
                    $q->expr->eq(
321
                        $this->db->quoteColumn('version', 'ezcontentclass'),
322
                        $q->bindValue(ContentType::STATUS_DEFINED, null, PDO::PARAM_INT)
323
                    )
324
                )
325
            )
326
            ->where(
327
                $q->expr->in(
328
                    $this->db->quoteColumn('id', 'ezcontentobject'),
329
                    $destinationContentIds
330
                )
331
            );
332
        $stmt = $q->prepare();
333
        $stmt->execute();
334
335
        return $stmt->fetchAll(PDO::FETCH_ASSOC | PDO::FETCH_GROUP);
336
    }
337
338
    /**
339
     * @return array
340
     */
341
    private static function dbAttributeMap()
342
    {
343
        return array(
344
            // 'identifier' => 'identifier',// not used
345
            'priority' => 'priority',
346
            // 'in-trash' => 'in_trash',// false by default and implies
347
            'contentobject-id' => 'ezcontentobject_id',
348
            'contentobject-version' => 'ezcontentobject_current_version',
349
            'node-id' => 'ezcontentobject_tree_node_id',
350
            'parent-node-id' => 'ezcontentobject_tree_parent_node_id',
351
            'contentclass-id' => 'ezcontentobject_contentclass_id',
352
            'contentclass-identifier' => 'ezcontentclass_identifier',
353
            // 'is-modified' => 'is_modified',// deprecated and not used
354
            'contentobject-remote-id' => 'ezcontentobject_remote_id',
355
        );
356
    }
357
}
358