1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Backend\Modules\Pages\Engine; |
4
|
|
|
|
5
|
|
|
use Backend\Core\Engine\Authentication as BackendAuthentication; |
6
|
|
|
use Backend\Core\Engine\Model as BackendModel; |
7
|
|
|
use Backend\Core\Language\Language as BL; |
8
|
|
|
use Backend\Core\Language\Locale; |
9
|
|
|
use Backend\Modules\Extensions\Engine\Model as BackendExtensionsModel; |
10
|
|
|
use Backend\Modules\Pages\Domain\ModuleExtra\ModuleExtraRepository; |
11
|
|
|
use Backend\Modules\Pages\Domain\Page\Page; |
12
|
|
|
use Backend\Modules\Pages\Domain\Page\PageRepository; |
13
|
|
|
use Backend\Modules\Pages\Domain\Page\Status; |
14
|
|
|
use Backend\Modules\Pages\Domain\Page\Type; |
15
|
|
|
use Backend\Modules\Pages\Domain\PageBlock\PageBlock; |
16
|
|
|
use Backend\Modules\Pages\Domain\PageBlock\PageBlockRepository; |
17
|
|
|
use Backend\Modules\Pages\Domain\PageBlock\Type as PageBlockType; |
18
|
|
|
use Backend\Modules\Tags\Engine\Model as BackendTagsModel; |
19
|
|
|
use Common\Doctrine\Entity\Meta; |
20
|
|
|
use Common\Doctrine\Repository\MetaRepository; |
21
|
|
|
use Common\Locale as AbstractLocale; |
22
|
|
|
use InvalidArgumentException; |
23
|
|
|
use RuntimeException; |
24
|
|
|
use Symfony\Component\Filesystem\Filesystem; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* In this file we store all generic functions that we will be using in the PagesModule |
28
|
|
|
*/ |
29
|
|
|
class Model |
30
|
|
|
{ |
31
|
|
|
const TYPE_OF_DROP_BEFORE = 'before'; |
32
|
|
|
const TYPE_OF_DROP_AFTER = 'after'; |
33
|
|
|
const TYPE_OF_DROP_INSIDE = 'inside'; |
34
|
|
|
const POSSIBLE_TYPES_OF_DROP = [ |
35
|
|
|
self::TYPE_OF_DROP_BEFORE, |
36
|
|
|
self::TYPE_OF_DROP_AFTER, |
37
|
|
|
self::TYPE_OF_DROP_INSIDE, |
38
|
|
|
]; |
39
|
|
|
|
40
|
|
|
const QUERY_BROWSE_RECENT = |
41
|
|
|
'SELECT i.id, i.title, UNIX_TIMESTAMP(i.edited_on) AS edited_on, i.user_id |
42
|
|
|
FROM PagesPage AS i |
43
|
|
|
WHERE i.status = ? AND i.locale = ? |
44
|
|
|
ORDER BY i.edited_on DESC |
45
|
|
|
LIMIT ?'; |
46
|
|
|
|
47
|
|
|
const QUERY_DATAGRID_BROWSE_DRAFTS = |
48
|
|
|
'SELECT i.id, i.revision_id, i.title, UNIX_TIMESTAMP(i.edited_on) AS edited_on, i.user_id |
49
|
|
|
FROM PagesPage AS i |
50
|
|
|
INNER JOIN |
51
|
|
|
( |
52
|
|
|
SELECT MAX(i.revision_id) AS revision_id |
53
|
|
|
FROM PagesPage AS i |
54
|
|
|
WHERE i.status = ? AND i.user_id = ? AND i.locale = ? |
55
|
|
|
GROUP BY i.id |
56
|
|
|
) AS p |
57
|
|
|
WHERE i.revision_id = p.revision_id'; |
58
|
|
|
|
59
|
|
|
public static function getCacheBuilder(): CacheBuilder |
60
|
|
|
{ |
61
|
|
|
static $cacheBuilder = null; |
62
|
|
|
if ($cacheBuilder === null) { |
63
|
|
|
$cacheBuilder = new CacheBuilder( |
64
|
|
|
BackendModel::get('database'), |
|
|
|
|
65
|
|
|
BackendModel::get('cache.pool'), |
|
|
|
|
66
|
|
|
BackendModel::get(ModuleExtraRepository::class) |
|
|
|
|
67
|
|
|
); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
return $cacheBuilder; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
public static function buildCache(AbstractLocale $locale = null): void |
74
|
|
|
{ |
75
|
|
|
$cacheBuilder = static::getCacheBuilder(); |
76
|
|
|
$cacheBuilder->buildCache($locale ?? Locale::workingLocale()); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @param int $id The id of the page to delete. |
81
|
|
|
* @param Locale $locale The locale wherein the page will be deleted, |
82
|
|
|
* if not provided we will use the working locale. |
83
|
|
|
* @param int $revisionId If specified the given revision will be deleted, used for deleting drafts. |
84
|
|
|
* |
85
|
|
|
* @return bool |
86
|
|
|
*/ |
87
|
|
|
public static function delete(int $id, Locale $locale = null, int $revisionId = null): bool |
88
|
|
|
{ |
89
|
|
|
/** @var PageRepository $pageRepository */ |
90
|
|
|
$pageRepository = BackendModel::get(PageRepository::class); |
91
|
|
|
|
92
|
|
|
/** @var MetaRepository $metaRepository */ |
93
|
|
|
$metaRepository = BackendModel::get('fork.repository.meta'); |
94
|
|
|
|
95
|
|
|
$locale = $locale ?? Locale::workingLocale(); |
96
|
|
|
|
97
|
|
|
// get record |
98
|
|
|
$page = self::get($id, $revisionId, $locale); |
99
|
|
|
|
100
|
|
|
// validate |
101
|
|
|
if (empty($page)) { |
102
|
|
|
return false; |
103
|
|
|
} |
104
|
|
|
if (!$page['allow_delete']) { |
105
|
|
|
return false; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
// get revision ids |
109
|
|
|
$pages = $pageRepository->findBy(['id' => $id, 'locale' => $locale]); |
110
|
|
|
|
111
|
|
|
$revisionIDs = array_map( |
112
|
|
|
function (Page $page) { |
113
|
|
|
return $page->getRevisionId(); |
114
|
|
|
}, |
115
|
|
|
$pages |
116
|
|
|
); |
117
|
|
|
|
118
|
|
|
// delete meta records |
119
|
|
|
foreach ($pages as $page) { |
120
|
|
|
$metaRepository->remove($page->getMeta()); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
// delete blocks and their revisions |
124
|
|
|
if (!empty($revisionIDs)) { |
125
|
|
|
/** @var PageBlockRepository $pageBlockRepository */ |
126
|
|
|
$pageBlockRepository = BackendModel::get(PageBlockRepository::class); |
127
|
|
|
$pageBlockRepository->deleteByRevisionIds($revisionIDs); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
// delete page and the revisions |
131
|
|
|
if (!empty($revisionIDs)) { |
132
|
|
|
$pageRepository->deleteByRevisionIds($revisionIDs); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
// delete tags |
136
|
|
|
BackendTagsModel::saveTags($id, '', 'Pages'); |
137
|
|
|
|
138
|
|
|
// return |
139
|
|
|
return true; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
public static function exists(int $pageId): bool |
143
|
|
|
{ |
144
|
|
|
/** @var PageRepository $pageRepository */ |
145
|
|
|
$pageRepository = BackendModel::get(PageRepository::class); |
146
|
|
|
$page = $pageRepository->findOneBy( |
147
|
|
|
[ |
148
|
|
|
'id' => $pageId, |
149
|
|
|
'locale' => Locale::workingLocale(), |
150
|
|
|
'status' => [Status::active(), Status::draft()], |
151
|
|
|
] |
152
|
|
|
); |
153
|
|
|
|
154
|
|
|
return $page instanceof Page; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* Get the data for a record |
159
|
|
|
* |
160
|
|
|
* @param int $pageId The Id of the page to fetch. |
161
|
|
|
* @param int $revisionId |
162
|
|
|
* @param Locale $locale The locale to use while fetching the page. |
163
|
|
|
* |
164
|
|
|
* @return mixed False if the record can't be found, otherwise an array with all data. |
165
|
|
|
*/ |
166
|
|
|
public static function get(int $pageId, int $revisionId = null, Locale $locale = null) |
167
|
|
|
{ |
168
|
|
|
// fetch revision if not specified |
169
|
|
|
if ($revisionId === null) { |
170
|
|
|
$revisionId = self::getLatestRevision($pageId, $locale); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
// redefine |
174
|
|
|
$locale = $locale ?? Locale::workingLocale(); |
175
|
|
|
|
176
|
|
|
/** @var PageRepository $pageRepository */ |
177
|
|
|
$pageRepository = BackendModel::get(PageRepository::class); |
178
|
|
|
$page = $pageRepository->getOne($pageId, $revisionId, $locale); |
179
|
|
|
|
180
|
|
|
// no page? |
181
|
|
|
if ($page === null) { |
182
|
|
|
return false; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
return $page; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
public static function isForbiddenToDelete(int $pageId): bool |
189
|
|
|
{ |
190
|
|
|
return in_array($pageId, [Page::HOME_PAGE_ID, Page::ERROR_PAGE_ID], true); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
public static function isForbiddenToMove(int $pageId): bool |
194
|
|
|
{ |
195
|
|
|
return in_array($pageId, [Page::HOME_PAGE_ID, Page::ERROR_PAGE_ID], true); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
public static function isForbiddenToHaveChildren(int $pageId): bool |
199
|
|
|
{ |
200
|
|
|
return $pageId === Page::ERROR_PAGE_ID; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
public static function getBlocks(int $pageId, int $revisionId = null, Locale $locale = null): array |
204
|
|
|
{ |
205
|
|
|
// fetch revision if not specified |
206
|
|
|
if ($revisionId === null) { |
207
|
|
|
$revisionId = self::getLatestRevision($pageId, $locale); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
// redefine |
211
|
|
|
$locale = $locale ?? Locale::workingLocale(); |
212
|
|
|
|
213
|
|
|
/** @var PageBlockRepository $pageBlockRepository */ |
214
|
|
|
$pageBlockRepository = BackendModel::get(PageBlockRepository::class); |
215
|
|
|
|
216
|
|
|
return $pageBlockRepository->getBlocksForPage($pageId, $revisionId, $locale); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
public static function getByTag(int $tagId): array |
220
|
|
|
{ |
221
|
|
|
// get the items |
222
|
|
|
$items = (array) BackendModel::getContainer()->get('database')->getRecords( |
223
|
|
|
'SELECT i.id AS url, i.title AS name, mt.moduleName AS module |
224
|
|
|
FROM TagsModuleTag AS mt |
225
|
|
|
INNER JOIN TagsTag AS t ON mt.tag_id = t.id AND mt.moduleName = ? AND mt.tag_id = ? |
226
|
|
|
INNER JOIN PagesPage AS i ON mt.moduleId = i.id AND i.status = ?', |
227
|
|
|
['pages', $tagId, 'active'] |
228
|
|
|
); |
229
|
|
|
|
230
|
|
|
// loop items |
231
|
|
|
foreach ($items as &$row) { |
232
|
|
|
$row['url'] = BackendModel::createUrlForAction( |
233
|
|
|
'Edit', |
234
|
|
|
'Pages', |
235
|
|
|
null, |
236
|
|
|
['id' => $row['url']] |
237
|
|
|
); |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
// return |
241
|
|
|
return $items; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Get the id first child for a given parent |
246
|
|
|
*/ |
247
|
|
|
public static function getFirstChildId(int $parentId): ?int |
248
|
|
|
{ |
249
|
|
|
/** @var PageRepository $pageRepository */ |
250
|
|
|
$pageRepository = BackendModel::get(PageRepository::class); |
251
|
|
|
$page = $pageRepository->getFirstChild($parentId, Status::active(), Locale::workingLocale()); |
252
|
|
|
|
253
|
|
|
if ($page instanceof Page) { |
254
|
|
|
return $page->getId(); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
return null; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
public static function getFullUrl(int $id): string |
261
|
|
|
{ |
262
|
|
|
$keys = static::getCacheBuilder()->getKeys(Locale::workingLocale()); |
263
|
|
|
$hasMultiLanguages = BackendModel::getContainer()->getParameter('site.multilanguage'); |
264
|
|
|
|
265
|
|
|
// available in generated file? |
266
|
|
|
if (isset($keys[$id])) { |
267
|
|
|
$url = $keys[$id]; |
268
|
|
|
} elseif ($id == Page::NO_PARENT_PAGE_ID) { |
269
|
|
|
// parent id 0 hasn't an url |
270
|
|
|
$url = '/'; |
271
|
|
|
|
272
|
|
|
// multilanguages? |
273
|
|
|
if ($hasMultiLanguages) { |
274
|
|
|
$url = '/' . BL::getWorkingLanguage(); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
// return the unique URL! |
278
|
|
|
return $url; |
279
|
|
|
} else { |
280
|
|
|
// not available |
281
|
|
|
return false; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
// if the is available in multiple languages we should add the current lang |
285
|
|
|
if ($hasMultiLanguages) { |
286
|
|
|
$url = '/' . BL::getWorkingLanguage() . '/' . $url; |
287
|
|
|
} else { |
288
|
|
|
// just prepend with slash |
289
|
|
|
$url = '/' . $url; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
// return the unique URL! |
293
|
|
|
return urldecode($url); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
public static function getLatestRevision(int $id, Locale $locale = null): int |
297
|
|
|
{ |
298
|
|
|
$locale = $locale ?? Locale::workingLocale(); |
299
|
|
|
|
300
|
|
|
/** @var PageRepository $pageRepository */ |
301
|
|
|
$pageRepository = BackendModel::get(PageRepository::class); |
302
|
|
|
|
303
|
|
|
return (int) $pageRepository->getLatestVersion($id, $locale); |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
public static function getMaximumPageId(Locale $locale = null): int |
307
|
|
|
{ |
308
|
|
|
/** @var PageRepository $pageRepository */ |
309
|
|
|
$pageRepository = BackendModel::get(PageRepository::class); |
310
|
|
|
|
311
|
|
|
return $pageRepository->getMaximumPageId( |
312
|
|
|
$locale ?? Locale::workingLocale(), |
313
|
|
|
BackendAuthentication::getUser()->isGod() |
314
|
|
|
); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
public static function getMaximumSequence(int $parentId, Locale $locale = null): int |
318
|
|
|
{ |
319
|
|
|
$locale = $locale ?? Locale::workingLocale(); |
320
|
|
|
|
321
|
|
|
/** @var PageRepository $pageRepository */ |
322
|
|
|
$pageRepository = BackendModel::get(PageRepository::class); |
323
|
|
|
|
324
|
|
|
// get the maximum sequence inside a certain leaf |
325
|
|
|
return $pageRepository->getMaximumSequence($parentId, $locale); |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
public static function getPagesForDropdown(Locale $locale = null): array |
329
|
|
|
{ |
330
|
|
|
$locale = $locale ?? Locale::workingLocale(); |
331
|
|
|
$titles = []; |
332
|
|
|
$sequences = [ |
333
|
|
|
'pages' => [], |
334
|
|
|
'footer' => [], |
335
|
|
|
]; |
336
|
|
|
$keys = []; |
337
|
|
|
$pages = []; |
338
|
|
|
$pageTree = self::getTree([Page::NO_PARENT_PAGE_ID], null, 1, $locale); |
339
|
|
|
$homepageTitle = $pageTree[1][Page::HOME_PAGE_ID]['title'] ?? \SpoonFilter::ucfirst(BL::lbl('Home')); |
340
|
|
|
|
341
|
|
|
foreach ($pageTree as $pageTreePages) { |
342
|
|
|
foreach ((array) $pageTreePages as $pageID => $page) { |
343
|
|
|
$parentID = (int) $page['parent_id']; |
344
|
|
|
|
345
|
|
|
$keys[$pageID] = trim(($keys[$parentID] ?? '') . '/' . $page['url'], '/'); |
346
|
|
|
|
347
|
|
|
$sequences[$page['type'] === 'footer' ? 'footer' : 'pages'][$keys[$pageID]] = $pageID; |
348
|
|
|
|
349
|
|
|
$parentTitle = str_replace([$homepageTitle . ' → ', $homepageTitle], '', $titles[$parentID] ?? ''); |
350
|
|
|
$titles[$pageID] = trim($parentTitle . ' → ' . $page['title'], ' → '); |
351
|
|
|
} |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
foreach ($sequences as $pageGroupSortList) { |
355
|
|
|
ksort($pageGroupSortList); |
356
|
|
|
|
357
|
|
|
foreach ($pageGroupSortList as $id) { |
358
|
|
|
if (isset($titles[$id])) { |
359
|
|
|
$pages[$id] = $titles[$id]; |
360
|
|
|
} |
361
|
|
|
} |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
return $pages; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
private static function getSubTreeForDropdown( |
368
|
|
|
array $navigation, |
369
|
|
|
int $parentId, |
370
|
|
|
string $parentTitle, |
371
|
|
|
callable $attributesFunction |
372
|
|
|
): array { |
373
|
|
|
if (!isset($navigation['page'][$parentId]) || empty($navigation['page'][$parentId])) { |
374
|
|
|
return []; |
375
|
|
|
} |
376
|
|
|
$tree = self::getEmptyTreeArray(); |
377
|
|
|
|
378
|
|
|
foreach ($navigation['page'][$parentId] as $page) { |
379
|
|
|
$pageId = $page['page_id']; |
380
|
|
|
$pageTitle = $parentTitle . ' → ' . $page['navigation_title']; |
381
|
|
|
$tree['pages'][$pageId] = $pageTitle; |
382
|
|
|
$tree['attributes'][$pageId] = $attributesFunction($page); |
383
|
|
|
|
384
|
|
|
$tree = self::mergeTreeForDropdownArrays( |
385
|
|
|
$tree, |
386
|
|
|
self::getSubTreeForDropdown($navigation, $pageId, $pageTitle, $attributesFunction) |
387
|
|
|
); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
return $tree; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
private static function mergeTreeForDropdownArrays(array $tree, array $subTree, string $treeLabel = null): array |
394
|
|
|
{ |
395
|
|
|
if (empty($subTree)) { |
396
|
|
|
return $tree; |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
$tree['attributes'] += $subTree['attributes']; |
400
|
|
|
|
401
|
|
|
if ($treeLabel === null) { |
402
|
|
|
$tree['pages'] += $subTree['pages']; |
403
|
|
|
|
404
|
|
|
return $tree; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
$tree['pages'][$treeLabel] += $subTree['pages']; |
408
|
|
|
|
409
|
|
|
return $tree; |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
private static function getEmptyTreeArray(): array |
413
|
|
|
{ |
414
|
|
|
return [ |
415
|
|
|
'pages' => [], |
416
|
|
|
'attributes' => [], |
417
|
|
|
]; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
private static function getAttributesFunctionForTreeName( |
421
|
|
|
string $treeName, |
422
|
|
|
string $treeLabel, |
423
|
|
|
int $currentPageId |
424
|
|
|
): callable { |
425
|
|
|
return function (array $page) use ($treeName, $treeLabel, $currentPageId) { |
426
|
|
|
$isCurrentPage = $currentPageId === $page['page_id']; |
427
|
|
|
|
428
|
|
|
return [ |
429
|
|
|
'data-tree-name' => $treeName, |
430
|
|
|
'data-tree-label' => $treeLabel, |
431
|
|
|
'data-allow-after' => (int) (!$isCurrentPage && $page['page_id'] !== Page::HOME_PAGE_ID), |
432
|
|
|
'data-allow-inside' => (int) (!$isCurrentPage && $page['allow_children']), |
433
|
|
|
'data-allow-before' => (int) (!$isCurrentPage && $page['page_id'] !== Page::HOME_PAGE_ID), |
434
|
|
|
]; |
435
|
|
|
}; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
private static function addMainPageToTreeForDropdown( |
439
|
|
|
array $tree, |
440
|
|
|
string $branchLabel, |
441
|
|
|
callable $attributesFunction, |
442
|
|
|
array $page, |
443
|
|
|
array $navigation |
444
|
|
|
): array { |
445
|
|
|
$tree['pages'][$branchLabel][$page['page_id']] = $page['navigation_title']; |
446
|
|
|
$tree['attributes'][$page['page_id']] = $attributesFunction($page); |
447
|
|
|
|
448
|
|
|
return self::mergeTreeForDropdownArrays( |
449
|
|
|
$tree, |
450
|
|
|
self::getSubTreeForDropdown( |
451
|
|
|
$navigation, |
452
|
|
|
$page['page_id'], |
453
|
|
|
$page['navigation_title'], |
454
|
|
|
$attributesFunction |
455
|
|
|
), |
456
|
|
|
BL::lbl('MainNavigation') |
457
|
|
|
); |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
public static function getMoveTreeForDropdown(int $currentPageId, Locale $locale = null): array |
461
|
|
|
{ |
462
|
|
|
$navigation = static::getCacheBuilder()->getNavigation($locale ?? Locale::workingLocale()); |
463
|
|
|
|
464
|
|
|
$tree = self::addMainPageToTreeForDropdown( |
465
|
|
|
self::getEmptyTreeArray(), |
466
|
|
|
BL::lbl('MainNavigation'), |
467
|
|
|
self::getAttributesFunctionForTreeName('main', BL::lbl('MainNavigation'), $currentPageId), |
468
|
|
|
$navigation['page'][0][Page::HOME_PAGE_ID], |
469
|
|
|
$navigation |
470
|
|
|
); |
471
|
|
|
|
472
|
|
|
$treeBranches = []; |
473
|
|
|
if (BackendModel::get('fork.settings')->get('Pages', 'meta_navigation', false)) { |
474
|
|
|
$treeBranches['meta'] = BL::lbl('Meta'); |
475
|
|
|
} |
476
|
|
|
$treeBranches['footer'] = BL::lbl('Footer'); |
477
|
|
|
$treeBranches['root'] = BL::lbl('Root'); |
478
|
|
|
|
479
|
|
|
foreach ($treeBranches as $branchName => $branchLabel) { |
480
|
|
|
if (!isset($navigation[$branchName][0]) || !is_array($navigation[$branchName][0])) { |
481
|
|
|
continue; |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
foreach ($navigation[$branchName][0] as $page) { |
485
|
|
|
$tree = self::addMainPageToTreeForDropdown( |
486
|
|
|
$tree, |
487
|
|
|
$branchLabel, |
488
|
|
|
self::getAttributesFunctionForTreeName($branchName, $branchLabel, $currentPageId), |
489
|
|
|
$page, |
490
|
|
|
$navigation |
491
|
|
|
); |
492
|
|
|
} |
493
|
|
|
} |
494
|
|
|
|
495
|
|
|
return $tree; |
496
|
|
|
} |
497
|
|
|
|
498
|
|
|
private static function getSubtree(Type $type, array $navigation, int $parentId): ?array |
499
|
|
|
{ |
500
|
|
|
$subPages = $navigation[(string) $type][$parentId] ?? null; |
501
|
|
|
|
502
|
|
|
if ($subPages === null || count($subPages) === 0) { |
503
|
|
|
return null; |
504
|
|
|
} |
505
|
|
|
|
506
|
|
|
$subTree = []; |
507
|
|
|
foreach ($subPages as $page) { |
508
|
|
|
$subTree[$page['page_id']] = [ |
509
|
|
|
'attr' => [ |
510
|
|
|
'rel' => $page['tree_type'], |
511
|
|
|
'data-jstree' => '{"type":"' . $page['tree_type'] . '"}', |
512
|
|
|
], |
513
|
|
|
'page' => $page, |
514
|
|
|
'children' => self::getSubtree(Type::page(), $navigation, $page['page_id']), |
515
|
|
|
]; |
516
|
|
|
} |
517
|
|
|
|
518
|
|
|
return $subTree; |
519
|
|
|
} |
520
|
|
|
|
521
|
|
|
/** |
522
|
|
|
* Get all pages/level |
523
|
|
|
* |
524
|
|
|
* @param int[] $ids The parentIds. |
525
|
|
|
* @param array $data A holder for the generated data. |
526
|
|
|
* @param int $level The counter for the level. |
527
|
|
|
* @param AbstractLocale $locale |
528
|
|
|
* |
529
|
|
|
* @return array |
530
|
|
|
*/ |
531
|
|
|
public static function getTree(array $ids, array $data = null, int $level = 1, AbstractLocale $locale = null): array |
532
|
|
|
{ |
533
|
|
|
$locale = $locale ?? Locale::workingLocale(); |
534
|
|
|
|
535
|
|
|
/** @var PageRepository $pageRepository */ |
536
|
|
|
$pageRepository = BackendModel::get(PageRepository::class); |
537
|
|
|
$data[$level] = $pageRepository->getPageTree($ids, $locale); |
538
|
|
|
|
539
|
|
|
// get the childIDs |
540
|
|
|
$childIds = array_keys($data[$level]); |
541
|
|
|
|
542
|
|
|
// build array |
543
|
|
|
if (!empty($data[$level])) { |
544
|
|
|
$data[$level] = array_map( |
545
|
|
|
function ($page) { |
546
|
|
|
$page['has_extra'] = (bool) $page['has_extra']; |
547
|
|
|
$page['has_children'] = (bool) $page['has_children']; |
548
|
|
|
$page['allow_move'] = (bool) $page['allow_move']; |
549
|
|
|
|
550
|
|
|
return $page; |
551
|
|
|
}, |
552
|
|
|
$data[$level] |
553
|
|
|
); |
554
|
|
|
|
555
|
|
|
return self::getTree($childIds, $data, ++$level, $locale); |
556
|
|
|
} |
557
|
|
|
|
558
|
|
|
unset($data[$level]); |
559
|
|
|
|
560
|
|
|
return $data; |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
public static function getTreeHTML(): string |
564
|
|
|
{ |
565
|
|
|
$navigation = static::getCacheBuilder()->getNavigation(Locale::workingLocale()); |
566
|
|
|
|
567
|
|
|
$tree = []; |
568
|
|
|
|
569
|
|
|
$tree['main'] = [ |
570
|
|
|
'name' => 'main', |
571
|
|
|
'label' => 'MainNavigation', |
572
|
|
|
'pages' => self::getSubtree(Type::page(), $navigation, 0), |
573
|
|
|
]; |
574
|
|
|
if (BackendModel::get('fork.settings')->get('Pages', 'meta_navigation', false)) { |
575
|
|
|
$tree['meta'] = [ |
576
|
|
|
'name' => 'meta', |
577
|
|
|
'label' => 'Meta', |
578
|
|
|
'pages' => self::getSubtree(Type::meta(), $navigation, 0), |
579
|
|
|
]; |
580
|
|
|
} |
581
|
|
|
$tree['footer'] = [ |
582
|
|
|
'name' => 'footer', |
583
|
|
|
'label' => 'Footer', |
584
|
|
|
'pages' => self::getSubtree(Type::footer(), $navigation, 0), |
585
|
|
|
]; |
586
|
|
|
$tree['root'] = [ |
587
|
|
|
'name' => 'root', |
588
|
|
|
'label' => 'Root', |
589
|
|
|
'pages' => self::getSubtree(Type::root(), $navigation, 0), |
590
|
|
|
]; |
591
|
|
|
|
592
|
|
|
// return |
593
|
|
|
return BackendModel::getContainer()->get('templating')->render( |
594
|
|
|
BACKEND_MODULES_PATH . '/Pages/Resources/views/NavigationTree.html.twig', |
595
|
|
|
[ |
596
|
|
|
'editUrl' => BackendModel::createUrlForAction('PageEdit', 'Pages'), |
597
|
|
|
'tree' => $tree, |
598
|
|
|
] |
599
|
|
|
); |
600
|
|
|
} |
601
|
|
|
|
602
|
|
|
private static function pageIsChildOfParent(array $navigation, int $childId, int $parentId): bool |
603
|
|
|
{ |
604
|
|
|
if (isset($navigation['page'][$parentId]) && !empty($navigation['page'][$parentId])) { |
605
|
|
|
foreach ($navigation['page'][$parentId] as $page) { |
606
|
|
|
if ($page['page_id'] === $childId) { |
607
|
|
|
return true; |
608
|
|
|
} |
609
|
|
|
|
610
|
|
|
if (self::pageIsChildOfParent($navigation, $childId, $page['page_id'])) { |
611
|
|
|
return true; |
612
|
|
|
} |
613
|
|
|
} |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
return false; |
617
|
|
|
} |
618
|
|
|
|
619
|
|
|
public static function getTreeNameForPageId(int $pageId): ?string |
620
|
|
|
{ |
621
|
|
|
$navigation = static::getCacheBuilder()->getNavigation(Locale::workingLocale()); |
622
|
|
|
|
623
|
|
|
if ($pageId === Page::HOME_PAGE_ID || self::pageIsChildOfParent($navigation, $pageId, Page::HOME_PAGE_ID)) { |
624
|
|
|
return 'main'; |
625
|
|
|
} |
626
|
|
|
|
627
|
|
|
$treeNames = ['footer', 'root']; |
628
|
|
|
|
629
|
|
|
// only show meta if needed |
630
|
|
|
if (BackendModel::get('fork.settings')->get('Pages', 'meta_navigation', false)) { |
631
|
|
|
$treeNames[] = 'meta'; |
632
|
|
|
} |
633
|
|
|
|
634
|
|
|
foreach ($treeNames as $treeName) { |
635
|
|
|
if (isset($navigation[$treeName][0]) && !empty($navigation[$treeName][0])) { |
636
|
|
|
// loop the items |
637
|
|
|
foreach ($navigation[$treeName][0] as $page) { |
638
|
|
|
if ($pageId === $page['page_id'] |
639
|
|
|
|| self::pageIsChildOfParent($navigation, $pageId, $page['page_id'])) { |
640
|
|
|
return $treeName; |
641
|
|
|
} |
642
|
|
|
} |
643
|
|
|
} |
644
|
|
|
} |
645
|
|
|
|
646
|
|
|
return null; |
647
|
|
|
} |
648
|
|
|
|
649
|
|
|
public static function getTypes(): array |
650
|
|
|
{ |
651
|
|
|
return [ |
652
|
|
|
'rich_text' => BL::lbl('Editor'), |
653
|
|
|
'block' => BL::lbl('Module'), |
654
|
|
|
'widget' => BL::lbl('Widget'), |
655
|
|
|
'usertemplate' => BL::lbl('UserTemplate'), |
656
|
|
|
]; |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
/** |
660
|
|
|
* @deprecated use the repository |
661
|
|
|
*/ |
662
|
|
|
public static function getUrl(string $url, int $id = null, int $parentId = null, bool $isAction = false): string |
663
|
|
|
{ |
664
|
|
|
/** @var PageRepository $pageRepository */ |
665
|
|
|
$pageRepository = BackendModel::get(PageRepository::class); |
666
|
|
|
|
667
|
|
|
return $pageRepository->getUrl($url, Locale::workingLocale(), $id, $parentId, $isAction); |
668
|
|
|
} |
669
|
|
|
|
670
|
|
|
/** |
671
|
|
|
* Insert multiple blocks at once |
672
|
|
|
* |
673
|
|
|
* @param array $blocks The blocks to insert. |
674
|
|
|
*/ |
675
|
|
|
public static function insertBlocks(array $blocks): void |
676
|
|
|
{ |
677
|
|
|
if (empty($blocks)) { |
678
|
|
|
return; |
679
|
|
|
} |
680
|
|
|
|
681
|
|
|
/** @var PageBlockRepository $pageBlockRepository */ |
682
|
|
|
$pageBlockRepository = BackendModel::get(PageBlockRepository::class); |
683
|
|
|
|
684
|
|
|
/** @var PageRepository $pageRepository */ |
685
|
|
|
$pageRepository = BackendModel::get(PageRepository::class); |
686
|
|
|
|
687
|
|
|
// loop blocks |
688
|
|
|
foreach ($blocks as $block) { |
689
|
|
|
$extraId = $block['extra_id']; |
690
|
|
|
|
691
|
|
|
if (!isset($block['page']) && isset($block['revision_id'])) { |
692
|
|
|
$block['page'] = $pageRepository->find($block['revision_id']); |
693
|
|
|
} |
694
|
|
|
|
695
|
|
|
$pageBlock = new PageBlock( |
696
|
|
|
$block['page'], |
697
|
|
|
$block['position'], |
698
|
|
|
$extraId, |
699
|
|
|
new PageBlockType($block['extra_type']), |
700
|
|
|
$block['extra_data'], |
701
|
|
|
$block['html'], |
702
|
|
|
$block['visible'], |
703
|
|
|
$block['sequence'] |
704
|
|
|
); |
705
|
|
|
|
706
|
|
|
$pageBlockRepository->add($pageBlock); |
707
|
|
|
$pageBlockRepository->save($pageBlock); |
708
|
|
|
} |
709
|
|
|
} |
710
|
|
|
|
711
|
|
|
public static function loadUserTemplates(): array |
712
|
|
|
{ |
713
|
|
|
$themePath = FRONTEND_PATH . '/Themes/'; |
714
|
|
|
$themePath .= BackendModel::get('fork.settings')->get('Core', 'theme', 'Fork'); |
715
|
|
|
$filePath = $themePath . '/Core/Layout/Templates/UserTemplates/Templates.json'; |
716
|
|
|
|
717
|
|
|
$userTemplates = []; |
718
|
|
|
|
719
|
|
|
$fs = new Filesystem(); |
720
|
|
|
if ($fs->exists($filePath)) { |
721
|
|
|
$userTemplates = json_decode(file_get_contents($filePath), true); |
722
|
|
|
|
723
|
|
|
foreach ($userTemplates as &$userTemplate) { |
724
|
|
|
$userTemplate['file'] = |
725
|
|
|
'/src/Frontend/Themes/' . |
726
|
|
|
BackendModel::get('fork.settings')->get('Core', 'theme', 'Fork') . |
727
|
|
|
'/Core/Layout/Templates/UserTemplates/' . |
728
|
|
|
$userTemplate['file']; |
729
|
|
|
} |
730
|
|
|
} |
731
|
|
|
|
732
|
|
|
return $userTemplates; |
733
|
|
|
} |
734
|
|
|
|
735
|
|
|
/** |
736
|
|
|
* Move a page |
737
|
|
|
* |
738
|
|
|
* @param int $pageId The id for the page that has to be moved. |
739
|
|
|
* @param int $droppedOnPageId The id for the page where to page has been dropped on. |
740
|
|
|
* @param string $typeOfDrop The type of drop, possible values are: before, after, inside. |
741
|
|
|
* @param string $tree The tree the item is dropped on, possible values are: main, meta, footer, root. |
742
|
|
|
* @param Locale $locale The locale to use, if not provided we will use the working locale. |
743
|
|
|
* |
744
|
|
|
* @return bool |
745
|
|
|
*/ |
746
|
|
|
public static function move( |
747
|
|
|
int $pageId, |
748
|
|
|
int $droppedOnPageId, |
749
|
|
|
string $typeOfDrop, |
750
|
|
|
string $tree, |
751
|
|
|
Locale $locale = null |
752
|
|
|
): bool { |
753
|
|
|
$typeOfDrop = \SpoonFilter::getValue($typeOfDrop, self::POSSIBLE_TYPES_OF_DROP, self::TYPE_OF_DROP_INSIDE); |
754
|
|
|
$tree = \SpoonFilter::getValue($tree, ['main', 'meta', 'footer', 'root'], 'root'); |
755
|
|
|
$locale = $locale ?? Locale::workingLocale(); |
756
|
|
|
|
757
|
|
|
// When dropping on the main navigation it should be added as a child of the home page |
758
|
|
|
if ($tree === 'main' && $droppedOnPageId === 0) { |
759
|
|
|
$droppedOnPageId = Page::HOME_PAGE_ID; |
760
|
|
|
$typeOfDrop = self::TYPE_OF_DROP_INSIDE; |
761
|
|
|
} |
762
|
|
|
|
763
|
|
|
// reset type of drop for special pages |
764
|
|
|
if ($droppedOnPageId === Page::HOME_PAGE_ID || $droppedOnPageId === Page::NO_PARENT_PAGE_ID) { |
765
|
|
|
$typeOfDrop = self::TYPE_OF_DROP_INSIDE; |
766
|
|
|
} |
767
|
|
|
|
768
|
|
|
$page = self::get($pageId, null, $locale); |
769
|
|
|
$droppedOnPage = self::get( |
770
|
|
|
($droppedOnPageId === Page::NO_PARENT_PAGE_ID ? Page::HOME_PAGE_ID : $droppedOnPageId), |
771
|
|
|
null, |
772
|
|
|
$locale |
773
|
|
|
); |
774
|
|
|
|
775
|
|
|
if (empty($page) || empty($droppedOnPage)) { |
776
|
|
|
return false; |
777
|
|
|
} |
778
|
|
|
|
779
|
|
|
try { |
780
|
|
|
$newParent = self::getNewParent($droppedOnPageId, $typeOfDrop, $droppedOnPage); |
781
|
|
|
} catch (InvalidArgumentException $invalidArgumentException) { |
782
|
|
|
// parent doesn't allow children |
783
|
|
|
return false; |
784
|
|
|
} |
785
|
|
|
|
786
|
|
|
self::recalculateSequenceAfterMove( |
787
|
|
|
$typeOfDrop, |
788
|
|
|
self::getNewType($droppedOnPageId, $tree, $newParent, $droppedOnPage), |
789
|
|
|
$pageId, |
790
|
|
|
$locale, |
791
|
|
|
$newParent, |
792
|
|
|
$droppedOnPage['id'] |
793
|
|
|
); |
794
|
|
|
|
795
|
|
|
self::updateUrlAfterMove($pageId, $page, $newParent); |
|
|
|
|
796
|
|
|
|
797
|
|
|
return true; |
798
|
|
|
} |
799
|
|
|
|
800
|
|
|
public static function update(array $page): int |
801
|
|
|
{ |
802
|
|
|
/** @var PageRepository $pageRepository */ |
803
|
|
|
$pageRepository = BackendModel::get(PageRepository::class); |
804
|
|
|
/** @var MetaRepository $metaRepository */ |
805
|
|
|
$metaRepository = BackendModel::get('fork.repository.meta'); |
806
|
|
|
|
807
|
|
|
$locale = Locale::fromString($page['locale']); |
808
|
|
|
|
809
|
|
|
if (self::isForbiddenToDelete($page['id'])) { |
810
|
|
|
$page['allow_delete'] = false; |
811
|
|
|
} |
812
|
|
|
|
813
|
|
|
if (self::isForbiddenToMove($page['id'])) { |
814
|
|
|
$page['allow_move'] = false; |
815
|
|
|
} |
816
|
|
|
|
817
|
|
|
if (self::isForbiddenToHaveChildren($page['id'])) { |
818
|
|
|
$page['allow_children'] = false; |
819
|
|
|
} |
820
|
|
|
|
821
|
|
|
// update old revisions |
822
|
|
|
if ($page['status'] !== (string) Status::draft()) { |
823
|
|
|
$pageEntities = $pageRepository->findBy( |
824
|
|
|
[ |
825
|
|
|
'id' => $page['id'], |
826
|
|
|
'locale' => $locale, |
827
|
|
|
] |
828
|
|
|
); |
829
|
|
|
|
830
|
|
|
foreach ($pageEntities as $pageEntity) { |
831
|
|
|
$pageEntity->archive(); |
832
|
|
|
$pageRepository->save($pageEntity); |
833
|
|
|
} |
834
|
|
|
} else { |
835
|
|
|
$pageRepository->deleteByIdAndUserIdAndStatusAndLocale( |
836
|
|
|
(int) $page['id'], |
837
|
|
|
BackendAuthentication::getUser()->getUserId(), |
838
|
|
|
Status::draft(), |
839
|
|
|
$locale |
840
|
|
|
); |
841
|
|
|
} |
842
|
|
|
|
843
|
|
|
$meta = $metaRepository->find($page['meta_id']); |
844
|
|
|
|
845
|
|
|
$pageEntity = new Page( |
846
|
|
|
$page['id'], |
847
|
|
|
$page['user_id'], |
848
|
|
|
$page['parent_id'], |
849
|
|
|
$page['template_id'], |
850
|
|
|
clone $meta, |
851
|
|
|
$locale, |
852
|
|
|
$page['title'], |
853
|
|
|
$page['navigation_title'], |
854
|
|
|
null, |
855
|
|
|
$page['publish_on'], |
856
|
|
|
null, |
857
|
|
|
$page['sequence'], |
858
|
|
|
$page['navigation_title_overwrite'], |
859
|
|
|
$page['hidden'], |
860
|
|
|
new Status($page['status']), |
861
|
|
|
new Type($page['type']), |
862
|
|
|
$page['data'], |
863
|
|
|
$page['allow_move'], |
864
|
|
|
$page['allow_children'], |
865
|
|
|
$page['allow_edit'], |
866
|
|
|
$page['allow_delete'] |
867
|
|
|
); |
868
|
|
|
|
869
|
|
|
$pageRepository->add($pageEntity); |
870
|
|
|
$pageRepository->save($pageEntity); |
871
|
|
|
|
872
|
|
|
// how many revisions should we keep |
873
|
|
|
$rowsToKeep = (int) BackendModel::get('fork.settings')->get('Pages', 'max_num_revisions', 20); |
874
|
|
|
|
875
|
|
|
// get revision-ids for items to keep |
876
|
|
|
$revisionIdsToKeep = $pageRepository->getRevisionIdsToKeep($page['id'], $rowsToKeep); |
877
|
|
|
|
878
|
|
|
// delete other revisions |
879
|
|
|
if (count($revisionIdsToKeep) !== 0) { |
880
|
|
|
// because blocks are linked by revision we should get all revisions we want to delete |
881
|
|
|
|
882
|
|
|
$revisionsToDelete = $pageRepository |
883
|
|
|
->getRevisionIdsToDelete( |
884
|
|
|
$page['id'], |
885
|
|
|
Status::archive(), |
886
|
|
|
$revisionIdsToKeep |
887
|
|
|
); |
888
|
|
|
|
889
|
|
|
// any revisions to delete |
890
|
|
|
if (count($revisionsToDelete) !== 0) { |
891
|
|
|
$pageRepository->deleteByRevisionIds($revisionsToDelete); |
892
|
|
|
|
893
|
|
|
/** @var PageBlockRepository $pageBlockRepository */ |
894
|
|
|
$pageBlockRepository = BackendModel::get(PageBlockRepository::class); |
895
|
|
|
$pageBlockRepository->deleteByRevisionIds($revisionsToDelete); |
896
|
|
|
} |
897
|
|
|
} |
898
|
|
|
|
899
|
|
|
// return the new revision id |
900
|
|
|
return $pageEntity->getRevisionId(); |
901
|
|
|
} |
902
|
|
|
|
903
|
|
|
/** |
904
|
|
|
* Switch templates for all existing pages |
905
|
|
|
* |
906
|
|
|
* @param int $oldTemplateId The id of the new template to replace. |
907
|
|
|
* @param int $newTemplateId The id of the new template to use. |
908
|
|
|
* @param bool $overwrite Overwrite all pages with default blocks. |
909
|
|
|
*/ |
910
|
|
|
public static function updatePagesTemplates(int $oldTemplateId, int $newTemplateId, bool $overwrite = false): void |
911
|
|
|
{ |
912
|
|
|
// fetch new template data |
913
|
|
|
$newTemplate = BackendExtensionsModel::getTemplate($newTemplateId); |
914
|
|
|
$newTemplate['data'] = @unserialize($newTemplate['data'], ['allowed_classes' => 'false']); |
915
|
|
|
|
916
|
|
|
// fetch all pages |
917
|
|
|
/** @var PageRepository $pageRepository */ |
918
|
|
|
$pageRepository = BackendModel::get(PageRepository::class); |
919
|
|
|
$pages = $pageRepository->findBy( |
920
|
|
|
['templateId' => $oldTemplateId, 'status' => [Status::active(), Status::draft()]] |
921
|
|
|
); |
922
|
|
|
|
923
|
|
|
// there is no active/draft page with the old template id |
924
|
|
|
if (count($pages) === 0) { |
925
|
|
|
return; |
926
|
|
|
} |
927
|
|
|
|
928
|
|
|
// loop pages |
929
|
|
|
/** @var Page $page */ |
930
|
|
|
foreach ($pages as $page) { |
931
|
|
|
// fetch blocks |
932
|
|
|
$blocksContent = self::getBlocks($page->getId(), $page->getRevisionId(), $page->getLocale()); |
933
|
|
|
|
934
|
|
|
// save new page revision |
935
|
|
|
$newPageRevisionId = self::update( |
936
|
|
|
[ |
937
|
|
|
'id' => $page->getId(), |
938
|
|
|
'user_id' => $page->getUserId(), |
939
|
|
|
'parent_id' => $page->getParentId(), |
940
|
|
|
'template_id' => $newTemplateId, |
941
|
|
|
'meta_id' => $page->getMeta()->getId(), |
942
|
|
|
'locale' => $page->getLocale(), |
943
|
|
|
'title' => $page->getTitle(), |
944
|
|
|
'navigation_title' => $page->getNavigationTitle(), |
945
|
|
|
'publish_on' => $page->getPublishOn(), |
946
|
|
|
'publish_until' => $page->getPublishUntil(), |
947
|
|
|
'sequence' => $page->getSequence(), |
948
|
|
|
'navigation_title_overwrite' => $page->isNavigationTitleOverwrite(), |
949
|
|
|
'hidden' => $page->isHidden(), |
950
|
|
|
'status' => (string) $page->getStatus(), |
951
|
|
|
'type' => (string) $page->getType(), |
952
|
|
|
'data' => $page->getData(), |
953
|
|
|
'allow_move' => $page->isAllowMove(), |
954
|
|
|
'allow_children' => $page->isAllowChildren(), |
955
|
|
|
'allow_edit' => $page->isAllowEdit(), |
956
|
|
|
'allow_delete' => $page->isAllowDelete(), |
957
|
|
|
] |
958
|
|
|
); |
959
|
|
|
|
960
|
|
|
// overwrite all blocks with current defaults |
961
|
|
|
if ($overwrite) { |
962
|
|
|
$blocksContent = []; |
963
|
|
|
|
964
|
|
|
// fetch default blocks for this page |
965
|
|
|
$defaultBlocks = []; |
966
|
|
|
if (isset($newTemplate['data']['default_extras_' . $page->getLocale()])) { |
967
|
|
|
$defaultBlocks = $newTemplate['data']['default_extras_' . $page->getLocale()]; |
968
|
|
|
} elseif (isset($newTemplate['data']['default_extras'])) { |
969
|
|
|
$defaultBlocks = $newTemplate['data']['default_extras']; |
970
|
|
|
} |
971
|
|
|
|
972
|
|
|
// loop positions |
973
|
|
|
foreach ($defaultBlocks as $position => $blocks) { |
974
|
|
|
// loop blocks |
975
|
|
|
foreach ($blocks as $extraId) { |
976
|
|
|
// add to the list |
977
|
|
|
$blocksContent[] = [ |
978
|
|
|
'revision_id' => $newPageRevisionId, |
979
|
|
|
'position' => $position, |
980
|
|
|
'extra_id' => $extraId, |
981
|
|
|
'extra_type' => 'rich_text', |
982
|
|
|
'html' => '', |
983
|
|
|
'created_on' => BackendModel::getUTCDate(), |
984
|
|
|
'edited_on' => BackendModel::getUTCDate(), |
985
|
|
|
'visible' => true, |
986
|
|
|
'sequence' => count($defaultBlocks[$position]) - 1, |
987
|
|
|
]; |
988
|
|
|
} |
989
|
|
|
} |
990
|
|
|
} else { |
991
|
|
|
// don't overwrite blocks, just re-use existing |
992
|
|
|
// set new page revision id |
993
|
|
|
foreach ($blocksContent as &$block) { |
994
|
|
|
$block['revision_id'] = $newPageRevisionId; |
995
|
|
|
$block['created_on'] = BackendModel::getUTCDate(null, $block['created_on']->getTimestamp()); |
996
|
|
|
$block['edited_on'] = BackendModel::getUTCDate(null, $block['edited_on']->getTimestamp()); |
997
|
|
|
} |
998
|
|
|
} |
999
|
|
|
|
1000
|
|
|
// insert the blocks |
1001
|
|
|
self::insertBlocks($blocksContent); |
1002
|
|
|
} |
1003
|
|
|
} |
1004
|
|
|
|
1005
|
|
|
public static function getEncodedRedirectUrl(string $redirectUrl): string |
1006
|
|
|
{ |
1007
|
|
|
preg_match('!(http[s]?)://(.*)!i', $redirectUrl, $matches); |
1008
|
|
|
$urlChunks = explode('/', $matches[2]); |
1009
|
|
|
if (!empty($urlChunks)) { |
1010
|
|
|
// skip domain name |
1011
|
|
|
$domain = array_shift($urlChunks); |
1012
|
|
|
foreach ($urlChunks as &$urlChunk) { |
1013
|
|
|
$urlChunk = rawurlencode($urlChunk); |
1014
|
|
|
} |
1015
|
|
|
unset($urlChunk); |
1016
|
|
|
$redirectUrl = $matches[1] . '://' . $domain . '/' . implode('/', $urlChunks); |
1017
|
|
|
} |
1018
|
|
|
|
1019
|
|
|
return $redirectUrl; |
1020
|
|
|
} |
1021
|
|
|
|
1022
|
|
|
private static function getNewParent(int $droppedOnPageId, string $typeOfDrop, array $droppedOnPage): int |
1023
|
|
|
{ |
1024
|
|
|
if ($droppedOnPageId === Page::NO_PARENT_PAGE_ID) { |
1025
|
|
|
return Page::NO_PARENT_PAGE_ID; |
1026
|
|
|
} |
1027
|
|
|
|
1028
|
|
|
if ($typeOfDrop === self::TYPE_OF_DROP_INSIDE) { |
1029
|
|
|
// check if item allows children |
1030
|
|
|
if (!$droppedOnPage['allow_children']) { |
1031
|
|
|
throw new InvalidArgumentException('Parent page is not allowed to have child pages'); |
1032
|
|
|
} |
1033
|
|
|
|
1034
|
|
|
return $droppedOnPage['id']; |
1035
|
|
|
} |
1036
|
|
|
|
1037
|
|
|
// if the item has to be moved before or after |
1038
|
|
|
return $droppedOnPage['parent_id']; |
1039
|
|
|
} |
1040
|
|
|
|
1041
|
|
|
private static function getNewType(int $droppedOnPageId, string $tree, int $newParent, array $droppedOnPage): Type |
1042
|
|
|
{ |
1043
|
|
|
if ($droppedOnPageId === Page::NO_PARENT_PAGE_ID) { |
1044
|
|
|
if ($tree === 'footer') { |
1045
|
|
|
return Type::footer(); |
1046
|
|
|
} |
1047
|
|
|
|
1048
|
|
|
if ($tree === 'meta') { |
1049
|
|
|
return Type::meta(); |
1050
|
|
|
} |
1051
|
|
|
|
1052
|
|
|
return Type::root(); |
1053
|
|
|
} |
1054
|
|
|
|
1055
|
|
|
if ($newParent === Page::NO_PARENT_PAGE_ID) { |
1056
|
|
|
return $droppedOnPage['type']; |
1057
|
|
|
} |
1058
|
|
|
|
1059
|
|
|
return Type::page(); |
1060
|
|
|
} |
1061
|
|
|
|
1062
|
|
|
private static function recalculateSequenceAfterMove( |
1063
|
|
|
string $typeOfDrop, |
1064
|
|
|
Type $newType, |
1065
|
|
|
int $pageId, |
1066
|
|
|
Locale $locale, |
1067
|
|
|
string $newParent, |
1068
|
|
|
int $droppedOnPageId |
1069
|
|
|
): void { |
1070
|
|
|
/** @var PageRepository $pageRepository */ |
1071
|
|
|
$pageRepository = BackendModel::get(PageRepository::class); |
1072
|
|
|
|
1073
|
|
|
// calculate new sequence for items that should be moved inside |
1074
|
|
|
if ($typeOfDrop === self::TYPE_OF_DROP_INSIDE) { |
1075
|
|
|
$newSequence = $pageRepository->getNewSequenceForMove((int) $newParent, $locale); |
1076
|
|
|
|
1077
|
|
|
$pages = $pageRepository->findBy(['id' => $pageId, 'locale' => $locale, 'status' => Status::active()]); |
1078
|
|
|
|
1079
|
|
|
foreach ($pages as $page) { |
1080
|
|
|
$page->move((int) $newParent, $newSequence, $newType); |
1081
|
|
|
$pageRepository->save($page); |
1082
|
|
|
} |
1083
|
|
|
|
1084
|
|
|
return; |
1085
|
|
|
} |
1086
|
|
|
|
1087
|
|
|
$droppedOnPage = $pageRepository |
1088
|
|
|
->findOneBy( |
1089
|
|
|
[ |
1090
|
|
|
'id' => $droppedOnPageId, |
1091
|
|
|
'locale' => $locale, |
1092
|
|
|
'status' => Status::active(), |
1093
|
|
|
] |
1094
|
|
|
); |
1095
|
|
|
|
1096
|
|
|
if (!$droppedOnPage instanceof Page) { |
1097
|
|
|
throw new RuntimeException('Drop on page not found'); |
1098
|
|
|
} |
1099
|
|
|
|
1100
|
|
|
// calculate new sequence for items that should be moved before or after |
1101
|
|
|
$droppedOnPageSequence = $droppedOnPage->getSequence(); |
1102
|
|
|
|
1103
|
|
|
$newSequence = $droppedOnPageSequence + ($typeOfDrop === self::TYPE_OF_DROP_BEFORE ? -1 : 1); |
1104
|
|
|
|
1105
|
|
|
// increment all pages with a sequence that is higher than the new sequence; |
1106
|
|
|
$pageRepository->incrementSequence($newParent, $locale, $newSequence); |
|
|
|
|
1107
|
|
|
|
1108
|
|
|
$pages = $pageRepository->findBy(['id' => $pageId, 'locale' => $locale, 'status' => Status::active()]); |
1109
|
|
|
|
1110
|
|
|
foreach ($pages as $page) { |
1111
|
|
|
$page->move((int) $newParent, $newSequence, $newType); |
1112
|
|
|
$pageRepository->save($page); |
1113
|
|
|
} |
1114
|
|
|
} |
1115
|
|
|
|
1116
|
|
|
private static function updateUrlAfterMove(int $pageId, array $page, int $newParent): void |
1117
|
|
|
{ |
1118
|
|
|
/** @var MetaRepository $metaRepository */ |
1119
|
|
|
$metaRepository = BackendModel::get('fork.repository.meta'); |
1120
|
|
|
$meta = $metaRepository->find($page['meta_id']); |
1121
|
|
|
|
1122
|
|
|
if (!$meta instanceof Meta) { |
1123
|
|
|
return; |
1124
|
|
|
} |
1125
|
|
|
|
1126
|
|
|
$newUrl = self::getUrl( |
|
|
|
|
1127
|
|
|
$meta->getUrl(), |
1128
|
|
|
$pageId, |
1129
|
|
|
$newParent, |
1130
|
|
|
isset($page['data']['is_action']) && $page['data']['is_action'] |
1131
|
|
|
); |
1132
|
|
|
|
1133
|
|
|
$meta->update( |
1134
|
|
|
$meta->getKeywords(), |
1135
|
|
|
$meta->isKeywordsOverwrite(), |
1136
|
|
|
$meta->getDescription(), |
1137
|
|
|
$meta->isDescriptionOverwrite(), |
1138
|
|
|
$meta->getTitle(), |
1139
|
|
|
$meta->isTitleOverwrite(), |
1140
|
|
|
$newUrl, |
1141
|
|
|
$meta->isUrlOverwrite() |
1142
|
|
|
); |
1143
|
|
|
|
1144
|
|
|
$metaRepository->save($meta); |
1145
|
|
|
} |
1146
|
|
|
} |
1147
|
|
|
|