Issues (281)

Branch: master

src/Backend/Modules/Pages/Actions/Add.php (1 issue)

1
<?php
2
3
namespace Backend\Modules\Pages\Actions;
4
5
use Backend\Core\Engine\Authentication;
6
use Backend\Core\Engine\Base\ActionAdd as BackendBaseActionAdd;
7
use Backend\Core\Engine\Authentication as BackendAuthentication;
8
use Backend\Core\Engine\Form as BackendForm;
9
use Backend\Core\Language\Language as BL;
10
use Backend\Core\Engine\Meta as BackendMeta;
11
use Backend\Core\Engine\Model as BackendModel;
12
use Backend\Modules\Extensions\Engine\Model as BackendExtensionsModel;
13
use Backend\Modules\Pages\Engine\Model as BackendPagesModel;
14
use Backend\Modules\Search\Engine\Model as BackendSearchModel;
15
use Backend\Modules\Tags\Engine\Model as BackendTagsModel;
16
use Backend\Modules\Profiles\Engine\Model as BackendProfilesModel;
17
use Common\Core\Model;
18
use ForkCMS\Utility\Thumbnails;
19
use SpoonFormHidden;
20
use Symfony\Component\DomCrawler\Crawler;
21
use Symfony\Component\Filesystem\Filesystem;
22
use Symfony\Component\HttpFoundation\Response;
23
24
/**
25
 * This is the add-action, it will display a form to create a new item
26
 */
27
class Add extends BackendBaseActionAdd
28
{
29
    /**
30
     * The blocks linked to this page
31
     *
32
     * @var array
33
     */
34
    private $blocksContent = [];
35
36
    /**
37
     * Is the current user a god user?
38
     *
39
     * @var bool
40
     */
41
    private $isGod = false;
42
43
    /**
44
     * Original image from the page we are cloning
45
     *
46
     * @var string|null
47
     */
48
    private $originalImage;
49
50
    /**
51
     * The positions
52
     *
53
     * @var array
54
     */
55
    private $positions = [];
56
57
    /**
58
     * The extras
59
     *
60
     * @var array
61
     */
62
    private $extras = [];
63
64
    /**
65
     * The hreflang fields
66
     *
67
     * @var array
68
     */
69
    private $hreflangFields = [];
70
71
    /**
72
     * The template data
73
     *
74
     * @var array
75
     */
76
    private $templates = [];
77
78
    public function execute(): void
79
    {
80
        // call parent, this will probably add some general CSS/JS or other required files
81
        parent::execute();
82
83
        // add js
84
        $this->header->addJS('jstree/jquery.tree.js', null, false);
85
        $this->header->addJS('jstree/lib/jquery.cookie.js', null, false);
86
        $this->header->addJS('jstree/plugins/jquery.tree.cookie.js', null, false);
87
        $this->header->addJS('/js/vendors/SimpleAjaxUploader.min.js', 'Core', false, true);
88
89
        // get the templates
90
        $this->templates = BackendExtensionsModel::getTemplates();
91
        $this->isGod = BackendAuthentication::getUser()->isGod();
92
93
        // init var
94
        $defaultTemplateId = $this->get('fork.settings')->get('Pages', 'default_template', false);
95
96
        // fallback
97
        if ($defaultTemplateId === false) {
98
            // get first key
99
            $keys = array_keys($this->templates);
100
101
            // set the first items as default if no template was set as default.
102
            $defaultTemplateId = $this->templates[$keys[0]]['id'];
103
        }
104
105
        // set the default template as checked
106
        $this->templates[$defaultTemplateId]['checked'] = true;
107
108
        // get the extras
109
        $this->extras = BackendExtensionsModel::getExtras();
110
111
        $this->loadForm();
112
        $this->validateForm();
113
        $this->parse();
114
        $this->display();
115
    }
116
117
    private function loadForm(): void
118
    {
119
        // get default template id
120
        $defaultTemplateId = $this->get('fork.settings')->get('Pages', 'default_template', 1);
121
122
        // create form
123
        $this->form = new BackendForm('add');
124
125
        $originalPage = $this->getOriginalPage();
126
127
        // assign in template
128
        $this->template->assign('defaultTemplateId', $defaultTemplateId);
129
130
        // assign if profiles module is installed
131
        $this->template->assign('showAuthenticationTab', BackendModel::isModuleInstalled('Profiles'));
132
133
        $this->form->addText(
134
            'title',
135
            isset($originalPage['title']) ? sprintf(BL::msg('CopiedTitle'), $originalPage['title']) : null,
136
            null,
137
            'form-control title',
138
            'form-control danger title'
139
        );
140
        $this->form->addEditor('html');
141
        $this->form->addHidden('template_id', $originalPage['template_id'] ?? $defaultTemplateId);
142
        $this->form->addRadiobutton(
143
            'hidden',
144
            [
145
                ['label' => BL::lbl('Hidden'), 'value' => 1],
146
                ['label' => BL::lbl('Published'), 'value' => 0],
147
            ],
148
            $originalPage['hidden'] ?? 0
149
        );
150
151
        // image related fields
152
        $this->form->addImage('image')->setAttribute('data-fork-cms-role', 'image-field');
153
        if ($originalPage !== null) {
154
            $this->form->addCheckbox('remove_image');
155
            $this->originalImage = $originalPage['data']['image'] ?? null;
156
            $this->template->assign('originalImage', $this->originalImage);
157
        }
158
159
        // just execute if the site is multi-language
160
        if ($this->getContainer()->getParameter('site.multilanguage')) {
161
            // loop active languages
162
            foreach (BL::getActiveLanguages() as $language) {
163
                if ($language !== BL::getWorkingLanguage()) {
164
                    $pages = BackendPagesModel::getPagesForDropdown($language);
165
                    // add field for each language
166
                    $field = $this->form->addDropdown('hreflang_' . $language, $pages)->setDefaultElement('');
167
                    $this->hreflangFields[$language]['field_hreflang'] = $field->parse();
168
                }
169
            }
170
        }
171
172
        // page auth related fields
173
        // check if profiles module is installed
174
        if (BackendModel::isModuleInstalled('Profiles')) {
175
            // add checkbox for auth_required
176
            $this->form->addCheckbox(
177
                'auth_required',
178
                $originalPage !== null && isset($originalPage['data']['auth_required']) && $originalPage['data']['auth_required']
179
            );
180
181
            // add checkbox for index page to search
182
            $this->form->addCheckbox(
183
                'remove_from_search_index',
184
                $originalPage !== null && isset($originalPage['data']['remove_from_search_index']) && $originalPage['data']['remove_from_search_index']
185
            );
186
187
            // get all groups and parse them in key value pair
188
            $groupItems = BackendProfilesModel::getGroups();
189
            if (!empty($groupItems)) {
190
                $groups = [];
191
                foreach ($groupItems as $key => $item) {
192
                    $groups[] = ['label' => $item, 'value' => $key];
193
                }
194
                // set checked values
195
                $checkedGroups = [];
196
                if ($originalPage !== null && isset($originalPage['data']['auth_groups'])
197
                    && is_array($originalPage['data']['auth_groups'])) {
198
                    foreach ($originalPage['data']['auth_groups'] as $group) {
199
                        $checkedGroups[] = $group;
200
                    }
201
                }
202
                // add multi checkbox
203
                $this->form->addMultiCheckbox('auth_groups', $groups, $checkedGroups);
204
            }
205
        }
206
207
        // a god user should be able to adjust the detailed settings for a page easily
208
        if ($this->isGod) {
209
            $permissions = [
210
                'move' => ['data-role' => 'allow-move-toggle'],
211
                'children' => ['data-role' => 'allow-children-toggle'],
212
                'edit' => ['data-role' => 'allow-edit-toggle'],
213
                'delete' => ['data-role' => 'allow-delete-toggle'],
214
            ];
215
            $checked = [];
216
            $values = [];
217
218
            foreach ($permissions as $permission => $attributes) {
219
                $allowPermission = 'allow_' . $permission;
220
                $values[] = [
221
                    'label' => BL::msg(\SpoonFilter::toCamelCase($allowPermission)),
222
                    'value' => $permission,
223
                    'attributes' => $attributes,
224
                ];
225
226
                if ($originalPage === null || (isset($originalPage[$allowPermission]) && $originalPage[$allowPermission])) {
227
                    $checked[] = $permission;
228
                }
229
            }
230
231
            $this->form->addMultiCheckbox('allow', $values, $checked);
232
233
            // css link class
234
            $this->form->addText('link_class');
235
        }
236
237
        // build prototype block
238
        $block = [];
239
        $block['index'] = 0;
240
        $block['formElements']['chkVisible'] = $this->form->addCheckbox('block_visible_' . $block['index'], true);
241
        $block['formElements']['hidExtraId'] = $this->form->addHidden('block_extra_id_' . $block['index'], 0);
242
        $block['formElements']['hidExtraType'] = $this->form->addHidden('block_extra_type_' . $block['index'], 'rich_text');
243
        $block['formElements']['hidExtraData'] = $this->form->addHidden('block_extra_data_' . $block['index']);
244
        $block['formElements']['hidPosition'] = $this->form->addHidden('block_position_' . $block['index'], 'fallback');
245
        $block['formElements']['txtHTML'] = $this->form->addTextarea(
246
            'block_html_' . $block['index']
247
        ); // this is no editor; we'll add the editor in JS
248
249
        // add default block to "fallback" position, the only one which we can rest assured to exist
250
        $this->positions['fallback']['blocks'][] = $block;
251
252
        // content has been submitted: re-create submitted content rather than the database-fetched content
253
        if ($this->getRequest()->request->has('block_html_0')) {
254
            $this->blocksContent = [];
255
            $hasBlock = false;
256
            $i = 1;
257
258
            $positions = [];
259
            // loop submitted blocks
260
            while ($this->getRequest()->request->has('block_position_' . $i)) {
261
                $block = [];
262
263
                // save block position
264
                $block['position'] = $this->getRequest()->request->get('block_position_' . $i);
265
                $positions[$block['position']][] = $block;
266
267
                // set linked extra
268
                $block['extra_id'] = $this->getRequest()->request->get('block_extra_id_' . $i);
269
                $block['extra_type'] = $this->getRequest()->request->get('block_extra_type_' . $i);
270
                $block['extra_data'] = $this->getRequest()->request->get('block_extra_data_' . $i);
271
272
                // reset some stuff
273
                if ($block['extra_id'] <= 0) {
274
                    $block['extra_id'] = null;
275
                }
276
277
                // init html
278
                $block['html'] = null;
279
280
                $html = $this->getRequest()->request->get('block_html_' . $i);
281
282
                // extra-type is HTML
283
                if ($block['extra_id'] === null || $block['extra_type'] === 'usertemplate') {
284
                    if ($this->getRequest()->request->get('block_extra_type_' . $i) === 'usertemplate') {
285
                        $block['extra_id'] = $this->getRequest()->request->get('block_extra_id_' . $i);
286
                        $_POST['block_extra_data_' . $i] = htmlspecialchars($_POST['block_extra_data_' . $i]);
287
                    } else {
288
                        // reset vars
289
                        $block['extra_id'] = null;
290
                    }
291
                    $block['html'] = $html;
292
                } elseif (isset($this->extras[$block['extra_id']]['type']) && $this->extras[$block['extra_id']]['type'] === 'block') {
293
                    // set error
294
                    if ($hasBlock) {
295
                        $this->form->addError(BL::err('CantAdd2Blocks'));
296
                    }
297
298
                    // reset var
299
                    $hasBlock = true;
300
                }
301
302
                // set data
303
                $block['created_on'] = BackendModel::getUTCDate();
304
                $block['edited_on'] = $block['created_on'];
305
                $block['visible'] = $this->getRequest()->request->getBoolean('block_visible_' . $i);
306
                $block['sequence'] = count($positions[$block['position']]) - 1;
307
308
                // add to blocks
309
                $this->blocksContent[] = $block;
310
311
                // increment counter; go fetch next block
312
                ++$i;
313
            }
314
        }
315
316
        // build blocks array
317
        foreach ($this->blocksContent as $i => $block) {
318
            $block['index'] = $i + 1;
319
            $block['formElements']['chkVisible'] = $this->form->addCheckbox(
320
                'block_visible_' . $block['index'],
321
                $block['visible']
322
            );
323
            $block['formElements']['hidExtraId'] = $this->form->addHidden(
324
                'block_extra_id_' . $block['index'],
325
                (int) $block['extra_id']
326
            );
327
            $block['formElements']['hidExtraType'] = $this->form->addHidden(
328
                'block_extra_type_' . $block['index'],
329
                $block['extra_type']
330
            );
331
            $this->form->add(
332
                $this->getHiddenJsonField(
333
                    'block_extra_data_' . $block['index'],
334
                    $block['extra_data']
335
                )
336
            );
337
            $block['formElements']['hidExtraData'] = $this->form->getField('block_extra_data_' . $block['index']);
338
            $block['formElements']['hidPosition'] = $this->form->addHidden(
339
                'block_position_' . $block['index'],
340
                $block['position']
341
            );
342
            $block['formElements']['txtHTML'] = $this->form->addTextarea(
343
                'block_html_' . $block['index'],
344
                $block['html']
345
            ); // this is no editor; we'll add the editor in JS
346
347
            $this->positions[$block['position']]['blocks'][] = $block;
348
        }
349
350
        // redirect
351
        $redirectValue = 'none';
352
        if ($originalPage !== null && isset($originalPage['data']['internal_redirect']['page_id'])) {
353
            $redirectValue = 'internal';
354
        }
355
        if ($originalPage !== null && isset($originalPage['data']['external_redirect']['url'])) {
356
            $redirectValue = 'external';
357
        }
358
        $redirectValues = [
359
            ['value' => 'none', 'label' => \SpoonFilter::ucfirst(BL::lbl('None'))],
360
            [
361
                'value' => 'internal',
362
                'label' => \SpoonFilter::ucfirst(BL::lbl('InternalLink')),
363
                'variables' => ['isInternal' => true],
364
            ],
365
            [
366
                'value' => 'external',
367
                'label' => \SpoonFilter::ucfirst(BL::lbl('ExternalLink')),
368
                'variables' => ['isExternal' => true],
369
            ],
370
        ];
371
        $this->form->addRadiobutton('redirect', $redirectValues, $redirectValue);
372
        $this->form->addDropdown(
373
            'internal_redirect',
374
            BackendPagesModel::getPagesForDropdown(),
375
            ($redirectValue === 'internal') ? $originalPage['data']['internal_redirect']['page_id'] : null
376
        );
377
        $this->form->addText(
378
            'external_redirect',
379
            ($redirectValue === 'external') ? urldecode($originalPage['data']['external_redirect']['url']) : null,
380
            null,
381
            null,
382
            null,
383
            true
384
        );
385
386
        // page info
387
        $this->form->addCheckbox('navigation_title_overwrite', $originalPage['navigation_title_overwrite'] ?? null);
388
        $this->form->addText('navigation_title', $originalPage['navigation_title'] ?? null);
389
390
        if ($this->showTags()) {
391
            // tags
392
            $this->form->addText(
393
                'tags',
394
                $originalPage === null ? null : BackendTagsModel::getTags($this->url->getModule(), $originalPage['id']),
395
                null,
396
                'form-control js-tags-input',
397
                'form-control danger js-tags-input'
398
            )->setAttribute('aria-describedby', 'tags-info');
399
        }
400
401
        // a specific action
402
        $isAction = $originalPage !== null && isset($originalPage['data']['is_action']) && $originalPage['data']['is_action'];
403
        $this->form->addCheckbox('is_action', $isAction);
404
405
        // extra
406
        $blockTypes = BackendPagesModel::getTypes();
407
        $this->form->addDropdown('extra_type', $blockTypes, key($blockTypes));
408
409
        // meta
410
        $this->meta = new BackendMeta($this->form, null, 'title', true);
411
412
        // set callback for generating an unique URL
413
        $this->meta->setUrlCallback(
414
            BackendPagesModel::class,
415
            'getUrl',
416
            [0, $this->getRequest()->query->getInt('parent'), false]
417
        );
418
    }
419
420
    protected function parse(): void
421
    {
422
        parent::parse();
423
424
        // parse some variables
425
        $this->template->assign('templates', $this->templates);
426
        $this->template->assign('isGod', $this->isGod);
427
        $this->template->assign('positions', $this->positions);
428
        $this->template->assign('extrasData', json_encode(BackendModel::recursiveHtmlspecialchars(BackendExtensionsModel::getExtrasData())));
429
        $this->template->assign('extrasById', json_encode(BackendExtensionsModel::getExtras()));
430
        $this->template->assign(
431
            'prefixURL',
432
            rtrim(
433
                BackendPagesModel::getFullUrl($this->getRequest()->query->getInt('parent', BackendModel::HOME_PAGE_ID)),
434
                '/'
435
            )
436
        );
437
        $this->template->assign('formErrors', (string) $this->form->getErrors());
438
        $this->template->assign('showTags', $this->showTags());
439
        $this->template->assign('hreflangFields', $this->hreflangFields);
440
441
        // get default template id
442
        $defaultTemplateId = $this->get('fork.settings')->get('Pages', 'default_template', 1);
443
444
        // assign template
445
        $this->template->assignArray($this->templates[$defaultTemplateId], 'template');
446
447
        // parse the form
448
        $this->form->parse($this->template);
449
450
        // parse the tree
451
        $this->template->assign('tree', BackendPagesModel::getTreeHTML());
452
453
        $this->header->addJsData(
454
            'pages',
455
            'userTemplates',
456
            BackendPagesModel::loadUserTemplates()
457
        );
458
    }
459
460
    private function validateForm(): void
461
    {
462
        // is the form submitted?
463
        if ($this->form->isSubmitted()) {
464
            // get the status
465
            $status = $this->getRequest()->request->get('status');
466
            if (!in_array($status, ['active', 'draft'])) {
467
                $status = 'active';
468
            }
469
470
            // validate redirect
471
            $redirectValue = $this->form->getField('redirect')->getValue();
472
            if ($redirectValue === 'internal') {
473
                $this->form->getField('internal_redirect')->isFilled(
474
                    BL::err('FieldIsRequired')
475
                );
476
            }
477
            if ($redirectValue === 'external') {
478
                $this->form->getField('external_redirect')->isURL(BL::err('InvalidURL'));
479
            }
480
481
            // set callback for generating an unique URL
482
            $this->meta->setUrlCallback(
483
                BackendPagesModel::class,
484
                'getUrl',
485
                [0, $this->getRequest()->query->getInt('parent'), $this->form->getField('is_action')->getChecked()]
486
            );
487
488
            // cleanup the submitted fields, ignore fields that were added by hackers
489
            $this->form->cleanupFields();
490
491
            // validate fields
492
            $this->form->getField('title')->isFilled(BL::err('TitleIsRequired'));
493
            if ($this->form->getField('navigation_title_overwrite')->isChecked()) {
494
                $this->form->getField('navigation_title')->isFilled(BL::err('FieldIsRequired'));
495
            }
496
            // validate meta
497
            $this->meta->validate();
498
499
            // no errors?
500
            if ($this->form->isCorrect()) {
501
                // init var
502
                $parentId = $this->getRequest()->query->getInt('parent');
503
                $parentPage = BackendPagesModel::get($parentId);
504
                if (!$parentPage || !$parentPage['children_allowed']) {
505
                    // no children allowed
506
                    $parentId = 0;
507
                    $parentPage = false;
508
                }
509
                $templateId = (int) $this->form->getField('template_id')->getValue();
510
                $data = null;
511
512
                // build data
513
                if ($this->form->getField('is_action')->isChecked()) {
514
                    $data['is_action'] = true;
515
                }
516
                if ($redirectValue === 'internal') {
517
                    $data['internal_redirect'] = [
518
                        'page_id' => $this->form->getField('internal_redirect')->getValue(),
519
                        'code' => Response::HTTP_TEMPORARY_REDIRECT,
520
                    ];
521
                }
522
                if ($redirectValue === 'external') {
523
                    $data['external_redirect'] = [
524
                        'url' => BackendPagesModel::getEncodedRedirectUrl(
525
                            $this->form->getField('external_redirect')->getValue()
526
                        ),
527
                        'code' => Response::HTTP_TEMPORARY_REDIRECT,
528
                    ];
529
                }
530
                if (array_key_exists('image', $this->templates[$templateId]['data'])) {
531
                    $data['image'] = $this->getImage($this->templates[$templateId]['data']['image']);
532
                }
533
534
                $data['auth_required'] = false;
535
                if (BackendModel::isModuleInstalled('Profiles') && $this->form->getField('auth_required')->isChecked()) {
536
                    $data['auth_required'] = true;
537
                    // get all groups and parse them in key value pair
538
                    $groupItems = BackendProfilesModel::getGroups();
539
540
                    if (!empty($groupItems)) {
541
                        $data['auth_groups'] = $this->form->getField('auth_groups')->getValue();
542
                    }
543
                }
544
545
                $data['remove_from_search_index'] = false;
546
                if (BackendModel::isModuleInstalled('Profiles')
547
                    && $this->form->getField('remove_from_search_index')->isChecked()
548
                    && $this->form->getField('auth_required')->isChecked()) {
549
                    $data['remove_from_search_index'] = true;
550
                }
551
552
                // just execute if the site is multi-language
553
                if ($this->getContainer()->getParameter('site.multilanguage')) {
554
                    // loop active languages
555
                    foreach (BL::getActiveLanguages() as $language) {
556
                        if ($language !== BL::getWorkingLanguage() && $this->form->getfield('hreflang_' . $language)->isFilled()) {
557
                            $data['hreflang_' . $language] = $this->form->getfield('hreflang_' . $language)->getValue();
558
                        }
559
                    }
560
                }
561
562
                // build page record
563
                $page = [];
564
                $page['id'] = BackendPagesModel::getMaximumPageId() + 1;
565
                $page['user_id'] = BackendAuthentication::getUser()->getUserId();
566
                $page['parent_id'] = $parentId;
567
                $page['template_id'] = $templateId;
568
                $page['meta_id'] = (int) $this->meta->save();
569
                $page['language'] = BL::getWorkingLanguage();
570
                $page['type'] = $parentPage ? 'page' : 'root';
571
                $page['title'] = $this->form->getField('title')->getValue();
572
                $page['navigation_title'] = ($this->form->getField('navigation_title')->getValue() != '')
573
                    ? $this->form->getField('navigation_title')->getValue()
574
                    : $this->form->getField('title')->getValue();
575
                $page['navigation_title_overwrite'] = $this->form->getField(
576
                    'navigation_title_overwrite'
577
                )->isChecked();
578
                $page['hidden'] = $this->form->getField('hidden')->getValue();
579
                $page['status'] = $status;
580
                $page['publish_on'] = BackendModel::getUTCDate();
581
                $page['created_on'] = BackendModel::getUTCDate();
582
                $page['edited_on'] = BackendModel::getUTCDate();
583
                $page['allow_move'] = true;
584
                $page['allow_children'] = true;
585
                $page['allow_edit'] = true;
586
                $page['allow_delete'] = true;
587
                $page['sequence'] = BackendPagesModel::getMaximumSequence($parentId) + 1;
588
                $page['data'] = ($data !== null) ? serialize($data) : null;
0 ignored issues
show
The condition $data !== null is always false.
Loading history...
589
590
                if ($this->isGod) {
591
                    $page['allow_move'] = in_array(
592
                        'move',
593
                        (array) $this->form->getField('allow')->getValue(),
594
                        true
595
                    );
596
                    $page['allow_children'] = in_array(
597
                        'children',
598
                        (array) $this->form->getField('allow')->getValue(),
599
                        true
600
                    );
601
                    $page['allow_edit'] = in_array(
602
                        'edit',
603
                        (array) $this->form->getField('allow')->getValue(),
604
                        true
605
                    );
606
                    $page['allow_delete'] = in_array(
607
                        'delete',
608
                        (array) $this->form->getField('allow')->getValue(),
609
                        true
610
                    );
611
612
                    // link class
613
                    $data['link_class'] = $this->form->getField('link_class')->getValue();
614
                }
615
616
                // set navigation title
617
                if ($page['navigation_title'] == '') {
618
                    $page['navigation_title'] = $page['title'];
619
                }
620
621
                // insert page, store the id, we need it when building the blocks
622
                $page['revision_id'] = BackendPagesModel::insert($page);
623
624
                // loop blocks
625
                foreach ($this->blocksContent as $i => $block) {
626
                    // add page revision id to blocks
627
                    $this->blocksContent[$i]['revision_id'] = $page['revision_id'];
628
629
                    // validate blocks, only save blocks for valid positions
630
                    if (!in_array(
631
                        $block['position'],
632
                        $this->templates[$this->form->getField('template_id')->getValue()]['data']['names']
633
                    )
634
                    ) {
635
                        unset($this->blocksContent[$i]);
636
                    }
637
                }
638
639
                // insert the blocks
640
                BackendPagesModel::insertBlocks($this->blocksContent);
641
642
                if ($this->showTags()) {
643
                    // save tags
644
                    BackendTagsModel::saveTags(
645
                        $page['id'],
646
                        $this->form->getField('tags')->getValue(),
647
                        $this->url->getModule()
648
                    );
649
                }
650
651
                // build the cache
652
                BackendPagesModel::buildCache(BL::getWorkingLanguage());
653
654
                // active
655
                if ($page['status'] === 'active') {
656
                    $this->saveSearchIndex($data['remove_from_search_index'] || $redirectValue !== 'none', $page);
657
658
                    // everything is saved, so redirect to the overview
659
                    $this->redirect(
660
                        BackendModel::createUrlForAction(
661
                            'Edit'
662
                        ) . '&id=' . $page['id'] . '&report=added&var=' . rawurlencode(
663
                            $page['title']
664
                        ) . '&highlight=row-' . $page['id']
665
                    );
666
                } elseif ($page['status'] === 'draft') {
667
                    // everything is saved, so redirect to the edit action
668
                    $this->redirect(
669
                        BackendModel::createUrlForAction(
670
                            'Edit'
671
                        ) . '&id=' . $page['id'] . '&report=saved-as-draft&var=' . rawurlencode(
672
                            $page['title']
673
                        ) . '&highlight=row-' . $page['revision_id'] . '&draft=' . $page['revision_id']
674
                    );
675
                }
676
            }
677
        }
678
    }
679
680
    private function getImage(bool $allowImage): ?string
681
    {
682
        if (!$allowImage
683
            || (!$this->form->getField('image')->isFilled() && $this->originalImage === null)
684
            || ($this->originalImage !== null && $this->form->getField('remove_image')->isChecked())) {
685
            return null;
686
        }
687
688
        $imagePath = FRONTEND_FILES_PATH . '/Pages/images';
689
690
        if ($this->originalImage !== null && !$this->form->getField('image')->isFilled()) {
691
            $originalImagePath = $imagePath . '/source/' . $this->originalImage;
692
            $imageFilename = $this->getImageFilenameForExtension(pathinfo($originalImagePath, PATHINFO_EXTENSION));
693
            $newImagePath = $imagePath . '/source/' . $imageFilename;
694
695
            // make sure we have a separate image for the copy in case the original image gets removed
696
            (new Filesystem())->copy($originalImagePath, $newImagePath);
697
            $this->get(Thumbnails::class)->generate($imagePath, $newImagePath);
698
699
            return $imageFilename;
700
        }
701
702
        $imageFilename = $this->getImageFilenameForExtension($this->form->getField('image')->getExtension());
703
        $this->form->getField('image')->generateThumbnails($imagePath, $imageFilename);
704
705
        return $imageFilename;
706
    }
707
708
    private function getImageFilenameForExtension(string $extension): string
709
    {
710
        return $this->meta->getUrl() . '_' . time() . '.' . $extension;
711
    }
712
713
    /**
714
     * Check if the user has the right to see/edit tags
715
     *
716
     * @return bool
717
     */
718
    private function showTags(): bool
719
    {
720
        return Authentication::isAllowedAction('Edit', 'Tags') && Authentication::isAllowedAction('GetAllTags', 'Tags');
721
    }
722
723
    private function getHiddenJsonField(string $name, ?string $json): SpoonFormHidden
724
    {
725
        return new class($name, htmlspecialchars($json)) extends SpoonFormHidden {
726
            public function getValue($allowHTML = null)
727
            {
728
                return parent::getValue(true);
729
            }
730
        };
731
    }
732
733
    private function getOriginalPage(): ?array
734
    {
735
        $id = $this->getRequest()->query->getInt('copy');
736
737
        // check if the page exists
738
        if ($id === 0 || !BackendPagesModel::exists($id)) {
739
            return null;
740
        }
741
742
        $this->template->assign('showCopyWarning', true);
743
744
        $originalPage = BackendPagesModel::get($id);
745
746
        // Handle usertemplate images on copying a page, as otherwise the same path will be used.
747
        // On changing/deleting a copied usertemplate image, the old image is deleted, breaking the usertemplate
748
        // with the same image on the original page
749
        $this->blocksContent = array_map(
750
            function (array $block) {
751
                // Only for usertemplates
752
                if ($block['extra_type'] !== 'usertemplate') {
753
                    return $block;
754
                }
755
756
                // Only usertemplates with image
757
                if (strpos($block['html'], 'data-ft-type="image"') === false) {
758
                    return $block;
759
                }
760
761
                // Find images in usertemplate
762
                $blockElements = new Crawler($block['html']);
763
                $images = $blockElements->filter('[data-ft-type="image"]');
764
                $filesystem = new Filesystem();
765
                $path = FRONTEND_FILES_PATH . '/Pages/UserTemplate';
766
                $url = FRONTEND_FILES_URL . '/Pages/UserTemplate';
767
                foreach ($images as $image) {
768
                    $imagePath = $image->getAttribute('src');
769
770
                    // skip empty images
771
                    if ($imagePath === '') {
772
                        continue;
773
                    }
774
775
                    $basename = pathinfo($imagePath, PATHINFO_FILENAME);
776
                    $extension = pathinfo($imagePath, PATHINFO_EXTENSION);
777
                    $originalFilename = $basename . '.' . $extension;
778
                    $filename = $originalFilename;
779
780
                    // Generate a non-existing filename
781
                    while ($filesystem->exists($path . '/' . $filename)) {
782
                        $basename = Model::addNumber($basename);
783
                        $filename = $basename . '.' . $extension;
784
                    }
785
786
                    $block['html'] = str_replace($imagePath, $url . '/' . $filename, $block['html']);
787
                    $filesystem->copy($path . '/' . $originalFilename, $path . '/' . $filename);
788
                }
789
790
                return $block;
791
            },
792
            BackendPagesModel::getBlocks($id, $originalPage['revision_id'])
793
        );
794
795
        return $originalPage;
796
    }
797
798
    private function saveSearchIndex(bool $removeFromSearchIndex, array $page): void
799
    {
800
        if ($removeFromSearchIndex) {
801
            BackendSearchModel::removeIndex(
802
                $this->getModule(),
803
                $page['id']
804
            );
805
806
            return;
807
        }
808
809
        $searchText = '';
810
        foreach ($this->blocksContent as $block) {
811
            $searchText .= ' ' . $block['html'];
812
        }
813
814
        BackendSearchModel::saveIndex(
815
            $this->getModule(),
816
            $page['id'],
817
            ['title' => $page['title'], 'text' => $searchText]
818
        );
819
    }
820
}
821