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-2016, Nerds & Company |
18
|
|
|
* @license MIT |
19
|
|
|
* |
20
|
|
|
* @link 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
|
|
View Code Duplication |
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
|
|
|
$sections = Craft::app()->sections->getAllSections('handle'); |
153
|
|
|
|
154
|
|
|
foreach ($sectionDefinitions as $sectionHandle => $sectionDefinition) { |
155
|
|
|
$section = array_key_exists($sectionHandle, $sections) |
156
|
|
|
? $sections[$sectionHandle] |
157
|
|
|
: new SectionModel(); |
158
|
|
|
|
159
|
|
|
unset($sections[$sectionHandle]); |
160
|
|
|
|
161
|
|
|
if($sectionDefinition === $this->getSectionDefinition($section, null)){ |
162
|
|
|
Craft::log(Craft::t('Skipping `{name}`, no changes detected', ['name' => $section->name])); |
163
|
|
|
continue; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
if (!array_key_exists('locales', $sectionDefinition)) { |
167
|
|
|
$this->addError('`sections[handle].locales` must be defined'); |
168
|
|
|
|
169
|
|
|
continue; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
if (!array_key_exists('entryTypes', $sectionDefinition)) { |
173
|
|
|
$this->addError('errors', '`sections[handle].entryTypes` must exist be defined'); |
174
|
|
|
|
175
|
|
|
continue; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
Craft::log(Craft::t('Importing section `{name}`', ['name' => $sectionDefinition['name']])); |
179
|
|
|
|
180
|
|
|
$this->populateSection($section, $sectionDefinition, $sectionHandle); |
181
|
|
|
$this->resetCraftFieldsSectionModelCache($section); |
182
|
|
|
|
183
|
|
|
// Create initial section record |
184
|
|
|
if (!$this->preSaveSection($section)) { |
185
|
|
|
$this->addErrors($section->getAllErrors()); |
186
|
|
|
|
187
|
|
|
continue; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
$this->importEntryTypes($section, $sectionDefinition['entryTypes'], $force); |
191
|
|
|
|
192
|
|
|
// Save section via craft after entrytypes have been created |
193
|
|
|
if (!Craft::app()->sections->saveSection($section)) { |
194
|
|
|
$this->addErrors($section->getAllErrors()); |
195
|
|
|
} |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
if ($force) { |
199
|
|
|
foreach ($sections as $section) { |
200
|
|
|
Craft::app()->sections->deleteSectionById($section->id); |
201
|
|
|
} |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
return $this->getResultModel(); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* @param SectionModel $section |
209
|
|
|
* @param array $entryTypeDefinitions |
210
|
|
|
* @param bool $force |
211
|
|
|
*/ |
212
|
|
|
private function importEntryTypes(SectionModel $section, array $entryTypeDefinitions, $force) |
213
|
|
|
{ |
214
|
|
|
$entryTypes = Craft::app()->sections->getEntryTypesBySectionId($section->id, 'handle'); |
215
|
|
|
|
216
|
|
|
foreach ($entryTypeDefinitions as $entryTypeHandle => $entryTypeDefinition) { |
217
|
|
|
$entryType = array_key_exists($entryTypeHandle, $entryTypes) |
218
|
|
|
? $entryTypes[$entryTypeHandle] |
219
|
|
|
: new EntryTypeModel(); |
220
|
|
|
|
221
|
|
|
unset($entryTypes[$entryTypeHandle]); |
222
|
|
|
|
223
|
|
|
$this->populateEntryType($entryType, $entryTypeDefinition, $entryTypeHandle, $section->id); |
224
|
|
|
|
225
|
|
|
if (!Craft::app()->sections->saveEntryType($entryType)) { |
226
|
|
|
$this->addError($entryType->getAllErrors()); |
227
|
|
|
|
228
|
|
|
continue; |
229
|
|
|
} |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
if ($force) { |
233
|
|
|
foreach ($entryTypes as $entryType) { |
234
|
|
|
Craft::app()->sections->deleteEntryTypeById($entryType->id); |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* Save the section manually if it is new to prevent craft from creating the default entry type |
241
|
|
|
* In case of a single we do want the default entry type and do a normal save |
242
|
|
|
* Todo: This method is a bit hackish, find a better way. |
243
|
|
|
* |
244
|
|
|
* @param SectionModel $section |
245
|
|
|
* |
246
|
|
|
* @return mixed |
247
|
|
|
*/ |
248
|
|
|
private function preSaveSection(SectionModel $section) |
249
|
|
|
{ |
250
|
|
|
if ($section->type != 'single' && !$section->id) { |
251
|
|
|
$sectionRecord = new SectionRecord(); |
252
|
|
|
|
253
|
|
|
// Shared attributes |
254
|
|
|
$sectionRecord->name = $section->name; |
255
|
|
|
$sectionRecord->handle = $section->handle; |
256
|
|
|
$sectionRecord->type = $section->type; |
257
|
|
|
$sectionRecord->enableVersioning = $section->enableVersioning; |
258
|
|
|
|
259
|
|
|
if (!$sectionRecord->save()) { |
260
|
|
|
$section->addErrors(['errors' => $sectionRecord->getErrors()]); |
261
|
|
|
|
262
|
|
|
return false; |
263
|
|
|
}; |
264
|
|
|
$section->id = $sectionRecord->id; |
265
|
|
|
|
266
|
|
|
return true; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
return Craft::app()->sections->saveSection($section); |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* Populate section. |
274
|
|
|
* |
275
|
|
|
* @param SectionModel $section |
276
|
|
|
* @param array $sectionDefinition |
277
|
|
|
* @param string $sectionHandle |
278
|
|
|
*/ |
279
|
|
|
private function populateSection(SectionModel $section, array $sectionDefinition, $sectionHandle) |
280
|
|
|
{ |
281
|
|
|
$section->setAttributes([ |
282
|
|
|
'handle' => $sectionHandle, |
283
|
|
|
'name' => $sectionDefinition['name'], |
284
|
|
|
'type' => $sectionDefinition['type'], |
285
|
|
|
'hasUrls' => $sectionDefinition['hasUrls'], |
286
|
|
|
'template' => $sectionDefinition['template'], |
287
|
|
|
'maxLevels' => $sectionDefinition['maxLevels'], |
288
|
|
|
'enableVersioning' => $sectionDefinition['enableVersioning'], |
289
|
|
|
]); |
290
|
|
|
|
291
|
|
|
$this->populateSectionLocales($section, $sectionDefinition['locales']); |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* Populate section locales. |
296
|
|
|
* |
297
|
|
|
* @param SectionModel $section |
298
|
|
|
* @param $localeDefinitions |
299
|
|
|
*/ |
300
|
|
View Code Duplication |
private function populateSectionLocales(SectionModel $section, $localeDefinitions) |
|
|
|
|
301
|
|
|
{ |
302
|
|
|
$locales = $section->getLocales(); |
303
|
|
|
|
304
|
|
|
foreach ($localeDefinitions as $localeId => $localeDef) { |
305
|
|
|
$locale = array_key_exists($localeId, $locales) ? $locales[$localeId] : new SectionLocaleModel(); |
306
|
|
|
|
307
|
|
|
$locale->setAttributes([ |
308
|
|
|
'locale' => $localeId, |
309
|
|
|
'enabledByDefault' => $localeDef['enabledByDefault'], |
310
|
|
|
'urlFormat' => $localeDef['urlFormat'], |
311
|
|
|
'nestedUrlFormat' => $localeDef['nestedUrlFormat'], |
312
|
|
|
]); |
313
|
|
|
|
314
|
|
|
// Todo: Is this a hack? I don't see another way. |
315
|
|
|
// Todo: Might need a sorting order as well? It's NULL at the moment. |
316
|
|
|
Craft::app()->db->createCommand()->insertOrUpdate('locales', [ |
317
|
|
|
'locale' => $locale->locale, |
318
|
|
|
], []); |
319
|
|
|
|
320
|
|
|
$locales[$localeId] = $locale; |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
$section->setLocales($locales); |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* Populate entry type. |
328
|
|
|
* |
329
|
|
|
* @param EntryTypeModel $entryType |
330
|
|
|
* @param array $entryTypeDefinition |
331
|
|
|
* @param string $entryTypeHandle |
332
|
|
|
* @param int $sectionId |
333
|
|
|
*/ |
334
|
|
View Code Duplication |
private function populateEntryType(EntryTypeModel $entryType, array $entryTypeDefinition, $entryTypeHandle, $sectionId) |
|
|
|
|
335
|
|
|
{ |
336
|
|
|
$entryType->setAttributes([ |
337
|
|
|
'handle' => $entryTypeHandle, |
338
|
|
|
'sectionId' => $sectionId, |
339
|
|
|
'name' => $entryTypeDefinition['name'], |
340
|
|
|
'hasTitleField' => $entryTypeDefinition['hasTitleField'], |
341
|
|
|
'titleLabel' => $entryTypeDefinition['titleLabel'], |
342
|
|
|
'titleFormat' => $entryTypeDefinition['titleFormat'], |
343
|
|
|
]); |
344
|
|
|
|
345
|
|
|
$fieldLayout = Craft::app()->schematic_fields->getFieldLayout($entryTypeDefinition['fieldLayout']); |
346
|
|
|
$entryType->setFieldLayout($fieldLayout); |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* Reset craft section model cache using reflection |
351
|
|
|
* @param SectionModel $section |
352
|
|
|
*/ |
353
|
|
View Code Duplication |
private function resetCraftFieldsSectionModelCache(SectionModel $section) |
|
|
|
|
354
|
|
|
{ |
355
|
|
|
$obj = $section; |
356
|
|
|
$refObject = new \ReflectionObject( $obj ); |
357
|
|
|
$refProperty = $refObject->getProperty( '_entryTypes' ); |
358
|
|
|
$refProperty->setAccessible( true ); |
359
|
|
|
$refProperty->setValue($obj, null); |
360
|
|
|
} |
361
|
|
|
} |
362
|
|
|
|
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.