Passed
Push — master ( 35e3bb...f8f687 )
by
unknown
14:50
created

registerHistoryButtonToButtonBar()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 19
nc 2
nop 3
dl 0
loc 29
rs 9.6333
c 0
b 0
f 0
1
<?php
2
declare(strict_types = 1);
3
namespace TYPO3\CMS\Backend\Controller;
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
use Psr\EventDispatcher\EventDispatcherInterface;
19
use Psr\Http\Message\ResponseInterface;
20
use Psr\Http\Message\ServerRequestInterface;
21
use TYPO3\CMS\Backend\Controller\Event\AfterFormEnginePageInitializedEvent;
22
use TYPO3\CMS\Backend\Controller\Event\BeforeFormEnginePageInitializedEvent;
23
use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
24
use TYPO3\CMS\Backend\Form\Exception\DatabaseRecordException;
25
use TYPO3\CMS\Backend\Form\FormDataCompiler;
26
use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
27
use TYPO3\CMS\Backend\Form\FormResultCompiler;
28
use TYPO3\CMS\Backend\Form\NodeFactory;
29
use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
30
use TYPO3\CMS\Backend\Routing\UriBuilder;
31
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
32
use TYPO3\CMS\Backend\Template\ModuleTemplate;
33
use TYPO3\CMS\Backend\Utility\BackendUtility;
34
use TYPO3\CMS\Core\Database\ConnectionPool;
35
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
36
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
37
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
38
use TYPO3\CMS\Core\Database\ReferenceIndex;
39
use TYPO3\CMS\Core\DataHandling\DataHandler;
40
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
41
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
42
use TYPO3\CMS\Core\Http\HtmlResponse;
43
use TYPO3\CMS\Core\Http\RedirectResponse;
44
use TYPO3\CMS\Core\Imaging\Icon;
45
use TYPO3\CMS\Core\Messaging\FlashMessage;
46
use TYPO3\CMS\Core\Messaging\FlashMessageService;
47
use TYPO3\CMS\Core\Page\PageRenderer;
48
use TYPO3\CMS\Core\Routing\UnableToLinkToPageException;
49
use TYPO3\CMS\Core\Site\Entity\NullSite;
50
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
51
use TYPO3\CMS\Core\Site\SiteFinder;
52
use TYPO3\CMS\Core\Type\Bitmask\Permission;
53
use TYPO3\CMS\Core\Utility\GeneralUtility;
54
use TYPO3\CMS\Core\Utility\HttpUtility;
55
use TYPO3\CMS\Core\Utility\MathUtility;
56
use TYPO3\CMS\Core\Utility\PathUtility;
57
58
/**
59
 * Main backend controller almost always used if some database record is edited in the backend.
60
 *
61
 * Main job of this controller is to evaluate and sanitize $request parameters,
62
 * call the DataHandler if records should be created or updated and
63
 * execute FormEngine for record rendering.
64
 */
65
class EditDocumentController
66
{
67
    protected const DOCUMENT_CLOSE_MODE_DEFAULT = 0;
68
    // works like DOCUMENT_CLOSE_MODE_DEFAULT
69
    protected const DOCUMENT_CLOSE_MODE_REDIRECT = 1;
70
    protected const DOCUMENT_CLOSE_MODE_CLEAR_ALL = 3;
71
    protected const DOCUMENT_CLOSE_MODE_NO_REDIRECT = 4;
72
73
    /**
74
     * An array looking approx like [tablename][list-of-ids]=command, eg. "&edit[pages][123]=edit".
75
     *
76
     * @var array
77
     */
78
    protected $editconf = [];
79
80
    /**
81
     * Comma list of field names to edit. If specified, only those fields will be rendered.
82
     * Otherwise all (available) fields in the record are shown according to the TCA type.
83
     *
84
     * @var string|null
85
     */
86
    protected $columnsOnly;
87
88
    /**
89
     * Default values for fields
90
     *
91
     * @var array|null [table][field]
92
     */
93
    protected $defVals;
94
95
    /**
96
     * Array of values to force being set as hidden fields in FormEngine
97
     *
98
     * @var array|null [table][field]
99
     */
100
    protected $overrideVals;
101
102
    /**
103
     * If set, this value will be set in $this->retUrl as "returnUrl", if not,
104
     * $this->retUrl will link to dummy controller
105
     *
106
     * @var string|null
107
     */
108
    protected $returnUrl;
109
110
    /**
111
     * Prepared return URL. Contains the URL that we should return to from FormEngine if
112
     * close button is clicked. Usually passed along as 'returnUrl', but falls back to
113
     * "dummy" controller.
114
     *
115
     * @var string
116
     */
117
    protected $retUrl;
118
119
    /**
120
     * Close document command. One of the DOCUMENT_CLOSE_MODE_* constants above
121
     *
122
     * @var int
123
     */
124
    protected $closeDoc;
125
126
    /**
127
     * If true, the processing of incoming data will be performed as if a save-button is pressed.
128
     * Used in the forms as a hidden field which can be set through
129
     * JavaScript if the form is somehow submitted by JavaScript.
130
     *
131
     * @var bool
132
     */
133
    protected $doSave;
134
135
    /**
136
     * Main DataHandler datamap array
137
     *
138
     * @var array
139
     * @todo: Will be set protected later, still used by ConditionMatcher
140
     * @internal Will be removed / protected in TYPO3 v10.x without further notice
141
     */
142
    public $data;
143
144
    /**
145
     * Main DataHandler cmdmap array
146
     *
147
     * @var array
148
     */
149
    protected $cmd;
150
151
    /**
152
     * DataHandler 'mirror' input
153
     *
154
     * @var array
155
     */
156
    protected $mirror;
157
158
    /**
159
     * Boolean: If set, then the GET var "&id=" will be added to the
160
     * retUrl string so that the NEW id of something is returned to the script calling the form.
161
     *
162
     * @var bool
163
     */
164
    protected $returnNewPageId = false;
165
166
    /**
167
     * Updated values for backendUser->uc. Used for new inline records to mark them
168
     * as expanded: uc[inlineView][...]
169
     *
170
     * @var array|null
171
     */
172
    protected $uc;
173
174
    /**
175
     * ID for displaying the page in the frontend, "save and view"
176
     *
177
     * @var int
178
     */
179
    protected $popViewId;
180
181
    /**
182
     * Alternative URL for viewing the frontend pages.
183
     *
184
     * @var string
185
     */
186
    protected $viewUrl;
187
188
    /**
189
     * Alternative title for the document handler.
190
     *
191
     * @var string
192
     */
193
    protected $recTitle;
194
195
    /**
196
     * If set, then no save & view button is printed
197
     *
198
     * @var bool
199
     */
200
    protected $noView;
201
202
    /**
203
     * @var string
204
     */
205
    protected $perms_clause;
206
207
    /**
208
     * If true, $this->editconf array is added a redirect response, used by Wizard/AddController
209
     *
210
     * @var bool
211
     */
212
    protected $returnEditConf;
213
214
    /**
215
     * Workspace used for the editing action.
216
     *
217
     * @var string|null
218
     */
219
    protected $workspace;
220
221
    /**
222
     * parse_url() of current requested URI, contains ['path'] and ['query'] parts.
223
     *
224
     * @var array
225
     */
226
    protected $R_URL_parts;
227
228
    /**
229
     * Contains $request query parameters. This array is the foundation for creating
230
     * the R_URI internal var which becomes the url to which forms are submitted
231
     *
232
     * @var array
233
     */
234
    protected $R_URL_getvars;
235
236
    /**
237
     * Set to the URL of this script including variables which is needed to re-display the form.
238
     *
239
     * @var string
240
     */
241
    protected $R_URI;
242
243
    /**
244
     * @var array
245
     */
246
    protected $pageinfo;
247
248
    /**
249
     * Is loaded with the "title" of the currently "open document"
250
     * used for the open document toolbar
251
     *
252
     * @var string
253
     */
254
    protected $storeTitle = '';
255
256
    /**
257
     * Contains an array with key/value pairs of GET parameters needed to reach the
258
     * current document displayed - used in the 'open documents' toolbar.
259
     *
260
     * @var array
261
     */
262
    protected $storeArray;
263
264
    /**
265
     * $this->storeArray imploded to url
266
     *
267
     * @var string
268
     */
269
    protected $storeUrl;
270
271
    /**
272
     * md5 hash of storeURL, used to identify a single open document in backend user uc
273
     *
274
     * @var string
275
     */
276
    protected $storeUrlMd5;
277
278
    /**
279
     * Backend user session data of this module
280
     *
281
     * @var array
282
     */
283
    protected $docDat;
284
285
    /**
286
     * An array of the "open documents" - keys are md5 hashes (see $storeUrlMd5) identifying
287
     * the various documents on the GET parameter list needed to open it. The values are
288
     * arrays with 0,1,2 keys with information about the document (see compileStoreData()).
289
     * The docHandler variable is stored in the $docDat session data, key "0".
290
     *
291
     * @var array
292
     */
293
    protected $docHandler;
294
295
    /**
296
     * Array of the elements to create edit forms for.
297
     *
298
     * @var array
299
     * @todo: Will be set protected later, still used by ConditionMatcher
300
     * @internal Will be removed / protected in TYPO3 v10.x without further notice
301
     */
302
    public $elementsData;
303
304
    /**
305
     * Pointer to the first element in $elementsData
306
     *
307
     * @var array
308
     */
309
    protected $firstEl;
310
311
    /**
312
     * Counter, used to count the number of errors (when users do not have edit permissions)
313
     *
314
     * @var int
315
     */
316
    protected $errorC;
317
318
    /**
319
     * Counter, used to count the number of new record forms displayed
320
     *
321
     * @var int
322
     */
323
    protected $newC;
324
325
    /**
326
     * Is set to the pid value of the last shown record - thus indicating which page to
327
     * show when clicking the SAVE/VIEW button
328
     *
329
     * @var int
330
     */
331
    protected $viewId;
332
333
    /**
334
     * Is set to additional parameters (like "&L=xxx") if the record supports it.
335
     *
336
     * @var string
337
     */
338
    protected $viewId_addParams;
339
340
    /**
341
     * @var FormResultCompiler
342
     */
343
    protected $formResultCompiler;
344
345
    /**
346
     * Used internally to disable the storage of the document reference (eg. new records)
347
     *
348
     * @var bool
349
     */
350
    protected $dontStoreDocumentRef = 0;
351
352
    /**
353
     * Stores information needed to preview the currently saved record
354
     *
355
     * @var array
356
     */
357
    protected $previewData = [];
358
359
    /**
360
     * ModuleTemplate object
361
     *
362
     * @var ModuleTemplate
363
     */
364
    protected $moduleTemplate;
365
366
    /**
367
     * Check if a record has been saved
368
     *
369
     * @var bool
370
     */
371
    protected $isSavedRecord;
372
373
    /**
374
     * Check if a page in free translation mode
375
     *
376
     * @var bool
377
     */
378
    protected $isPageInFreeTranslationMode = false;
379
380
    /**
381
     * @var EventDispatcherInterface
382
     */
383
    protected $eventDispatcher;
384
385
    /**
386
     * @var UriBuilder
387
     */
388
    protected $uriBuilder;
389
390
    public function __construct(EventDispatcherInterface $eventDispatcher)
391
    {
392
        $this->eventDispatcher = $eventDispatcher;
393
        $this->uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
394
        $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
395
        $this->moduleTemplate->setUiBlock(true);
396
        // @todo Used by TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching
397
        $GLOBALS['SOBE'] = $this;
398
        $this->getLanguageService()->includeLLFile('EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf');
399
    }
400
401
    /**
402
     * Main dispatcher entry method registered as "record_edit" end point
403
     *
404
     * @param ServerRequestInterface $request the current request
405
     * @return ResponseInterface the response with the content
406
     */
407
    public function mainAction(ServerRequestInterface $request): ResponseInterface
408
    {
409
        // Unlock all locked records
410
        BackendUtility::lockRecords();
411
        if ($response = $this->preInit($request)) {
412
            return $response;
413
        }
414
415
        // Process incoming data via DataHandler?
416
        $parsedBody = $request->getParsedBody();
417
        if ($this->doSave
418
            || isset($parsedBody['_savedok'])
419
            || isset($parsedBody['_saveandclosedok'])
420
            || isset($parsedBody['_savedokview'])
421
            || isset($parsedBody['_savedoknew'])
422
            || isset($parsedBody['_duplicatedoc'])
423
        ) {
424
            if ($response = $this->processData($request)) {
425
                return $response;
426
            }
427
        }
428
429
        $this->init($request);
430
        $this->main($request);
431
432
        return new HtmlResponse($this->moduleTemplate->renderContent());
433
    }
434
435
    /**
436
     * First initialization, always called, even before processData() executes DataHandler processing.
437
     *
438
     * @param ServerRequestInterface $request
439
     * @return ResponseInterface Possible redirect response
440
     */
441
    protected function preInit(ServerRequestInterface $request): ?ResponseInterface
442
    {
443
        if ($response = $this->localizationRedirect($request)) {
444
            return $response;
445
        }
446
447
        $parsedBody = $request->getParsedBody();
448
        $queryParams = $request->getQueryParams();
449
450
        $this->editconf = $parsedBody['edit'] ?? $queryParams['edit'] ?? [];
451
        $this->defVals = $parsedBody['defVals'] ?? $queryParams['defVals'] ?? null;
452
        $this->overrideVals = $parsedBody['overrideVals'] ?? $queryParams['overrideVals'] ?? null;
453
        $this->columnsOnly = $parsedBody['columnsOnly'] ?? $queryParams['columnsOnly'] ?? null;
454
        $this->returnUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? null);
455
        $this->closeDoc = (int)($parsedBody['closeDoc'] ?? $queryParams['closeDoc'] ?? self::DOCUMENT_CLOSE_MODE_DEFAULT);
456
        $this->doSave = (bool)($parsedBody['doSave'] ?? $queryParams['doSave'] ?? false);
457
        $this->returnEditConf = (bool)($parsedBody['returnEditConf'] ?? $queryParams['returnEditConf'] ?? false);
458
        $this->workspace = $parsedBody['workspace'] ?? $queryParams['workspace'] ?? null;
459
        $this->uc = $parsedBody['uc'] ?? $queryParams['uc'] ?? null;
460
461
        // Set overrideVals as default values if defVals does not exist.
462
        // @todo: Why?
463
        if (!is_array($this->defVals) && is_array($this->overrideVals)) {
464
            $this->defVals = $this->overrideVals;
465
        }
466
        $this->addSlugFieldsToColumnsOnly($queryParams);
467
468
        // Set final return URL
469
        $this->retUrl = $this->returnUrl ?: (string)$this->uriBuilder->buildUriFromRoute('dummy');
470
471
        // Change $this->editconf if versioning applies to any of the records
472
        $this->fixWSversioningInEditConf();
473
474
        // Prepare R_URL (request url)
475
        $this->R_URL_parts = parse_url($request->getAttribute('normalizedParams')->getRequestUri());
476
        $this->R_URL_getvars = $queryParams;
477
        $this->R_URL_getvars['edit'] = $this->editconf;
478
479
        // Prepare 'open documents' url, this is later modified again various times
480
        $this->compileStoreData();
481
        // Backend user session data of this module
482
        $this->docDat = $this->getBackendUser()->getModuleData('FormEngine', 'ses');
483
        $this->docHandler = $this->docDat[0];
484
485
        // Close document if a request for closing the document has been sent
486
        if ((int)$this->closeDoc > self::DOCUMENT_CLOSE_MODE_DEFAULT) {
487
            if ($response = $this->closeDocument($this->closeDoc, $request)) {
488
                return $response;
489
            }
490
        }
491
492
        // Sets a temporary workspace, this request is based on
493
        if ($this->workspace !== null) {
494
            $this->getBackendUser()->setTemporaryWorkspace($this->workspace);
495
        }
496
497
        $event = new BeforeFormEnginePageInitializedEvent($this, $request);
498
        $this->eventDispatcher->dispatch($event);
499
        return null;
500
    }
501
502
    /**
503
     * Always add required fields of slug field
504
     *
505
     * @param array $queryParams
506
     */
507
    protected function addSlugFieldsToColumnsOnly(array $queryParams): void
508
    {
509
        $data = $queryParams['edit'] ?? [];
510
        $data = array_keys($data);
511
        $table = reset($data);
512
        if ($this->columnsOnly && $table !== false && isset($GLOBALS['TCA'][$table])) {
513
            $fields = GeneralUtility::trimExplode(',', $this->columnsOnly, true);
514
            foreach ($fields as $field) {
515
                if (isset($GLOBALS['TCA'][$table]['columns'][$field]) && $GLOBALS['TCA'][$table]['columns'][$field]['config']['type'] === 'slug') {
516
                    foreach ($GLOBALS['TCA'][$table]['columns'][$field]['config']['generatorOptions']['fields'] as $fields) {
517
                        $this->columnsOnly .= ',' . (is_array($fields) ? implode(',', $fields) : $fields);
518
                    }
519
                }
520
            }
521
        }
522
    }
523
524
    /**
525
     * Do processing of data, submitting it to DataHandler. May return a RedirectResponse
526
     *
527
     * @param ServerRequestInterface $request
528
     * @return ResponseInterface|null
529
     */
530
    protected function processData(ServerRequestInterface $request): ?ResponseInterface
531
    {
532
        $parsedBody = $request->getParsedBody();
533
        $queryParams = $request->getQueryParams();
534
535
        $beUser = $this->getBackendUser();
536
537
        // Processing related GET / POST vars
538
        $this->data = $parsedBody['data'] ?? $queryParams['data'] ?? [];
539
        $this->cmd = $parsedBody['cmd'] ?? $queryParams['cmd'] ?? [];
540
        $this->mirror = $parsedBody['mirror'] ?? $queryParams['mirror'] ?? [];
541
        $this->returnNewPageId = (bool)($parsedBody['returnNewPageId'] ?? $queryParams['returnNewPageId'] ?? false);
542
543
        // Only options related to $this->data submission are included here
544
        $tce = GeneralUtility::makeInstance(DataHandler::class);
545
546
        $tce->setControl($parsedBody['control'] ?? $queryParams['control'] ?? []);
547
548
        // Set internal vars
549
        if (isset($beUser->uc['neverHideAtCopy']) && $beUser->uc['neverHideAtCopy']) {
550
            $tce->neverHideAtCopy = 1;
551
        }
552
        // Load DataHandler with data
553
        $tce->start($this->data, $this->cmd);
554
        if (is_array($this->mirror)) {
555
            $tce->setMirror($this->mirror);
556
        }
557
558
        // Perform the saving operation with DataHandler:
559
        if ($this->doSave === true) {
560
            $tce->process_datamap();
561
            $tce->process_cmdmap();
562
        }
563
        // If pages are being edited, we set an instruction about updating the page tree after this operation.
564
        if ($tce->pagetreeNeedsRefresh
565
            && (isset($this->data['pages']) || $beUser->workspace != 0 && !empty($this->data))
566
        ) {
567
            BackendUtility::setUpdateSignal('updatePageTree');
568
        }
569
        // If there was saved any new items, load them:
570
        if (!empty($tce->substNEWwithIDs_table)) {
571
            // Save the expanded/collapsed states for new inline records, if any
572
            FormEngineUtility::updateInlineView($this->uc, $tce);
573
            $newEditConf = [];
574
            foreach ($this->editconf as $tableName => $tableCmds) {
575
                $keys = array_keys($tce->substNEWwithIDs_table, $tableName);
576
                if (!empty($keys)) {
577
                    foreach ($keys as $key) {
578
                        $editId = $tce->substNEWwithIDs[$key];
579
                        // Check if the $editId isn't a child record of an IRRE action
580
                        if (!(is_array($tce->newRelatedIDs[$tableName])
581
                            && in_array($editId, $tce->newRelatedIDs[$tableName]))
582
                        ) {
583
                            // Translate new id to the workspace version
584
                            if ($versionRec = BackendUtility::getWorkspaceVersionOfRecord(
585
                                $beUser->workspace,
586
                                $tableName,
587
                                $editId,
588
                                'uid'
589
                            )) {
590
                                $editId = $versionRec['uid'];
591
                            }
592
                            $newEditConf[$tableName][$editId] = 'edit';
593
                        }
594
                        // Traverse all new records and forge the content of ->editconf so we can continue to edit these records!
595
                        if ($tableName === 'pages'
596
                            && $this->retUrl != (string)$this->uriBuilder->buildUriFromRoute('dummy')
597
                            && $this->returnNewPageId
598
                        ) {
599
                            $this->retUrl .= '&id=' . $tce->substNEWwithIDs[$key];
600
                        }
601
                    }
602
                } else {
603
                    $newEditConf[$tableName] = $tableCmds;
604
                }
605
            }
606
            // Reset editconf if newEditConf has values
607
            if (!empty($newEditConf)) {
608
                $this->editconf = $newEditConf;
609
            }
610
            // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
611
            $this->R_URL_getvars['edit'] = $this->editconf;
612
            // Unset default values since we don't need them anymore.
613
            unset($this->R_URL_getvars['defVals']);
614
            // Recompile the store* values since editconf changed
615
            $this->compileStoreData();
616
        }
617
        // See if any records was auto-created as new versions?
618
        if (!empty($tce->autoVersionIdMap)) {
619
            $this->fixWSversioningInEditConf($tce->autoVersionIdMap);
620
        }
621
        // If a document is saved and a new one is created right after.
622
        if (isset($parsedBody['_savedoknew']) && is_array($this->editconf)) {
623
            if ($redirect = $this->closeDocument(self::DOCUMENT_CLOSE_MODE_NO_REDIRECT, $request)) {
624
                return $redirect;
625
            }
626
            // Find the current table
627
            reset($this->editconf);
628
            $nTable = key($this->editconf);
629
            // Finding the first id, getting the records pid+uid
630
            reset($this->editconf[$nTable]);
631
            $nUid = key($this->editconf[$nTable]);
632
            $recordFields = 'pid,uid';
633
            if (BackendUtility::isTableWorkspaceEnabled($nTable)) {
634
                $recordFields .= ',t3ver_oid';
635
            }
636
            $nRec = BackendUtility::getRecord($nTable, $nUid, $recordFields);
0 ignored issues
show
Bug introduced by
It seems like $nUid can also be of type string; however, parameter $uid of TYPO3\CMS\Backend\Utilit...endUtility::getRecord() does only seem to accept integer, 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 ignore-type  annotation

636
            $nRec = BackendUtility::getRecord($nTable, /** @scrutinizer ignore-type */ $nUid, $recordFields);
Loading history...
637
            // Determine insertion mode: 'top' is self-explaining,
638
            // otherwise new elements are inserted after one using a negative uid
639
            $insertRecordOnTop = ($this->getTsConfigOption($nTable, 'saveDocNew') === 'top');
640
            // Setting a blank editconf array for a new record:
641
            $this->editconf = [];
642
            // Determine related page ID for regular live context
643
            if ((int)$nRec['t3ver_oid'] === 0) {
644
                if ($insertRecordOnTop) {
645
                    $relatedPageId = $nRec['pid'];
646
                } else {
647
                    $relatedPageId = -$nRec['uid'];
648
                }
649
            } else {
650
                // Determine related page ID for workspace context
651
                if ($insertRecordOnTop) {
652
                    // Fetch live version of workspace version since the pid value is always -1 in workspaces
653
                    $liveRecord = BackendUtility::getRecord($nTable, $nRec['t3ver_oid'], $recordFields);
654
                    $relatedPageId = $liveRecord['pid'];
655
                } else {
656
                    // Use uid of live version of workspace version
657
                    $relatedPageId = -$nRec['t3ver_oid'];
658
                }
659
            }
660
            $this->editconf[$nTable][$relatedPageId] = 'new';
661
            // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
662
            $this->R_URL_getvars['edit'] = $this->editconf;
663
            // Recompile the store* values since editconf changed...
664
            $this->compileStoreData();
665
        }
666
        // If a document should be duplicated.
667
        if (isset($parsedBody['_duplicatedoc']) && is_array($this->editconf)) {
668
            $this->closeDocument(self::DOCUMENT_CLOSE_MODE_NO_REDIRECT, $request);
669
            // Find current table
670
            reset($this->editconf);
671
            $nTable = key($this->editconf);
672
            // Find the first id, getting the records pid+uid
673
            reset($this->editconf[$nTable]);
674
            $nUid = key($this->editconf[$nTable]);
675
            if (!MathUtility::canBeInterpretedAsInteger($nUid)) {
676
                $nUid = $tce->substNEWwithIDs[$nUid];
677
            }
678
679
            $recordFields = 'pid,uid';
680
            if (!BackendUtility::isTableWorkspaceEnabled($nTable)) {
681
                $recordFields .= ',t3ver_oid';
682
            }
683
            $nRec = BackendUtility::getRecord($nTable, $nUid, $recordFields);
684
685
            // Setting a blank editconf array for a new record:
686
            $this->editconf = [];
687
688
            if ((int)$nRec['t3ver_oid'] === 0) {
689
                $relatedPageId = -$nRec['uid'];
690
            } else {
691
                $relatedPageId = -$nRec['t3ver_oid'];
692
            }
693
694
            /** @var \TYPO3\CMS\Core\DataHandling\DataHandler $duplicateTce */
695
            $duplicateTce = GeneralUtility::makeInstance(DataHandler::class);
696
697
            $duplicateCmd = [
698
                $nTable => [
699
                    $nUid => [
700
                        'copy' => $relatedPageId
701
                    ]
702
                ]
703
            ];
704
705
            $duplicateTce->start([], $duplicateCmd);
706
            $duplicateTce->process_cmdmap();
707
708
            $duplicateMappingArray = $duplicateTce->copyMappingArray;
709
            $duplicateUid = $duplicateMappingArray[$nTable][$nUid];
710
711
            if ($nTable === 'pages') {
712
                BackendUtility::setUpdateSignal('updatePageTree');
713
            }
714
715
            $this->editconf[$nTable][$duplicateUid] = 'edit';
716
            // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
717
            $this->R_URL_getvars['edit'] = $this->editconf;
718
            // Recompile the store* values since editconf changed...
719
            $this->compileStoreData();
720
721
            // Inform the user of the duplication
722
            $flashMessage = GeneralUtility::makeInstance(
723
                FlashMessage::class,
724
                $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.recordDuplicated'),
725
                '',
726
                FlashMessage::OK
727
            );
728
            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
729
            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
730
            $defaultFlashMessageQueue->enqueue($flashMessage);
731
        }
732
        // If a preview is requested
733
        if (isset($parsedBody['_savedokview'])) {
734
            // Get the first table and id of the data array from DataHandler
735
            $table = reset(array_keys($this->data));
736
            $id = reset(array_keys($this->data[$table]));
737
            if (!MathUtility::canBeInterpretedAsInteger($id)) {
738
                $id = $tce->substNEWwithIDs[$id];
739
            }
740
            // Store this information for later use
741
            $this->previewData['table'] = $table;
742
            $this->previewData['id'] = $id;
743
        }
744
        $tce->printLogErrorMessages();
745
746
        if ((int)$this->closeDoc < self::DOCUMENT_CLOSE_MODE_DEFAULT
747
            || isset($parsedBody['_saveandclosedok'])
748
        ) {
749
            // Redirect if element should be closed after save
750
            return $this->closeDocument((int)abs($this->closeDoc), $request);
751
        }
752
        return null;
753
    }
754
755
    /**
756
     * Initialize the view part of the controller logic.
757
     *
758
     * @param ServerRequestInterface $request
759
     */
760
    protected function init(ServerRequestInterface $request): void
761
    {
762
        $parsedBody = $request->getParsedBody();
763
        $queryParams = $request->getQueryParams();
764
765
        $beUser = $this->getBackendUser();
766
767
        $this->popViewId = (int)($parsedBody['popViewId'] ?? $queryParams['popViewId'] ?? 0);
768
        $this->viewUrl = (string)($parsedBody['viewUrl'] ?? $queryParams['viewUrl'] ?? '');
769
        $this->recTitle = (string)($parsedBody['recTitle'] ?? $queryParams['recTitle'] ?? '');
770
        $this->noView = (bool)($parsedBody['noView'] ?? $queryParams['noView'] ?? false);
771
        $this->perms_clause = $beUser->getPagePermsClause(Permission::PAGE_SHOW);
772
        // Set other internal variables:
773
        $this->R_URL_getvars['returnUrl'] = $this->retUrl;
774
        $this->R_URI = $this->R_URL_parts['path'] . HttpUtility::buildQueryString($this->R_URL_getvars, '?');
775
776
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
777
        $pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf');
778
779
        $this->moduleTemplate->addJavaScriptCode(
780
            'previewCode',
781
            (isset($parsedBody['_savedokview']) && $this->popViewId ? $this->generatePreviewCode() : '')
782
        );
783
        // Set context sensitive menu
784
        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
785
786
        $event = new AfterFormEnginePageInitializedEvent($this, $request);
787
        $this->eventDispatcher->dispatch($event);
788
    }
789
790
    /**
791
     * Generate the Javascript for opening the preview window
792
     *
793
     * @return string
794
     */
795
    protected function generatePreviewCode(): string
796
    {
797
        $previewPageId = $this->getPreviewPageId();
798
        $previewPageRootLine = BackendUtility::BEgetRootLine($previewPageId);
799
        $anchorSection = $this->getPreviewUrlAnchorSection();
800
801
        try {
802
            $previewUrlParameters = $this->getPreviewUrlParameters($previewPageId);
803
            return '
804
            if (window.opener) {
805
                '
806
                . BackendUtility::viewOnClick(
807
                    $previewPageId,
808
                    '',
809
                    $previewPageRootLine,
810
                    $anchorSection,
811
                    $this->viewUrl,
812
                    $previewUrlParameters,
813
                    false
814
                )
815
                . '
816
            } else {
817
            '
818
                . BackendUtility::viewOnClick(
819
                    $previewPageId,
820
                    '',
821
                    $previewPageRootLine,
822
                    $anchorSection,
823
                    $this->viewUrl,
824
                    $previewUrlParameters
825
                )
826
                . '
827
            }';
828
        } catch (UnableToLinkToPageException $e) {
829
            return '';
830
        }
831
    }
832
833
    /**
834
     * Returns the parameters for the preview URL
835
     *
836
     * @param int $previewPageId
837
     * @return string
838
     */
839
    protected function getPreviewUrlParameters(int $previewPageId): string
840
    {
841
        $linkParameters = [];
842
        $table = $this->previewData['table'] ?: $this->firstEl['table'];
843
        $recordId = $this->previewData['id'] ?: $this->firstEl['uid'];
844
        $previewConfiguration = BackendUtility::getPagesTSconfig($previewPageId)['TCEMAIN.']['preview.'][$table . '.'] ?? [];
845
        $recordArray = BackendUtility::getRecord($table, $recordId);
846
847
        // language handling
848
        $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'] ?? '';
849
        if ($languageField && !empty($recordArray[$languageField])) {
850
            $recordId = $this->resolvePreviewRecordId($table, $recordArray, $previewConfiguration);
851
            $language = $recordArray[$languageField];
852
            if ($language > 0) {
853
                $linkParameters['L'] = $language;
854
            }
855
        }
856
857
        // Always use live workspace record uid for the preview
858
        if (BackendUtility::isTableWorkspaceEnabled($table) && $recordArray['t3ver_oid'] > 0) {
859
            $recordId = $recordArray['t3ver_oid'];
860
        }
861
862
        // map record data to GET parameters
863
        if (isset($previewConfiguration['fieldToParameterMap.'])) {
864
            foreach ($previewConfiguration['fieldToParameterMap.'] as $field => $parameterName) {
865
                $value = $recordArray[$field];
866
                if ($field === 'uid') {
867
                    $value = $recordId;
868
                }
869
                $linkParameters[$parameterName] = $value;
870
            }
871
        }
872
873
        // add/override parameters by configuration
874
        if (isset($previewConfiguration['additionalGetParameters.'])) {
875
            $additionalGetParameters = [];
876
            $this->parseAdditionalGetParameters(
877
                $additionalGetParameters,
878
                $previewConfiguration['additionalGetParameters.']
879
            );
880
            $linkParameters = array_replace($linkParameters, $additionalGetParameters);
881
        }
882
883
        return HttpUtility::buildQueryString($linkParameters, '&');
884
    }
885
886
    /**
887
     * @param string $table
888
     * @param array $recordArray
889
     * @param array $previewConfiguration
890
     *
891
     * @return int
892
     */
893
    protected function resolvePreviewRecordId(string $table, array $recordArray, array $previewConfiguration): int
894
    {
895
        $l10nPointer = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? '';
896
        if ($l10nPointer
897
            && !empty($recordArray[$l10nPointer])
898
            && (
899
                // not set -> default to true
900
                !isset($previewConfiguration['useDefaultLanguageRecord'])
901
                // or set -> use value
902
                || $previewConfiguration['useDefaultLanguageRecord']
903
            )
904
        ) {
905
            return $recordArray[$l10nPointer];
906
        }
907
        return $recordArray['uid'];
908
    }
909
910
    /**
911
     * Returns the anchor section for the preview url
912
     *
913
     * @return string
914
     */
915
    protected function getPreviewUrlAnchorSection(): string
916
    {
917
        $table = $this->previewData['table'] ?: $this->firstEl['table'];
918
        $recordId = $this->previewData['id'] ?: $this->firstEl['uid'];
919
920
        return $table === 'tt_content' ? '#c' . (int)$recordId : '';
921
    }
922
923
    /**
924
     * Returns the preview page id
925
     *
926
     * @return int
927
     */
928
    protected function getPreviewPageId(): int
929
    {
930
        $previewPageId = 0;
931
        $table = $this->previewData['table'] ?: $this->firstEl['table'];
932
        $recordId = $this->previewData['id'] ?: $this->firstEl['uid'];
933
        $pageId = $this->popViewId ?: $this->viewId;
934
935
        if ($table === 'pages') {
936
            $currentPageId = (int)$recordId;
937
        } else {
938
            $currentPageId = MathUtility::convertToPositiveInteger($pageId);
939
        }
940
941
        $previewConfiguration = BackendUtility::getPagesTSconfig($currentPageId)['TCEMAIN.']['preview.'][$table . '.'] ?? [];
942
943
        if (isset($previewConfiguration['previewPageId'])) {
944
            $previewPageId = (int)$previewConfiguration['previewPageId'];
945
        }
946
        // if no preview page was configured
947
        if (!$previewPageId) {
948
            $rootPageData = null;
949
            $rootLine = BackendUtility::BEgetRootLine($currentPageId);
950
            $currentPage = reset($rootLine);
951
            // Allow all doktypes below 200
952
            // This makes custom doktype work as well with opening a frontend page.
953
            if ((int)$currentPage['doktype'] <= PageRepository::DOKTYPE_SPACER) {
954
                // try the current page
955
                $previewPageId = $currentPageId;
956
            } else {
957
                // or search for the root page
958
                foreach ($rootLine as $page) {
959
                    if ($page['is_siteroot']) {
960
                        $rootPageData = $page;
961
                        break;
962
                    }
963
                }
964
                $previewPageId = isset($rootPageData)
965
                    ? (int)$rootPageData['uid']
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
966
                    : $currentPageId;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
967
            }
968
        }
969
970
        $this->popViewId = $previewPageId;
971
972
        return $previewPageId;
973
    }
974
975
    /**
976
     * Migrates a set of (possibly nested) GET parameters in TypoScript syntax to a plain array
977
     *
978
     * This basically removes the trailing dots of sub-array keys in TypoScript.
979
     * The result can be used to create a query string with GeneralUtility::implodeArrayForUrl().
980
     *
981
     * @param array $parameters Should be an empty array by default
982
     * @param array $typoScript The TypoScript configuration
983
     */
984
    protected function parseAdditionalGetParameters(array &$parameters, array $typoScript)
985
    {
986
        foreach ($typoScript as $key => $value) {
987
            if (is_array($value)) {
988
                $key = rtrim($key, '.');
989
                $parameters[$key] = [];
990
                $this->parseAdditionalGetParameters($parameters[$key], $value);
991
            } else {
992
                $parameters[$key] = $value;
993
            }
994
        }
995
    }
996
997
    /**
998
     * Main module operation
999
     *
1000
     * @param ServerRequestInterface $request
1001
     */
1002
    protected function main(ServerRequestInterface $request): void
1003
    {
1004
        $body = '';
1005
        // Begin edit
1006
        if (is_array($this->editconf)) {
0 ignored issues
show
introduced by
The condition is_array($this->editconf) is always true.
Loading history...
1007
            $this->formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
1008
1009
            // Creating the editing form, wrap it with buttons, document selector etc.
1010
            $editForm = $this->makeEditForm();
1011
            if ($editForm) {
1012
                $this->firstEl = reset($this->elementsData);
1013
                // Checking if the currently open document is stored in the list of "open documents" - if not, add it:
1014
                if (($this->docDat[1] !== $this->storeUrlMd5 || !isset($this->docHandler[$this->storeUrlMd5]))
1015
                    && !$this->dontStoreDocumentRef
1016
                ) {
1017
                    $this->docHandler[$this->storeUrlMd5] = [
1018
                        $this->storeTitle,
1019
                        $this->storeArray,
1020
                        $this->storeUrl,
1021
                        $this->firstEl
1022
                    ];
1023
                    $this->getBackendUser()->pushModuleData('FormEngine', [$this->docHandler, $this->storeUrlMd5]);
1024
                    BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
1025
                }
1026
                $body = $this->formResultCompiler->addCssFiles();
1027
                $body .= $this->compileForm($editForm);
1028
                $body .= $this->formResultCompiler->printNeededJSFunctions();
1029
                $body .= '</form>';
1030
            }
1031
        }
1032
        // Access check...
1033
        // The page will show only if there is a valid page and if this page may be viewed by the user
1034
        $this->pageinfo = BackendUtility::readPageAccess($this->viewId, $this->perms_clause);
1035
        if ($this->pageinfo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->pageinfo of type array<string,string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1036
            $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
1037
        }
1038
        // Setting up the buttons and markers for doc header
1039
        $this->getButtons($request);
1040
        $this->languageSwitch(
1041
            (string)($this->firstEl['table'] ?? ''),
1042
            (int)($this->firstEl['uid'] ?? 0),
1043
            isset($this->firstEl['pid']) ? (int)$this->firstEl['pid'] : null
1044
        );
1045
        $this->moduleTemplate->setContent($body);
1046
    }
1047
1048
    /**
1049
     * Creates the editing form with FormEngine, based on the input from GPvars.
1050
     *
1051
     * @return string HTML form elements wrapped in tables
1052
     */
1053
    protected function makeEditForm(): string
1054
    {
1055
        // Initialize variables
1056
        $this->elementsData = [];
1057
        $this->errorC = 0;
1058
        $this->newC = 0;
1059
        $editForm = '';
1060
        $beUser = $this->getBackendUser();
1061
        // Traverse the GPvar edit array tables
1062
        foreach ($this->editconf as $table => $conf) {
1063
            if (is_array($conf) && $GLOBALS['TCA'][$table] && $beUser->check('tables_modify', $table)) {
1064
                // Traverse the keys/comments of each table (keys can be a comma list of uids)
1065
                foreach ($conf as $cKey => $command) {
1066
                    if ($command === 'edit' || $command === 'new') {
1067
                        // Get the ids:
1068
                        $ids = GeneralUtility::trimExplode(',', $cKey, true);
1069
                        // Traverse the ids:
1070
                        foreach ($ids as $theUid) {
1071
                            // Don't save this document title in the document selector if the document is new.
1072
                            if ($command === 'new') {
1073
                                $this->dontStoreDocumentRef = 1;
0 ignored issues
show
Documentation Bug introduced by
The property $dontStoreDocumentRef was declared of type boolean, but 1 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1074
                            }
1075
1076
                            try {
1077
                                $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
1078
                                $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
1079
                                $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
1080
1081
                                // Reset viewId - it should hold data of last entry only
1082
                                $this->viewId = 0;
1083
                                $this->viewId_addParams = '';
1084
1085
                                $formDataCompilerInput = [
1086
                                    'tableName' => $table,
1087
                                    'vanillaUid' => (int)$theUid,
1088
                                    'command' => $command,
1089
                                    'returnUrl' => $this->R_URI,
1090
                                ];
1091
                                if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
1092
                                    $formDataCompilerInput['overrideValues'] = $this->overrideVals[$table];
1093
                                }
1094
                                if (!empty($this->defVals) && is_array($this->defVals)) {
1095
                                    $formDataCompilerInput['defaultValues'] = $this->defVals;
1096
                                }
1097
1098
                                $formData = $formDataCompiler->compile($formDataCompilerInput);
1099
1100
                                // Set this->viewId if possible
1101
                                if ($command === 'new'
1102
                                    && $table !== 'pages'
1103
                                    && !empty($formData['parentPageRow']['uid'])
1104
                                ) {
1105
                                    $this->viewId = $formData['parentPageRow']['uid'];
1106
                                } else {
1107
                                    if ($table === 'pages') {
1108
                                        $this->viewId = $formData['databaseRow']['uid'];
1109
                                    } elseif (!empty($formData['parentPageRow']['uid'])) {
1110
                                        $this->viewId = $formData['parentPageRow']['uid'];
1111
                                        // Adding "&L=xx" if the record being edited has a languageField with a value larger than zero!
1112
                                        if (!empty($formData['processedTca']['ctrl']['languageField'])
1113
                                            && is_array($formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']])
1114
                                            && $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0] > 0
1115
                                        ) {
1116
                                            $this->viewId_addParams = '&L=' . $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0];
1117
                                        }
1118
                                    }
1119
                                }
1120
1121
                                // Determine if delete button can be shown
1122
                                $deleteAccess = false;
1123
                                if (
1124
                                    $command === 'edit'
1125
                                    || $command === 'new'
1126
                                ) {
1127
                                    $permission = $formData['userPermissionOnPage'];
1128
                                    if ($formData['tableName'] === 'pages') {
1129
                                        $deleteAccess = $permission & Permission::PAGE_DELETE ? true : false;
1130
                                    } else {
1131
                                        $deleteAccess = $permission & Permission::CONTENT_EDIT ? true : false;
1132
                                    }
1133
                                }
1134
1135
                                // Display "is-locked" message
1136
                                if ($command === 'edit') {
1137
                                    $lockInfo = BackendUtility::isRecordLocked($table, $formData['databaseRow']['uid']);
1138
                                    if ($lockInfo) {
1139
                                        $flashMessage = GeneralUtility::makeInstance(
1140
                                            FlashMessage::class,
1141
                                            $lockInfo['msg'],
1142
                                            '',
1143
                                            FlashMessage::WARNING
1144
                                        );
1145
                                        $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1146
                                        $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1147
                                        $defaultFlashMessageQueue->enqueue($flashMessage);
1148
                                    }
1149
                                }
1150
1151
                                // Record title
1152
                                if (!$this->storeTitle) {
1153
                                    $this->storeTitle = $this->recTitle
1154
                                        ? htmlspecialchars($this->recTitle)
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
1155
                                        : BackendUtility::getRecordTitle($table, FormEngineUtility::databaseRowCompatibility($formData['databaseRow']), true);
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
1156
                                }
1157
1158
                                $this->elementsData[] = [
1159
                                    'table' => $table,
1160
                                    'uid' => $formData['databaseRow']['uid'],
1161
                                    'pid' => $formData['databaseRow']['pid'],
1162
                                    'cmd' => $command,
1163
                                    'deleteAccess' => $deleteAccess
1164
                                ];
1165
1166
                                if ($command !== 'new') {
1167
                                    BackendUtility::lockRecords($table, $formData['databaseRow']['uid'], $table === 'tt_content' ? $formData['databaseRow']['pid'] : 0);
1168
                                }
1169
1170
                                // Set list if only specific fields should be rendered. This will trigger
1171
                                // ListOfFieldsContainer instead of FullRecordContainer in OuterWrapContainer
1172
                                if ($this->columnsOnly) {
1173
                                    if (is_array($this->columnsOnly)) {
1174
                                        $formData['fieldListToRender'] = $this->columnsOnly[$table];
1175
                                    } else {
1176
                                        $formData['fieldListToRender'] = $this->columnsOnly;
1177
                                    }
1178
                                }
1179
1180
                                $formData['renderType'] = 'outerWrapContainer';
1181
                                $formResult = $nodeFactory->create($formData)->render();
1182
1183
                                $html = $formResult['html'];
1184
1185
                                $formResult['html'] = '';
1186
                                $formResult['doSaveFieldName'] = 'doSave';
1187
1188
                                // @todo: Put all the stuff into FormEngine as final "compiler" class
1189
                                // @todo: This is done here for now to not rewrite addCssFiles()
1190
                                // @todo: and printNeededJSFunctions() now
1191
                                $this->formResultCompiler->mergeResult($formResult);
1192
1193
                                // Seems the pid is set as hidden field (again) at end?!
1194
                                if ($command === 'new') {
1195
                                    // @todo: looks ugly
1196
                                    $html .= LF
1197
                                        . '<input type="hidden"'
1198
                                        . ' name="data[' . htmlspecialchars($table) . '][' . htmlspecialchars($formData['databaseRow']['uid']) . '][pid]"'
1199
                                        . ' value="' . (int)$formData['databaseRow']['pid'] . '" />';
1200
                                    $this->newC++;
1201
                                }
1202
1203
                                $editForm .= $html;
1204
                            } catch (AccessDeniedException $e) {
1205
                                $this->errorC++;
1206
                                // Try to fetch error message from "recordInternals" be user object
1207
                                // @todo: This construct should be logged and localized and de-uglified
1208
                                $message = (!empty($beUser->errorMsg)) ? $beUser->errorMsg : $message = $e->getMessage() . ' ' . $e->getCode();
0 ignored issues
show
Unused Code introduced by
The assignment to $message is dead and can be removed.
Loading history...
1209
                                $title = $this->getLanguageService()
1210
                                    ->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noEditPermission');
1211
                                $editForm .= $this->getInfobox($message, $title);
1212
                            } catch (DatabaseRecordException $e) {
1213
                                $editForm .= $this->getInfobox($e->getMessage());
1214
                            }
1215
                        } // End of for each uid
1216
                    }
1217
                }
1218
            }
1219
        }
1220
        return $editForm;
1221
    }
1222
1223
    /**
1224
     * Helper function for rendering an Infobox
1225
     *
1226
     * @param string $message
1227
     * @param string|null $title
1228
     * @return string
1229
     */
1230
    protected function getInfobox(string $message, ?string $title = null): string
1231
    {
1232
        return '<div class="callout callout-danger">' .
1233
                '<div class="media">' .
1234
                    '<div class="media-left">' .
1235
                        '<span class="fa-stack fa-lg callout-icon">' .
1236
                            '<i class="fa fa-circle fa-stack-2x"></i>' .
1237
                            '<i class="fa fa-times fa-stack-1x"></i>' .
1238
                        '</span>' .
1239
                    '</div>' .
1240
                    '<div class="media-body">' .
1241
                        ($title ? '<h4 class="callout-title">' . htmlspecialchars($title) . '</h4>' : '') .
1242
                        '<div class="callout-body">' . htmlspecialchars($message) . '</div>' .
1243
                    '</div>' .
1244
                '</div>' .
1245
            '</div>';
1246
    }
1247
1248
    /**
1249
     * Create the panel of buttons for submitting the form or otherwise perform operations.
1250
     *
1251
     * @param ServerRequestInterface $request
1252
     */
1253
    protected function getButtons(ServerRequestInterface $request): void
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1253
    protected function getButtons(/** @scrutinizer ignore-unused */ ServerRequestInterface $request): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1254
    {
1255
        $record = BackendUtility::getRecord($this->firstEl['table'], $this->firstEl['uid']);
1256
        $TCActrl = $GLOBALS['TCA'][$this->firstEl['table']]['ctrl'];
1257
1258
        $this->setIsSavedRecord();
1259
1260
        $sysLanguageUid = 0;
1261
        if (
1262
            $this->isSavedRecord
1263
            && isset($TCActrl['languageField'], $record[$TCActrl['languageField']])
1264
        ) {
1265
            $sysLanguageUid = (int)$record[$TCActrl['languageField']];
1266
        } elseif (isset($this->defVals['sys_language_uid'])) {
1267
            $sysLanguageUid = (int)$this->defVals['sys_language_uid'];
1268
        }
1269
1270
        $l18nParent = isset($TCActrl['transOrigPointerField'], $record[$TCActrl['transOrigPointerField']])
1271
            ? (int)$record[$TCActrl['transOrigPointerField']]
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
1272
            : 0;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
1273
1274
        $this->setIsPageInFreeTranslationMode($record, $sysLanguageUid);
1275
1276
        $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
1277
1278
        $this->registerCloseButtonToButtonBar($buttonBar, ButtonBar::BUTTON_POSITION_LEFT, 1);
1279
1280
        // Show buttons when table is not read-only
1281
        if (
1282
            !$this->errorC
1283
            && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1284
        ) {
1285
            $this->registerSaveButtonToButtonBar($buttonBar, ButtonBar::BUTTON_POSITION_LEFT, 2);
1286
            $this->registerViewButtonToButtonBar($buttonBar, ButtonBar::BUTTON_POSITION_LEFT, 3);
1287
            if ($this->firstEl['cmd'] !== 'new') {
1288
                $this->registerNewButtonToButtonBar(
1289
                    $buttonBar,
1290
                    ButtonBar::BUTTON_POSITION_LEFT,
1291
                    4,
1292
                    $sysLanguageUid,
1293
                    $l18nParent
1294
                );
1295
                $this->registerDuplicationButtonToButtonBar(
1296
                    $buttonBar,
1297
                    ButtonBar::BUTTON_POSITION_LEFT,
1298
                    5,
1299
                    $sysLanguageUid,
1300
                    $l18nParent
1301
                );
1302
            }
1303
            $this->registerDeleteButtonToButtonBar($buttonBar, ButtonBar::BUTTON_POSITION_LEFT, 6);
1304
            $this->registerColumnsOnlyButtonToButtonBar($buttonBar, ButtonBar::BUTTON_POSITION_LEFT, 7);
1305
            $this->registerHistoryButtonToButtonBar($buttonBar, ButtonBar::BUTTON_POSITION_RIGHT, 1);
1306
        }
1307
1308
        $this->registerOpenInNewWindowButtonToButtonBar($buttonBar, ButtonBar::BUTTON_POSITION_RIGHT, 2);
1309
        $this->registerShortcutButtonToButtonBar($buttonBar, ButtonBar::BUTTON_POSITION_RIGHT, 3);
1310
        $this->registerCshButtonToButtonBar($buttonBar, ButtonBar::BUTTON_POSITION_RIGHT, 4);
1311
    }
1312
1313
    /**
1314
     * Set the boolean to check if the record is saved
1315
     */
1316
    protected function setIsSavedRecord()
1317
    {
1318
        if (!is_bool($this->isSavedRecord)) {
0 ignored issues
show
introduced by
The condition is_bool($this->isSavedRecord) is always true.
Loading history...
1319
            $this->isSavedRecord = (
1320
                $this->firstEl['cmd'] !== 'new'
1321
                && MathUtility::canBeInterpretedAsInteger($this->firstEl['uid'])
1322
            );
1323
        }
1324
    }
1325
1326
    /**
1327
     * Returns if inconsistent language handling is allowed
1328
     *
1329
     * @return bool
1330
     */
1331
    protected function isInconsistentLanguageHandlingAllowed(): bool
1332
    {
1333
        $allowInconsistentLanguageHandling = BackendUtility::getPagesTSconfig(
1334
            $this->pageinfo['uid']
1335
        )['mod']['web_layout']['allowInconsistentLanguageHandling'];
1336
1337
        return $allowInconsistentLanguageHandling['value'] === '1';
1338
    }
1339
1340
    /**
1341
     * Set the boolean to check if the page is in free translation mode
1342
     *
1343
     * @param array|null $record
1344
     * @param int $sysLanguageUid
1345
     */
1346
    protected function setIsPageInFreeTranslationMode($record, int $sysLanguageUid)
1347
    {
1348
        if ($this->firstEl['table'] === 'tt_content') {
1349
            if (!$this->isSavedRecord) {
1350
                $this->isPageInFreeTranslationMode = $this->getFreeTranslationMode(
1351
                    (int)$this->pageinfo['uid'],
1352
                    (int)$this->defVals['colPos'],
1353
                    $sysLanguageUid
1354
                );
1355
            } else {
1356
                $this->isPageInFreeTranslationMode = $this->getFreeTranslationMode(
1357
                    (int)$this->pageinfo['uid'],
1358
                    (int)$record['colPos'],
1359
                    $sysLanguageUid
1360
                );
1361
            }
1362
        }
1363
    }
1364
1365
    /**
1366
     * Check if the page is in free translation mode
1367
     *
1368
     * @param int $page
1369
     * @param int $column
1370
     * @param int $language
1371
     * @return bool
1372
     */
1373
    protected function getFreeTranslationMode(int $page, int $column, int $language): bool
1374
    {
1375
        $freeTranslationMode = false;
1376
1377
        if (
1378
            $this->getConnectedContentElementTranslationsCount($page, $column, $language) === 0
1379
            && $this->getStandAloneContentElementTranslationsCount($page, $column, $language) >= 0
1380
        ) {
1381
            $freeTranslationMode = true;
1382
        }
1383
1384
        return $freeTranslationMode;
1385
    }
1386
1387
    /**
1388
     * Register the close button to the button bar
1389
     *
1390
     * @param ButtonBar $buttonBar
1391
     * @param string $position
1392
     * @param int $group
1393
     */
1394
    protected function registerCloseButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group)
1395
    {
1396
        $closeButton = $buttonBar->makeLinkButton()
1397
            ->setHref('#')
1398
            ->setClasses('t3js-editform-close')
1399
            ->setTitle($this->getLanguageService()->sL(
1400
                'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'
1401
            ))
1402
            ->setShowLabelText(true)
1403
            ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1404
                'actions-close',
1405
                Icon::SIZE_SMALL
1406
            ));
1407
1408
        $buttonBar->addButton($closeButton, $position, $group);
1409
    }
1410
1411
    /**
1412
     * Register the save button to the button bar
1413
     *
1414
     * @param ButtonBar $buttonBar
1415
     * @param string $position
1416
     * @param int $group
1417
     */
1418
    protected function registerSaveButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group)
1419
    {
1420
        $saveButton = $buttonBar->makeInputButton()
1421
            ->setForm('EditDocumentController')
1422
            ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL))
1423
            ->setName('_savedok')
1424
            ->setShowLabelText(true)
1425
            ->setTitle($this->getLanguageService()->sL(
1426
                'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'
1427
            ))
1428
            ->setValue('1');
1429
1430
        $buttonBar->addButton($saveButton, $position, $group);
1431
    }
1432
1433
    /**
1434
     * Register the view button to the button bar
1435
     *
1436
     * @param ButtonBar $buttonBar
1437
     * @param string $position
1438
     * @param int $group
1439
     */
1440
    protected function registerViewButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group)
1441
    {
1442
        if (
1443
            $this->viewId // Pid to show the record
1444
            && !$this->noView // Passed parameter
1445
            && !empty($this->firstEl['table']) // No table
1446
1447
            // @TODO: TsConfig option should change to viewDoc
1448
            && $this->getTsConfigOption($this->firstEl['table'], 'saveDocView')
1449
        ) {
1450
            $classNames = 't3js-editform-view';
1451
1452
            $pagesTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid']);
1453
1454
            if (isset($pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'])) {
1455
                $excludeDokTypes = GeneralUtility::intExplode(
1456
                    ',',
1457
                    $pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'],
1458
                    true
1459
                );
1460
            } else {
1461
                // exclude sysfolders, spacers and recycler by default
1462
                $excludeDokTypes = [
1463
                    PageRepository::DOKTYPE_RECYCLER,
1464
                    PageRepository::DOKTYPE_SYSFOLDER,
1465
                    PageRepository::DOKTYPE_SPACER
1466
                ];
1467
            }
1468
1469
            if (
1470
                !in_array((int)$this->pageinfo['doktype'], $excludeDokTypes, true)
1471
                || isset($pagesTSconfig['TCEMAIN.']['preview.'][$this->firstEl['table'] . '.']['previewPageId'])
1472
            ) {
1473
                $previewPageId = $this->getPreviewPageId();
1474
                try {
1475
                    $previewUrl = BackendUtility::getPreviewUrl(
1476
                        $previewPageId,
1477
                        '',
1478
                        BackendUtility::BEgetRootLine($previewPageId),
1479
                        $this->getPreviewUrlAnchorSection(),
1480
                        $this->viewUrl,
1481
                        $this->getPreviewUrlParameters($previewPageId)
1482
                    );
1483
1484
                    $viewButton = $buttonBar->makeLinkButton()
1485
                        ->setHref($previewUrl)
1486
                        ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1487
                            'actions-view',
1488
                            Icon::SIZE_SMALL
1489
                        ))
1490
                        ->setShowLabelText(true)
1491
                        ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.viewDoc'));
1492
1493
                    if (!$this->isSavedRecord) {
1494
                        if ($this->firstEl['table'] === 'pages') {
1495
                            $viewButton->setDataAttributes(['is-new' => '']);
1496
                        }
1497
                    }
1498
1499
                    if ($classNames !== '') {
0 ignored issues
show
introduced by
The condition $classNames !== '' is always true.
Loading history...
1500
                        $viewButton->setClasses($classNames);
1501
                    }
1502
1503
                    $buttonBar->addButton($viewButton, $position, $group);
1504
                } catch (UnableToLinkToPageException $e) {
1505
                    // Do not add any button
1506
                }
1507
            }
1508
        }
1509
    }
1510
1511
    /**
1512
     * Register the new button to the button bar
1513
     *
1514
     * @param ButtonBar $buttonBar
1515
     * @param string $position
1516
     * @param int $group
1517
     * @param int $sysLanguageUid
1518
     * @param int $l18nParent
1519
     */
1520
    protected function registerNewButtonToButtonBar(
1521
        ButtonBar $buttonBar,
1522
        string $position,
1523
        int $group,
1524
        int $sysLanguageUid,
1525
        int $l18nParent
1526
    ) {
1527
        if (
1528
            $this->firstEl['table'] !== 'sys_file_metadata'
1529
            && !empty($this->firstEl['table'])
1530
            && (
1531
                (
1532
                    (
1533
                        $this->isInconsistentLanguageHandlingAllowed()
1534
                        || $this->isPageInFreeTranslationMode
1535
                    )
1536
                    && $this->firstEl['table'] === 'tt_content'
1537
                )
1538
                || (
1539
                    $this->firstEl['table'] !== 'tt_content'
1540
                    && (
1541
                        $sysLanguageUid === 0
1542
                        || $l18nParent === 0
1543
                    )
1544
                )
1545
            )
1546
        ) {
1547
            $classNames = 't3js-editform-new';
1548
1549
            $newButton = $buttonBar->makeLinkButton()
1550
                ->setHref('#')
1551
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1552
                    'actions-add',
1553
                    Icon::SIZE_SMALL
1554
                ))
1555
                ->setShowLabelText(true)
1556
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.newDoc'));
1557
1558
            if (!$this->isSavedRecord) {
1559
                $newButton->setDataAttributes(['is-new' => '']);
1560
            }
1561
1562
            if ($classNames !== '') {
0 ignored issues
show
introduced by
The condition $classNames !== '' is always true.
Loading history...
1563
                $newButton->setClasses($classNames);
1564
            }
1565
1566
            $buttonBar->addButton($newButton, $position, $group);
1567
        }
1568
    }
1569
1570
    /**
1571
     * Register the duplication button to the button bar
1572
     *
1573
     * @param ButtonBar $buttonBar
1574
     * @param string $position
1575
     * @param int $group
1576
     * @param int $sysLanguageUid
1577
     * @param int $l18nParent
1578
     */
1579
    protected function registerDuplicationButtonToButtonBar(
1580
        ButtonBar $buttonBar,
1581
        string $position,
1582
        int $group,
1583
        int $sysLanguageUid,
1584
        int $l18nParent
1585
    ) {
1586
        if (
1587
            $this->firstEl['table'] !== 'sys_file_metadata'
1588
            && !empty($this->firstEl['table'])
1589
            && (
1590
                (
1591
                    (
1592
                        $this->isInconsistentLanguageHandlingAllowed()
1593
                        || $this->isPageInFreeTranslationMode
1594
                    )
1595
                    && $this->firstEl['table'] === 'tt_content'
1596
                )
1597
                || (
1598
                    $this->firstEl['table'] !== 'tt_content'
1599
                    && (
1600
                        $sysLanguageUid === 0
1601
                        || $l18nParent === 0
1602
                    )
1603
                )
1604
            )
1605
            && $this->getTsConfigOption($this->firstEl['table'], 'showDuplicate')
1606
        ) {
1607
            $classNames = 't3js-editform-duplicate';
1608
1609
            $duplicateButton = $buttonBar->makeLinkButton()
1610
                ->setHref('#')
1611
                ->setShowLabelText(true)
1612
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.duplicateDoc'))
1613
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1614
                    'actions-document-duplicates-select',
1615
                    Icon::SIZE_SMALL
1616
                ));
1617
1618
            if (!$this->isSavedRecord) {
1619
                $duplicateButton->setDataAttributes(['is-new' => '']);
1620
            }
1621
1622
            if ($classNames !== '') {
0 ignored issues
show
introduced by
The condition $classNames !== '' is always true.
Loading history...
1623
                $duplicateButton->setClasses($classNames);
1624
            }
1625
1626
            $buttonBar->addButton($duplicateButton, $position, $group);
1627
        }
1628
    }
1629
1630
    /**
1631
     * Register the delete button to the button bar
1632
     *
1633
     * @param ButtonBar $buttonBar
1634
     * @param string $position
1635
     * @param int $group
1636
     */
1637
    protected function registerDeleteButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group)
1638
    {
1639
        if (
1640
            $this->firstEl['deleteAccess']
1641
            && !$this->getDisableDelete()
1642
            && $this->isSavedRecord
1643
            && count($this->elementsData) === 1
1644
        ) {
1645
            $classNames = 't3js-editform-delete-record';
1646
            $returnUrl = $this->retUrl;
1647
            if ($this->firstEl['table'] === 'pages') {
1648
                parse_str((string)parse_url($returnUrl, PHP_URL_QUERY), $queryParams);
1649
                if (
1650
                    isset($queryParams['route'], $queryParams['id'])
1651
                    && (string)$this->firstEl['uid'] === (string)$queryParams['id']
1652
                ) {
1653
                    // TODO: Use the page's pid instead of 0, this requires a clean API to manipulate the page
1654
                    // tree from the outside to be able to mark the pid as active
1655
                    $returnUrl = (string)$this->uriBuilder->buildUriFromRoutePath($queryParams['route'], ['id' => 0]);
1656
                }
1657
            }
1658
1659
            /** @var ReferenceIndex $referenceIndex */
1660
            $referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
1661
            $numberOfReferences = $referenceIndex->getNumberOfReferencedRecords(
1662
                $this->firstEl['table'],
1663
                (int)$this->firstEl['uid']
1664
            );
1665
1666
            $referenceCountMessage = BackendUtility::referenceCount(
1667
                $this->firstEl['table'],
1668
                (int)$this->firstEl['uid'],
1669
                $this->getLanguageService()->sL(
1670
                    'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToRecord'
1671
                ),
1672
                $numberOfReferences
1673
            );
1674
            $translationCountMessage = BackendUtility::translationCount(
1675
                $this->firstEl['table'],
1676
                (int)$this->firstEl['uid'],
1677
                $this->getLanguageService()->sL(
1678
                    'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.translationsOfRecord'
1679
                )
1680
            );
1681
1682
            $deleteUrl = (string)$this->uriBuilder->buildUriFromRoute('tce_db', [
1683
                'cmd' => [
1684
                    $this->firstEl['table'] => [
1685
                        $this->firstEl['uid'] => [
1686
                            'delete' => '1'
1687
                        ]
1688
                    ]
1689
                ],
1690
                'redirect' => $this->retUrl
1691
            ]);
1692
1693
            $deleteButton = $buttonBar->makeLinkButton()
1694
                ->setClasses($classNames)
1695
                ->setDataAttributes([
1696
                    'return-url' => $returnUrl,
1697
                    'uid' => $this->firstEl['uid'],
1698
                    'table' => $this->firstEl['table'],
1699
                    'reference-count-message' => $referenceCountMessage,
1700
                    'translation-count-message' => $translationCountMessage
1701
                ])
1702
                ->setHref($deleteUrl)
1703
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1704
                    'actions-edit-delete',
1705
                    Icon::SIZE_SMALL
1706
                ))
1707
                ->setShowLabelText(true)
1708
                ->setTitle($this->getLanguageService()->getLL('deleteItem'));
1709
1710
            $buttonBar->addButton($deleteButton, $position, $group);
1711
        }
1712
    }
1713
1714
    /**
1715
     * Register the history button to the button bar
1716
     *
1717
     * @param ButtonBar $buttonBar
1718
     * @param string $position
1719
     * @param int $group
1720
     */
1721
    protected function registerHistoryButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group)
1722
    {
1723
        if (
1724
            count($this->elementsData) === 1
1725
            && !empty($this->firstEl['table'])
1726
            && $this->getTsConfigOption($this->firstEl['table'], 'showHistory')
1727
        ) {
1728
            $historyButtonOnClick = 'window.location.href=' .
1729
                GeneralUtility::quoteJSvalue(
1730
                    (string)$this->uriBuilder->buildUriFromRoute(
1731
                        'record_history',
1732
                        [
1733
                            'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1734
                            'returnUrl' => $this->R_URI,
1735
                        ]
1736
                    )
1737
                ) . '; return false;';
1738
1739
            $historyButton = $buttonBar->makeLinkButton()
1740
                ->setHref('#')
1741
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1742
                    'actions-document-history-open',
1743
                    Icon::SIZE_SMALL
1744
                ))
1745
                ->setOnClick($historyButtonOnClick)
1746
                ->setTitle('Open history of this record')
1747
                ;
0 ignored issues
show
Coding Style introduced by
Space found before semicolon; expected ");" but found ")
;"
Loading history...
1748
1749
            $buttonBar->addButton($historyButton, $position, $group);
1750
        }
1751
    }
1752
1753
    /**
1754
     * Register the columns only button to the button bar
1755
     *
1756
     * @param ButtonBar $buttonBar
1757
     * @param string $position
1758
     * @param int $group
1759
     */
1760
    protected function registerColumnsOnlyButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group)
1761
    {
1762
        if (
1763
            $this->columnsOnly
1764
            && count($this->elementsData) === 1
1765
        ) {
1766
            $columnsOnlyButton = $buttonBar->makeLinkButton()
1767
                ->setHref($this->R_URI . '&columnsOnly=')
1768
                ->setTitle($this->getLanguageService()->getLL('editWholeRecord'))
1769
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1770
                    'actions-open',
1771
                    Icon::SIZE_SMALL
1772
                ));
1773
1774
            $buttonBar->addButton($columnsOnlyButton, $position, $group);
1775
        }
1776
    }
1777
1778
    /**
1779
     * Register the open in new window button to the button bar
1780
     *
1781
     * @param ButtonBar $buttonBar
1782
     * @param string $position
1783
     * @param int $group
1784
     */
1785
    protected function registerOpenInNewWindowButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group)
1786
    {
1787
        $closeUrl = $this->getCloseUrl();
1788
        if ($this->returnUrl !== $closeUrl) {
1789
            $requestUri = GeneralUtility::linkThisScript([
1790
                'returnUrl' => $closeUrl,
1791
            ]);
1792
            $aOnClick = 'vHWin=window.open('
1793
                . GeneralUtility::quoteJSvalue($requestUri) . ','
1794
                . GeneralUtility::quoteJSvalue(md5($this->R_URI))
1795
                . ',\'width=670,height=500,status=0,menubar=0,scrollbars=1,resizable=1\');vHWin.focus();return false;';
1796
1797
            $openInNewWindowButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()
1798
                ->makeLinkButton()
1799
                ->setHref('#')
1800
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.openInNewWindow'))
1801
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-window-open', Icon::SIZE_SMALL))
1802
                ->setOnClick($aOnClick);
1803
1804
            $buttonBar->addButton($openInNewWindowButton, $position, $group);
1805
        }
1806
    }
1807
1808
    /**
1809
     * Register the shortcut button to the button bar
1810
     *
1811
     * @param ButtonBar $buttonBar
1812
     * @param string $position
1813
     * @param int $group
1814
     */
1815
    protected function registerShortcutButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group)
1816
    {
1817
        if ($this->returnUrl !== $this->getCloseUrl()) {
1818
            $shortCutButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->makeShortcutButton();
1819
            $shortCutButton->setModuleName('xMOD_alt_doc.php')
1820
                ->setGetVariables([
1821
                    'returnUrl',
1822
                    'edit',
1823
                    'defVals',
1824
                    'overrideVals',
1825
                    'columnsOnly',
1826
                    'returnNewPageId',
1827
                    'noView']);
1828
1829
            $buttonBar->addButton($shortCutButton, $position, $group);
1830
        }
1831
    }
1832
1833
    /**
1834
     * Register the CSH button to the button bar
1835
     *
1836
     * @param ButtonBar $buttonBar
1837
     * @param string $position
1838
     * @param int $group
1839
     */
1840
    protected function registerCshButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group)
1841
    {
1842
        $cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('TCEforms');
1843
1844
        $buttonBar->addButton($cshButton, $position, $group);
1845
    }
1846
1847
    /**
1848
     * Get the count of connected translated content elements
1849
     *
1850
     * @param int $page
1851
     * @param int $column
1852
     * @param int $language
1853
     * @return int
1854
     */
1855
    protected function getConnectedContentElementTranslationsCount(int $page, int $column, int $language): int
1856
    {
1857
        $queryBuilder = $this->getQueryBuilderForTranslationMode($page, $column, $language);
1858
1859
        return (int)$queryBuilder
1860
            ->andWhere(
1861
                $queryBuilder->expr()->gt(
1862
                    $GLOBALS['TCA']['tt_content']['ctrl']['transOrigPointerField'],
1863
                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1864
                )
1865
            )
1866
            ->execute()
1867
            ->fetchColumn(0);
1868
    }
1869
1870
    /**
1871
     * Get the count of standalone translated content elements
1872
     *
1873
     * @param int $page
1874
     * @param int $column
1875
     * @param int $language
1876
     * @return int
1877
     */
1878
    protected function getStandAloneContentElementTranslationsCount(int $page, int $column, int $language): int
1879
    {
1880
        $queryBuilder = $this->getQueryBuilderForTranslationMode($page, $column, $language);
1881
1882
        return (int)$queryBuilder
1883
            ->andWhere(
1884
                $queryBuilder->expr()->eq(
1885
                    $GLOBALS['TCA']['tt_content']['ctrl']['transOrigPointerField'],
1886
                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1887
                )
1888
            )
1889
            ->execute()
1890
            ->fetchColumn(0);
1891
    }
1892
1893
    /**
1894
     * Get the query builder for the translation mode
1895
     *
1896
     * @param int $page
1897
     * @param int $column
1898
     * @param int $language
1899
     * @return QueryBuilder
1900
     */
1901
    protected function getQueryBuilderForTranslationMode(int $page, int $column, int $language): QueryBuilder
1902
    {
1903
        $languageField = $GLOBALS['TCA']['tt_content']['ctrl']['languageField'];
1904
1905
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1906
            ->getQueryBuilderForTable('tt_content');
1907
1908
        $queryBuilder->getRestrictions()
1909
            ->removeAll()
1910
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1911
            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1912
1913
        return $queryBuilder
1914
            ->count('uid')
1915
            ->from('tt_content')
1916
            ->where(
1917
                $queryBuilder->expr()->eq(
1918
                    'pid',
1919
                    $queryBuilder->createNamedParameter($page, \PDO::PARAM_INT)
1920
                ),
1921
                $queryBuilder->expr()->eq(
1922
                    $languageField,
1923
                    $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
1924
                ),
1925
                $queryBuilder->expr()->eq(
1926
                    'colPos',
1927
                    $queryBuilder->createNamedParameter($column, \PDO::PARAM_INT)
1928
                )
1929
            );
1930
    }
1931
1932
    /**
1933
     * Put together the various elements (buttons, selectors, form) into a table
1934
     *
1935
     * @param string $editForm HTML form.
1936
     * @return string Composite HTML
1937
     */
1938
    protected function compileForm(string $editForm): string
1939
    {
1940
        $formContent = '
1941
            <form
1942
                action="' . htmlspecialchars($this->R_URI) . '"
1943
                method="post"
1944
                enctype="multipart/form-data"
1945
                name="editform"
1946
                id="EditDocumentController"
1947
            >
1948
            ' . $editForm . '
1949
            <input type="hidden" name="returnUrl" value="' . htmlspecialchars($this->retUrl) . '" />
1950
            <input type="hidden" name="viewUrl" value="' . htmlspecialchars($this->viewUrl) . '" />
1951
            <input type="hidden" name="popViewId" value="' . htmlspecialchars((string)$this->viewId) . '" />
1952
            <input type="hidden" name="closeDoc" value="0" />
1953
            <input type="hidden" name="doSave" value="0" />
1954
            <input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />
1955
            <input type="hidden" name="_scrollPosition" value="" />';
1956
        if ($this->returnNewPageId) {
1957
            $formContent .= '<input type="hidden" name="returnNewPageId" value="1" />';
1958
        }
1959
        if ($this->viewId_addParams) {
1960
            $formContent .= '<input type="hidden" name="popViewId_addParams" value="' . htmlspecialchars($this->viewId_addParams) . '" />';
1961
        }
1962
        return $formContent;
1963
    }
1964
1965
    /**
1966
     * Returns if delete for the current table is disabled by configuration.
1967
     * For sys_file_metadata in default language delete is always disabled.
1968
     *
1969
     * @return bool
1970
     */
1971
    protected function getDisableDelete(): bool
1972
    {
1973
        $disableDelete = false;
1974
        if ($this->firstEl['table'] === 'sys_file_metadata') {
1975
            $row = BackendUtility::getRecord('sys_file_metadata', $this->firstEl['uid'], 'sys_language_uid');
1976
            $languageUid = $row['sys_language_uid'];
1977
            if ($languageUid === 0) {
1978
                $disableDelete = true;
1979
            }
1980
        } else {
1981
            $disableDelete = (bool)$this->getTsConfigOption($this->firstEl['table'] ?? '', 'disableDelete');
1982
        }
1983
        return $disableDelete;
1984
    }
1985
1986
    /**
1987
     * Returns the URL (usually for the "returnUrl") which closes the current window.
1988
     * Used when editing a record in a popup.
1989
     *
1990
     * @return string
1991
     */
1992
    protected function getCloseUrl(): string
1993
    {
1994
        $closeUrl = GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Public/Html/Close.html');
1995
        return PathUtility::getAbsoluteWebPath($closeUrl);
1996
    }
1997
1998
    /***************************
1999
     *
2000
     * Localization stuff
2001
     *
2002
     ***************************/
2003
    /**
2004
     * Make selector box for creating new translation for a record or switching to edit the record in an existing
2005
     * language.
2006
     * Displays only languages which are available for the current page.
2007
     *
2008
     * @param string $table Table name
2009
     * @param int $uid Uid for which to create a new language
2010
     * @param int|null $pid Pid of the record
2011
     */
2012
    protected function languageSwitch(string $table, int $uid, $pid = null)
2013
    {
2014
        $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
2015
        $transOrigPointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
2016
        // Table editable and activated for languages?
2017
        if ($this->getBackendUser()->check('tables_modify', $table)
2018
            && $languageField
2019
            && $transOrigPointerField
2020
        ) {
2021
            if ($pid === null) {
2022
                $row = BackendUtility::getRecord($table, $uid, 'pid');
2023
                $pid = $row['pid'];
2024
            }
2025
            // Get all available languages for the page
2026
            // If editing a page, the translations of the current UID need to be fetched
2027
            if ($table === 'pages') {
2028
                $row = BackendUtility::getRecord($table, $uid, $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']);
2029
                // Ensure the check is always done against the default language page
2030
                $availableLanguages = $this->getLanguages(
2031
                    (int)$row[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']] ?: $uid,
2032
                    $table
2033
                );
2034
            } else {
2035
                $availableLanguages = $this->getLanguages((int)$pid, $table);
2036
            }
2037
            // Page available in other languages than default language?
2038
            if (count($availableLanguages) > 1) {
2039
                $rowsByLang = [];
2040
                $fetchFields = 'uid,' . $languageField . ',' . $transOrigPointerField;
2041
                // Get record in current language
2042
                $rowCurrent = BackendUtility::getLiveVersionOfRecord($table, $uid, $fetchFields);
2043
                if (!is_array($rowCurrent)) {
2044
                    $rowCurrent = BackendUtility::getRecord($table, $uid, $fetchFields);
2045
                }
2046
                $currentLanguage = (int)$rowCurrent[$languageField];
2047
                // Disabled for records with [all] language!
2048
                if ($currentLanguage > -1) {
2049
                    // Get record in default language if needed
2050
                    if ($currentLanguage && $rowCurrent[$transOrigPointerField]) {
2051
                        $rowsByLang[0] = BackendUtility::getLiveVersionOfRecord(
2052
                            $table,
2053
                            $rowCurrent[$transOrigPointerField],
2054
                            $fetchFields
2055
                        );
2056
                        if (!is_array($rowsByLang[0])) {
2057
                            $rowsByLang[0] = BackendUtility::getRecord(
2058
                                $table,
2059
                                $rowCurrent[$transOrigPointerField],
2060
                                $fetchFields
2061
                            );
2062
                        }
2063
                    } else {
2064
                        $rowsByLang[$rowCurrent[$languageField]] = $rowCurrent;
2065
                    }
2066
                    if ($rowCurrent[$transOrigPointerField] || $currentLanguage === 0) {
2067
                        // Get record in other languages to see what's already available
2068
2069
                        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
2070
                            ->getQueryBuilderForTable($table);
2071
2072
                        $queryBuilder->getRestrictions()
2073
                            ->removeAll()
2074
                            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2075
                            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
2076
2077
                        $result = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fetchFields, true))
2078
                            ->from($table)
2079
                            ->where(
2080
                                $queryBuilder->expr()->eq(
2081
                                    'pid',
2082
                                    $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)
2083
                                ),
2084
                                $queryBuilder->expr()->gt(
2085
                                    $languageField,
2086
                                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
2087
                                ),
2088
                                $queryBuilder->expr()->eq(
2089
                                    $transOrigPointerField,
2090
                                    $queryBuilder->createNamedParameter($rowsByLang[0]['uid'], \PDO::PARAM_INT)
2091
                                )
2092
                            )
2093
                            ->execute();
2094
2095
                        while ($row = $result->fetch()) {
2096
                            $rowsByLang[$row[$languageField]] = $row;
2097
                        }
2098
                    }
2099
                    $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
2100
                    $languageMenu->setIdentifier('_langSelector');
2101
                    foreach ($availableLanguages as $language) {
2102
                        $languageId = $language->getLanguageId();
2103
                        $selectorOptionLabel = $language->getTitle();
2104
                        // Create url for creating a localized record
2105
                        $addOption = true;
2106
                        $href = '';
2107
                        if (!isset($rowsByLang[$languageId])) {
2108
                            // Translation in this language does not exist
2109
                            $selectorOptionLabel .= ' [' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.new')) . ']';
2110
                            $redirectUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', [
2111
                                'justLocalized' => $table . ':' . $rowsByLang[0]['uid'] . ':' . $languageId,
2112
                                'returnUrl' => $this->retUrl
2113
                            ]);
2114
2115
                            if (array_key_exists(0, $rowsByLang)) {
2116
                                $href = BackendUtility::getLinkToDataHandlerAction(
2117
                                    '&cmd[' . $table . '][' . $rowsByLang[0]['uid'] . '][localize]=' . $languageId,
2118
                                    $redirectUrl
2119
                                );
2120
                            } else {
2121
                                $addOption = false;
2122
                            }
2123
                        } else {
2124
                            $params = [
2125
                                'edit[' . $table . '][' . $rowsByLang[$languageId]['uid'] . ']' => 'edit',
2126
                                'returnUrl' => $this->retUrl
2127
                            ];
2128
                            if ($table === 'pages') {
2129
                                // Disallow manual adjustment of the language field for pages
2130
                                $params['overrideVals'] = [
2131
                                    'pages' => [
2132
                                        'sys_language_uid' => $languageId
2133
                                    ]
2134
                                ];
2135
                            }
2136
                            $href = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $params);
2137
                        }
2138
                        if ($addOption) {
2139
                            $menuItem = $languageMenu->makeMenuItem()
2140
                                ->setTitle($selectorOptionLabel)
2141
                                ->setHref($href);
2142
                            if ($languageId === $currentLanguage) {
2143
                                $menuItem->setActive(true);
2144
                            }
2145
                            $languageMenu->addMenuItem($menuItem);
2146
                        }
2147
                    }
2148
                    $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
2149
                }
2150
            }
2151
        }
2152
    }
2153
2154
    /**
2155
     * Redirects to FormEngine with new parameters to edit a just created localized record
2156
     *
2157
     * @param ServerRequestInterface $request Incoming request object
2158
     * @return ResponseInterface|null Possible redirect response
2159
     */
2160
    protected function localizationRedirect(ServerRequestInterface $request): ?ResponseInterface
2161
    {
2162
        $justLocalized = $request->getQueryParams()['justLocalized'];
2163
2164
        if (empty($justLocalized)) {
2165
            return null;
2166
        }
2167
2168
        [$table, $origUid, $language] = explode(':', $justLocalized);
2169
2170
        if ($GLOBALS['TCA'][$table]
2171
            && $GLOBALS['TCA'][$table]['ctrl']['languageField']
2172
            && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
2173
        ) {
2174
            $parsedBody = $request->getParsedBody();
2175
            $queryParams = $request->getQueryParams();
2176
2177
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
2178
            $queryBuilder->getRestrictions()
2179
                ->removeAll()
2180
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2181
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
2182
            $localizedRecord = $queryBuilder->select('uid')
2183
                ->from($table)
2184
                ->where(
2185
                    $queryBuilder->expr()->eq(
2186
                        $GLOBALS['TCA'][$table]['ctrl']['languageField'],
2187
                        $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
2188
                    ),
2189
                    $queryBuilder->expr()->eq(
2190
                        $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
2191
                        $queryBuilder->createNamedParameter($origUid, \PDO::PARAM_INT)
2192
                    )
2193
                )
2194
                ->execute()
2195
                ->fetch();
2196
            $returnUrl = $parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '';
2197
            if (is_array($localizedRecord)) {
2198
                // Create redirect response to self to edit just created record
2199
                return new RedirectResponse(
2200
                    (string)$this->uriBuilder->buildUriFromRoute(
2201
                        'record_edit',
2202
                        [
2203
                            'edit[' . $table . '][' . $localizedRecord['uid'] . ']' => 'edit',
2204
                            'returnUrl' => GeneralUtility::sanitizeLocalUrl($returnUrl)
2205
                        ]
2206
                    ),
2207
                    303
2208
                );
2209
            }
2210
        }
2211
        return null;
2212
    }
2213
2214
    /**
2215
     * Returns languages  available for record translations on given page.
2216
     *
2217
     * @param int $id Page id: If zero, the query will select all sys_language records from root level which are NOT
2218
     *                hidden. If set to another value, the query will select all sys_language records that has a
2219
     *                translation record on that page (and is not hidden, unless you are admin user)
2220
     * @param string $table For pages we want all languages, for other records the languages of the page translations
2221
     * @return SiteLanguage[] Language
2222
     */
2223
    protected function getLanguages(int $id, string $table): array
2224
    {
2225
        // This usually happens when a non-pages record is added after another, so we are fetching the proper page ID
2226
        if ($id < 0 && $table !== 'pages') {
2227
            $pageId = $this->pageinfo['uid'] ?? null;
2228
            if ($pageId !== null) {
2229
                $pageId = (int)$pageId;
2230
            } else {
2231
                $fullRecord = BackendUtility::getRecord($table, abs($id));
0 ignored issues
show
Bug introduced by
It seems like abs($id) can also be of type double; however, parameter $uid of TYPO3\CMS\Backend\Utilit...endUtility::getRecord() does only seem to accept integer, 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 ignore-type  annotation

2231
                $fullRecord = BackendUtility::getRecord($table, /** @scrutinizer ignore-type */ abs($id));
Loading history...
2232
                $pageId = (int)$fullRecord['pid'];
2233
            }
2234
        } else {
2235
            if ($table === 'pages' && $id > 0) {
2236
                $fullRecord = BackendUtility::getRecordWSOL('pages', $id);
2237
                $id = (int)($fullRecord['t3ver_oid'] ?: $fullRecord['uid']);
2238
            }
2239
            $pageId = $id;
2240
        }
2241
        try {
2242
            $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId);
2243
        } catch (SiteNotFoundException $e) {
2244
            $site = new NullSite();
2245
        }
2246
2247
        // Fetch the current translations of this page, to only show the ones where there is a page translation
2248
        $allLanguages = $site->getAvailableLanguages($this->getBackendUser(), false, $pageId);
2249
        if ($table !== 'pages' && $id > 0) {
2250
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
2251
            $queryBuilder->getRestrictions()->removeAll()
2252
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2253
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
2254
            $statement = $queryBuilder->select('uid', $GLOBALS['TCA']['pages']['ctrl']['languageField'])
2255
                ->from('pages')
2256
                ->where(
2257
                    $queryBuilder->expr()->eq(
2258
                        $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
2259
                        $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
2260
                    )
2261
                )
2262
                ->execute();
2263
2264
            $availableLanguages = [];
2265
2266
            if ($allLanguages[0] ?? false) {
2267
                $availableLanguages = [
2268
                    0 => $allLanguages[0]
2269
                ];
2270
            }
2271
2272
            while ($row = $statement->fetch()) {
2273
                $languageId = (int)$row[$GLOBALS['TCA']['pages']['ctrl']['languageField']];
2274
                if (isset($allLanguages[$languageId])) {
2275
                    $availableLanguages[$languageId] = $allLanguages[$languageId];
2276
                }
2277
            }
2278
            return $availableLanguages;
2279
        }
2280
        return $allLanguages;
2281
    }
2282
2283
    /**
2284
     * Fix $this->editconf if versioning applies to any of the records
2285
     *
2286
     * @param array|bool $mapArray Mapping between old and new ids if auto-versioning has been performed.
2287
     */
2288
    protected function fixWSversioningInEditConf($mapArray = false): void
2289
    {
2290
        // Traverse the editConf array
2291
        if (is_array($this->editconf)) {
0 ignored issues
show
introduced by
The condition is_array($this->editconf) is always true.
Loading history...
2292
            // Tables:
2293
            foreach ($this->editconf as $table => $conf) {
2294
                if (is_array($conf) && $GLOBALS['TCA'][$table]) {
2295
                    // Traverse the keys/comments of each table (keys can be a comma list of uids)
2296
                    $newConf = [];
2297
                    foreach ($conf as $cKey => $cmd) {
2298
                        if ($cmd === 'edit') {
2299
                            // Traverse the ids:
2300
                            $ids = GeneralUtility::trimExplode(',', $cKey, true);
2301
                            foreach ($ids as $idKey => $theUid) {
2302
                                if (is_array($mapArray)) {
2303
                                    if ($mapArray[$table][$theUid]) {
2304
                                        $ids[$idKey] = $mapArray[$table][$theUid];
2305
                                    }
2306
                                } else {
2307
                                    // Default, look for versions in workspace for record:
2308
                                    $calcPRec = $this->getRecordForEdit((string)$table, (int)$theUid);
2309
                                    if (is_array($calcPRec)) {
2310
                                        // Setting UID again if it had changed, eg. due to workspace versioning.
2311
                                        $ids[$idKey] = $calcPRec['uid'];
2312
                                    }
2313
                                }
2314
                            }
2315
                            // Add the possibly manipulated IDs to the new-build newConf array:
2316
                            $newConf[implode(',', $ids)] = $cmd;
2317
                        } else {
2318
                            $newConf[$cKey] = $cmd;
2319
                        }
2320
                    }
2321
                    // Store the new conf array:
2322
                    $this->editconf[$table] = $newConf;
2323
                }
2324
            }
2325
        }
2326
    }
2327
2328
    /**
2329
     * Get record for editing.
2330
     *
2331
     * @param string $table Table name
2332
     * @param int $theUid Record UID
2333
     * @return array|false Returns record to edit, false if none
2334
     */
2335
    protected function getRecordForEdit(string $table, int $theUid)
2336
    {
2337
        $tableSupportsVersioning = BackendUtility::isTableWorkspaceEnabled($table);
2338
        // Fetch requested record:
2339
        $reqRecord = BackendUtility::getRecord($table, $theUid, 'uid,pid' . ($tableSupportsVersioning ? ',t3ver_oid' : ''));
2340
        if (is_array($reqRecord)) {
2341
            // If workspace is OFFLINE:
2342
            if ($this->getBackendUser()->workspace != 0) {
2343
                // Check for versioning support of the table:
2344
                if ($tableSupportsVersioning) {
2345
                    // If the record is already a version of "something" pass it by.
2346
                    if ($reqRecord['t3ver_oid'] > 0) {
2347
                        // (If it turns out not to be a version of the current workspace there will be trouble, but
2348
                        // that is handled inside DataHandler then and in the interface it would clearly be an error of
2349
                        // links if the user accesses such a scenario)
2350
                        return $reqRecord;
2351
                    }
2352
                    // The input record was online and an offline version must be found or made:
2353
                    // Look for version of this workspace:
2354
                    $versionRec = BackendUtility::getWorkspaceVersionOfRecord(
2355
                        $this->getBackendUser()->workspace,
2356
                        $table,
2357
                        $reqRecord['uid'],
2358
                        'uid,pid,t3ver_oid'
2359
                    );
2360
                    return is_array($versionRec) ? $versionRec : $reqRecord;
2361
                }
2362
                // This means that editing cannot occur on this record because it was not supporting versioning
2363
                // which is required inside an offline workspace.
2364
                return false;
2365
            }
2366
            // In ONLINE workspace, just return the originally requested record:
2367
            return $reqRecord;
2368
        }
2369
        // Return FALSE because the table/uid was not found anyway.
2370
        return false;
2371
    }
2372
2373
    /**
2374
     * Populates the variables $this->storeArray, $this->storeUrl, $this->storeUrlMd5
2375
     * to prepare 'open documents' urls
2376
     */
2377
    protected function compileStoreData(): void
2378
    {
2379
        // @todo: Refactor in TYPO3 v10: This GeneralUtility method fiddles with _GP()
2380
        $this->storeArray = GeneralUtility::compileSelectedGetVarsFromArray(
2381
            'edit,defVals,overrideVals,columnsOnly,noView,workspace',
2382
            $this->R_URL_getvars
2383
        );
2384
        $this->storeUrl = HttpUtility::buildQueryString($this->storeArray, '&');
2385
        $this->storeUrlMd5 = md5($this->storeUrl);
2386
    }
2387
2388
    /**
2389
     * Get a TSConfig 'option.' array, possibly for a specific table.
2390
     *
2391
     * @param string $table Table name
2392
     * @param string $key Options key
2393
     * @return string
2394
     */
2395
    protected function getTsConfigOption(string $table, string $key): string
2396
    {
2397
        return \trim((string)(
2398
            $this->getBackendUser()->getTSConfig()['options.'][$key . '.'][$table]
2399
            ?? $this->getBackendUser()->getTSConfig()['options.'][$key]
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "??"; newline found
Loading history...
2400
            ?? ''
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "??"; newline found
Loading history...
2401
        ));
2402
    }
2403
2404
    /**
2405
     * Handling the closing of a document
2406
     * The argument $mode can be one of this values:
2407
     * - 0/1 will redirect to $this->retUrl [self::DOCUMENT_CLOSE_MODE_DEFAULT || self::DOCUMENT_CLOSE_MODE_REDIRECT]
2408
     * - 3 will clear the docHandler (thus closing all documents) [self::DOCUMENT_CLOSE_MODE_CLEAR_ALL]
2409
     * - 4 will do no redirect [self::DOCUMENT_CLOSE_MODE_NO_REDIRECT]
2410
     * - other values will call setDocument with ->retUrl
2411
     *
2412
     * @param int $mode the close mode: one of self::DOCUMENT_CLOSE_MODE_*
2413
     * @param ServerRequestInterface $request Incoming request
2414
     * @return ResponseInterface|null Redirect response if needed
2415
     */
2416
    protected function closeDocument($mode, ServerRequestInterface $request): ?ResponseInterface
2417
    {
2418
        $setupArr = [];
2419
        $mode = (int)$mode;
2420
        // If current document is found in docHandler,
2421
        // then unset it, possibly unset it ALL and finally, write it to the session data
2422
        if (isset($this->docHandler[$this->storeUrlMd5])) {
2423
            // add the closing document to the recent documents
2424
            $recentDocs = $this->getBackendUser()->getModuleData('opendocs::recent');
2425
            if (!is_array($recentDocs)) {
2426
                $recentDocs = [];
2427
            }
2428
            $closedDoc = $this->docHandler[$this->storeUrlMd5];
2429
            $recentDocs = array_merge([$this->storeUrlMd5 => $closedDoc], $recentDocs);
2430
            if (count($recentDocs) > 8) {
2431
                $recentDocs = array_slice($recentDocs, 0, 8);
2432
            }
2433
            // remove it from the list of the open documents
2434
            unset($this->docHandler[$this->storeUrlMd5]);
2435
            if ($mode === self::DOCUMENT_CLOSE_MODE_CLEAR_ALL) {
2436
                $recentDocs = array_merge($this->docHandler, $recentDocs);
2437
                $this->docHandler = [];
2438
            }
2439
            $this->getBackendUser()->pushModuleData('opendocs::recent', $recentDocs);
2440
            $this->getBackendUser()->pushModuleData('FormEngine', [$this->docHandler, $this->docDat[1]]);
2441
            BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
2442
        }
2443
        if ($mode === self::DOCUMENT_CLOSE_MODE_NO_REDIRECT) {
2444
            return null;
2445
        }
2446
        // If ->returnEditConf is set, then add the current content of editconf to the ->retUrl variable: used by
2447
        // other scripts, like wizard_add, to know which records was created or so...
2448
        if ($this->returnEditConf && $this->retUrl != (string)$this->uriBuilder->buildUriFromRoute('dummy')) {
2449
            $this->retUrl .= '&returnEditConf=' . rawurlencode(json_encode($this->editconf));
2450
        }
2451
        // If mode is NOT set (means 0) OR set to 1, then make a header location redirect to $this->retUrl
2452
        if ($mode === self::DOCUMENT_CLOSE_MODE_DEFAULT || $mode === self::DOCUMENT_CLOSE_MODE_REDIRECT) {
2453
            return new RedirectResponse($this->retUrl, 303);
2454
        }
2455
        if ($this->retUrl === '') {
2456
            return null;
2457
        }
2458
        $retUrl = $this->returnUrl;
2459
        if (is_array($this->docHandler) && !empty($this->docHandler)) {
2460
            if (!empty($setupArr[2])) {
2461
                $sParts = parse_url($request->getAttribute('normalizedParams')->getRequestUri());
2462
                $retUrl = $sParts['path'] . '?' . $setupArr[2] . '&returnUrl=' . rawurlencode($retUrl);
2463
            }
2464
        }
2465
        return new RedirectResponse($retUrl, 303);
2466
    }
2467
2468
    /**
2469
     * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
2470
     */
2471
    protected function getBackendUser()
2472
    {
2473
        return $GLOBALS['BE_USER'];
2474
    }
2475
2476
    /**
2477
     * Returns LanguageService
2478
     *
2479
     * @return \TYPO3\CMS\Core\Localization\LanguageService
2480
     */
2481
    protected function getLanguageService()
2482
    {
2483
        return $GLOBALS['LANG'];
2484
    }
2485
}
2486