1 | <?php |
||||
2 | |||||
3 | namespace Backend\Modules\Pages\Engine; |
||||
4 | |||||
5 | use Backend\Modules\ContentBlocks\Domain\ContentBlock\Command\CopyContentBlocksToOtherLocale; |
||||
6 | use Backend\Modules\FormBuilder\Command\CopyFormWidgetsToOtherLocale; |
||||
7 | use Backend\Modules\Location\Command\CopyLocationWidgetsToOtherLocale; |
||||
8 | use Common\Doctrine\Entity\Meta; |
||||
9 | use ForkCMS\Utility\Thumbnails; |
||||
10 | use SimpleBus\Message\Bus\MessageBus; |
||||
11 | use InvalidArgumentException; |
||||
12 | use SpoonFilter; |
||||
13 | use Symfony\Component\Filesystem\Filesystem; |
||||
14 | use Backend\Core\Engine\Authentication as BackendAuthentication; |
||||
15 | use Backend\Core\Language\Language as BL; |
||||
16 | use Backend\Core\Engine\Model as BackendModel; |
||||
17 | use Backend\Core\Language\Locale; |
||||
18 | use Backend\Modules\Extensions\Engine\Model as BackendExtensionsModel; |
||||
19 | use Backend\Modules\Search\Engine\Model as BackendSearchModel; |
||||
20 | use Backend\Modules\Tags\Engine\Model as BackendTagsModel; |
||||
21 | use ForkCMS\App\ForkController; |
||||
22 | use Frontend\Core\Language\Language as FrontendLanguage; |
||||
23 | |||||
24 | /** |
||||
25 | * In this file we store all generic functions that we will be using in the PagesModule |
||||
26 | */ |
||||
27 | class Model |
||||
28 | { |
||||
29 | const NO_PARENT_PAGE_ID = 0; |
||||
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 pages AS i |
||||
43 | WHERE i.status = ? AND i.language = ? |
||||
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 pages AS i |
||||
50 | INNER JOIN |
||||
51 | ( |
||||
52 | SELECT MAX(i.revision_id) AS revision_id |
||||
53 | FROM pages AS i |
||||
54 | WHERE i.status = ? AND i.user_id = ? AND i.language = ? |
||||
55 | GROUP BY i.id |
||||
56 | ) AS p |
||||
57 | WHERE i.revision_id = p.revision_id'; |
||||
58 | |||||
59 | const QUERY_BROWSE_REVISIONS = |
||||
60 | 'SELECT i.id, i.revision_id, i.title, UNIX_TIMESTAMP(i.edited_on) AS edited_on, i.user_id |
||||
61 | FROM pages AS i |
||||
62 | WHERE i.id = ? AND i.status = ? AND i.language = ? |
||||
63 | ORDER BY i.edited_on DESC'; |
||||
64 | |||||
65 | const QUERY_DATAGRID_BROWSE_SPECIFIC_DRAFTS = |
||||
66 | 'SELECT i.id, i.revision_id, i.title, UNIX_TIMESTAMP(i.edited_on) AS edited_on, i.user_id |
||||
67 | FROM pages AS i |
||||
68 | WHERE i.id = ? AND i.status = ? AND i.language = ? |
||||
69 | ORDER BY i.edited_on DESC'; |
||||
70 | |||||
71 | const QUERY_BROWSE_TEMPLATES = |
||||
72 | 'SELECT i.id, i.label AS title |
||||
73 | FROM pages_templates AS i |
||||
74 | 38 | WHERE i.theme = ? |
|||
75 | ORDER BY i.label ASC'; |
||||
76 | 38 | ||||
77 | 38 | public static function getCacheBuilder(): CacheBuilder |
|||
78 | 38 | { |
|||
79 | static $cacheBuilder = null; |
||||
80 | if ($cacheBuilder === null) { |
||||
81 | 38 | $cacheBuilder = new CacheBuilder(BackendModel::get('database'), BackendModel::get('cache.pool')); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() It seems like
Backend\Core\Engine\Model::get('database') can also be of type null ; however, parameter $database of Backend\Modules\Pages\En...eBuilder::__construct() does only seem to accept SpoonDatabase , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
82 | } |
||||
83 | |||||
84 | 2 | return $cacheBuilder; |
|||
85 | } |
||||
86 | 2 | ||||
87 | 2 | public static function buildCache(string $language = null): void |
|||
88 | 2 | { |
|||
89 | $cacheBuilder = static::getCacheBuilder(); |
||||
90 | $cacheBuilder->buildCache($language ?? BL::getWorkingLanguage()); |
||||
91 | } |
||||
92 | |||||
93 | public static function copy(string $fromLanguage, string $toLanguage): void |
||||
94 | { |
||||
95 | // get database |
||||
96 | $database = BackendModel::getContainer()->get('database'); |
||||
97 | |||||
98 | /** @var MessageBus $commanBus */ |
||||
99 | $commandBus = BackendModel::get('command_bus'); |
||||
100 | |||||
101 | $toLocale = Locale::fromString($toLanguage); |
||||
102 | $fromLocale = Locale::fromString($fromLanguage); |
||||
103 | |||||
104 | // copy contentBlocks and get copied contentBlockIds |
||||
105 | $copyContentBlocks = new CopyContentBlocksToOtherLocale($toLocale, $fromLocale); |
||||
106 | $commandBus->handle($copyContentBlocks); |
||||
107 | $contentBlockIds = $copyContentBlocks->extraIdMap; |
||||
108 | |||||
109 | // define old block ids |
||||
110 | $contentBlockOldIds = array_keys($contentBlockIds); |
||||
111 | $locationWidgetOldIds = []; |
||||
112 | $locationWidgetIds = []; |
||||
113 | if (BackendModel::isModuleInstalled('Location')) { |
||||
114 | // copy location widgets and get copied widget ids |
||||
115 | $copyLocationWidgets = new CopyLocationWidgetsToOtherLocale($toLocale, $fromLocale); |
||||
116 | $commandBus->handle($copyLocationWidgets); |
||||
117 | $locationWidgetIds = $copyLocationWidgets->extraIdMap; |
||||
118 | |||||
119 | // define old block ids |
||||
120 | $locationWidgetOldIds = array_keys($locationWidgetIds); |
||||
121 | } |
||||
122 | |||||
123 | $formWidgetOldIds = []; |
||||
124 | $formWidgetIds = []; |
||||
125 | if (BackendModel::isModuleInstalled('FormBuilder')) { |
||||
126 | // copy form widgets and get copied widget ids |
||||
127 | $copyFormWidgets = new CopyFormWidgetsToOtherLocale($toLocale, $fromLocale); |
||||
128 | $commandBus->handle($copyFormWidgets); |
||||
129 | $formWidgetIds = $copyFormWidgets->extraIdMap; |
||||
130 | |||||
131 | // define old block ids |
||||
132 | $formWidgetOldIds = array_keys($formWidgetIds); |
||||
133 | } |
||||
134 | |||||
135 | // get all old pages |
||||
136 | $ids = $database->getColumn( |
||||
137 | 'SELECT id |
||||
138 | FROM pages AS i |
||||
139 | WHERE i.language = ? AND i.status = ?', |
||||
140 | [$toLanguage, 'active'] |
||||
141 | ); |
||||
142 | |||||
143 | // any old pages |
||||
144 | if (!empty($ids)) { |
||||
145 | // delete existing pages |
||||
146 | foreach ($ids as $id) { |
||||
147 | // redefine |
||||
148 | $id = (int) $id; |
||||
149 | |||||
150 | // get revision ids |
||||
151 | $revisionIDs = (array) $database->getColumn( |
||||
152 | 'SELECT i.revision_id |
||||
153 | FROM pages AS i |
||||
154 | WHERE i.id = ? AND i.language = ?', |
||||
155 | [$id, $toLanguage] |
||||
156 | ); |
||||
157 | |||||
158 | // get meta ids |
||||
159 | $metaIDs = (array) $database->getColumn( |
||||
160 | 'SELECT i.meta_id |
||||
161 | FROM pages AS i |
||||
162 | WHERE i.id = ? AND i.language = ?', |
||||
163 | [$id, $toLanguage] |
||||
164 | ); |
||||
165 | |||||
166 | // delete meta records |
||||
167 | if (!empty($metaIDs)) { |
||||
168 | $database->delete('meta', 'id IN (' . implode(',', $metaIDs) . ')'); |
||||
169 | } |
||||
170 | |||||
171 | // delete blocks and their revisions |
||||
172 | if (!empty($revisionIDs)) { |
||||
173 | $database->delete( |
||||
174 | 'pages_blocks', |
||||
175 | 'revision_id IN (' . implode(',', $revisionIDs) . ')' |
||||
176 | ); |
||||
177 | } |
||||
178 | |||||
179 | // delete page and the revisions |
||||
180 | if (!empty($revisionIDs)) { |
||||
181 | $database->delete('pages', 'revision_id IN (' . implode(',', $revisionIDs) . ')'); |
||||
182 | } |
||||
183 | } |
||||
184 | } |
||||
185 | |||||
186 | // delete search indexes |
||||
187 | $database->delete('search_index', 'module = ? AND language = ?', ['pages', $toLanguage]); |
||||
188 | |||||
189 | // get all active pages |
||||
190 | $ids = BackendModel::getContainer()->get('database')->getColumn( |
||||
191 | 'SELECT id |
||||
192 | FROM pages AS i |
||||
193 | WHERE i.language = ? AND i.status = ?', |
||||
194 | [$fromLanguage, 'active'] |
||||
195 | ); |
||||
196 | |||||
197 | // loop |
||||
198 | foreach ($ids as $id) { |
||||
199 | // get data |
||||
200 | $sourceData = self::get($id, null, $fromLanguage); |
||||
201 | |||||
202 | // get and build meta |
||||
203 | $meta = $database->getRecord( |
||||
204 | 'SELECT * |
||||
205 | FROM meta |
||||
206 | WHERE id = ?', |
||||
207 | [$sourceData['meta_id']] |
||||
208 | ); |
||||
209 | |||||
210 | // remove id |
||||
211 | unset($meta['id']); |
||||
212 | |||||
213 | // init page |
||||
214 | $page = []; |
||||
215 | |||||
216 | // Get data from original page and copy image if one is set |
||||
217 | $serializedData = null; |
||||
218 | if ($sourceData['data'] !== null) { |
||||
219 | $data = $sourceData['data']; |
||||
220 | |||||
221 | if (array_key_exists('image', $data)) { |
||||
222 | $data['image'] = self::copyImage($data['image'], $meta['url']); |
||||
223 | } |
||||
224 | |||||
225 | $serializedData = serialize($data); |
||||
226 | } |
||||
227 | |||||
228 | // build page |
||||
229 | $page['id'] = $sourceData['id']; |
||||
230 | $page['user_id'] = BackendAuthentication::getUser()->getUserId(); |
||||
231 | $page['parent_id'] = $sourceData['parent_id']; |
||||
232 | $page['template_id'] = $sourceData['template_id']; |
||||
233 | $page['meta_id'] = (int) $database->insert('meta', $meta); |
||||
234 | $page['language'] = $toLanguage; |
||||
235 | $page['type'] = $sourceData['type']; |
||||
236 | $page['title'] = $sourceData['title']; |
||||
237 | $page['navigation_title'] = $sourceData['navigation_title']; |
||||
238 | $page['navigation_title_overwrite'] = $sourceData['navigation_title_overwrite']; |
||||
239 | $page['hidden'] = $sourceData['hidden']; |
||||
240 | $page['status'] = 'active'; |
||||
241 | $page['publish_on'] = BackendModel::getUTCDate(); |
||||
242 | $page['created_on'] = BackendModel::getUTCDate(); |
||||
243 | $page['edited_on'] = BackendModel::getUTCDate(); |
||||
244 | $page['allow_move'] = $sourceData['allow_move']; |
||||
245 | $page['allow_children'] = $sourceData['allow_children']; |
||||
246 | $page['allow_edit'] = $sourceData['allow_edit']; |
||||
247 | $page['allow_delete'] = $sourceData['allow_delete']; |
||||
248 | $page['sequence'] = $sourceData['sequence']; |
||||
249 | $page['data'] = $serializedData; |
||||
250 | |||||
251 | // insert page, store the id, we need it when building the blocks |
||||
252 | $revisionId = self::insert($page); |
||||
253 | |||||
254 | $blocks = []; |
||||
255 | |||||
256 | // get the blocks |
||||
257 | $sourceBlocks = self::getBlocks($id, null, $fromLanguage); |
||||
258 | |||||
259 | // loop blocks |
||||
260 | foreach ($sourceBlocks as $sourceBlock) { |
||||
261 | // build block |
||||
262 | $block = $sourceBlock; |
||||
263 | $block['revision_id'] = $revisionId; |
||||
264 | $block['created_on'] = BackendModel::getUTCDate(); |
||||
265 | $block['edited_on'] = BackendModel::getUTCDate(); |
||||
266 | |||||
267 | // Overwrite the extra_id of the old content block with the id of the new one |
||||
268 | if (in_array((int) $block['extra_id'], $contentBlockOldIds, true)) { |
||||
269 | $block['extra_id'] = $contentBlockIds[$block['extra_id']]; |
||||
270 | } |
||||
271 | |||||
272 | // Overwrite the extra_id of the old location widget with the id of the new one |
||||
273 | if ((count($locationWidgetOldIds) > 0) && in_array((int) $block['extra_id'], $locationWidgetOldIds, true)) { |
||||
274 | $block['extra_id'] = $locationWidgetIds[$block['extra_id']]; |
||||
275 | } |
||||
276 | |||||
277 | // Overwrite the extra_id of the old form widget with the id of the new one |
||||
278 | if ((count($formWidgetOldIds) > 0) && in_array((int) $block['extra_id'], $formWidgetOldIds, true)) { |
||||
279 | $block['extra_id'] = $formWidgetIds[(int) $block['extra_id']]; |
||||
280 | } |
||||
281 | |||||
282 | // add block |
||||
283 | $blocks[] = $block; |
||||
284 | } |
||||
285 | |||||
286 | // insert the blocks |
||||
287 | self::insertBlocks($blocks); |
||||
288 | |||||
289 | $text = ''; |
||||
290 | |||||
291 | // build search-text |
||||
292 | foreach ($blocks as $block) { |
||||
293 | $text .= ' ' . $block['html']; |
||||
294 | } |
||||
295 | |||||
296 | // add |
||||
297 | BackendSearchModel::saveIndex( |
||||
298 | 'Pages', |
||||
299 | (int) $page['id'], |
||||
300 | ['title' => $page['title'], 'text' => $text], |
||||
301 | $toLanguage |
||||
302 | ); |
||||
303 | |||||
304 | // get tags |
||||
305 | $tags = BackendTagsModel::getTags('pages', $id, 'string', $fromLanguage); |
||||
306 | |||||
307 | // save tags |
||||
308 | if ($tags != '') { |
||||
309 | $saveWorkingLanguage = BL::getWorkingLanguage(); |
||||
310 | |||||
311 | // If we don't set the working language to the target language, |
||||
312 | // BackendTagsModel::getUrl() will use the current working |
||||
313 | // language, possibly causing unnecessary '-2' suffixes in |
||||
314 | // tags.url |
||||
315 | BL::setWorkingLanguage($toLanguage); |
||||
316 | |||||
317 | BackendTagsModel::saveTags($page['id'], $tags, 'pages', $toLanguage); |
||||
318 | BL::setWorkingLanguage($saveWorkingLanguage); |
||||
319 | } |
||||
320 | } |
||||
321 | |||||
322 | // build cache |
||||
323 | self::buildCache($toLanguage); |
||||
324 | } |
||||
325 | |||||
326 | /** |
||||
327 | * @deprecated Will become private since it is only used in this class |
||||
328 | */ |
||||
329 | public static function createHtml( |
||||
330 | string $navigationType = 'page', |
||||
331 | int $depth = 0, |
||||
332 | int $parentId = BackendModel::HOME_PAGE_ID, |
||||
333 | string $html = '' |
||||
334 | ): string { |
||||
335 | $navigation = static::getCacheBuilder()->getNavigation(BL::getWorkingLanguage()); |
||||
336 | |||||
337 | // check if item exists |
||||
338 | if (isset($navigation[$navigationType][$depth][$parentId])) { |
||||
339 | // start html |
||||
340 | $html .= '<ul>' . "\n"; |
||||
341 | |||||
342 | // loop elements |
||||
343 | foreach ($navigation[$navigationType][$depth][$parentId] as $key => $aValue) { |
||||
344 | $html .= "\t<li>" . "\n"; |
||||
345 | $html .= "\t\t" . '<a href="#">' . htmlspecialchars($aValue['navigation_title']) . '</a>' . "\n"; |
||||
346 | |||||
347 | // insert recursive here! |
||||
348 | if (isset($navigation[$navigationType][$depth + 1][$key])) { |
||||
349 | $html .= self::createHtml( |
||||
350 | $navigationType, |
||||
351 | $depth + 1, |
||||
352 | $parentId, |
||||
353 | '' |
||||
354 | ); |
||||
355 | } |
||||
356 | |||||
357 | // add html |
||||
358 | $html .= '</li>' . "\n"; |
||||
359 | } |
||||
360 | |||||
361 | // end html |
||||
362 | $html .= '</ul>' . "\n"; |
||||
363 | } |
||||
364 | |||||
365 | // return |
||||
366 | return $html; |
||||
367 | } |
||||
368 | |||||
369 | /** |
||||
370 | * @param int $id The id of the page to delete. |
||||
371 | * @param string $language The language wherein the page will be deleted, |
||||
372 | * if not provided we will use the working language. |
||||
373 | * @param int $revisionId If specified the given revision will be deleted, used for deleting drafts. |
||||
374 | * |
||||
375 | * @return bool |
||||
376 | */ |
||||
377 | public static function delete(int $id, string $language = null, int $revisionId = null): bool |
||||
378 | { |
||||
379 | $language = $language ?? BL::getWorkingLanguage(); |
||||
380 | |||||
381 | // get database |
||||
382 | $database = BackendModel::getContainer()->get('database'); |
||||
383 | |||||
384 | // get record |
||||
385 | $page = self::get($id, $revisionId, $language); |
||||
386 | |||||
387 | // validate |
||||
388 | if (empty($page)) { |
||||
389 | return false; |
||||
390 | } |
||||
391 | if (!$page['allow_delete']) { |
||||
392 | return false; |
||||
393 | } |
||||
394 | |||||
395 | // get revision ids |
||||
396 | $revisionIDs = (array) $database->getColumn( |
||||
397 | 'SELECT i.revision_id |
||||
398 | FROM pages AS i |
||||
399 | WHERE i.id = ? AND i.language = ?', |
||||
400 | [$id, $language] |
||||
401 | ); |
||||
402 | |||||
403 | // get meta ids |
||||
404 | $metaIDs = (array) $database->getColumn( |
||||
405 | 'SELECT i.meta_id |
||||
406 | FROM pages AS i |
||||
407 | WHERE i.id = ? AND i.language = ?', |
||||
408 | [$id, $language] |
||||
409 | ); |
||||
410 | |||||
411 | // delete meta records |
||||
412 | if (!empty($metaIDs)) { |
||||
413 | $database->delete('meta', 'id IN (' . implode(',', $metaIDs) . ')'); |
||||
414 | } |
||||
415 | |||||
416 | // delete blocks and their revisions |
||||
417 | if (!empty($revisionIDs)) { |
||||
418 | $database->delete('pages_blocks', 'revision_id IN (' . implode(',', $revisionIDs) . ')'); |
||||
419 | } |
||||
420 | |||||
421 | // delete page and the revisions |
||||
422 | 2 | if (!empty($revisionIDs)) { |
|||
423 | $database->delete('pages', 'revision_id IN (' . implode(',', $revisionIDs) . ')'); |
||||
424 | } |
||||
425 | 2 | ||||
426 | 2 | // delete tags |
|||
427 | BackendTagsModel::saveTags($id, '', 'Pages'); |
||||
428 | |||||
429 | // return |
||||
430 | 2 | return true; |
|||
431 | } |
||||
432 | |||||
433 | 2 | public static function exists(int $pageId): bool |
|||
434 | 2 | { |
|||
435 | return (bool) BackendModel::getContainer()->get('database')->getVar( |
||||
436 | 'SELECT 1 |
||||
437 | FROM pages AS i |
||||
438 | WHERE i.id = ? AND i.language = ? AND i.status IN (?, ?) |
||||
439 | LIMIT 1', |
||||
440 | [$pageId, BL::getWorkingLanguage(), 'active', 'draft'] |
||||
441 | ); |
||||
442 | } |
||||
443 | 2 | ||||
444 | /** |
||||
445 | * Get the data for a record |
||||
446 | * |
||||
447 | 2 | * @param int $pageId The Id of the page to fetch. |
|||
448 | * @param int $revisionId |
||||
449 | * @param string $language The language to use while fetching the page. |
||||
450 | * |
||||
451 | 2 | * @return mixed False if the record can't be found, otherwise an array with all data. |
|||
452 | 2 | */ |
|||
453 | 2 | public static function get(int $pageId, int $revisionId = null, string $language = null) |
|||
454 | { |
||||
455 | 2 | // fetch revision if not specified |
|||
456 | 2 | if ($revisionId === null) { |
|||
457 | $revisionId = self::getLatestRevision($pageId, $language); |
||||
458 | } |
||||
459 | 2 | ||||
460 | 2 | // redefine |
|||
461 | $language = $language ?? BL::getWorkingLanguage(); |
||||
462 | |||||
463 | 2 | // get page (active version) |
|||
464 | $return = (array) BackendModel::getContainer()->get('database')->getRecord( |
||||
465 | 'SELECT i.*, UNIX_TIMESTAMP(i.publish_on) AS publish_on, UNIX_TIMESTAMP(i.created_on) AS created_on, |
||||
466 | UNIX_TIMESTAMP(i.edited_on) AS edited_on, |
||||
467 | IF(COUNT(e.id) > 0, 1, 0) AS has_extra, |
||||
468 | 2 | GROUP_CONCAT(b.extra_id) AS extra_ids |
|||
469 | 2 | FROM pages AS i |
|||
470 | LEFT OUTER JOIN pages_blocks AS b ON b.revision_id = i.revision_id AND b.extra_id IS NOT NULL |
||||
471 | LEFT OUTER JOIN modules_extras AS e ON e.id = b.extra_id AND e.type = ? |
||||
472 | 2 | WHERE i.id = ? AND i.revision_id = ? AND i.language = ? |
|||
473 | GROUP BY i.revision_id', |
||||
474 | ['block', $pageId, $revisionId, $language] |
||||
475 | ); |
||||
476 | |||||
477 | 2 | // no page? |
|||
478 | if (empty($return)) { |
||||
479 | return false; |
||||
480 | 2 | } |
|||
481 | |||||
482 | 2 | $return['move_allowed'] = (bool) $return['allow_move']; |
|||
483 | $return['children_allowed'] = (bool) $return['allow_children']; |
||||
484 | $return['delete_allowed'] = (bool) $return['allow_delete']; |
||||
485 | 2 | ||||
486 | if (self::isForbiddenToDelete($return['id'])) { |
||||
487 | 2 | $return['allow_delete'] = false; |
|||
488 | } |
||||
489 | |||||
490 | 2 | if (self::isForbiddenToMove($return['id'])) { |
|||
491 | $return['allow_move'] = false; |
||||
492 | 2 | } |
|||
493 | |||||
494 | if (self::isForbiddenToHaveChildren($return['id'])) { |
||||
495 | $return['allow_children'] = false; |
||||
496 | } |
||||
497 | |||||
498 | // convert into bools for use in template engine |
||||
499 | $return['edit_allowed'] = (bool) $return['allow_edit']; |
||||
500 | $return['has_extra'] = (bool) $return['has_extra']; |
||||
501 | |||||
502 | // unserialize data |
||||
503 | if ($return['data'] !== null) { |
||||
504 | $return['data'] = unserialize($return['data'], ['allowed_classes' => false]); |
||||
505 | } |
||||
506 | |||||
507 | // return |
||||
508 | return $return; |
||||
509 | } |
||||
510 | |||||
511 | public static function isForbiddenToDelete(int $pageId): bool |
||||
512 | { |
||||
513 | return in_array($pageId, [BackendModel::HOME_PAGE_ID, BackendModel::ERROR_PAGE_ID], true); |
||||
514 | } |
||||
515 | |||||
516 | 3 | public static function isForbiddenToMove(int $pageId): bool |
|||
517 | { |
||||
518 | return in_array($pageId, [BackendModel::HOME_PAGE_ID, BackendModel::ERROR_PAGE_ID], true); |
||||
519 | 3 | } |
|||
520 | 3 | ||||
521 | public static function isForbiddenToHaveChildren(int $pageId): bool |
||||
522 | { |
||||
523 | return $pageId === BackendModel::ERROR_PAGE_ID; |
||||
524 | } |
||||
525 | 3 | ||||
526 | public static function getBlocks(int $pageId, int $revisionId = null, string $language = null): array |
||||
527 | { |
||||
528 | // fetch revision if not specified |
||||
529 | 3 | if ($revisionId === null) { |
|||
530 | 3 | $revisionId = self::getLatestRevision($pageId, $language); |
|||
531 | 3 | } |
|||
532 | 3 | ||||
533 | 3 | // redefine |
|||
534 | 3 | $language = $language ?? BL::getWorkingLanguage(); |
|||
535 | |||||
536 | // get page (active version) |
||||
537 | return (array) BackendModel::getContainer()->get('database')->getRecords( |
||||
538 | 'SELECT b.*, UNIX_TIMESTAMP(b.created_on) AS created_on, UNIX_TIMESTAMP(b.edited_on) AS edited_on |
||||
539 | 3 | FROM pages_blocks AS b |
|||
540 | INNER JOIN pages AS i ON b.revision_id = i.revision_id |
||||
541 | WHERE i.id = ? AND i.revision_id = ? AND i.language = ? |
||||
542 | ORDER BY b.sequence ASC', |
||||
543 | [$pageId, $revisionId, $language] |
||||
544 | ); |
||||
545 | } |
||||
546 | |||||
547 | public static function getByTag(int $tagId): array |
||||
548 | { |
||||
549 | // get the items |
||||
550 | $items = (array) BackendModel::getContainer()->get('database')->getRecords( |
||||
551 | 'SELECT i.id AS url, i.title AS name, mt.module |
||||
552 | FROM modules_tags AS mt |
||||
553 | INNER JOIN tags AS t ON mt.tag_id = t.id |
||||
554 | INNER JOIN pages AS i ON mt.other_id = i.id |
||||
555 | WHERE mt.module = ? AND mt.tag_id = ? AND i.status = ? AND i.language = ?', |
||||
556 | ['pages', $tagId, 'active', BL::getWorkingLanguage()] |
||||
557 | ); |
||||
558 | |||||
559 | // loop items |
||||
560 | foreach ($items as &$row) { |
||||
561 | $row['url'] = BackendModel::createUrlForAction( |
||||
562 | 'Edit', |
||||
563 | 'Pages', |
||||
564 | null, |
||||
565 | ['id' => $row['url']] |
||||
566 | ); |
||||
567 | } |
||||
568 | |||||
569 | // return |
||||
570 | return $items; |
||||
571 | } |
||||
572 | |||||
573 | /** |
||||
574 | * Get the first child for a given parent |
||||
575 | * |
||||
576 | * @param int $pageId The Id of the page to get the first child for. |
||||
577 | * |
||||
578 | * @return mixed |
||||
579 | */ |
||||
580 | public static function getFirstChildId(int $pageId) |
||||
581 | { |
||||
582 | // get child |
||||
583 | $childId = (int) BackendModel::getContainer()->get('database')->getVar( |
||||
584 | 'SELECT i.id |
||||
585 | FROM pages AS i |
||||
586 | WHERE i.parent_id = ? AND i.status = ? AND i.language = ? |
||||
587 | ORDER BY i.sequence ASC |
||||
588 | LIMIT 1', |
||||
589 | [$pageId, 'active', BL::getWorkingLanguage()] |
||||
590 | ); |
||||
591 | |||||
592 | // return |
||||
593 | if ($childId !== 0) { |
||||
594 | return $childId; |
||||
595 | } |
||||
596 | |||||
597 | // fallback |
||||
598 | return false; |
||||
599 | } |
||||
600 | |||||
601 | public static function getFullUrl(int $id): string |
||||
602 | { |
||||
603 | $keys = static::getCacheBuilder()->getKeys(BL::getWorkingLanguage()); |
||||
604 | $hasMultiLanguages = BackendModel::getContainer()->getParameter('site.multilanguage'); |
||||
605 | |||||
606 | 2 | // available in generated file? |
|||
607 | if (isset($keys[$id])) { |
||||
608 | 2 | $url = $keys[$id]; |
|||
609 | } elseif ($id == self::NO_PARENT_PAGE_ID) { |
||||
610 | 2 | // parent id 0 hasn't an url |
|||
611 | 2 | $url = '/'; |
|||
612 | |||||
613 | // multilanguages? |
||||
614 | 2 | if ($hasMultiLanguages) { |
|||
615 | $url = '/' . BL::getWorkingLanguage(); |
||||
616 | } |
||||
617 | |||||
618 | // return the unique URL! |
||||
619 | return $url; |
||||
620 | } else { |
||||
621 | // not available |
||||
622 | return false; |
||||
623 | } |
||||
624 | |||||
625 | // if the is available in multiple languages we should add the current lang |
||||
626 | if ($hasMultiLanguages) { |
||||
627 | $url = '/' . BL::getWorkingLanguage() . '/' . $url; |
||||
628 | } else { |
||||
629 | // just prepend with slash |
||||
630 | $url = '/' . $url; |
||||
631 | } |
||||
632 | |||||
633 | // return the unique URL! |
||||
634 | return urldecode($url); |
||||
635 | } |
||||
636 | |||||
637 | public static function getLatestRevision(int $id, string $language = null): int |
||||
638 | { |
||||
639 | $language = $language ?? BL::getWorkingLanguage(); |
||||
640 | |||||
641 | return (int) BackendModel::getContainer()->get('database')->getVar( |
||||
642 | 'SELECT revision_id |
||||
643 | FROM pages AS i |
||||
644 | WHERE i.id = ? AND i.language = ? AND i.status != ?', |
||||
645 | [$id, $language, 'archive'] |
||||
646 | ); |
||||
647 | } |
||||
648 | |||||
649 | public static function getMaximumBlockId(): int |
||||
650 | { |
||||
651 | return (int) BackendModel::getContainer()->get('database')->getVar( |
||||
652 | 'SELECT MAX(i.id) FROM pages_blocks AS i' |
||||
653 | ); |
||||
654 | } |
||||
655 | |||||
656 | public static function getMaximumPageId($language = null): int |
||||
657 | { |
||||
658 | $language = $language ?? BL::getWorkingLanguage(); |
||||
659 | |||||
660 | // get the maximum id |
||||
661 | $maximumMenuId = (int) BackendModel::getContainer()->get('database')->getVar( |
||||
662 | 'SELECT MAX(i.id) FROM pages AS i WHERE i.language = ?', |
||||
663 | [$language] |
||||
664 | ); |
||||
665 | |||||
666 | // pages created by a user that isn't a god should have an id higher then 1000 |
||||
667 | // with this hack we can easily find which pages are added by a user |
||||
668 | if ($maximumMenuId < 1000 && !BackendAuthentication::getUser()->isGod()) { |
||||
669 | return $maximumMenuId + 1000; |
||||
670 | } |
||||
671 | |||||
672 | // fallback |
||||
673 | return $maximumMenuId; |
||||
674 | } |
||||
675 | |||||
676 | public static function getMaximumSequence(int $parentId, string $language = null): int |
||||
677 | { |
||||
678 | $language = $language ?? BL::getWorkingLanguage(); |
||||
679 | |||||
680 | // get the maximum sequence inside a certain leaf |
||||
681 | return (int) BackendModel::getContainer()->get('database')->getVar( |
||||
682 | 'SELECT MAX(i.sequence) |
||||
683 | FROM pages AS i |
||||
684 | WHERE i.language = ? AND i.parent_id = ?', |
||||
685 | [$language, $parentId] |
||||
686 | ); |
||||
687 | } |
||||
688 | |||||
689 | public static function getPagesForDropdown(string $language = null): array |
||||
690 | { |
||||
691 | $language = $language ?? BL::getWorkingLanguage(); |
||||
692 | $titles = []; |
||||
693 | $sequences = [ |
||||
694 | 'pages' => [], |
||||
695 | 'footer' => [], |
||||
696 | ]; |
||||
697 | $keys = []; |
||||
698 | $pages = []; |
||||
699 | $pageTree = self::getTree([self::NO_PARENT_PAGE_ID], null, 1, $language); |
||||
700 | $homepageTitle = $pageTree[1][BackendModel::HOME_PAGE_ID]['title'] ?? SpoonFilter::ucfirst(BL::lbl('Home')); |
||||
701 | |||||
702 | foreach ($pageTree as $pageTreePages) { |
||||
703 | foreach ((array) $pageTreePages as $pageID => $page) { |
||||
704 | $parentID = (int) $page['parent_id']; |
||||
705 | |||||
706 | $keys[$pageID] = trim(($keys[$parentID] ?? '') . '/' . $page['url'], '/'); |
||||
707 | |||||
708 | $sequences[$page['type'] === 'footer' ? 'footer' : 'pages'][$keys[$pageID]] = $pageID; |
||||
709 | |||||
710 | $parentTitle = str_replace([$homepageTitle . ' → ', $homepageTitle], '', $titles[$parentID] ?? ''); |
||||
711 | $titles[$pageID] = htmlspecialchars(trim($parentTitle . ' → ' . $page['title'], ' → ')); |
||||
712 | } |
||||
713 | } |
||||
714 | |||||
715 | foreach ($sequences as $pageGroupSortList) { |
||||
716 | ksort($pageGroupSortList); |
||||
717 | |||||
718 | foreach ($pageGroupSortList as $id) { |
||||
719 | if (isset($titles[$id])) { |
||||
720 | $pages[$id] = $titles[$id]; |
||||
721 | } |
||||
722 | } |
||||
723 | } |
||||
724 | |||||
725 | return $pages; |
||||
726 | } |
||||
727 | |||||
728 | private static function getSubTreeForDropdown( |
||||
729 | array $navigation, |
||||
730 | int $parentId, |
||||
731 | string $parentTitle, |
||||
732 | callable $attributesFunction |
||||
733 | ): array { |
||||
734 | if (!isset($navigation['page'][$parentId]) || empty($navigation['page'][$parentId])) { |
||||
735 | return []; |
||||
736 | } |
||||
737 | $tree = self::getEmptyTreeArray(); |
||||
738 | |||||
739 | foreach ($navigation['page'][$parentId] as $page) { |
||||
740 | $pageId = $page['page_id']; |
||||
741 | $pageTitle = htmlspecialchars($parentTitle . ' → ' . $page['navigation_title']); |
||||
742 | $tree['pages'][$pageId] = $pageTitle; |
||||
743 | $tree['attributes'][$pageId] = $attributesFunction($page); |
||||
744 | |||||
745 | $tree = self::mergeTreeForDropdownArrays( |
||||
746 | $tree, |
||||
747 | self::getSubTreeForDropdown($navigation, $pageId, $pageTitle, $attributesFunction) |
||||
748 | ); |
||||
749 | } |
||||
750 | |||||
751 | return $tree; |
||||
752 | } |
||||
753 | |||||
754 | private static function mergeTreeForDropdownArrays(array $tree, array $subTree, string $treeLabel = null): array |
||||
755 | { |
||||
756 | if (empty($subTree)) { |
||||
757 | return $tree; |
||||
758 | } |
||||
759 | |||||
760 | $tree['attributes'] += $subTree['attributes']; |
||||
761 | |||||
762 | if ($treeLabel === null) { |
||||
763 | $tree['pages'] += $subTree['pages']; |
||||
764 | |||||
765 | return $tree; |
||||
766 | } |
||||
767 | |||||
768 | $tree['pages'][$treeLabel] += $subTree['pages']; |
||||
769 | |||||
770 | return $tree; |
||||
771 | } |
||||
772 | |||||
773 | private static function getEmptyTreeArray(): array |
||||
774 | { |
||||
775 | return [ |
||||
776 | 'pages' => [], |
||||
777 | 'attributes' => [], |
||||
778 | ]; |
||||
779 | } |
||||
780 | |||||
781 | private static function getAttributesFunctionForTreeName( |
||||
782 | string $treeName, |
||||
783 | string $treeLabel, |
||||
784 | int $currentPageId |
||||
785 | ): callable { |
||||
786 | return function (array $page) use ($treeName, $treeLabel, $currentPageId) { |
||||
787 | $isCurrentPage = $currentPageId === $page['page_id']; |
||||
788 | |||||
789 | return [ |
||||
790 | 'data-tree-name' => $treeName, |
||||
791 | 'data-tree-label' => $treeLabel, |
||||
792 | 'data-allow-after' => (int) (!$isCurrentPage && $page['page_id'] !== BackendModel::HOME_PAGE_ID), |
||||
793 | 'data-allow-inside' => (int) (!$isCurrentPage && $page['allow_children']), |
||||
794 | 'data-allow-before' => (int) (!$isCurrentPage && $page['page_id'] !== BackendModel::HOME_PAGE_ID), |
||||
795 | ]; |
||||
796 | }; |
||||
797 | } |
||||
798 | |||||
799 | private static function addMainPageToTreeForDropdown( |
||||
800 | array $tree, |
||||
801 | string $branchLabel, |
||||
802 | callable $attributesFunction, |
||||
803 | array $page, |
||||
804 | array $navigation |
||||
805 | ): array { |
||||
806 | $tree['pages'][$branchLabel][$page['page_id']] = SpoonFilter::htmlentities($page['navigation_title']); |
||||
807 | $tree['attributes'][$page['page_id']] = $attributesFunction($page); |
||||
808 | |||||
809 | return self::mergeTreeForDropdownArrays( |
||||
810 | $tree, |
||||
811 | self::getSubTreeForDropdown( |
||||
812 | $navigation, |
||||
813 | $page['page_id'], |
||||
814 | $page['navigation_title'], |
||||
815 | $attributesFunction |
||||
816 | ), |
||||
817 | BL::lbl('MainNavigation') |
||||
818 | ); |
||||
819 | } |
||||
820 | |||||
821 | public static function getMoveTreeForDropdown(int $currentPageId, string $language = null): array |
||||
822 | { |
||||
823 | $navigation = static::getCacheBuilder()->getNavigation($language = $language ?? BL::getWorkingLanguage()); |
||||
824 | |||||
825 | $tree = self::addMainPageToTreeForDropdown( |
||||
826 | self::getEmptyTreeArray(), |
||||
827 | BL::lbl('MainNavigation'), |
||||
828 | 2 | self::getAttributesFunctionForTreeName('main', BL::lbl('MainNavigation'), $currentPageId), |
|||
829 | $navigation['page'][0][BackendModel::HOME_PAGE_ID], |
||||
830 | 2 | $navigation |
|||
831 | ); |
||||
832 | |||||
833 | 2 | $treeBranches = []; |
|||
834 | if (BackendModel::get('fork.settings')->get('Pages', 'meta_navigation', false)) { |
||||
835 | 2 | $treeBranches['meta'] = BL::lbl('Meta'); |
|||
836 | } |
||||
837 | $treeBranches['footer'] = BL::lbl('Footer'); |
||||
838 | 2 | $treeBranches['root'] = BL::lbl('Root'); |
|||
839 | |||||
840 | 2 | foreach ($treeBranches as $branchName => $branchLabel) { |
|||
841 | if (!isset($navigation[$branchName][0]) || !is_array($navigation[$branchName][0])) { |
||||
842 | continue; |
||||
843 | 2 | } |
|||
844 | 2 | ||||
845 | 2 | foreach ($navigation[$branchName][0] as $page) { |
|||
846 | 2 | $tree = self::addMainPageToTreeForDropdown( |
|||
847 | 2 | $tree, |
|||
848 | 2 | $branchLabel, |
|||
849 | self::getAttributesFunctionForTreeName($branchName, $branchLabel, $currentPageId), |
||||
850 | $page, |
||||
851 | 2 | $navigation |
|||
852 | ); |
||||
853 | } |
||||
854 | 2 | } |
|||
855 | |||||
856 | return $tree; |
||||
857 | } |
||||
858 | 2 | ||||
859 | public static function getSubtree(array $navigation, int $parentId): string |
||||
860 | { |
||||
861 | $html = ''; |
||||
862 | 2 | ||||
863 | // any elements |
||||
864 | if (isset($navigation['page'][$parentId]) && !empty($navigation['page'][$parentId])) { |
||||
865 | // start |
||||
866 | $html .= '<ul>' . "\n"; |
||||
867 | |||||
868 | // loop pages |
||||
869 | foreach ($navigation['page'][$parentId] as $page) { |
||||
870 | // start |
||||
871 | $html .= '<li id="page-' . $page['page_id'] . '" rel="' . $page['tree_type'] . '">' . "\n"; |
||||
872 | |||||
873 | // insert link |
||||
874 | $html .= ' <a href="' . BackendModel::createUrlForAction( |
||||
875 | 2 | 'Edit', |
|||
876 | null, |
||||
877 | 2 | null, |
|||
878 | ['id' => $page['page_id']] |
||||
879 | ) . '"><ins> </ins>' . htmlspecialchars($page['navigation_title']) . '</a>' . "\n"; |
||||
880 | 2 | ||||
881 | // get childs |
||||
882 | $html .= self::getSubtree($navigation, $page['page_id']); |
||||
883 | |||||
884 | // end |
||||
885 | $html .= '</li>' . "\n"; |
||||
886 | } |
||||
887 | |||||
888 | // end |
||||
889 | $html .= '</ul>' . "\n"; |
||||
890 | } |
||||
891 | |||||
892 | // return |
||||
893 | return $html; |
||||
894 | } |
||||
895 | |||||
896 | /** |
||||
897 | 2 | * Get all pages/level |
|||
898 | * |
||||
899 | * @param int[] $ids The parentIds. |
||||
900 | * @param array $data A holder for the generated data. |
||||
901 | 2 | * @param int $level The counter for the level. |
|||
902 | 2 | * @param string $language The language. |
|||
903 | * |
||||
904 | * @return array |
||||
905 | */ |
||||
906 | 2 | public static function getTree(array $ids, array $data = null, int $level = 1, string $language = null): array |
|||
907 | { |
||||
908 | $language = $language ?? BL::getWorkingLanguage(); |
||||
909 | 2 | ||||
910 | 2 | // get data |
|||
911 | $data[$level] = (array) BackendModel::getContainer()->get('database')->getRecords( |
||||
912 | 2 | 'SELECT |
|||
913 | 2 | i.id, i.title, i.parent_id, i.navigation_title, i.type, i.hidden, i.data, |
|||
914 | m.url, m.data AS meta_data, m.seo_follow, m.seo_index, i.allow_children, |
||||
915 | 2 | IF(COUNT(e.id) > 0, 1, 0) AS has_extra, |
|||
916 | 2 | GROUP_CONCAT(b.extra_id) AS extra_ids, |
|||
917 | 2 | IF(COUNT(p.id), 1, 0) AS has_children |
|||
918 | FROM pages AS i |
||||
919 | INNER JOIN meta AS m ON i.meta_id = m.id |
||||
920 | 2 | LEFT OUTER JOIN pages_blocks AS b ON b.revision_id = i.revision_id |
|||
921 | LEFT OUTER JOIN modules_extras AS e ON e.id = b.extra_id AND e.type = ? |
||||
922 | LEFT OUTER JOIN pages AS p |
||||
923 | 2 | ON p.parent_id = i.id |
|||
924 | AND p.status = "active" |
||||
925 | 2 | AND p.hidden = "N" |
|||
926 | AND p.data NOT LIKE "%s:9:\"is_action\";b:1;%" |
||||
927 | AND p.language = i.language |
||||
928 | 2 | WHERE i.parent_id IN (' . implode(', ', $ids) . ') |
|||
929 | AND i.status = ? AND i.language = ? |
||||
930 | 2 | GROUP BY i.revision_id |
|||
931 | ORDER BY i.sequence ASC', |
||||
932 | ['block', 'active', $language], |
||||
933 | 2 | 'id' |
|||
934 | 2 | ); |
|||
935 | 2 | ||||
936 | 2 | // get the childIDs |
|||
937 | $childIds = array_keys($data[$level]); |
||||
938 | |||||
939 | 2 | // build array |
|||
940 | 2 | if (!empty($data[$level])) { |
|||
941 | 2 | $data[$level] = array_map( |
|||
942 | 2 | function ($page) { |
|||
943 | 2 | $page['has_extra'] = (bool) $page['has_extra']; |
|||
944 | 2 | $page['has_children'] = (bool) $page['has_children']; |
|||
945 | 2 | ||||
946 | return $page; |
||||
947 | }, |
||||
948 | 2 | $data[$level] |
|||
949 | ); |
||||
950 | |||||
951 | 2 | return self::getTree($childIds, $data, ++$level, $language); |
|||
952 | 2 | } |
|||
953 | 2 | ||||
954 | unset($data[$level]); |
||||
955 | |||||
956 | 2 | return $data; |
|||
957 | } |
||||
958 | |||||
959 | public static function getTreeHTML(): string |
||||
960 | { |
||||
961 | $navigation = static::getCacheBuilder()->getNavigation(BL::getWorkingLanguage()); |
||||
962 | |||||
963 | // start HTML |
||||
964 | $html = '<h4>' . SpoonFilter::ucfirst(BL::lbl('MainNavigation')) . '</h4>' . "\n"; |
||||
965 | $html .= '<div class="clearfix" data-tree="main">' . "\n"; |
||||
966 | $html .= ' <ul>' . "\n"; |
||||
967 | $html .= ' <li id="page-"' . BackendModel::HOME_PAGE_ID . ' rel="home">'; |
||||
968 | |||||
969 | // create homepage anchor from title |
||||
970 | $homePage = self::get(BackendModel::HOME_PAGE_ID); |
||||
971 | $html .= ' <a href="' . BackendModel::createUrlForAction( |
||||
972 | 'Edit', |
||||
973 | null, |
||||
974 | null, |
||||
975 | ['id' => BackendModel::HOME_PAGE_ID] |
||||
976 | ) . '"><ins> </ins>' . htmlentities($homePage['title']) . '</a>' . "\n"; |
||||
977 | |||||
978 | // add subpages |
||||
979 | $html .= self::getSubtree($navigation, BackendModel::HOME_PAGE_ID); |
||||
980 | |||||
981 | // end |
||||
982 | $html .= ' </li>' . "\n"; |
||||
983 | $html .= ' </ul>' . "\n"; |
||||
984 | $html .= '</div>' . "\n"; |
||||
985 | |||||
986 | // only show meta if needed |
||||
987 | if (BackendModel::get('fork.settings')->get('Pages', 'meta_navigation', false)) { |
||||
988 | // meta pages |
||||
989 | $html .= '<h4>' . SpoonFilter::ucfirst(BL::lbl('Meta')) . '</h4>' . "\n"; |
||||
990 | $html .= '<div class="clearfix" data-tree="meta">' . "\n"; |
||||
991 | 2 | $html .= ' <ul>' . "\n"; |
|||
992 | |||||
993 | // are there any meta pages |
||||
994 | 2 | if (isset($navigation['meta'][0]) && !empty($navigation['meta'][0])) { |
|||
995 | 2 | // loop the items |
|||
996 | foreach ($navigation['meta'][0] as $page) { |
||||
997 | // start |
||||
998 | 2 | $html .= ' <li id="page-' . $page['page_id'] . '" rel="' . $page['tree_type'] . '">' . "\n"; |
|||
999 | |||||
1000 | 2 | // insert link |
|||
1001 | $html .= ' <a href="' . BackendModel::createUrlForAction( |
||||
1002 | 2 | 'Edit', |
|||
1003 | null, |
||||
1004 | null, |
||||
1005 | 2 | ['id' => $page['page_id']] |
|||
1006 | 2 | ) . '"><ins> </ins>' . htmlspecialchars($page['navigation_title']) . '</a>' . "\n"; |
|||
1007 | 2 | ||||
1008 | 2 | // insert subtree |
|||
1009 | 2 | $html .= self::getSubtree($navigation, $page['page_id']); |
|||
1010 | 2 | ||||
1011 | // end |
||||
1012 | $html .= ' </li>' . "\n"; |
||||
1013 | 2 | } |
|||
1014 | } |
||||
1015 | |||||
1016 | 2 | // end |
|||
1017 | $html .= ' </ul>' . "\n"; |
||||
1018 | $html .= '</div>' . "\n"; |
||||
1019 | } |
||||
1020 | |||||
1021 | 2 | // footer pages |
|||
1022 | 2 | $html .= '<h4>' . SpoonFilter::ucfirst(BL::lbl('Footer')) . '</h4>' . "\n"; |
|||
1023 | |||||
1024 | // start |
||||
1025 | 2 | $html .= '<div class="clearfix" data-tree="footer">' . "\n"; |
|||
1026 | $html .= ' <ul>' . "\n"; |
||||
1027 | 2 | ||||
1028 | // are there any footer pages |
||||
1029 | if (isset($navigation['footer'][0]) && !empty($navigation['footer'][0])) { |
||||
1030 | 2 | // loop the items |
|||
1031 | 2 | foreach ($navigation['footer'][0] as $page) { |
|||
1032 | // start |
||||
1033 | $html .= ' <li id="page-' . $page['page_id'] . '" rel="' . $page['tree_type'] . '">' . "\n"; |
||||
1034 | 2 | ||||
1035 | // insert link |
||||
1036 | 2 | $html .= ' <a href="' . BackendModel::createUrlForAction( |
|||
1037 | 'Edit', |
||||
1038 | null, |
||||
1039 | 2 | null, |
|||
1040 | 2 | ['id' => $page['page_id']] |
|||
1041 | 2 | ) . '"><ins> </ins>' . htmlspecialchars($page['navigation_title']) . '</a>' . "\n"; |
|||
1042 | 2 | ||||
1043 | 2 | // insert subtree |
|||
1044 | 2 | $html .= self::getSubtree($navigation, $page['page_id']); |
|||
1045 | |||||
1046 | // end |
||||
1047 | 2 | $html .= ' </li>' . "\n"; |
|||
1048 | } |
||||
1049 | } |
||||
1050 | 2 | ||||
1051 | // end |
||||
1052 | $html .= ' </ul>' . "\n"; |
||||
1053 | $html .= '</div>' . "\n"; |
||||
1054 | 2 | ||||
1055 | 2 | // are there any root pages |
|||
1056 | if (isset($navigation['root'][0]) && !empty($navigation['root'][0])) { |
||||
1057 | // meta pages |
||||
1058 | $html .= '<h4>' . SpoonFilter::ucfirst(BL::lbl('Root')) . '</h4>' . "\n"; |
||||
1059 | 2 | ||||
1060 | // start |
||||
1061 | $html .= '<div class="clearfix" data-tree="root">' . "\n"; |
||||
1062 | $html .= ' <ul>' . "\n"; |
||||
1063 | |||||
1064 | // loop the items |
||||
1065 | foreach ($navigation['root'][0] as $page) { |
||||
1066 | // start |
||||
1067 | $html .= ' <li id="page-' . $page['page_id'] . '" rel="' . $page['tree_type'] . '">' . "\n"; |
||||
1068 | |||||
1069 | // insert link |
||||
1070 | $html .= ' <a href="' . BackendModel::createUrlForAction( |
||||
1071 | 'Edit', |
||||
1072 | null, |
||||
1073 | null, |
||||
1074 | ['id' => $page['page_id']] |
||||
1075 | ) . '"><ins> </ins>' . htmlspecialchars($page['navigation_title']) . '</a>' . "\n"; |
||||
1076 | |||||
1077 | // insert subtree |
||||
1078 | $html .= self::getSubtree($navigation, $page['page_id']); |
||||
1079 | |||||
1080 | // end |
||||
1081 | $html .= ' </li>' . "\n"; |
||||
1082 | } |
||||
1083 | |||||
1084 | // end |
||||
1085 | $html .= ' </ul>' . "\n"; |
||||
1086 | $html .= '</div>' . "\n"; |
||||
1087 | } |
||||
1088 | |||||
1089 | // return |
||||
1090 | return $html; |
||||
1091 | } |
||||
1092 | |||||
1093 | private static function pageIsChildOfParent(array $navigation, int $childId, int $parentId): bool |
||||
1094 | { |
||||
1095 | if (isset($navigation['page'][$parentId]) && !empty($navigation['page'][$parentId])) { |
||||
1096 | foreach ($navigation['page'][$parentId] as $page) { |
||||
1097 | if ($page['page_id'] === $childId) { |
||||
1098 | return true; |
||||
1099 | } |
||||
1100 | |||||
1101 | if (self::pageIsChildOfParent($navigation, $childId, $page['page_id'])) { |
||||
1102 | return true; |
||||
1103 | } |
||||
1104 | } |
||||
1105 | } |
||||
1106 | |||||
1107 | return false; |
||||
1108 | } |
||||
1109 | |||||
1110 | public static function getTreeNameForPageId(int $pageId): ?string |
||||
1111 | { |
||||
1112 | $navigation = static::getCacheBuilder()->getNavigation(BL::getWorkingLanguage()); |
||||
1113 | |||||
1114 | if ($pageId === BackendModel::HOME_PAGE_ID || self::pageIsChildOfParent($navigation, $pageId, BackendModel::HOME_PAGE_ID)) { |
||||
1115 | return 'main'; |
||||
1116 | } |
||||
1117 | |||||
1118 | $treeNames = ['footer', 'root']; |
||||
1119 | |||||
1120 | // only show meta if needed |
||||
1121 | if (BackendModel::get('fork.settings')->get('Pages', 'meta_navigation', false)) { |
||||
1122 | $treeNames[] = 'meta'; |
||||
1123 | } |
||||
1124 | |||||
1125 | foreach ($treeNames as $treeName) { |
||||
1126 | if (isset($navigation[$treeName][0]) && !empty($navigation[$treeName][0])) { |
||||
1127 | // loop the items |
||||
1128 | foreach ($navigation[$treeName][0] as $page) { |
||||
1129 | if ($pageId === $page['page_id'] || self::pageIsChildOfParent($navigation, $pageId, $page['page_id'])) { |
||||
1130 | return $treeName; |
||||
1131 | } |
||||
1132 | } |
||||
1133 | } |
||||
1134 | } |
||||
1135 | |||||
1136 | return null; |
||||
1137 | } |
||||
1138 | |||||
1139 | public static function getTypes(): array |
||||
1140 | { |
||||
1141 | return [ |
||||
1142 | 'rich_text' => BL::lbl('Editor'), |
||||
1143 | 'block' => BL::lbl('Module'), |
||||
1144 | 'widget' => BL::lbl('Widget'), |
||||
1145 | 'usertemplate' => BL::lbl('UserTemplate'), |
||||
1146 | ]; |
||||
1147 | } |
||||
1148 | |||||
1149 | public static function getUrl(string $url, int $id = null, int $parentId = null, bool $isAction = false): string |
||||
1150 | { |
||||
1151 | $parentIds = [$parentId ?? self::NO_PARENT_PAGE_ID]; |
||||
1152 | |||||
1153 | // 0, 1, 2, 3, 4 are all top levels, so we should place them on the same level |
||||
1154 | if ($parentId === self::NO_PARENT_PAGE_ID |
||||
1155 | || $parentId === BackendModel::HOME_PAGE_ID |
||||
1156 | || $parentId === 2 |
||||
1157 | || $parentId === 3 |
||||
1158 | || $parentId === 4 |
||||
1159 | ) { |
||||
1160 | $parentIds = [ |
||||
1161 | self::NO_PARENT_PAGE_ID, |
||||
1162 | BackendModel::HOME_PAGE_ID, |
||||
1163 | 2, |
||||
1164 | 3, |
||||
1165 | 4, |
||||
1166 | ]; |
||||
1167 | } |
||||
1168 | |||||
1169 | // get database |
||||
1170 | $database = BackendModel::getContainer()->get('database'); |
||||
1171 | |||||
1172 | // no specific id |
||||
1173 | if ($id === null) { |
||||
1174 | // no items? |
||||
1175 | if ((bool) $database->getVar( |
||||
1176 | 'SELECT 1 |
||||
1177 | FROM pages AS i |
||||
1178 | INNER JOIN meta AS m ON i.meta_id = m.id |
||||
1179 | WHERE i.parent_id IN(' . implode(',', $parentIds) . ') AND i.status = ? AND m.url = ? |
||||
1180 | AND i.language = ? |
||||
1181 | LIMIT 1', |
||||
1182 | ['active', $url, BL::getWorkingLanguage()] |
||||
1183 | ) |
||||
1184 | ) { |
||||
1185 | // add a number |
||||
1186 | $url = BackendModel::addNumber($url); |
||||
1187 | |||||
1188 | // recall this method, but with a new URL |
||||
1189 | return self::getUrl($url, null, $parentId, $isAction); |
||||
1190 | } |
||||
1191 | } else { |
||||
1192 | // one item should be ignored |
||||
1193 | // there are items so, call this method again. |
||||
1194 | if ((bool) $database->getVar( |
||||
1195 | 'SELECT 1 |
||||
1196 | FROM pages AS i |
||||
1197 | INNER JOIN meta AS m ON i.meta_id = m.id |
||||
1198 | WHERE i.parent_id IN(' . implode(',', $parentIds) . ') AND i.status = ? |
||||
1199 | AND m.url = ? AND i.id != ? AND i.language = ? |
||||
1200 | LIMIT 1', |
||||
1201 | ['active', $url, $id, BL::getWorkingLanguage()] |
||||
1202 | ) |
||||
1203 | ) { |
||||
1204 | // add a number |
||||
1205 | $url = BackendModel::addNumber($url); |
||||
1206 | |||||
1207 | // recall this method, but with a new URL |
||||
1208 | return self::getUrl($url, $id, $parentId, $isAction); |
||||
1209 | } |
||||
1210 | } |
||||
1211 | |||||
1212 | // get full URL |
||||
1213 | $fullUrl = self::getFullUrl($parentId) . '/' . $url; |
||||
1214 | |||||
1215 | // get info about parent page |
||||
1216 | $parentPageInfo = self::get($parentId, null, BL::getWorkingLanguage()); |
||||
1217 | |||||
1218 | // does the parent have extras? |
||||
1219 | if (!$isAction && is_array($parentPageInfo) && isset($parentPageInfo['has_extra']) && $parentPageInfo['has_extra']) { |
||||
1220 | // set locale |
||||
1221 | FrontendLanguage::setLocale(BL::getWorkingLanguage(), true); |
||||
1222 | |||||
1223 | // get all on-site action |
||||
1224 | $actions = FrontendLanguage::getActions(); |
||||
1225 | |||||
1226 | // if the new URL conflicts with an action we should rebuild the URL |
||||
1227 | if (in_array($url, $actions)) { |
||||
1228 | // add a number |
||||
1229 | $url = BackendModel::addNumber($url); |
||||
1230 | |||||
1231 | // recall this method, but with a new URL |
||||
1232 | return self::getUrl($url, $id, $parentId, $isAction); |
||||
1233 | } |
||||
1234 | } |
||||
1235 | |||||
1236 | // check if folder exists |
||||
1237 | if (is_dir(PATH_WWW . '/' . $fullUrl) || is_file(PATH_WWW . '/' . $fullUrl)) { |
||||
1238 | // add a number |
||||
1239 | $url = BackendModel::addNumber($url); |
||||
1240 | |||||
1241 | // recall this method, but with a new URL |
||||
1242 | return self::getUrl($url, $id, $parentId, $isAction); |
||||
1243 | } |
||||
1244 | |||||
1245 | // check if it is an application |
||||
1246 | if (array_key_exists(trim($fullUrl, '/'), ForkController::getRoutes())) { |
||||
1247 | // add a number |
||||
1248 | $url = BackendModel::addNumber($url); |
||||
1249 | |||||
1250 | // recall this method, but with a new URL |
||||
1251 | return self::getUrl($url, $id, $parentId, $isAction); |
||||
1252 | } |
||||
1253 | |||||
1254 | // return the unique URL! |
||||
1255 | return $url; |
||||
1256 | } |
||||
1257 | |||||
1258 | public static function insert(array $page): int |
||||
1259 | { |
||||
1260 | return (int) BackendModel::getContainer()->get('database')->insert('pages', $page); |
||||
1261 | } |
||||
1262 | |||||
1263 | /** |
||||
1264 | * Insert multiple blocks at once |
||||
1265 | * |
||||
1266 | * @param array $blocks The blocks to insert. |
||||
1267 | */ |
||||
1268 | public static function insertBlocks(array $blocks): void |
||||
1269 | { |
||||
1270 | if (empty($blocks)) { |
||||
1271 | return; |
||||
1272 | } |
||||
1273 | |||||
1274 | // get database |
||||
1275 | $database = BackendModel::getContainer()->get('database'); |
||||
1276 | |||||
1277 | // loop blocks |
||||
1278 | foreach ($blocks as $key => $block) { |
||||
1279 | if ($block['extra_type'] === 'usertemplate') { |
||||
1280 | $blocks[$key]['extra_id'] = null; |
||||
1281 | } |
||||
1282 | } |
||||
1283 | |||||
1284 | // insert blocks |
||||
1285 | $database->insert('pages_blocks', $blocks); |
||||
1286 | } |
||||
1287 | |||||
1288 | public static function loadUserTemplates(): array |
||||
1289 | { |
||||
1290 | $themePath = FRONTEND_PATH . '/Themes/'; |
||||
1291 | $themePath .= BackendModel::get('fork.settings')->get('Core', 'theme', 'Fork'); |
||||
1292 | $filePath = $themePath . '/Core/Layout/Templates/UserTemplates/Templates.json'; |
||||
1293 | |||||
1294 | $userTemplates = []; |
||||
1295 | |||||
1296 | $fs = new Filesystem(); |
||||
1297 | if ($fs->exists($filePath)) { |
||||
1298 | $userTemplates = json_decode(file_get_contents($filePath), true); |
||||
1299 | |||||
1300 | foreach ($userTemplates as &$userTemplate) { |
||||
1301 | $userTemplate['file'] = |
||||
1302 | '/src/Frontend/Themes/' . |
||||
1303 | BackendModel::get('fork.settings')->get('Core', 'theme', 'Fork') . |
||||
1304 | '/Core/Layout/Templates/UserTemplates/' . |
||||
1305 | $userTemplate['file']; |
||||
1306 | } |
||||
1307 | } |
||||
1308 | |||||
1309 | return $userTemplates; |
||||
1310 | } |
||||
1311 | |||||
1312 | /** |
||||
1313 | * Move a page |
||||
1314 | * |
||||
1315 | * @param int $pageId The id for the page that has to be moved. |
||||
1316 | * @param int $droppedOnPageId The id for the page where to page has been dropped on. |
||||
1317 | * @param string $typeOfDrop The type of drop, possible values are: before, after, inside. |
||||
1318 | * @param string $tree The tree the item is dropped on, possible values are: main, meta, footer, root. |
||||
1319 | * @param string $language The language to use, if not provided we will use the working language. |
||||
1320 | * |
||||
1321 | * @return bool |
||||
1322 | */ |
||||
1323 | public static function move( |
||||
1324 | int $pageId, |
||||
1325 | int $droppedOnPageId, |
||||
1326 | string $typeOfDrop, |
||||
1327 | string $tree, |
||||
1328 | string $language = null |
||||
1329 | ): bool { |
||||
1330 | $typeOfDrop = SpoonFilter::getValue($typeOfDrop, self::POSSIBLE_TYPES_OF_DROP, self::TYPE_OF_DROP_INSIDE); |
||||
1331 | $tree = SpoonFilter::getValue($tree, ['main', 'meta', 'footer', 'root'], 'root'); |
||||
1332 | $language = $language ?? BL::getWorkingLanguage(); |
||||
1333 | |||||
1334 | // When dropping on the main navigation it should be added as a child of the home page |
||||
1335 | if ($tree === 'main' && $droppedOnPageId === 0) { |
||||
1336 | $droppedOnPageId = BackendModel::HOME_PAGE_ID; |
||||
1337 | $typeOfDrop = self::TYPE_OF_DROP_INSIDE; |
||||
1338 | } |
||||
1339 | |||||
1340 | // reset type of drop for special pages |
||||
1341 | if ($droppedOnPageId === BackendModel::HOME_PAGE_ID || $droppedOnPageId === self::NO_PARENT_PAGE_ID) { |
||||
1342 | $typeOfDrop = self::TYPE_OF_DROP_INSIDE; |
||||
1343 | } |
||||
1344 | |||||
1345 | $page = self::get($pageId, null, $language); |
||||
1346 | $droppedOnPage = self::get( |
||||
1347 | ($droppedOnPageId === self::NO_PARENT_PAGE_ID ? BackendModel::HOME_PAGE_ID : $droppedOnPageId), |
||||
1348 | null, |
||||
1349 | $language |
||||
1350 | ); |
||||
1351 | |||||
1352 | if (empty($page) || empty($droppedOnPage)) { |
||||
1353 | return false; |
||||
1354 | } |
||||
1355 | |||||
1356 | try { |
||||
1357 | $newParent = self::getNewParent($droppedOnPageId, $typeOfDrop, $droppedOnPage); |
||||
1358 | } catch (InvalidArgumentException $invalidArgumentException) { |
||||
1359 | // parent doesn't allow children |
||||
1360 | return false; |
||||
1361 | } |
||||
1362 | |||||
1363 | self::recalculateSequenceAfterMove( |
||||
1364 | $typeOfDrop, |
||||
1365 | self::getNewType($droppedOnPageId, $tree, $newParent, $droppedOnPage), |
||||
1366 | $pageId, |
||||
1367 | $language, |
||||
1368 | $newParent, |
||||
1369 | $droppedOnPage['id'] |
||||
1370 | ); |
||||
1371 | |||||
1372 | self::updateUrlAfterMove($pageId, $page, $newParent); |
||||
1373 | |||||
1374 | return true; |
||||
1375 | } |
||||
1376 | |||||
1377 | public static function update(array $page): int |
||||
1378 | { |
||||
1379 | // get database |
||||
1380 | $database = BackendModel::getContainer()->get('database'); |
||||
1381 | |||||
1382 | if (self::isForbiddenToDelete($page['id'])) { |
||||
1383 | $page['allow_delete'] = false; |
||||
1384 | } |
||||
1385 | |||||
1386 | if (self::isForbiddenToMove($page['id'])) { |
||||
1387 | $page['allow_move'] = false; |
||||
1388 | } |
||||
1389 | |||||
1390 | if (self::isForbiddenToHaveChildren($page['id'])) { |
||||
1391 | $page['allow_children'] = false; |
||||
1392 | } |
||||
1393 | |||||
1394 | // update old revisions |
||||
1395 | if ($page['status'] != 'draft') { |
||||
1396 | $database->update( |
||||
1397 | 'pages', |
||||
1398 | ['status' => 'archive'], |
||||
1399 | 'id = ? AND language = ?', |
||||
1400 | [(int) $page['id'], $page['language']] |
||||
1401 | ); |
||||
1402 | } else { |
||||
1403 | $database->delete( |
||||
1404 | 'pages', |
||||
1405 | 'id = ? AND user_id = ? AND status = ? AND language = ?', |
||||
1406 | [(int) $page['id'], BackendAuthentication::getUser()->getUserId(), 'draft', $page['language']] |
||||
1407 | ); |
||||
1408 | } |
||||
1409 | |||||
1410 | // insert |
||||
1411 | $page['revision_id'] = (int) $database->insert('pages', $page); |
||||
1412 | |||||
1413 | // how many revisions should we keep |
||||
1414 | $rowsToKeep = (int) BackendModel::get('fork.settings')->get('Pages', 'max_num_revisions', 20); |
||||
1415 | |||||
1416 | // get revision-ids for items to keep |
||||
1417 | $revisionIdsToKeep = (array) $database->getColumn( |
||||
1418 | 'SELECT i.revision_id |
||||
1419 | FROM pages AS i |
||||
1420 | WHERE i.id = ? AND i.status = ? |
||||
1421 | ORDER BY i.edited_on DESC |
||||
1422 | LIMIT ?', |
||||
1423 | [(int) $page['id'], 'archive', $rowsToKeep] |
||||
1424 | ); |
||||
1425 | |||||
1426 | // delete other revisions |
||||
1427 | if (!empty($revisionIdsToKeep)) { |
||||
1428 | // because blocks are linked by revision we should get all revisions we want to delete |
||||
1429 | $revisionsToDelete = (array) $database->getColumn( |
||||
1430 | 'SELECT i.revision_id |
||||
1431 | FROM pages AS i |
||||
1432 | WHERE i.id = ? AND i.status = ? AND i.revision_id NOT IN(' . implode(', ', $revisionIdsToKeep) . ')', |
||||
1433 | [(int) $page['id'], 'archive'] |
||||
1434 | ); |
||||
1435 | |||||
1436 | // any revisions to delete |
||||
1437 | if (!empty($revisionsToDelete)) { |
||||
1438 | $database->delete('pages', 'revision_id IN(' . implode(', ', $revisionsToDelete) . ')'); |
||||
1439 | $database->delete('pages_blocks', 'revision_id IN(' . implode(', ', $revisionsToDelete) . ')'); |
||||
1440 | } |
||||
1441 | } |
||||
1442 | |||||
1443 | // return the new revision id |
||||
1444 | return $page['revision_id']; |
||||
1445 | } |
||||
1446 | |||||
1447 | /** |
||||
1448 | * @param array $page |
||||
1449 | */ |
||||
1450 | public static function updateRevisionData(int $pageId, int $revisionId, array $data): void |
||||
1451 | { |
||||
1452 | // get database |
||||
1453 | $database = BackendModel::getContainer()->get('database'); |
||||
1454 | |||||
1455 | // serialize the data |
||||
1456 | $data['data'] = serialize($data['data']); |
||||
1457 | |||||
1458 | $database->update( |
||||
1459 | 'pages', |
||||
1460 | $data, |
||||
1461 | 'id = ? AND revision_id = ?', |
||||
1462 | [$pageId, $revisionId] |
||||
1463 | ); |
||||
1464 | } |
||||
1465 | |||||
1466 | /** |
||||
1467 | * Switch templates for all existing pages |
||||
1468 | * |
||||
1469 | * @param int $oldTemplateId The id of the new template to replace. |
||||
1470 | * @param int $newTemplateId The id of the new template to use. |
||||
1471 | * @param bool $overwrite Overwrite all pages with default blocks. |
||||
1472 | */ |
||||
1473 | public static function updatePagesTemplates(int $oldTemplateId, int $newTemplateId, bool $overwrite = false): void |
||||
1474 | { |
||||
1475 | // fetch new template data |
||||
1476 | $newTemplate = BackendExtensionsModel::getTemplate($newTemplateId); |
||||
1477 | $newTemplate['data'] = @unserialize($newTemplate['data'], ['allowed_classes' => false]); |
||||
1478 | |||||
1479 | // fetch all pages |
||||
1480 | $pages = (array) BackendModel::getContainer()->get('database')->getRecords( |
||||
1481 | 'SELECT * |
||||
1482 | FROM pages |
||||
1483 | WHERE template_id = ? AND status IN (?, ?)', |
||||
1484 | [$oldTemplateId, 'active', 'draft'] |
||||
1485 | ); |
||||
1486 | |||||
1487 | // there is no active/draft page with the old template id |
||||
1488 | if (empty($pages)) { |
||||
1489 | return; |
||||
1490 | } |
||||
1491 | |||||
1492 | // loop pages |
||||
1493 | foreach ($pages as $page) { |
||||
1494 | // fetch blocks |
||||
1495 | $blocksContent = self::getBlocks($page['id'], $page['revision_id'], $page['language']); |
||||
1496 | |||||
1497 | // unset revision id |
||||
1498 | unset($page['revision_id']); |
||||
1499 | |||||
1500 | // change template |
||||
1501 | $page['template_id'] = $newTemplateId; |
||||
1502 | |||||
1503 | // save new page revision |
||||
1504 | $page['revision_id'] = self::update($page); |
||||
1505 | |||||
1506 | // overwrite all blocks with current defaults |
||||
1507 | if ($overwrite) { |
||||
1508 | $blocksContent = []; |
||||
1509 | |||||
1510 | // fetch default blocks for this page |
||||
1511 | $defaultBlocks = []; |
||||
1512 | if (isset($newTemplate['data']['default_extras_' . $page['language']])) { |
||||
1513 | $defaultBlocks = $newTemplate['data']['default_extras_' . $page['language']]; |
||||
1514 | } elseif (isset($newTemplate['data']['default_extras'])) { |
||||
1515 | $defaultBlocks = $newTemplate['data']['default_extras']; |
||||
1516 | } |
||||
1517 | |||||
1518 | // loop positions |
||||
1519 | foreach ($defaultBlocks as $position => $blocks) { |
||||
1520 | 1 | // loop blocks |
|||
1521 | foreach ($blocks as $extraId) { |
||||
1522 | 1 | // add to the list |
|||
1523 | 1 | $blocksContent[] = [ |
|||
1524 | 1 | 'revision_id' => $page['revision_id'], |
|||
1525 | 'position' => $position, |
||||
1526 | 1 | 'extra_id' => $extraId, |
|||
1527 | 1 | 'extra_type' => 'rich_text', |
|||
1528 | 1 | 'html' => '', |
|||
1529 | 'created_on' => BackendModel::getUTCDate(), |
||||
1530 | 1 | 'edited_on' => BackendModel::getUTCDate(), |
|||
1531 | 1 | 'visible' => true, |
|||
1532 | 'sequence' => count($defaultBlocks[$position]) - 1, |
||||
1533 | ]; |
||||
1534 | 1 | } |
|||
1535 | } |
||||
1536 | } else { |
||||
1537 | // don't overwrite blocks, just re-use existing |
||||
1538 | // set new page revision id |
||||
1539 | foreach ($blocksContent as &$block) { |
||||
1540 | $block['revision_id'] = $page['revision_id']; |
||||
1541 | $block['created_on'] = BackendModel::getUTCDate(null, $block['created_on']); |
||||
1542 | $block['edited_on'] = BackendModel::getUTCDate(null, $block['edited_on']); |
||||
1543 | } |
||||
1544 | } |
||||
1545 | |||||
1546 | // insert the blocks |
||||
1547 | self::insertBlocks($blocksContent); |
||||
1548 | } |
||||
1549 | } |
||||
1550 | |||||
1551 | public static function getEncodedRedirectUrl(string $redirectUrl): string |
||||
1552 | { |
||||
1553 | preg_match('!(http[s]?)://(.*)!i', $redirectUrl, $matches); |
||||
1554 | $urlChunks = explode('/', $matches[2]); |
||||
1555 | if (!empty($urlChunks)) { |
||||
1556 | // skip domain name |
||||
1557 | $domain = array_shift($urlChunks); |
||||
1558 | foreach ($urlChunks as &$urlChunk) { |
||||
1559 | $urlChunk = rawurlencode($urlChunk); |
||||
1560 | } |
||||
1561 | unset($urlChunk); |
||||
1562 | $redirectUrl = $matches[1] . '://' . $domain . '/' . implode('/', $urlChunks); |
||||
1563 | } |
||||
1564 | |||||
1565 | return $redirectUrl; |
||||
1566 | } |
||||
1567 | |||||
1568 | private static function getNewParent(int $droppedOnPageId, string $typeOfDrop, array $droppedOnPage): int |
||||
1569 | { |
||||
1570 | if ($droppedOnPageId === self::NO_PARENT_PAGE_ID) { |
||||
1571 | return self::NO_PARENT_PAGE_ID; |
||||
1572 | } |
||||
1573 | |||||
1574 | if ($typeOfDrop === self::TYPE_OF_DROP_INSIDE) { |
||||
1575 | // check if item allows children |
||||
1576 | if (!$droppedOnPage['allow_children']) { |
||||
1577 | throw new InvalidArgumentException('Parent page is not allowed to have child pages'); |
||||
1578 | } |
||||
1579 | |||||
1580 | return $droppedOnPage['id']; |
||||
1581 | } |
||||
1582 | |||||
1583 | // if the item has to be moved before or after |
||||
1584 | return $droppedOnPage['parent_id']; |
||||
1585 | } |
||||
1586 | |||||
1587 | private static function getNewType(int $droppedOnPageId, string $tree, int $newParent, array $droppedOnPage): string |
||||
1588 | { |
||||
1589 | if ($droppedOnPageId === self::NO_PARENT_PAGE_ID) { |
||||
1590 | if ($tree === 'footer') { |
||||
1591 | return 'footer'; |
||||
1592 | } |
||||
1593 | |||||
1594 | if ($tree === 'meta') { |
||||
1595 | return 'meta'; |
||||
1596 | } |
||||
1597 | |||||
1598 | return 'root'; |
||||
1599 | } |
||||
1600 | |||||
1601 | if ($newParent === self::NO_PARENT_PAGE_ID) { |
||||
1602 | return $droppedOnPage['type']; |
||||
1603 | } |
||||
1604 | |||||
1605 | return 'page'; |
||||
1606 | } |
||||
1607 | |||||
1608 | private static function recalculateSequenceAfterMove( |
||||
1609 | string $typeOfDrop, |
||||
1610 | string $newType, |
||||
1611 | int $pageId, |
||||
1612 | string $language, |
||||
1613 | string $newParent, |
||||
1614 | int $droppedOnPageId |
||||
1615 | ): void { |
||||
1616 | $database = BackendModel::getContainer()->get('database'); |
||||
1617 | |||||
1618 | // calculate new sequence for items that should be moved inside |
||||
1619 | if ($typeOfDrop === self::TYPE_OF_DROP_INSIDE) { |
||||
1620 | $newSequence = (int) $database->getVar( |
||||
1621 | 'SELECT MAX(i.sequence) |
||||
1622 | FROM pages AS i |
||||
1623 | WHERE i.id = ? AND i.language = ? AND i.status = ?', |
||||
1624 | [$newParent, $language, 'active'] |
||||
1625 | ) + 1; |
||||
1626 | |||||
1627 | $database->update( |
||||
1628 | 'pages', |
||||
1629 | [ |
||||
1630 | 'parent_id' => $newParent, |
||||
1631 | 'sequence' => $newSequence, |
||||
1632 | 'type' => $newType |
||||
1633 | ], |
||||
1634 | 'id = ? AND language = ? AND status = ?', |
||||
1635 | [$pageId, $language, 'active'] |
||||
1636 | ); |
||||
1637 | |||||
1638 | return; |
||||
1639 | } |
||||
1640 | |||||
1641 | // calculate new sequence for items that should be moved before or after |
||||
1642 | $droppedOnPageSequence = (int) $database->getVar( |
||||
1643 | 'SELECT i.sequence |
||||
1644 | FROM pages AS i |
||||
1645 | WHERE i.id = ? AND i.language = ? AND i.status = ? |
||||
1646 | LIMIT 1', |
||||
1647 | [$droppedOnPageId, $language, 'active'] |
||||
1648 | ); |
||||
1649 | |||||
1650 | $newSequence = $droppedOnPageSequence + ($typeOfDrop === self::TYPE_OF_DROP_BEFORE ? -1 : 1); |
||||
1651 | |||||
1652 | // increment all pages with a sequence that is higher than the new sequence; |
||||
1653 | $database->execute( |
||||
1654 | 'UPDATE pages |
||||
1655 | SET sequence = sequence + 1 |
||||
1656 | WHERE parent_id = ? AND language = ? AND sequence > ?', |
||||
1657 | [$newParent, $language, $newSequence] |
||||
1658 | ); |
||||
1659 | |||||
1660 | $database->update( |
||||
1661 | 'pages', |
||||
1662 | [ |
||||
1663 | 'parent_id' => $newParent, |
||||
1664 | 'sequence' => $newSequence, |
||||
1665 | 'type' => $newType |
||||
1666 | ], |
||||
1667 | 'id = ? AND language = ? AND status = ?', |
||||
1668 | [$pageId, $language, 'active'] |
||||
1669 | ); |
||||
1670 | } |
||||
1671 | |||||
1672 | private static function updateUrlAfterMove(int $pageId, array $page, int $newParent): void |
||||
1673 | { |
||||
1674 | $database = BackendModel::getContainer()->get('database'); |
||||
1675 | |||||
1676 | $currentUrl = (string) $database->getVar( |
||||
1677 | 'SELECT url |
||||
1678 | FROM meta AS m |
||||
1679 | WHERE m.id = ?', |
||||
1680 | [$page['meta_id']] |
||||
1681 | ); |
||||
1682 | |||||
1683 | $newUrl = self::getUrl( |
||||
1684 | $currentUrl, |
||||
1685 | $pageId, |
||||
1686 | $newParent, |
||||
1687 | isset($page['data']['is_action']) && $page['data']['is_action'] |
||||
1688 | ); |
||||
1689 | |||||
1690 | $database->update('meta', ['url' => $newUrl], 'id = ?', [$page['meta_id']]); |
||||
1691 | } |
||||
1692 | |||||
1693 | private static function copyImage(?string $image, string $metaUrl): ?string |
||||
1694 | { |
||||
1695 | if ($image === null || $image === '') { |
||||
1696 | return null; |
||||
1697 | } |
||||
1698 | |||||
1699 | $imagePath = FRONTEND_FILES_PATH . '/Pages/images'; |
||||
1700 | |||||
1701 | $originalImagePath = $imagePath . '/source/' . $image; |
||||
1702 | $extension = pathinfo($originalImagePath, PATHINFO_EXTENSION); |
||||
1703 | $imageFilename = $metaUrl . '_' . time() . '.' . $extension; |
||||
1704 | $newImagePath = $imagePath . '/source/' . $imageFilename; |
||||
1705 | |||||
1706 | // make sure we have a separate image for the copy in case the original image gets removed |
||||
1707 | (new Filesystem())->copy($originalImagePath, $newImagePath); |
||||
1708 | BackendModel::get(Thumbnails::class)->generate($imagePath, $newImagePath); |
||||
1709 | |||||
1710 | return $imageFilename; |
||||
1711 | } |
||||
1712 | } |
||||
1713 |