Completed
Push — main ( 512e28...d7fef9 )
by Dante
23s queued 15s
created

Import::translatedFields()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
c 1
b 0
f 0
dl 0
loc 16
rs 9.6111
cc 5
nc 5
nop 1
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * BEdita, API-first content management framework
6
 * Copyright 2023 Atlas Srl, Chialab Srl
7
 *
8
 * This file is part of BEdita: you can redistribute it and/or modify
9
 * it under the terms of the GNU Lesser General Public License as published
10
 * by the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
14
 */
15
16
namespace BEdita\ImportTools\Utility;
17
18
use BEdita\Core\Model\Entity\ObjectEntity;
19
use BEdita\Core\Model\Entity\Translation;
20
use Cake\Http\Exception\BadRequestException;
21
use Cake\Log\LogTrait;
22
use Cake\ORM\Locator\LocatorAwareTrait;
23
use Cake\Utility\Hash;
24
25
class Import
26
{
27
    use CsvTrait;
28
    use LocatorAwareTrait;
29
    use LogTrait;
30
    use TreeTrait;
31
32
    /**
33
     * @inheritDoc
34
     */
35
    protected $_defaultConfig = [
36
        'defaults' => [
37
            'status' => 'on',
38
        ],
39
        'csv' => [
40
            'delimiter' => ',',
41
            'enclosure' => '"',
42
            'escape' => '"',
43
        ],
44
    ];
45
46
    /**
47
     * Dry run mode flag
48
     *
49
     * @var bool
50
     */
51
    public bool $dryrun = false;
52
53
    /**
54
     * Full filename path
55
     *
56
     * @var string|null
57
     */
58
    public ?string $filename = '';
59
60
    /**
61
     * Parent uname or ID
62
     *
63
     * @var string|null
64
     */
65
    public ?string $parent = '';
66
67
    /**
68
     * Number of processed entities
69
     *
70
     * @var int
71
     */
72
    public int $processed = 0;
73
74
    /**
75
     * Number of saved entities
76
     *
77
     * @var int
78
     */
79
    public int $saved = 0;
80
81
    /**
82
     * Number of errors
83
     *
84
     * @var int
85
     */
86
    public int $errors = 0;
87
88
    /**
89
     * Errors details
90
     *
91
     * @var array
92
     */
93
    public array $errorsDetails = [];
94
95
    /**
96
     * Number of skipped
97
     *
98
     * @var int
99
     */
100
    public int $skipped = 0;
101
102
    /**
103
     * Entity type
104
     *
105
     * @var string
106
     */
107
    public string $type = '';
108
109
    /**
110
     * Objects table
111
     *
112
     * @var \BEdita\Core\Model\Table\ObjectsTable
113
     */
114
    protected $objectsTable;
115
116
    /**
117
     * Type table
118
     *
119
     * @var \BEdita\Core\Model\Table\ObjectsTable
120
     */
121
    protected $typeTable;
122
123
    /**
124
     * Translations table
125
     *
126
     * @var \BEdita\Core\Model\Table\TranslationsTable
127
     */
128
    protected $translationsTable;
129
130
    /**
131
     * Constructor
132
     *
133
     * @param string|null $filename Full filename path
134
     * @param string|null $type Entity type
135
     * @param string|null $parent Parent uname or ID
136
     * @param bool|null $dryrun Dry run mode flag
137
     * @return void
138
     */
139
    public function __construct(
140
        ?string $filename = null,
141
        ?string $type = 'objects',
142
        ?string $parent = null,
143
        ?bool $dryrun = false
144
    ) {
145
        $this->filename = $filename;
146
        $this->type = $type;
147
        $this->parent = $parent;
148
        $this->dryrun = $dryrun;
149
        $this->processed = 0;
150
        $this->saved = 0;
151
        $this->errors = 0;
152
        $this->skipped = 0;
153
        $this->errorsDetails = [];
154
        /** @var \BEdita\Core\Model\Table\ObjectsTable $objectsTable */
155
        $objectsTable = $this->fetchTable('objects');
156
        $this->objectsTable = $objectsTable;
157
        /** @var \BEdita\Core\Model\Table\ObjectsTable $typesTable */
158
        $typesTable = $this->fetchTable($this->type);
159
        $this->typeTable = $typesTable;
160
        /** @var \BEdita\Core\Model\Table\TranslationsTable $translationsTable */
161
        $translationsTable = $this->fetchTable('translations');
162
        $this->translationsTable = $translationsTable;
163
    }
164
165
    /**
166
     * Save objects
167
     *
168
     * @return void
169
     */
170
    public function saveObjects(): void
171
    {
172
        foreach ($this->readCsv($this->filename) as $obj) {
173
            try {
174
                $this->saveObject($obj);
175
            } catch (\Exception $e) {
176
                $this->errorsDetails[] = $e->getMessage();
177
                $this->errors++;
178
            } finally {
179
                $this->processed++;
180
            }
181
        }
182
    }
183
184
    /**
185
     * Save object
186
     *
187
     * @param array $obj Object data
188
     * @return \BEdita\Core\Model\Entity\ObjectEntity
189
     */
190
    public function saveObject(array $obj): ObjectEntity
191
    {
192
        $entity = $this->typeTable->newEmptyEntity();
193
        if (!empty($obj['uname'])) {
194
            $uname = $obj['uname'];
195
            if ($this->objectsTable->exists(compact('uname'))) {
196
                /** @var \BEdita\Core\Model\Entity\ObjectEntity $o */
197
                $o = $this->objectsTable->find()->where(compact('uname'))->firstOrFail();
198
                if ($o->type !== $this->type) {
199
                    throw new BadRequestException(
200
                        sprintf('Object uname "%s" already present with another type "%s"', $uname, $o->type)
201
                    );
202
                }
203
                $entity = $this->typeTable->get($this->typeTable->getId($uname));
204
            }
205
        }
206
        $entity = $this->typeTable->patchEntity($entity, $obj);
207
        $entity->set('type', $this->type);
208
        if ($this->dryrun === true) {
209
            $this->skipped++;
210
211
            return $entity;
212
        }
213
        $this->typeTable->saveOrFail($entity);
214
        if (!empty($this->parent)) {
215
            $this->setParent($entity, $this->parent);
216
        }
217
        $this->saved++;
218
219
        return $entity;
220
    }
221
222
    /**
223
     * Save translations
224
     *
225
     * @return void
226
     */
227
    public function saveTranslations(): void
228
    {
229
        foreach ($this->readCsv($this->filename) as $translation) {
230
            try {
231
                $this->saveTranslation($translation);
232
            } catch (\Exception $e) {
233
                $this->errorsDetails[] = $e->getMessage();
234
                $this->errors++;
235
            } finally {
236
                $this->processed++;
237
            }
238
        }
239
    }
240
241
    /**
242
     * Save translation
243
     *
244
     * @param array $data Translation data
245
     * @return \BEdita\Core\Model\Entity\Translation
246
     * @throws \Cake\Http\Exception\BadRequestException
247
     */
248
    public function saveTranslation(array $data): Translation
249
    {
250
        $uname = (string)Hash::get($data, 'object_uname');
251
        if (!$this->objectsTable->exists(compact('uname'))) {
252
            throw new BadRequestException(sprintf('Object "%s" not found', $uname));
253
        }
254
        /** @var \BEdita\Core\Model\Entity\ObjectEntity $o */
255
        $o = $this->objectsTable->find()->where(compact('uname'))->firstOrFail();
256
        $objectId = $o->id;
257
        /** @var \BEdita\Core\Model\Entity\Translation $entity */
258
        $entity = $this->translationsTable->find()
259
            ->where([
260
                'object_id' => $objectId,
261
                'lang' => $data['lang'],
262
            ])
263
            ->first();
264
        $translation = [
265
            'object_id' => $objectId,
266
        ];
267
        if ($entity != null) {
268
            $entity = $this->translationsTable->patchEntity($entity, $translation);
269
        } else {
270
            $entity = $this->translationsTable->newEntity($translation);
271
        }
272
        $entity->set('translated_fields', $this->translatedFields($data));
273
        $entity->set('status', $this->getConfig('defaults')['status']);
274
        $entity->set('lang', $data['lang']);
275
        if ($this->dryrun === true) {
276
            $this->skipped++;
277
278
            return $entity;
279
        }
280
        $this->translationsTable->saveOrFail($entity);
281
        $this->saved++;
282
283
        return $entity;
284
    }
285
286
    /**
287
     * Get translated fields
288
     *
289
     * @param array $source Source data
290
     * @return array
291
     */
292
    public function translatedFields(array $source): array
293
    {
294
        $fields = (string)Hash::get($source, 'translated_fields');
295
        if (!empty($fields)) {
296
            return json_decode($fields, true);
297
        }
298
        $fields = [];
299
        foreach ($source as $key => $value) {
300
            if (in_array($key, ['id', 'object_uname', 'lang'])) {
301
                continue;
302
            }
303
            $subkey = strpos($key, 'translation_') === 0 ? substr($key, 12) : $key;
304
            $fields[$subkey] = $value;
305
        }
306
307
        return $fields;
308
    }
309
}
310