Completed
Pull Request — master (#113)
by Bob Olde
02:32
created

Sections::import()   C

Complexity

Conditions 10
Paths 26

Size

Total Lines 59
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
dl 0
loc 59
ccs 0
cts 34
cp 0
rs 6.5919
c 0
b 0
f 0
cc 10
eloc 31
nc 26
nop 2
crap 110

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace NerdsAndCompany\Schematic\Services;
4
5
use Craft\Craft;
6
use Craft\SectionRecord;
7
use Craft\SectionModel;
8
use Craft\SectionLocaleModel;
9
use Craft\EntryTypeModel;
10
11
/**
12
 * Schematic Result Model.
13
 *
14
 * Sync Craft Setups.
15
 *
16
 * @author    Nerds & Company
17
 * @copyright Copyright (c) 2015-2017, Nerds & Company
18
 * @license   MIT
19
 *
20
 * @see      http://www.nerds.company
21
 */
22
class Sections extends Base
23
{
24
    /**
25
     * Export sections.
26
     *
27
     * @param SectionModel[] $sections
28
     * @param array|null     $allowedEntryTypeIds
29
     *
30
     * @return array
31
     */
32
    public function export(array $sections = [], array $allowedEntryTypeIds = null)
33
    {
34
        Craft::log(Craft::t('Exporting Sections'));
35
36
        $sectionDefinitions = [];
37
38
        foreach ($sections as $section) {
39
            $sectionDefinitions[$section->handle] = $this->getSectionDefinition($section, $allowedEntryTypeIds);
40
        }
41
42
        return $sectionDefinitions;
43
    }
44
45
    /**
46
     * Get section definition.
47
     *
48
     * @param SectionModel $section
49
     * @param $allowedEntryTypeIds
50
     *
51
     * @return array
52
     */
53
    private function getSectionDefinition(SectionModel $section, $allowedEntryTypeIds)
54
    {
55
        return [
56
            'name' => $section->name,
57
            'type' => $section->type,
58
            'hasUrls' => $section->hasUrls,
59
            'template' => $section->template,
60
            'maxLevels' => $section->maxLevels,
61
            'enableVersioning' => $section->enableVersioning,
62
            'locales' => $this->getLocaleDefinitions($section->getLocales()),
63
            'entryTypes' => $this->getEntryTypeDefinitions($section->getEntryTypes(), $allowedEntryTypeIds),
64
        ];
65
    }
66
67
    /**
68
     * Get locale definitions.
69
     *
70
     * @param SectionLocaleModel[] $locales
71
     *
72
     * @return array
73
     */
74
    private function getLocaleDefinitions(array $locales)
75
    {
76
        $localeDefinitions = [];
77
78
        foreach ($locales as $locale) {
79
            $localeDefinitions[$locale->locale] = $this->getLocaleDefinition($locale);
80
        }
81
82
        return $localeDefinitions;
83
    }
84
85
    /**
86
     * Get locale definition.
87
     *
88
     * @param SectionLocaleModel $locale
89
     *
90
     * @return array
91
     */
92
    private function getLocaleDefinition(SectionLocaleModel $locale)
93
    {
94
        return [
95
            'enabledByDefault' => $locale->enabledByDefault,
96
            'urlFormat' => $locale->urlFormat,
97
            'nestedUrlFormat' => $locale->nestedUrlFormat,
98
        ];
99
    }
100
101
    /**
102
     * Get entry type definitions.
103
     *
104
     * @param array $entryTypes
105
     * @param $allowedEntryTypeIds
106
     *
107
     * @return array
108
     */
109
    private function getEntryTypeDefinitions(array $entryTypes, $allowedEntryTypeIds)
110
    {
111
        $entryTypeDefinitions = [];
112
113
        foreach ($entryTypes as $entryType) {
114
            if ($allowedEntryTypeIds === null || in_array($entryType->id, $allowedEntryTypeIds)) {
115
                $entryTypeDefinitions[$entryType->handle] = $this->getEntryTypeDefinition($entryType);
116
            }
117
        }
118
119
        return $entryTypeDefinitions;
120
    }
121
122
    /**
123
     * Get entry type definition.
124
     *
125
     * @param EntryTypeModel $entryType
126
     *
127
     * @return array
128
     */
129
    private function getEntryTypeDefinition(EntryTypeModel $entryType)
130
    {
131
        return [
132
            'name' => $entryType->name,
133
            'hasTitleField' => $entryType->hasTitleField,
134
            'titleLabel' => $entryType->titleLabel,
135
            'titleFormat' => $entryType->titleFormat,
136
            'fieldLayout' => Craft::app()->schematic_fields->getFieldLayoutDefinition($entryType->getFieldLayout()),
137
        ];
138
    }
139
140
    /**
141
     * Attempt to import sections.
142
     *
143
     * @param array $sectionDefinitions
144
     * @param bool  $force              If set to true sections not included in the import will be deleted
145
     *
146
     * @return Result
147
     */
148
    public function import(array $sectionDefinitions, $force = false)
149
    {
150
        Craft::log(Craft::t('Importing Sections'));
151
152
        $this->resetCraftSectionsServiceCache();
153
        $sections = Craft::app()->sections->getAllSections('handle');
154
155
        foreach ($sectionDefinitions as $sectionHandle => $sectionDefinition) {
156
            $section = array_key_exists($sectionHandle, $sections)
157
                ? $sections[$sectionHandle]
158
                : new SectionModel();
159
160
            unset($sections[$sectionHandle]);
161
162
            if ($sectionDefinition === $this->getSectionDefinition($section, null)) {
163
                Craft::log(Craft::t('Skipping `{name}`, no changes detected', ['name' => $section->name]));
164
                continue;
165
            }
166
167
            if (!array_key_exists('locales', $sectionDefinition)) {
168
                $this->addError('`sections[handle].locales` must be defined');
169
170
                continue;
171
            }
172
173
            if (!array_key_exists('entryTypes', $sectionDefinition)) {
174
                $this->addError('errors', '`sections[handle].entryTypes` must exist be defined');
175
176
                continue;
177
            }
178
179
            Craft::log(Craft::t('Importing section `{name}`', ['name' => $sectionDefinition['name']]));
180
181
            $this->populateSection($section, $sectionDefinition, $sectionHandle);
182
            $this->resetCraftFieldsSectionModelCache($section);
183
184
            // Create initial section record
185
            if (!$this->preSaveSection($section)) {
186
                $this->addErrors($section->getAllErrors());
187
188
                continue;
189
            }
190
191
            $this->importEntryTypes($section, $sectionDefinition['entryTypes'], $force);
192
193
            // Save section via craft after entrytypes have been created
194
            if (!Craft::app()->sections->saveSection($section)) {
195
                $this->addErrors($section->getAllErrors());
196
            }
197
        }
198
199
        if ($force) {
200
            foreach ($sections as $section) {
201
                Craft::app()->sections->deleteSectionById($section->id);
202
            }
203
        }
204
205
        return $this->getResultModel();
206
    }
207
208
    /**
209
     * @param SectionModel $section
210
     * @param array        $entryTypeDefinitions
211
     * @param bool         $force
212
     */
213
    private function importEntryTypes(SectionModel $section, array $entryTypeDefinitions, $force)
214
    {
215
        $entryTypes = Craft::app()->sections->getEntryTypesBySectionId($section->id, 'handle');
216
217
        foreach ($entryTypeDefinitions as $entryTypeHandle => $entryTypeDefinition) {
218
            $entryType = array_key_exists($entryTypeHandle, $entryTypes)
219
                ? $entryTypes[$entryTypeHandle]
220
                : new EntryTypeModel();
221
222
            unset($entryTypes[$entryTypeHandle]);
223
224
            $this->populateEntryType($entryType, $entryTypeDefinition, $entryTypeHandle, $section->id);
225
226
            if (!Craft::app()->sections->saveEntryType($entryType)) {
227
                $this->addError($entryType->getAllErrors());
228
229
                continue;
230
            }
231
        }
232
233
        if ($force) {
234
            foreach ($entryTypes as $entryType) {
235
                Craft::app()->sections->deleteEntryTypeById($entryType->id);
236
            }
237
        }
238
    }
239
240
    /**
241
     * Save the section manually if it is new to prevent craft from creating the default entry type
242
     * In case of a single we do want the default entry type and do a normal save
243
     * Todo: This method is a bit hackish, find a better way.
244
     *
245
     * @param SectionModel $section
246
     *
247
     * @return mixed
248
     */
249
    private function preSaveSection(SectionModel $section)
250
    {
251
        if ($section->type != 'single' && !$section->id) {
252
            $sectionRecord = new SectionRecord();
253
254
            // Shared attributes
255
            $sectionRecord->name = $section->name;
256
            $sectionRecord->handle = $section->handle;
257
            $sectionRecord->type = $section->type;
258
            $sectionRecord->enableVersioning = $section->enableVersioning;
259
260
            if (!$sectionRecord->save()) {
261
                $section->addErrors(['errors' => $sectionRecord->getErrors()]);
262
263
                return false;
264
            }
265
            $section->id = $sectionRecord->id;
266
267
            return true;
268
        }
269
270
        return Craft::app()->sections->saveSection($section);
271
    }
272
273
    /**
274
     * Populate section.
275
     *
276
     * @param SectionModel $section
277
     * @param array        $sectionDefinition
278
     * @param string       $sectionHandle
279
     */
280
    private function populateSection(SectionModel $section, array $sectionDefinition, $sectionHandle)
281
    {
282
        $section->setAttributes([
283
            'handle' => $sectionHandle,
284
            'name' => $sectionDefinition['name'],
285
            'type' => $sectionDefinition['type'],
286
            'hasUrls' => $sectionDefinition['hasUrls'],
287
            'template' => $sectionDefinition['template'],
288
            'maxLevels' => $sectionDefinition['maxLevels'],
289
            'enableVersioning' => $sectionDefinition['enableVersioning'],
290
        ]);
291
292
        $this->populateSectionLocales($section, $sectionDefinition['locales']);
293
    }
294
295
    /**
296
     * Populate section locales.
297
     *
298
     * @param SectionModel $section
299
     * @param $localeDefinitions
300
     */
301
    private function populateSectionLocales(SectionModel $section, $localeDefinitions)
302
    {
303
        $locales = $section->getLocales();
304
305
        foreach ($localeDefinitions as $localeId => $localeDef) {
306
            $locale = array_key_exists($localeId, $locales) ? $locales[$localeId] : new SectionLocaleModel();
307
308
            $locale->setAttributes([
309
                'locale' => $localeId,
310
                'enabledByDefault' => $localeDef['enabledByDefault'],
311
                'urlFormat' => $localeDef['urlFormat'],
312
                'nestedUrlFormat' => $localeDef['nestedUrlFormat'],
313
            ]);
314
315
            // Todo: Is this a hack? I don't see another way.
316
            // Todo: Might need a sorting order as well? It's NULL at the moment.
317
            Craft::app()->db->createCommand()->insertOrUpdate('locales', [
318
                'locale' => $locale->locale,
319
            ], []);
320
321
            $locales[$localeId] = $locale;
322
        }
323
324
        $section->setLocales($locales);
325
    }
326
327
    /**
328
     * Populate entry type.
329
     *
330
     * @param EntryTypeModel $entryType
331
     * @param array          $entryTypeDefinition
332
     * @param string         $entryTypeHandle
333
     * @param int            $sectionId
334
     */
335
    private function populateEntryType(EntryTypeModel $entryType, array $entryTypeDefinition, $entryTypeHandle, $sectionId)
336
    {
337
        $entryType->setAttributes([
338
            'handle' => $entryTypeHandle,
339
            'sectionId' => $sectionId,
340
            'name' => $entryTypeDefinition['name'],
341
            'hasTitleField' => $entryTypeDefinition['hasTitleField'],
342
            'titleLabel' => $entryTypeDefinition['titleLabel'],
343
            'titleFormat' => $entryTypeDefinition['titleFormat'],
344
        ]);
345
346
        $fieldLayout = Craft::app()->schematic_fields->getFieldLayout($entryTypeDefinition['fieldLayout']);
347
        $entryType->setFieldLayout($fieldLayout);
348
    }
349
350
    /**
351
     * Reset craft sections service cache using reflection.
352
     */
353
    private function resetCraftSectionsServiceCache()
354
    {
355
        $obj = Craft::app()->sections;
356
        $refObject = new \ReflectionObject($obj);
357
        if ($refObject->hasProperty('_fetchedAllSections')) {
358
            $refProperty = $refObject->getProperty('_fetchedAllSections');
359
            $refProperty->setAccessible(true);
360
            $refProperty->setValue($obj, false);
361
        }
362
        if ($refObject->hasProperty('_sectionsById')) {
363
            $refProperty = $refObject->getProperty('_sectionsById');
364
            $refProperty->setAccessible(true);
365
            $refProperty->setValue($obj, array());
366
        }
367
    }
368
369
    /**
370
     * Reset craft section model cache using reflection.
371
     *
372
     * @param SectionModel $section
373
     */
374
    private function resetCraftFieldsSectionModelCache(SectionModel $section)
375
    {
376
        $obj = $section;
377
        $refObject = new \ReflectionObject($obj);
378
        $refProperty = $refObject->getProperty('_entryTypes');
379
        $refProperty->setAccessible(true);
380
        $refProperty->setValue($obj, null);
381
    }
382
}
383