Completed
Pull Request — master (#34)
by Bart
05:30
created

Fields::getFieldDefinition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 8
rs 9.4285
ccs 0
cts 5
cp 0
cc 1
eloc 5
nc 1
nop 1
crap 2
1
<?php
2
3
namespace NerdsAndCompany\Schematic\Services;
4
5
use Craft\Craft;
6
use Craft\Exception;
7
use Craft\FieldModel;
8
use Craft\FieldGroupModel;
9
use Craft\FieldLayoutModel;
10
use Craft\ElementType;
11
use NerdsAndCompany\Schematic\Models\FieldFactory;
12
13
/**
14
 * Schematic Fields Service.
15
 *
16
 * Sync Craft Setups.
17
 *
18
 * @author    Nerds & Company
19
 * @copyright Copyright (c) 2015, Nerds & Company
20
 * @license   MIT
21
 *
22
 * @link      http://www.nerds.company
23
 */
24
class Fields extends Base
25
{
26
    /**
27
     * @var FieldModel[]
28
     */
29
    private $fields = [];
30
31
    /**
32
     * @var FieldGroupModel[]
33
     */
34
    private $groups = [];
35
36
    /**
37
     * @var FieldFactory
38
     */
39
    private $fieldFactory;
40
41
    /**
42
     * @return FieldFactory
43
     */
44
    public function getFieldFactory()
45
    {
46
        return isset($this->fieldFactory) ? $this->fieldFactory : new FieldFactory();
47
    }
48
49
    //==============================================================================================================
50
    //===============================================  SERVICES  ===================================================
51
    //==============================================================================================================
52
53
    /**
54
     * Returns fields service.
55
     *
56
     * @return FieldsService
57
     */
58
    private function getFieldsService()
59
    {
60
        return Craft::app()->fields;
61
    }
62
63
    /**
64
     * Returns content service.
65
     *
66
     * @return ContentService
67
     */
68
    private function getContentService()
69
    {
70
        return Craft::app()->content;
71
    }
72
73
    //==============================================================================================================
74
    //================================================  EXPORT  ====================================================
75
    //==============================================================================================================
76
77
    /**
78
     * Export fields.
79
     *
80
     * @param FieldGroupModel[] $groups
81
     *
82
     * @return array
83
     */
84
    public function export(array $groups = [])
85
    {
86
        Craft::log(Craft::t('Exporting Fields'));
87
88
        $groupDefinitions = [];
89
90
        foreach ($groups as $group) {
91
            $fieldDefinitions = [];
92
93
            foreach ($group->getFields() as $field) {
94
                $fieldDefinitions[$field->handle] = $this->getFieldDefinition($field);
95
            }
96
97
            $groupDefinitions[$group->name] = $fieldDefinitions;
98
        }
99
100
        return $groupDefinitions;
101
    }
102
103
    /**
104
     * Get field definition.
105
     *
106
     * @param FieldModel $field
107
     *
108
     * @return array
109
     */
110
    private function getFieldDefinition(FieldModel $field)
111
    {
112
        $fieldFactory = $this->getFieldFactory();
113
        $schematicFieldModel = $fieldFactory->build($field->type);
114
        $definition = $schematicFieldModel->getDefinition($field, true);
115
116
        return $definition;
117
    }
118
119
    //==============================================================================================================
120
    //================================================  IMPORT  ====================================================
121
    //==============================================================================================================
122
123
    /**
124
     * Attempt to import fields.
125
     *
126
     * @param array $groupDefinitions
127
     * @param bool  $force            if set to true items not in the import will be deleted
128
     *
129
     * @return Result
130
     */
131
    public function import(array $groupDefinitions, $force = false)
132
    {
133
        Craft::log(Craft::t('Importing Fields'));
134
135
        if (!empty($groupDefinitions)) {
136
            $contentService = $this->getContentService();
137
138
            $contentService->fieldContext = 'global';
139
            $contentService->contentTable = 'content';
140
141
            $this->resetCraftFieldsServiceCache();
142
            $this->groups = $this->getFieldsService()->getAllGroups('name');
143
            $this->fields = $this->getFieldsService()->getAllFields('handle');
144
145
            foreach ($groupDefinitions as $name => $fieldDefinitions) {
146
                try {
147
                    $this->beginTransaction();
148
149
                    $group = $this->createFieldGroupModel($name);
150
151
                    $this->importFields($fieldDefinitions, $group);
152
153
                    $this->commitTransaction();
154
                } catch (\Exception $e) {
155
                    $this->rollbackTransaction();
156
157
                    $this->addError($e->getMessage());
158
                }
159
160
                $this->unsetData($name, $fieldDefinitions);
161
            }
162
163
            if ($force) { // Remove not imported data
164
                $this->deleteFieldsAndGroups();
165
            }
166
        }
167
168
        return $this->getResultModel();
169
    }
170
171
    /**
172
     * Save field group.
173
     *
174
     * @param FieldGroupModel $group
175
     *
176
     * @throws Exception
177
     */
178
    private function saveFieldGroupModel(FieldGroupModel $group)
179
    {
180
        if (!$this->getFieldsService()->saveGroup($group)) {
181
            $this->addErrors($group->getAllErrors());
182
183
            throw new Exception('Failed to save group');
184
        }
185
    }
186
187
    /**
188
     * Save field.
189
     *
190
     * @param FieldModel $field
191
     *
192
     * @throws \Exception
193
     */
194
    private function saveFieldModel(FieldModel $field)
195
    {
196
        $this->validateFieldModel($field); // Validate field
197
        if (!$this->getFieldsService()->saveField($field)) {
198
            $this->addErrors($field->getAllErrors());
199
200
            throw new Exception('Failed to save field');
201
        }
202
    }
203
204
    /**
205
     * Removes fields that where not imported.
206
     */
207
    private function deleteFields()
208
    {
209
        $fieldsService = $this->getFieldsService();
210
        foreach ($this->fields as $field) {
211
            $fieldsService->deleteFieldById($field->id);
212
        }
213
    }
214
215
    /**
216
     * Removes groups that where not imported.
217
     */
218
    private function deleteGroups()
219
    {
220
        $fieldsService = $this->getFieldsService();
221
        foreach ($this->groups as $group) {
222
            $fieldsService->deleteGroupById($group->id);
223
        }
224
    }
225
226
    /**
227
     * Removes fields and groups that where not imported.
228
     */
229
    private function deleteFieldsAndGroups()
230
    {
231
        $this->deleteFields();
232
        $this->deleteGroups();
233
    }
234
235
    /**
236
     * Creates new or updates existing group model.
237
     *
238
     * @param string $group
239
     *
240
     * @return FieldGroupModel
241
     */
242
    private function createFieldGroupModel($group)
243
    {
244
        $groupModel = (array_key_exists($group, $this->groups) ? $this->groups[$group] : new FieldGroupModel());
245
        $groupModel->name = $group;
246
247
        $this->saveFieldGroupModel($groupModel);
248
249
        return $groupModel;
250
    }
251
252
    /**
253
     * @param string $field
254
     *
255
     * @return FieldModel
256
     */
257
    private function getFieldModel($field)
258
    {
259
        return (array_key_exists($field, $this->fields) ? $this->fields[$field] : new FieldModel());
260
    }
261
262
    /**
263
     * Validates field type, throw error when it's incorrect.
264
     *
265
     * @param FieldModel $field
266
     *
267
     * @throws \Exception
268
     */
269
    private function validateFieldModel(FieldModel $field)
270
    {
271
        if (!$field->getFieldType()) {
272
            $fieldType = $field->type;
273
            ($fieldType == 'Matrix')
274
                ? $this->addError("One of the field's types does not exist. Are you missing a plugin?")
275
                : $this->addError("Field type '$fieldType' does not exist. Are you missing a plugin?");
276
277
            throw new Exception('Failed to save field');
278
        }
279
    }
280
281
    /**
282
     * Import field group fields.
283
     *
284
     * @param array           $fieldDefinitions
285
     * @param FieldGroupModel $group
286
     *
287
     * @throws \Exception
288
     */
289
    private function importFields(array $fieldDefinitions, FieldGroupModel $group)
290
    {
291
        $fieldFactory = $this->getFieldFactory();
292
293
        foreach ($fieldDefinitions as $fieldHandle => $fieldDef) {
294
            $field = $this->getFieldModel($fieldHandle);
295
            $schematicFieldModel = $fieldFactory->build($fieldDef['type']);
296
297
            if ($schematicFieldModel->getDefinition($field, true) === $fieldDef ) {
298
                Craft::log(Craft::t('Skipping `{name}`, no changes detected', ['name' => $field->name]));
299
                continue;
300
            }
301
302
            Craft::log(Craft::t('Importing `{name}`', ['name' => $fieldDef['name']]));
303
304
            $schematicFieldModel->populate($fieldDef, $field, $fieldHandle, $group);
305
            $this->saveFieldModel($field);
306
        }
307
    }
308
309
    /**
310
     * Unset group and field data else $force flag will delete it.
311
     *
312
     * @param string $name
313
     * @param array  $definitions
314
     */
315
    private function unsetData($name, array $definitions)
316
    {
317
        if (array_key_exists($name, $this->groups)) {
318
            unset($this->groups[$name]);
319
            foreach ($definitions as $handle => $definition) {
320
                unset($this->fields[$handle]);
321
            }
322
        }
323
    }
324
325
    //==============================================================================================================
326
    //=============================================  FIELD LAYOUT  =================================================
327
    //==============================================================================================================
328
329
    /**
330
     * Get field layout definition.
331
     *
332
     * @param FieldLayoutModel $fieldLayout
333
     *
334
     * @return array
335
     */
336
    public function getFieldLayoutDefinition(FieldLayoutModel $fieldLayout)
337
    {
338
        if ($fieldLayout->getTabs()) {
339
            $tabDefinitions = [];
340
341
            foreach ($fieldLayout->getTabs() as $tab) {
342
                $tabDefinitions[$tab->name] = $this->getFieldLayoutFieldsDefinition($tab->getFields());
343
            }
344
345
            return ['tabs' => $tabDefinitions];
346
        }
347
348
        return ['fields' => $this->getFieldLayoutFieldsDefinition($fieldLayout->getFields())];
349
    }
350
351
    /**
352
     * Get field layout fields definition.
353
     *
354
     * @param FieldLayoutFieldModel[] $fields
355
     *
356
     * @return array
357
     */
358
    private function getFieldLayoutFieldsDefinition(array $fields)
359
    {
360
        $fieldDefinitions = [];
361
362
        foreach ($fields as $field) {
363
            $fieldDefinitions[$field->getField()->handle] = $field->required;
364
        }
365
366
        return $fieldDefinitions;
367
    }
368
369
    /**
370
     * Attempt to import a field layout.
371
     *
372
     * @param array $fieldLayoutDef
373
     *
374
     * @return FieldLayoutModel
375
     */
376
    public function getFieldLayout(array $fieldLayoutDef)
377
    {
378
        $layoutFields = [];
379
        $requiredFields = [];
380
381
        if (array_key_exists('tabs', $fieldLayoutDef)) {
382
            foreach ($fieldLayoutDef['tabs'] as $tabName => $tabDef) {
383
                $layoutTabFields = $this->getPrepareFieldLayout($tabDef);
384
                $requiredFields = array_merge($requiredFields, $layoutTabFields['required']);
385
                $layoutFields[$tabName] = $layoutTabFields['fields'];
386
            }
387
        } elseif (array_key_exists('fields', $fieldLayoutDef)) {
388
            $layoutTabFields = $this->getPrepareFieldLayout($fieldLayoutDef);
389
            $requiredFields = $layoutTabFields['required'];
390
            $layoutFields = $layoutTabFields['fields'];
391
        }
392
393
        $fieldLayout = Craft::app()->fields->assembleLayout($layoutFields, $requiredFields);
394
        $fieldLayout->type = ElementType::Entry;
395
396
        return $fieldLayout;
397
    }
398
399
    /**
400
     * Get a prepared fieldLayout for the craft assembleLayout function.
401
     *
402
     * @param array $fieldLayoutDef
403
     *
404
     * @return array
405
     */
406
    private function getPrepareFieldLayout(array $fieldLayoutDef)
407
    {
408
        $layoutFields = [];
409
        $requiredFields = [];
410
411
        foreach ($fieldLayoutDef as $fieldHandle => $required) {
412
            $field = Craft::app()->fields->getFieldByHandle($fieldHandle);
413
414
            if ($field instanceof FieldModel) {
0 ignored issues
show
Bug introduced by
The class Craft\FieldModel does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
415
                $layoutFields[] = $field->id;
416
417
                if ($required) {
418
                    $requiredFields[] = $field->id;
419
                }
420
            }
421
        }
422
423
        return [
424
            'fields' => $layoutFields,
425
            'required' => $requiredFields,
426
        ];
427
    }
428
429
    /**
430
     * Reset craft fields service cache using reflection
431
     */
432
    private function resetCraftFieldsServiceCache()
433
    {
434
        $obj         = $this->getFieldsService();
435
        $refObject   = new \ReflectionObject( $obj );
436
        $refProperty = $refObject->getProperty( '_fetchedAllGroups' );
437
        $refProperty->setAccessible( true );
438
        $refProperty->setValue($obj, false);
439
    }
440
}
441