Passed
Branch master (6c65a4)
by Christian
27:15 queued 11:09
created

EditDocumentController::getNewIconMode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
namespace TYPO3\CMS\Backend\Controller;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use Psr\Http\Message\ResponseInterface;
18
use Psr\Http\Message\ServerRequestInterface;
19
use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
20
use TYPO3\CMS\Backend\Form\FormDataCompiler;
21
use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
22
use TYPO3\CMS\Backend\Form\FormResultCompiler;
23
use TYPO3\CMS\Backend\Form\NodeFactory;
24
use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
25
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
26
use TYPO3\CMS\Backend\Template\ModuleTemplate;
27
use TYPO3\CMS\Backend\Utility\BackendUtility;
28
use TYPO3\CMS\Core\Database\ConnectionPool;
29
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
30
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
31
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
32
use TYPO3\CMS\Core\DataHandling\DataHandler;
33
use TYPO3\CMS\Core\Imaging\Icon;
34
use TYPO3\CMS\Core\Messaging\FlashMessage;
35
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
36
use TYPO3\CMS\Core\Messaging\FlashMessageService;
37
use TYPO3\CMS\Core\Page\PageRenderer;
38
use TYPO3\CMS\Core\Type\Bitmask\Permission;
39
use TYPO3\CMS\Core\Utility\GeneralUtility;
40
use TYPO3\CMS\Core\Utility\HttpUtility;
41
use TYPO3\CMS\Core\Utility\MathUtility;
42
use TYPO3\CMS\Core\Utility\PathUtility;
43
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
44
use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
45
use TYPO3\CMS\Frontend\Page\PageRepository;
46
47
/**
48
 * Script Class: Drawing the editing form for editing records in TYPO3.
49
 * Notice: It does NOT use tce_db.php to submit data to, rather it handles submissions itself
50
 */
51
class EditDocumentController
52
{
53
    const DOCUMENT_CLOSE_MODE_DEFAULT = 0;
54
    const DOCUMENT_CLOSE_MODE_REDIRECT = 1; // works like DOCUMENT_CLOSE_MODE_DEFAULT
55
    const DOCUMENT_CLOSE_MODE_CLEAR_ALL = 3;
56
    const DOCUMENT_CLOSE_MODE_NO_REDIRECT = 4;
57
58
    /**
59
     * GPvar "edit": Is an array looking approx like [tablename][list-of-ids]=command, eg.
60
     * "&edit[pages][123]=edit". See \TYPO3\CMS\Backend\Utility\BackendUtility::editOnClick(). Value can be seen
61
     * modified internally (converting NEW keyword to id, workspace/versioning etc).
62
     *
63
     * @var array
64
     */
65
    public $editconf;
66
67
    /**
68
     * Commalist of fieldnames to edit. The point is IF you specify this list, only those
69
     * fields will be rendered in the form. Otherwise all (available) fields in the record
70
     * is shown according to the types configuration in $GLOBALS['TCA']
71
     *
72
     * @var bool
73
     */
74
    public $columnsOnly;
75
76
    /**
77
     * Default values for fields (array with tablenames, fields etc. as keys).
78
     * Can be seen modified internally.
79
     *
80
     * @var array
81
     */
82
    public $defVals;
83
84
    /**
85
     * Array of values to force being set (as hidden fields). Will be set as $this->defVals
86
     * IF defVals does not exist.
87
     *
88
     * @var array
89
     */
90
    public $overrideVals;
91
92
    /**
93
     * If set, this value will be set in $this->retUrl (which is used quite many places
94
     * as the return URL). If not set, "dummy.php" will be set in $this->retUrl
95
     *
96
     * @var string
97
     */
98
    public $returnUrl;
99
100
    /**
101
     * Close-document command. Not really sure of all options...
102
     *
103
     * @var int
104
     */
105
    public $closeDoc;
106
107
    /**
108
     * Quite simply, if this variable is set, then the processing of incoming data will be performed
109
     * as if a save-button is pressed. Used in the forms as a hidden field which can be set through
110
     * JavaScript if the form is somehow submitted by JavaScript).
111
     *
112
     * @var bool
113
     */
114
    public $doSave;
115
116
    /**
117
     * The data array from which the data comes...
118
     *
119
     * @var array
120
     */
121
    public $data;
122
123
    /**
124
     * @var string
125
     */
126
    public $cmd;
127
128
    /**
129
     * @var array
130
     */
131
    public $mirror;
132
133
    /**
134
     * Clear-cache cmd.
135
     *
136
     * @var string
137
     */
138
    public $cacheCmd;
139
140
    /**
141
     * Redirect (not used???)
142
     *
143
     * @var string
144
     */
145
    public $redirect;
146
147
    /**
148
     * Boolean: If set, then the GET var "&id=" will be added to the
149
     * retUrl string so that the NEW id of something is returned to the script calling the form.
150
     *
151
     * @var bool
152
     */
153
    public $returnNewPageId;
154
155
    /**
156
     * update BE_USER->uc
157
     *
158
     * @var array
159
     */
160
    public $uc;
161
162
    /**
163
     * ID for displaying the page in the frontend (used for SAVE/VIEW operations)
164
     *
165
     * @var int
166
     */
167
    public $popViewId;
168
169
    /**
170
     * Additional GET vars for the link, eg. "&L=xxx"
171
     *
172
     * @var string
173
     */
174
    public $popViewId_addParams;
175
176
    /**
177
     * Alternative URL for viewing the frontend pages.
178
     *
179
     * @var string
180
     */
181
    public $viewUrl;
182
183
    /**
184
     * Alternative title for the document handler.
185
     *
186
     * @var string
187
     */
188
    public $recTitle;
189
190
    /**
191
     * If set, then no SAVE/VIEW button is printed
192
     *
193
     * @var bool
194
     */
195
    public $noView;
196
197
    /**
198
     * @var string
199
     */
200
    public $perms_clause;
201
202
    /**
203
     * If set, the $this->editconf array is returned to the calling script
204
     * (used by wizard_add.php for instance)
205
     *
206
     * @var bool
207
     */
208
    public $returnEditConf;
209
210
    /**
211
     * Workspace used for the editing action.
212
     *
213
     * @var int|null
214
     */
215
    protected $workspace;
216
217
    /**
218
     * document template object
219
     *
220
     * @var \TYPO3\CMS\Backend\Template\DocumentTemplate
221
     */
222
    public $doc;
223
224
    /**
225
     * a static HTML template, usually in templates/alt_doc.html
226
     *
227
     * @var string
228
     */
229
    public $template;
230
231
    /**
232
     * Content accumulation
233
     *
234
     * @var string
235
     */
236
    public $content;
237
238
    /**
239
     * Return URL script, processed. This contains the script (if any) that we should
240
     * RETURN TO from the FormEngine script IF we press the close button. Thus this
241
     * variable is normally passed along from the calling script so we can properly return if needed.
242
     *
243
     * @var string
244
     */
245
    public $retUrl;
246
247
    /**
248
     * Contains the parts of the REQUEST_URI (current url). By parts we mean the result of resolving
249
     * REQUEST_URI (current url) by the parse_url() function. The result is an array where eg. "path"
250
     * is the script path and "query" is the parameters...
251
     *
252
     * @var array
253
     */
254
    public $R_URL_parts;
255
256
    /**
257
     * Contains the current GET vars array; More specifically this array is the foundation for creating
258
     * the R_URI internal var (which becomes the "url of this script" to which we submit the forms etc.)
259
     *
260
     * @var array
261
     */
262
    public $R_URL_getvars;
263
264
    /**
265
     * Set to the URL of this script including variables which is needed to re-display the form. See main()
266
     *
267
     * @var string
268
     */
269
    public $R_URI;
270
271
    /**
272
     * @var array
273
     */
274
    public $MCONF;
275
276
    /**
277
     * @var array
278
     */
279
    public $pageinfo;
280
281
    /**
282
     * Is loaded with the "title" of the currently "open document" - this is used in the
283
     * Document Selector box. (see makeDocSel())
284
     *
285
     * @var string
286
     */
287
    public $storeTitle = '';
288
289
    /**
290
     * Contains an array with key/value pairs of GET parameters needed to reach the
291
     * current document displayed - used in the Document Selector box. (see compileStoreDat())
292
     *
293
     * @var array
294
     */
295
    public $storeArray;
296
297
    /**
298
     * Contains storeArray, but imploded into a GET parameter string (see compileStoreDat())
299
     *
300
     * @var string
301
     */
302
    public $storeUrl;
303
304
    /**
305
     * Hashed value of storeURL (see compileStoreDat())
306
     *
307
     * @var string
308
     */
309
    public $storeUrlMd5;
310
311
    /**
312
     * Module session data
313
     *
314
     * @var array
315
     */
316
    public $docDat;
317
318
    /**
319
     * An array of the "open documents" - keys are md5 hashes (see $storeUrlMd5) identifying
320
     * the various documents on the GET parameter list needed to open it. The values are
321
     * arrays with 0,1,2 keys with information about the document (see compileStoreDat()).
322
     * The docHandler variable is stored in the $docDat session data, key "0".
323
     *
324
     * @var array
325
     */
326
    public $docHandler;
327
328
    /**
329
     * Array of the elements to create edit forms for.
330
     *
331
     * @var array
332
     */
333
    public $elementsData;
334
335
    /**
336
     * Pointer to the first element in $elementsData
337
     *
338
     * @var array
339
     */
340
    public $firstEl;
341
342
    /**
343
     * Counter, used to count the number of errors (when users do not have edit permissions)
344
     *
345
     * @var int
346
     */
347
    public $errorC;
348
349
    /**
350
     * Counter, used to count the number of new record forms displayed
351
     *
352
     * @var int
353
     */
354
    public $newC;
355
356
    /**
357
     * Is set to the pid value of the last shown record - thus indicating which page to
358
     * show when clicking the SAVE/VIEW button
359
     *
360
     * @var int
361
     */
362
    public $viewId;
363
364
    /**
365
     * Is set to additional parameters (like "&L=xxx") if the record supports it.
366
     *
367
     * @var string
368
     */
369
    public $viewId_addParams;
370
371
    /**
372
     * Module TSconfig, loaded from main() based on the page id value of viewId
373
     *
374
     * @var array
375
     */
376
    public $modTSconfig;
377
378
    /**
379
     * @var FormResultCompiler
380
     */
381
    protected $formResultCompiler;
382
383
    /**
384
     * Used internally to disable the storage of the document reference (eg. new records)
385
     *
386
     * @var bool
387
     */
388
    public $dontStoreDocumentRef = 0;
389
390
    /**
391
     * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
392
     */
393
    protected $signalSlotDispatcher;
394
395
    /**
396
     * Stores information needed to preview the currently saved record
397
     *
398
     * @var array
399
     */
400
    protected $previewData = [];
401
402
    /**
403
     * ModuleTemplate object
404
     *
405
     * @var ModuleTemplate
406
     */
407
    protected $moduleTemplate;
408
409
    /**
410
     * Constructor
411
     */
412
    public function __construct()
413
    {
414
        $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
415
        $this->moduleTemplate->setUiBlock(true);
416
        $GLOBALS['SOBE'] = $this;
417
        $this->getLanguageService()->includeLLFile('EXT:lang/Resources/Private/Language/locallang_alt_doc.xlf');
418
    }
419
420
    /**
421
     * Get the SignalSlot dispatcher
422
     *
423
     * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
424
     */
425
    protected function getSignalSlotDispatcher()
426
    {
427
        if (!isset($this->signalSlotDispatcher)) {
428
            $this->signalSlotDispatcher = GeneralUtility::makeInstance(Dispatcher::class);
429
        }
430
        return $this->signalSlotDispatcher;
431
    }
432
433
    /**
434
     * Emits a signal after a function was executed
435
     *
436
     * @param string $signalName
437
     */
438
    protected function emitFunctionAfterSignal($signalName)
439
    {
440
        $this->getSignalSlotDispatcher()->dispatch(__CLASS__, $signalName . 'After', [$this]);
441
    }
442
443
    /**
444
     * First initialization.
445
     */
446
    public function preInit()
447
    {
448
        if (GeneralUtility::_GP('justLocalized')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression TYPO3\CMS\Core\Utility\G...y::_GP('justLocalized') of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
449
            $this->localizationRedirect(GeneralUtility::_GP('justLocalized'));
450
        }
451
        // Setting GPvars:
452
        $this->editconf = GeneralUtility::_GP('edit');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...ralUtility::_GP('edit') can also be of type string. However, the property $editconf is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
453
        $this->defVals = GeneralUtility::_GP('defVals');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...Utility::_GP('defVals') can also be of type string. However, the property $defVals is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
454
        $this->overrideVals = GeneralUtility::_GP('overrideVals');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...ty::_GP('overrideVals') can also be of type string. However, the property $overrideVals is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
455
        $this->columnsOnly = GeneralUtility::_GP('columnsOnly');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...ity::_GP('columnsOnly') can also be of type string. However, the property $columnsOnly is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
456
        $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
457
        $this->closeDoc = (int)GeneralUtility::_GP('closeDoc');
458
        $this->doSave = GeneralUtility::_GP('doSave');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...lUtility::_GP('doSave') can also be of type string. However, the property $doSave is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
459
        $this->returnEditConf = GeneralUtility::_GP('returnEditConf');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...::_GP('returnEditConf') can also be of type string. However, the property $returnEditConf is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
460
        $this->workspace = GeneralUtility::_GP('workspace');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...ility::_GP('workspace') can also be of type string. However, the property $workspace is declared as type null|integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
461
        $this->uc = GeneralUtility::_GP('uc');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\GeneralUtility::_GP('uc') can also be of type string. However, the property $uc is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
462
        // Setting override values as default if defVals does not exist.
463
        if (!is_array($this->defVals) && is_array($this->overrideVals)) {
464
            $this->defVals = $this->overrideVals;
465
        }
466
        /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
467
        $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
468
        // Setting return URL
469
        $this->retUrl = $this->returnUrl ?: (string)$uriBuilder->buildUriFromRoute('dummy');
470
        // Fix $this->editconf if versioning applies to any of the records
471
        $this->fixWSversioningInEditConf();
472
        // Make R_URL (request url) based on input GETvars:
473
        $this->R_URL_parts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
474
        $this->R_URL_getvars = GeneralUtility::_GET();
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\GeneralUtility::_GET() can also be of type string. However, the property $R_URL_getvars is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
475
        $this->R_URL_getvars['edit'] = $this->editconf;
476
        // MAKE url for storing
477
        $this->compileStoreDat();
478
        // Get session data for the module:
479
        $this->docDat = $this->getBackendUser()->getModuleData('FormEngine', 'ses');
480
        $this->docHandler = $this->docDat[0];
481
        // If a request for closing the document has been sent, act accordingly:
482
        if ((int)$this->closeDoc > self::DOCUMENT_CLOSE_MODE_DEFAULT) {
483
            $this->closeDocument($this->closeDoc);
484
        }
485
        // If NO vars are sent to the script, try to read first document:
486
        // Added !is_array($this->editconf) because editConf must not be set either.
487
        // Anyways I can't figure out when this situation here will apply...
488
        if (is_array($this->R_URL_getvars) && count($this->R_URL_getvars) < 2 && !is_array($this->editconf)) {
489
            $this->setDocument($this->docDat[1]);
490
        }
491
492
        // Sets a temporary workspace, this request is based on
493
        if ($this->workspace !== null) {
494
            $this->getBackendUser()->setTemporaryWorkspace($this->workspace);
0 ignored issues
show
Bug introduced by
$this->workspace of type string is incompatible with the type integer expected by parameter $workspaceId of TYPO3\CMS\Core\Authentic...setTemporaryWorkspace(). ( Ignorable by Annotation )

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

494
            $this->getBackendUser()->setTemporaryWorkspace(/** @scrutinizer ignore-type */ $this->workspace);
Loading history...
495
        }
496
497
        $this->emitFunctionAfterSignal(__FUNCTION__);
498
    }
499
500
    /**
501
     * Detects, if a save command has been triggered.
502
     *
503
     * @return bool TRUE, then save the document (data submitted)
504
     */
505
    public function doProcessData()
506
    {
507
        $out = $this->doSave
508
            || isset($_POST['_savedok'])
509
            || isset($_POST['_saveandclosedok'])
510
            || isset($_POST['_savedokview'])
511
            || isset($_POST['_savedoknew'])
512
            || isset($_POST['_translation_savedok'])
513
            || isset($_POST['_translation_savedokclear']);
514
        return $out;
515
    }
516
517
    /**
518
     * Do processing of data, submitting it to DataHandler.
519
     */
520
    public function processData()
521
    {
522
        $beUser = $this->getBackendUser();
523
        // GPvars specifically for processing:
524
        $control = GeneralUtility::_GP('control');
525
        $this->data = GeneralUtility::_GP('data');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...ralUtility::_GP('data') can also be of type string. However, the property $data is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
526
        $this->cmd = GeneralUtility::_GP('cmd');
527
        $this->mirror = GeneralUtility::_GP('mirror');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...lUtility::_GP('mirror') can also be of type string. However, the property $mirror is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
528
        $this->cacheCmd = GeneralUtility::_GP('cacheCmd');
529
        $this->redirect = GeneralUtility::_GP('redirect');
530
        $this->returnNewPageId = GeneralUtility::_GP('returnNewPageId');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...:_GP('returnNewPageId') can also be of type string. However, the property $returnNewPageId is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
531
        // See tce_db.php for relevate options here:
532
        // Only options related to $this->data submission are included here.
533
        /** @var $tce \TYPO3\CMS\Core\DataHandling\DataHandler */
534
        $tce = GeneralUtility::makeInstance(DataHandler::class);
535
536
        if (!empty($control)) {
537
            $tce->setControl($control);
0 ignored issues
show
Bug introduced by
$control of type string is incompatible with the type array expected by parameter $control of TYPO3\CMS\Core\DataHandl...taHandler::setControl(). ( Ignorable by Annotation )

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

537
            $tce->setControl(/** @scrutinizer ignore-type */ $control);
Loading history...
538
        }
539
        if (isset($_POST['_translation_savedok'])) {
540
            $tce->updateModeL10NdiffData = 'FORCE_FFUPD';
541
        }
542
        if (isset($_POST['_translation_savedokclear'])) {
543
            $tce->updateModeL10NdiffData = 'FORCE_FFUPD';
544
            $tce->updateModeL10NdiffDataClear = true;
545
        }
546
        // Setting default values specific for the user:
547
        $TCAdefaultOverride = $beUser->getTSConfigProp('TCAdefaults');
548
        if (is_array($TCAdefaultOverride)) {
549
            $tce->setDefaultsFromUserTS($TCAdefaultOverride);
550
        }
551
        // Setting internal vars:
552
        if ($beUser->uc['neverHideAtCopy']) {
553
            $tce->neverHideAtCopy = 1;
0 ignored issues
show
Documentation Bug introduced by
The property $neverHideAtCopy 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...
554
        }
555
        // Loading DataHandler with data:
556
        $tce->start($this->data, $this->cmd);
0 ignored issues
show
Bug introduced by
It seems like $this->cmd can also be of type string; however, parameter $cmd of TYPO3\CMS\Core\DataHandling\DataHandler::start() does only seem to accept array, 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

556
        $tce->start($this->data, /** @scrutinizer ignore-type */ $this->cmd);
Loading history...
Bug introduced by
It seems like $this->data can also be of type string; however, parameter $data of TYPO3\CMS\Core\DataHandling\DataHandler::start() does only seem to accept array, 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

556
        $tce->start(/** @scrutinizer ignore-type */ $this->data, $this->cmd);
Loading history...
557
        if (is_array($this->mirror)) {
558
            $tce->setMirror($this->mirror);
559
        }
560
        // Checking referer / executing
561
        $refInfo = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
562
        $httpHost = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY');
563
        if ($httpHost != $refInfo['host']
564
            && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer']
565
        ) {
566
            $tce->log(
567
                '',
568
                0,
569
                0,
570
                0,
571
                1,
572
                'Referer host \'%s\' and server host \'%s\' did not match!',
573
                1,
574
                [$refInfo['host'], $httpHost]
575
            );
576
            debug('Error: Referer host did not match with server host.');
577
        } else {
578
            // Perform the saving operation with DataHandler:
579
            $tce->process_uploads($_FILES);
580
            $tce->process_datamap();
581
            $tce->process_cmdmap();
582
            // If pages are being edited, we set an instruction about updating the page tree after this operation.
583
            if ($tce->pagetreeNeedsRefresh
584
                && (isset($this->data['pages']) || $beUser->workspace != 0 && !empty($this->data))
585
            ) {
586
                BackendUtility::setUpdateSignal('updatePageTree');
587
            }
588
            // If there was saved any new items, load them:
589
            if (!empty($tce->substNEWwithIDs_table)) {
590
                // save the expanded/collapsed states for new inline records, if any
591
                FormEngineUtility::updateInlineView($this->uc, $tce);
592
                $newEditConf = [];
593
                foreach ($this->editconf as $tableName => $tableCmds) {
594
                    $keys = array_keys($tce->substNEWwithIDs_table, $tableName);
595
                    if (!empty($keys)) {
596
                        foreach ($keys as $key) {
597
                            $editId = $tce->substNEWwithIDs[$key];
598
                            // Check if the $editId isn't a child record of an IRRE action
599
                            if (!(is_array($tce->newRelatedIDs[$tableName])
600
                                && in_array($editId, $tce->newRelatedIDs[$tableName]))
601
                            ) {
602
                                // Translate new id to the workspace version:
603
                                if ($versionRec = BackendUtility::getWorkspaceVersionOfRecord(
604
                                    $beUser->workspace,
605
                                    $tableName,
606
                                    $editId,
607
                                    'uid'
608
                                )) {
609
                                    $editId = $versionRec['uid'];
610
                                }
611
                                $newEditConf[$tableName][$editId] = 'edit';
612
                            }
613
                            /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
614
                            $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
615
                            // Traverse all new records and forge the content of ->editconf so we can continue to EDIT
616
                            // these records!
617
                            if ($tableName === 'pages'
618
                                && $this->retUrl != (string)$uriBuilder->buildUriFromRoute('dummy')
619
                                && $this->returnNewPageId
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->returnNewPageId of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
620
                            ) {
621
                                $this->retUrl .= '&id=' . $tce->substNEWwithIDs[$key];
622
                            }
623
                        }
624
                    } else {
625
                        $newEditConf[$tableName] = $tableCmds;
626
                    }
627
                }
628
                // Resetting editconf if newEditConf has values:
629
                if (!empty($newEditConf)) {
630
                    $this->editconf = $newEditConf;
631
                }
632
                // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
633
                $this->R_URL_getvars['edit'] = $this->editconf;
634
                // Unsetting default values since we don't need them anymore.
635
                unset($this->R_URL_getvars['defVals']);
636
                // Re-compile the store* values since editconf changed...
637
                $this->compileStoreDat();
638
            }
639
            // See if any records was auto-created as new versions?
640
            if (!empty($tce->autoVersionIdMap)) {
641
                $this->fixWSversioningInEditConf($tce->autoVersionIdMap);
642
            }
643
            // If a document is saved and a new one is created right after.
644
            if (isset($_POST['_savedoknew']) && is_array($this->editconf)) {
645
                $this->closeDocument(self::DOCUMENT_CLOSE_MODE_NO_REDIRECT);
646
                // Finding the current table:
647
                reset($this->editconf);
648
                $nTable = key($this->editconf);
649
                // Finding the first id, getting the records pid+uid
650
                reset($this->editconf[$nTable]);
651
                $nUid = key($this->editconf[$nTable]);
652
                $recordFields = 'pid,uid';
653
                if (!empty($GLOBALS['TCA'][$nTable]['ctrl']['versioningWS'])) {
654
                    $recordFields .= ',t3ver_oid';
655
                }
656
                $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

656
                $nRec = BackendUtility::getRecord($nTable, /** @scrutinizer ignore-type */ $nUid, $recordFields);
Loading history...
657
                // Determine insertion mode ('top' is self-explaining,
658
                // otherwise new elements are inserted after one using a negative uid)
659
                $insertRecordOnTop = ($this->getNewIconMode($nTable) === 'top');
660
                // Setting a blank editconf array for a new record:
661
                $this->editconf = [];
662
                // Determine related page ID for regular live context
663
                if ($nRec['pid'] != -1) {
664
                    if ($insertRecordOnTop) {
665
                        $relatedPageId = $nRec['pid'];
666
                    } else {
667
                        $relatedPageId = -$nRec['uid'];
668
                    }
669
                } else {
670
                    // Determine related page ID for workspace context
671
                    if ($insertRecordOnTop) {
672
                        // Fetch live version of workspace version since the pid value is always -1 in workspaces
673
                        $liveRecord = BackendUtility::getRecord($nTable, $nRec['t3ver_oid'], $recordFields);
674
                        $relatedPageId = $liveRecord['pid'];
675
                    } else {
676
                        // Use uid of live version of workspace version
677
                        $relatedPageId = -$nRec['t3ver_oid'];
678
                    }
679
                }
680
                $this->editconf[$nTable][$relatedPageId] = 'new';
681
                // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
682
                $this->R_URL_getvars['edit'] = $this->editconf;
683
                // Re-compile the store* values since editconf changed...
684
                $this->compileStoreDat();
685
            }
686
            // If a preview is requested
687
            if (isset($_POST['_savedokview'])) {
688
                // Get the first table and id of the data array from DataHandler
689
                $table = reset(array_keys($this->data));
0 ignored issues
show
Bug introduced by
$this->data of type null|string is incompatible with the type array expected by parameter $input of array_keys(). ( Ignorable by Annotation )

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

689
                $table = reset(array_keys(/** @scrutinizer ignore-type */ $this->data));
Loading history...
690
                $id = reset(array_keys($this->data[$table]));
691
                if (!MathUtility::canBeInterpretedAsInteger($id)) {
692
                    $id = $tce->substNEWwithIDs[$id];
693
                }
694
                // Store this information for later use
695
                $this->previewData['table'] = $table;
696
                $this->previewData['id'] = $id;
697
            }
698
            $tce->printLogErrorMessages();
699
        }
700
        //  || count($tce->substNEWwithIDs)... If any new items has been save, the document is CLOSED
701
        // because if not, we just get that element re-listed as new. And we don't want that!
702
        if ((int)$this->closeDoc < self::DOCUMENT_CLOSE_MODE_DEFAULT
703
            || isset($_POST['_saveandclosedok'])
704
            || isset($_POST['_translation_savedok'])
705
        ) {
706
            $this->closeDocument(abs($this->closeDoc));
0 ignored issues
show
Bug introduced by
It seems like abs($this->closeDoc) can also be of type double; however, parameter $mode of TYPO3\CMS\Backend\Contro...roller::closeDocument() 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

706
            $this->closeDocument(/** @scrutinizer ignore-type */ abs($this->closeDoc));
Loading history...
707
        }
708
    }
709
710
    /**
711
     * Initialize the normal module operation
712
     */
713
    public function init()
714
    {
715
        $beUser = $this->getBackendUser();
716
        // Setting more GPvars:
717
        $this->popViewId = GeneralUtility::_GP('popViewId');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...ility::_GP('popViewId') can also be of type string. However, the property $popViewId is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
718
        $this->popViewId_addParams = GeneralUtility::_GP('popViewId_addParams');
719
        $this->viewUrl = GeneralUtility::_GP('viewUrl');
720
        $this->recTitle = GeneralUtility::_GP('recTitle');
721
        $this->noView = GeneralUtility::_GP('noView');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...lUtility::_GP('noView') can also be of type string. However, the property $noView is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
722
        $this->perms_clause = $beUser->getPagePermsClause(Permission::PAGE_SHOW);
723
        // Set other internal variables:
724
        $this->R_URL_getvars['returnUrl'] = $this->retUrl;
725
        $this->R_URI = $this->R_URL_parts['path'] . '?' . ltrim(GeneralUtility::implodeArrayForUrl(
726
            '',
727
            $this->R_URL_getvars
728
        ), '&');
729
        // Setting virtual document name
730
        $this->MCONF['name'] = 'xMOD_alt_doc.php';
731
732
        // Create an instance of the document template object
733
        $this->doc = $GLOBALS['TBE_TEMPLATE'];
734
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
735
        $pageRenderer->addInlineLanguageLabelFile('EXT:lang/Resources/Private/Language/locallang_alt_doc.xlf');
736
        /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
737
        $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
738
        // override the default jumpToUrl
739
        $this->moduleTemplate->addJavaScriptCode(
740
            'jumpToUrl',
741
            '
742
			function jumpToUrl(URL,formEl) {
743
				if (!TBE_EDITOR.isFormChanged()) {
744
					window.location.href = URL;
745
				} else if (formEl && formEl.type=="checkbox") {
746
					formEl.checked = formEl.checked ? 0 : 1;
747
				}
748
			}
749
750
				// Info view:
751
			function launchView(table,uid) {
752
				var thePreviewWindow = window.open(
753
					' . GeneralUtility::quoteJSvalue((string)$uriBuilder->buildUriFromRoute('show_item') . '&table=') . ' + encodeURIComponent(table) + "&uid=" + encodeURIComponent(uid),
754
					"ShowItem" + Math.random().toString(16).slice(2),
755
					"height=300,width=410,status=0,menubar=0,resizable=0,location=0,directories=0,scrollbars=1,toolbar=0"
756
				);
757
				if (thePreviewWindow && thePreviewWindow.focus) {
758
					thePreviewWindow.focus();
759
				}
760
			}
761
			function deleteRecord(table,id,url) {
762
				window.location.href = ' . GeneralUtility::quoteJSvalue((string)$uriBuilder->buildUriFromRoute('tce_db') . '&cmd[') . '+table+"]["+id+"][delete]=1&redirect="+escape(url);
763
			}
764
		' . (isset($_POST['_savedokview']) && $this->popViewId ? $this->generatePreviewCode() : '')
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->popViewId of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
765
        );
766
        // Setting up the context sensitive menu:
767
        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
768
769
        $this->emitFunctionAfterSignal(__FUNCTION__);
770
    }
771
772
    /**
773
     * @return string
774
     */
775
    protected function generatePreviewCode()
776
    {
777
        $table = $this->previewData['table'];
778
        $recordId = $this->previewData['id'];
779
780
        if ($table === 'pages') {
781
            $currentPageId = $recordId;
782
        } else {
783
            $currentPageId = MathUtility::convertToPositiveInteger($this->popViewId);
784
        }
785
786
        $pageTsConfig = BackendUtility::getPagesTSconfig($currentPageId);
787
        $previewConfiguration = $pageTsConfig['TCEMAIN.']['preview.'][$table . '.'] ?? [];
788
789
        $recordArray = BackendUtility::getRecord($table, $recordId);
790
791
        // find the right preview page id
792
        $previewPageId = 0;
793
        if (isset($previewConfiguration['previewPageId'])) {
794
            $previewPageId = $previewConfiguration['previewPageId'];
795
        }
796
        // if no preview page was configured
797
        if (!$previewPageId) {
798
            $rootPageData = null;
799
            $rootLine = BackendUtility::BEgetRootLine($currentPageId);
800
            $currentPage = reset($rootLine);
801
            // Allow all doktypes below 200
802
            // This makes custom doktype work as well with opening a frontend page.
803
            if ((int)$currentPage['doktype'] <= PageRepository::DOKTYPE_SPACER) {
804
                // try the current page
805
                $previewPageId = $currentPageId;
806
            } else {
807
                // or search for the root page
808
                foreach ($rootLine as $page) {
809
                    if ($page['is_siteroot']) {
810
                        $rootPageData = $page;
811
                        break;
812
                    }
813
                }
814
                $previewPageId = isset($rootPageData)
815
                    ? (int)$rootPageData['uid']
816
                    : $currentPageId;
817
            }
818
        }
819
820
        $linkParameters = [];
821
822
        // language handling
823
        $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'] ?? '';
824
        if ($languageField && !empty($recordArray[$languageField])) {
825
            $l18nPointer = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? '';
826
            if ($l18nPointer && !empty($recordArray[$l18nPointer])
827
                && isset($previewConfiguration['useDefaultLanguageRecord'])
828
                && !$previewConfiguration['useDefaultLanguageRecord']
829
            ) {
830
                // use parent record
831
                $recordId = $recordArray[$l18nPointer];
832
            }
833
            $linkParameters['L'] = $recordArray[$languageField];
834
        }
835
836
        // map record data to GET parameters
837
        if (isset($previewConfiguration['fieldToParameterMap.'])) {
838
            foreach ($previewConfiguration['fieldToParameterMap.'] as $field => $parameterName) {
839
                $value = $recordArray[$field];
840
                if ($field === 'uid') {
841
                    $value = $recordId;
842
                }
843
                $linkParameters[$parameterName] = $value;
844
            }
845
        }
846
847
        // add/override parameters by configuration
848
        if (isset($previewConfiguration['additionalGetParameters.'])) {
849
            $additionalGetParameters = [];
850
            $this->parseAdditionalGetParameters(
851
                $additionalGetParameters,
852
                $previewConfiguration['additionalGetParameters.']
853
            );
854
            $linkParameters = array_replace($linkParameters, $additionalGetParameters);
855
        }
856
857
        if (!empty($previewConfiguration['useCacheHash'])) {
858
            /** @var CacheHashCalculator */
859
            $cacheHashCalculator = GeneralUtility::makeInstance(CacheHashCalculator::class);
860
            $fullLinkParameters = GeneralUtility::implodeArrayForUrl('', array_merge($linkParameters, ['id' => $previewPageId]));
861
            $cacheHashParameters = $cacheHashCalculator->getRelevantParameters($fullLinkParameters);
862
            $linkParameters['cHash'] = $cacheHashCalculator->calculateCacheHash($cacheHashParameters);
863
        } else {
864
            $linkParameters['no_cache'] = 1;
865
        }
866
867
        $this->popViewId = $previewPageId;
868
        $this->popViewId_addParams = GeneralUtility::implodeArrayForUrl('', $linkParameters, '', false, true);
869
        $anchorSection = $table === 'tt_content' ? '#c' . $recordId : '';
870
871
        $previewPageRootline = BackendUtility::BEgetRootLine($this->popViewId);
872
        return '
873
				if (window.opener) {
874
				'
875
            . BackendUtility::viewOnClick(
876
                $this->popViewId,
877
                '',
878
                $previewPageRootline,
879
                $anchorSection,
880
                $this->viewUrl,
881
                $this->popViewId_addParams,
882
                false
883
            )
884
            . '
885
				} else {
886
				'
887
            . BackendUtility::viewOnClick(
888
                $this->popViewId,
889
                '',
890
                $previewPageRootline,
891
                $anchorSection,
892
                $this->viewUrl,
893
                $this->popViewId_addParams
894
            )
895
            . '
896
				}';
897
    }
898
899
    /**
900
     * Migrates a set of (possibly nested) GET parameters in TypoScript syntax to a plain array
901
     *
902
     * This basically removes the trailing dots of sub-array keys in TypoScript.
903
     * The result can be used to create a query string with GeneralUtility::implodeArrayForUrl().
904
     *
905
     * @param array $parameters Should be an empty array by default
906
     * @param array $typoScript The TypoScript configuration
907
     */
908
    protected function parseAdditionalGetParameters(array &$parameters, array $typoScript)
909
    {
910
        foreach ($typoScript as $key => $value) {
911
            if (is_array($value)) {
912
                $key = rtrim($key, '.');
913
                $parameters[$key] = [];
914
                $this->parseAdditionalGetParameters($parameters[$key], $value);
915
            } else {
916
                $parameters[$key] = $value;
917
            }
918
        }
919
    }
920
921
    /**
922
     * Main module operation
923
     */
924
    public function main()
925
    {
926
        $body = '';
927
        // Begin edit:
928
        if (is_array($this->editconf)) {
929
            /** @var FormResultCompiler formResultCompiler */
930
            $this->formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
931
932
            // Creating the editing form, wrap it with buttons, document selector etc.
933
            $editForm = $this->makeEditForm();
934
            if ($editForm) {
935
                $this->firstEl = reset($this->elementsData);
936
                // Checking if the currently open document is stored in the list of "open documents" - if not, add it:
937
                if (($this->docDat[1] !== $this->storeUrlMd5
938
                        || !isset($this->docHandler[$this->storeUrlMd5]))
939
                    && !$this->dontStoreDocumentRef
940
                ) {
941
                    $this->docHandler[$this->storeUrlMd5] = [
942
                        $this->storeTitle,
943
                        $this->storeArray,
944
                        $this->storeUrl,
945
                        $this->firstEl
946
                    ];
947
                    $this->getBackendUser()->pushModuleData('FormEngine', [$this->docHandler, $this->storeUrlMd5]);
948
                    BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
949
                }
950
                // Module configuration
951
                $this->modTSconfig = $this->viewId ? BackendUtility::getModTSconfig(
952
                    $this->viewId,
953
                    'mod.xMOD_alt_doc'
954
                ) : [];
955
                $body = $this->formResultCompiler->addCssFiles();
956
                $body .= $this->compileForm($editForm);
957
                $body .= $this->formResultCompiler->printNeededJSFunctions();
958
                $body .= '</form>';
959
            }
960
        }
961
        // Access check...
962
        // The page will show only if there is a valid page and if this page may be viewed by the user
963
        $this->pageinfo = BackendUtility::readPageAccess($this->viewId, $this->perms_clause);
964
        if ($this->pageinfo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->pageinfo of type array 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...
965
            $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
966
        }
967
        // Setting up the buttons and markers for docheader
968
        $this->getButtons();
969
        $this->languageSwitch($this->firstEl['table'], $this->firstEl['uid'], $this->firstEl['pid']);
970
        $this->moduleTemplate->setContent($body);
971
    }
972
973
    /***************************
974
     *
975
     * Sub-content functions, rendering specific parts of the module content.
976
     *
977
     ***************************/
978
    /**
979
     * Creates the editing form with FormEnigne, based on the input from GPvars.
980
     *
981
     * @return string HTML form elements wrapped in tables
982
     */
983
    public function makeEditForm()
984
    {
985
        // Initialize variables:
986
        $this->elementsData = [];
987
        $this->errorC = 0;
988
        $this->newC = 0;
989
        $editForm = '';
990
        $trData = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $trData is dead and can be removed.
Loading history...
991
        $beUser = $this->getBackendUser();
992
        // Traverse the GPvar edit array
993
        // Tables:
994
        foreach ($this->editconf as $table => $conf) {
995
            if (is_array($conf) && $GLOBALS['TCA'][$table] && $beUser->check('tables_modify', $table)) {
996
                // Traverse the keys/comments of each table (keys can be a commalist of uids)
997
                foreach ($conf as $cKey => $command) {
998
                    if ($command === 'edit' || $command === 'new') {
999
                        // Get the ids:
1000
                        $ids = GeneralUtility::trimExplode(',', $cKey, true);
1001
                        // Traverse the ids:
1002
                        foreach ($ids as $theUid) {
1003
                            // Don't save this document title in the document selector if the document is new.
1004
                            if ($command === 'new') {
1005
                                $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...
1006
                            }
1007
1008
                            /** @var TcaDatabaseRecord $formDataGroup */
1009
                            $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
1010
                            /** @var FormDataCompiler $formDataCompiler */
1011
                            $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
0 ignored issues
show
Bug introduced by
$formDataGroup of type TYPO3\CMS\Backend\Form\F...Group\TcaDatabaseRecord is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

1011
                            $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, /** @scrutinizer ignore-type */ $formDataGroup);
Loading history...
1012
                            /** @var NodeFactory $nodeFactory */
1013
                            $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
1014
1015
                            try {
1016
                                // Reset viewId - it should hold data of last entry only
1017
                                $this->viewId = 0;
1018
                                $this->viewId_addParams = '';
1019
1020
                                $formDataCompilerInput = [
1021
                                    'tableName' => $table,
1022
                                    'vanillaUid' => (int)$theUid,
1023
                                    'command' => $command,
1024
                                    'returnUrl' => $this->R_URI,
1025
                                ];
1026
                                if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
1027
                                    $formDataCompilerInput['overrideValues'] = $this->overrideVals[$table];
1028
                                }
1029
1030
                                $formData = $formDataCompiler->compile($formDataCompilerInput);
1031
1032
                                // Set this->viewId if possible
1033
                                if ($command === 'new'
1034
                                    && $table !== 'pages'
1035
                                    && !empty($formData['parentPageRow']['uid'])
1036
                                ) {
1037
                                    $this->viewId = $formData['parentPageRow']['uid'];
1038
                                } else {
1039
                                    if ($table === 'pages') {
1040
                                        $this->viewId = $formData['databaseRow']['uid'];
1041
                                    } elseif (!empty($formData['parentPageRow']['uid'])) {
1042
                                        $this->viewId = $formData['parentPageRow']['uid'];
1043
                                        // Adding "&L=xx" if the record being edited has a languageField with a value larger than zero!
1044
                                        if (!empty($formData['processedTca']['ctrl']['languageField'])
1045
                                            && is_array($formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']])
1046
                                            && $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0] > 0
1047
                                        ) {
1048
                                            $this->viewId_addParams = '&L=' . $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0];
1049
                                        }
1050
                                    }
1051
                                }
1052
1053
                                // Determine if delete button can be shown
1054
                                $deleteAccess = false;
1055
                                if ($command === 'edit') {
1056
                                    $permission = $formData['userPermissionOnPage'];
1057
                                    if ($formData['tableName'] === 'pages') {
1058
                                        $deleteAccess = $permission & Permission::PAGE_DELETE ? true : false;
1059
                                    } else {
1060
                                        $deleteAccess = $permission & Permission::CONTENT_EDIT ? true : false;
1061
                                    }
1062
                                }
1063
1064
                                // Display "is-locked" message:
1065
                                if ($command === 'edit') {
1066
                                    $lockInfo = BackendUtility::isRecordLocked($table, $formData['databaseRow']['uid']);
1067
                                    if ($lockInfo) {
1068
                                        /** @var $flashMessage \TYPO3\CMS\Core\Messaging\FlashMessage */
1069
                                        $flashMessage = GeneralUtility::makeInstance(
1070
                                            FlashMessage::class,
1071
                                            $lockInfo['msg'],
1072
                                            '',
0 ignored issues
show
Bug introduced by
'' of type string is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

1072
                                            /** @scrutinizer ignore-type */ '',
Loading history...
1073
                                            FlashMessage::WARNING
0 ignored issues
show
Bug introduced by
TYPO3\CMS\Core\Messaging\FlashMessage::WARNING of type integer is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

1073
                                            /** @scrutinizer ignore-type */ FlashMessage::WARNING
Loading history...
1074
                                        );
1075
                                        /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
1076
                                        $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1077
                                        /** @var $defaultFlashMessageQueue FlashMessageQueue */
1078
                                        $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1079
                                        $defaultFlashMessageQueue->enqueue($flashMessage);
1080
                                    }
1081
                                }
1082
1083
                                // Record title
1084
                                if (!$this->storeTitle) {
1085
                                    $this->storeTitle = $this->recTitle
1086
                                        ? htmlspecialchars($this->recTitle)
1087
                                        : BackendUtility::getRecordTitle($table, FormEngineUtility::databaseRowCompatibility($formData['databaseRow']), true);
1088
                                }
1089
1090
                                $this->elementsData[] = [
1091
                                    'table' => $table,
1092
                                    'uid' => $formData['databaseRow']['uid'],
1093
                                    'pid' => $formData['databaseRow']['pid'],
1094
                                    'cmd' => $command,
1095
                                    'deleteAccess' => $deleteAccess
1096
                                ];
1097
1098
                                if ($command !== 'new') {
1099
                                    BackendUtility::lockRecords($table, $formData['databaseRow']['uid'], $table === 'tt_content' ? $formData['databaseRow']['pid'] : 0);
1100
                                }
1101
1102
                                // Set list if only specific fields should be rendered. This will trigger
1103
                                // ListOfFieldsContainer instead of FullRecordContainer in OuterWrapContainer
1104
                                if ($this->columnsOnly) {
1105
                                    if (is_array($this->columnsOnly)) {
1106
                                        $formData['fieldListToRender'] = $this->columnsOnly[$table];
1107
                                    } else {
1108
                                        $formData['fieldListToRender'] = $this->columnsOnly;
1109
                                    }
1110
                                }
1111
1112
                                $formData['renderType'] = 'outerWrapContainer';
1113
                                $formResult = $nodeFactory->create($formData)->render();
1114
1115
                                $html = $formResult['html'];
1116
1117
                                $formResult['html'] = '';
1118
                                $formResult['doSaveFieldName'] = 'doSave';
1119
1120
                                // @todo: Put all the stuff into FormEngine as final "compiler" class
1121
                                // @todo: This is done here for now to not rewrite addCssFiles()
1122
                                // @todo: and printNeededJSFunctions() now
1123
                                $this->formResultCompiler->mergeResult($formResult);
1124
1125
                                // Seems the pid is set as hidden field (again) at end?!
1126
                                if ($command === 'new') {
1127
                                    // @todo: looks ugly
1128
                                    $html .= LF
1129
                                        . '<input type="hidden"'
1130
                                        . ' name="data[' . htmlspecialchars($table) . '][' . htmlspecialchars($formData['databaseRow']['uid']) . '][pid]"'
1131
                                        . ' value="' . (int)$formData['databaseRow']['pid'] . '" />';
1132
                                    $this->newC++;
1133
                                }
1134
1135
                                $editForm .= $html;
1136
                            } catch (AccessDeniedException $e) {
1137
                                $this->errorC++;
1138
                                // Try to fetch error message from "recordInternals" be user object
1139
                                // @todo: This construct should be logged and localized and de-uglified
1140
                                $message = $beUser->errorMsg;
1141
                                if (empty($message)) {
1142
                                    // Create message from exception.
1143
                                    $message = $e->getMessage() . ' ' . $e->getCode();
1144
                                }
1145
                                $editForm .= htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.noEditPermission'))
1146
                                    . '<br /><br />' . htmlspecialchars($message) . '<br /><br />';
1147
                            }
1148
                        } // End of for each uid
1149
                    }
1150
                }
1151
            }
1152
        }
1153
        return $editForm;
1154
    }
1155
1156
    /**
1157
     * Create the panel of buttons for submitting the form or otherwise perform operations.
1158
     *
1159
     * @return array All available buttons as an assoc. array
1160
     */
1161
    protected function getButtons()
1162
    {
1163
        $lang = $this->getLanguageService();
1164
        /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
1165
        $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
1166
        // Render SAVE type buttons:
1167
        // The action of each button is decided by its name attribute. (See doProcessData())
1168
        $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
1169
        if (!$this->errorC && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']) {
1170
            $saveSplitButton = $buttonBar->makeSplitButton();
1171
            // SAVE button:
1172
            $saveButton = $buttonBar->makeInputButton()
1173
                ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'))
1174
                ->setName('_savedok')
1175
                ->setValue('1')
1176
                ->setForm('EditDocumentController')
1177
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL));
1178
            $saveSplitButton->addItem($saveButton, true);
1179
1180
            // SAVE / VIEW button:
1181
            if ($this->viewId && !$this->noView && $this->getNewIconMode($this->firstEl['table'], 'saveDocView')) {
1182
                $pagesTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid']);
1183
                if (isset($pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'])) {
1184
                    $excludeDokTypes = GeneralUtility::intExplode(
1185
                        ',',
1186
                        $pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'],
1187
                        true
1188
                    );
1189
                } else {
1190
                    // exclude sysfolders, spacers and recycler by default
1191
                    $excludeDokTypes = [
1192
                        PageRepository::DOKTYPE_RECYCLER,
1193
                        PageRepository::DOKTYPE_SYSFOLDER,
1194
                        PageRepository::DOKTYPE_SPACER
1195
                    ];
1196
                }
1197
                if (!in_array((int)$this->pageinfo['doktype'], $excludeDokTypes, true)
1198
                    || isset($pagesTSconfig['TCEMAIN.']['preview.'][$this->firstEl['table'] . '.']['previewPageId'])
1199
                ) {
1200
                    $saveAndOpenButton = $buttonBar->makeInputButton()
1201
                        ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveDocShow'))
1202
                        ->setName('_savedokview')
1203
                        ->setValue('1')
1204
                        ->setForm('EditDocumentController')
1205
                        ->setOnClick("window.open('', 'newTYPO3frontendWindow');")
1206
                        ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1207
                            'actions-document-save-view',
1208
                            Icon::SIZE_SMALL
1209
                        ));
1210
                    $saveSplitButton->addItem($saveAndOpenButton);
1211
                }
1212
            }
1213
            // SAVE / NEW button:
1214
            if (count($this->elementsData) === 1 && $this->getNewIconMode($this->firstEl['table'])) {
1215
                $saveAndNewButton = $buttonBar->makeInputButton()
1216
                    ->setName('_savedoknew')
1217
                    ->setClasses('t3js-editform-submitButton')
1218
                    ->setValue('1')
1219
                    ->setForm('EditDocumentController')
1220
                    ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveNewDoc'))
1221
                    ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1222
                        'actions-document-save-new',
1223
                        Icon::SIZE_SMALL
1224
                    ));
1225
                $saveSplitButton->addItem($saveAndNewButton);
1226
            }
1227
            // SAVE / CLOSE
1228
            $saveAndCloseButton = $buttonBar->makeInputButton()
1229
                ->setName('_saveandclosedok')
1230
                ->setClasses('t3js-editform-submitButton')
1231
                ->setValue('1')
1232
                ->setForm('EditDocumentController')
1233
                ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveCloseDoc'))
1234
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1235
                    'actions-document-save-close',
1236
                    Icon::SIZE_SMALL
1237
                ));
1238
            $saveSplitButton->addItem($saveAndCloseButton);
1239
            // FINISH TRANSLATION / SAVE / CLOSE
1240
            if ($GLOBALS['TYPO3_CONF_VARS']['BE']['explicitConfirmationOfTranslation']) {
1241
                $saveTranslationButton = $buttonBar->makeInputButton()
1242
                    ->setName('_translation_savedok')
1243
                    ->setValue('1')
1244
                    ->setForm('EditDocumentController')
1245
                    ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.translationSaveDoc'))
1246
                    ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1247
                        'actions-document-save-cleartranslationcache',
1248
                        Icon::SIZE_SMALL
1249
                    ));
1250
                $saveSplitButton->addItem($saveTranslationButton);
1251
                $saveAndClearTranslationButton = $buttonBar->makeInputButton()
1252
                    ->setName('_translation_savedokclear')
1253
                    ->setValue('1')
1254
                    ->setForm('EditDocumentController')
1255
                    ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.translationSaveDocClear'))
1256
                    ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1257
                        'actions-document-save-cleartranslationcache',
1258
                        Icon::SIZE_SMALL
1259
                    ));
1260
                $saveSplitButton->addItem($saveAndClearTranslationButton);
1261
            }
1262
            $buttonBar->addButton($saveSplitButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
1263
        }
1264
        // CLOSE button:
1265
        $closeButton = $buttonBar->makeLinkButton()
1266
            ->setHref('#')
1267
            ->setClasses('t3js-editform-close')
1268
            ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
1269
            ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1270
                'actions-close',
1271
                Icon::SIZE_SMALL
1272
            ));
1273
        $buttonBar->addButton($closeButton);
1274
        // DELETE + UNDO buttons:
1275
        if (!$this->errorC
1276
            && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1277
            && count($this->elementsData) === 1
1278
        ) {
1279
            if ($this->firstEl['cmd'] !== 'new' && MathUtility::canBeInterpretedAsInteger($this->firstEl['uid'])) {
1280
                // Delete:
1281
                if ($this->firstEl['deleteAccess']
1282
                    && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1283
                    && !$this->getNewIconMode($this->firstEl['table'], 'disableDelete')
1284
                ) {
1285
                    $returnUrl = $this->retUrl;
1286
                    if ($this->firstEl['table'] === 'pages') {
1287
                        parse_str((string)parse_url($returnUrl, PHP_URL_QUERY), $queryParams);
1288
                        if (isset($queryParams['route'])
1289
                            && isset($queryParams['id'])
1290
                            && (string)$this->firstEl['uid'] === (string)$queryParams['id']
1291
                        ) {
1292
                            // TODO: Use the page's pid instead of 0, this requires a clean API to manipulate the page
1293
                            // tree from the outside to be able to mark the pid as active
1294
                            $returnUrl = (string)$uriBuilder->buildUriFromRoutePath($queryParams['route'], ['id' => 0]);
1295
                        }
1296
                    }
1297
                    $deleteButton = $buttonBar->makeLinkButton()
1298
                        ->setHref('#')
1299
                        ->setClasses('t3js-editform-delete-record')
1300
                        ->setTitle($lang->getLL('deleteItem'))
1301
                        ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1302
                            'actions-edit-delete',
1303
                            Icon::SIZE_SMALL
1304
                        ))
1305
                        ->setDataAttributes([
1306
                            'return-url' => $returnUrl,
1307
                            'uid' => $this->firstEl['uid'],
1308
                            'table' => $this->firstEl['table']
1309
                        ]);
1310
                    $buttonBar->addButton($deleteButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1311
                }
1312
                // Undo:
1313
                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1314
                    ->getQueryBuilderForTable('sys_history');
1315
1316
                $undoButtonR = $queryBuilder->select('tstamp')
1317
                    ->from('sys_history')
1318
                    ->where(
1319
                        $queryBuilder->expr()->eq(
1320
                            'tablename',
1321
                            $queryBuilder->createNamedParameter($this->firstEl['table'], \PDO::PARAM_STR)
1322
                        ),
1323
                        $queryBuilder->expr()->eq(
1324
                            'recuid',
1325
                            $queryBuilder->createNamedParameter($this->firstEl['uid'], \PDO::PARAM_INT)
1326
                        )
1327
                    )
1328
                    ->orderBy('tstamp', 'DESC')
1329
                    ->setMaxResults(1)
1330
                    ->execute()
1331
                    ->fetch();
1332
1333
                if ($undoButtonR !== false) {
1334
                    $aOnClick = 'window.location.href=' .
1335
                        GeneralUtility::quoteJSvalue(
1336
                            (string)$uriBuilder->buildUriFromRoute(
1337
                                'record_history',
1338
                                [
1339
                                    'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1340
                                    'revert' => 'ALL_FIELDS',
1341
                                    'returnUrl' => $this->R_URI,
1342
                                ]
1343
                            )
1344
                        ) . '; return false;';
1345
1346
                    $undoButton = $buttonBar->makeLinkButton()
1347
                        ->setHref('#')
1348
                        ->setOnClick($aOnClick)
1349
                        ->setTitle(
1350
                            sprintf(
1351
                                $lang->getLL('undoLastChange'),
1352
                                BackendUtility::calcAge(
1353
                                    ($GLOBALS['EXEC_TIME'] - $undoButtonR['tstamp']),
1354
                                    $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
1355
                                )
1356
                            )
1357
                        )
1358
                        ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1359
                            'actions-document-history-open',
1360
                            Icon::SIZE_SMALL
1361
                        ));
1362
                    $buttonBar->addButton($undoButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1363
                }
1364
                if ($this->getNewIconMode($this->firstEl['table'], 'showHistory')) {
1365
                    $aOnClick = 'window.location.href=' .
1366
                        GeneralUtility::quoteJSvalue(
1367
                            (string)$uriBuilder->buildUriFromRoute(
1368
                                'record_history',
1369
                                [
1370
                                    'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1371
                                    'returnUrl' => $this->R_URI,
1372
                                ]
1373
                            )
1374
                        ) . '; return false;';
1375
1376
                    $historyButton = $buttonBar->makeLinkButton()
1377
                        ->setHref('#')
1378
                        ->setOnClick($aOnClick)
1379
                        ->setTitle('Open history of this record')
1380
                        ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1381
                            'actions-document-history-open',
1382
                            Icon::SIZE_SMALL
1383
                        ));
1384
                    $buttonBar->addButton($historyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1385
                }
1386
                // If only SOME fields are shown in the form, this will link the user to the FULL form:
1387
                if ($this->columnsOnly) {
1388
                    $columnsOnlyButton = $buttonBar->makeLinkButton()
1389
                        ->setHref($this->R_URI . '&columnsOnly=')
1390
                        ->setTitle($lang->getLL('editWholeRecord'))
1391
                        ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1392
                            'actions-open',
1393
                            Icon::SIZE_SMALL
1394
                        ));
1395
                    $buttonBar->addButton($columnsOnlyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1396
                }
1397
            }
1398
        }
1399
        $cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('TCEforms');
1400
        $buttonBar->addButton($cshButton);
1401
        $this->shortCutLink();
1402
        $this->openInNewWindowLink();
1403
    }
1404
1405
    /**
1406
     * Put together the various elements (buttons, selectors, form) into a table
1407
     *
1408
     * @param string $editForm HTML form.
1409
     * @return string Composite HTML
1410
     */
1411
    public function compileForm($editForm)
1412
    {
1413
        $formContent = '
1414
			<!-- EDITING FORM -->
1415
			<form
1416
            action="' . htmlspecialchars($this->R_URI) . '"
1417
            method="post"
1418
            enctype="multipart/form-data"
1419
            name="editform"
1420
            id="EditDocumentController"
1421
            onsubmit="TBE_EDITOR.checkAndDoSubmit(1); return false;">
1422
			' . $editForm . '
1423
1424
			<input type="hidden" name="returnUrl" value="' . htmlspecialchars($this->retUrl) . '" />
1425
			<input type="hidden" name="viewUrl" value="' . htmlspecialchars($this->viewUrl) . '" />';
1426
        if ($this->returnNewPageId) {
1427
            $formContent .= '<input type="hidden" name="returnNewPageId" value="1" />';
1428
        }
1429
        $formContent .= '<input type="hidden" name="popViewId" value="' . htmlspecialchars($this->viewId) . '" />';
1430
        if ($this->viewId_addParams) {
1431
            $formContent .= '<input type="hidden" name="popViewId_addParams" value="' . htmlspecialchars($this->viewId_addParams) . '" />';
1432
        }
1433
        $formContent .= '
1434
			<input type="hidden" name="closeDoc" value="0" />
1435
			<input type="hidden" name="doSave" value="0" />
1436
			<input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />
1437
			<input type="hidden" name="_scrollPosition" value="" />';
1438
        return $formContent;
1439
    }
1440
1441
    /**
1442
     * Create shortcut icon
1443
     */
1444
    public function shortCutLink()
1445
    {
1446
        if ($this->returnUrl !== $this->getCloseUrl()) {
1447
            $shortCutButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->makeShortcutButton();
1448
            $shortCutButton->setModuleName($this->MCONF['name'])
1449
                ->setGetVariables([
1450
                    'returnUrl',
1451
                    'edit',
1452
                    'defVals',
1453
                    'overrideVals',
1454
                    'columnsOnly',
1455
                    'returnNewPageId',
1456
                    'noView']);
1457
            $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton($shortCutButton);
1458
        }
1459
    }
1460
1461
    /**
1462
     * Creates open-in-window link
1463
     */
1464
    public function openInNewWindowLink()
1465
    {
1466
        $closeUrl = $this->getCloseUrl();
1467
        if ($this->returnUrl !== $closeUrl) {
1468
            $aOnClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue(GeneralUtility::linkThisScript(
1469
                ['returnUrl' => $closeUrl]
1470
            ))
1471
                . ','
1472
                . GeneralUtility::quoteJSvalue(md5($this->R_URI))
1473
                . ',\'width=670,height=500,status=0,menubar=0,scrollbars=1,resizable=1\');vHWin.focus();return false;';
1474
            $openInNewWindowButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()
1475
                ->makeLinkButton()
1476
                ->setHref('#')
1477
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.openInNewWindow'))
1478
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-window-open', Icon::SIZE_SMALL))
1479
                ->setOnClick($aOnClick);
1480
            $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton(
1481
                $openInNewWindowButton,
1482
                ButtonBar::BUTTON_POSITION_RIGHT
1483
            );
1484
        }
1485
    }
1486
1487
    /**
1488
     * Returns the URL (usually for the "returnUrl") which closes the current window.
1489
     * Used when editing a record in a popup.
1490
     *
1491
     * @return string
1492
     */
1493
    protected function getCloseUrl(): string
1494
    {
1495
        $closeUrl = GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Public/Html/Close.html');
1496
        return PathUtility::getAbsoluteWebPath($closeUrl);
1497
    }
1498
1499
    /***************************
1500
     *
1501
     * Localization stuff
1502
     *
1503
     ***************************/
1504
    /**
1505
     * Make selector box for creating new translation for a record or switching to edit the record in an existing
1506
     * language.
1507
     * Displays only languages which are available for the current page.
1508
     *
1509
     * @param string $table Table name
1510
     * @param int $uid Uid for which to create a new language
1511
     * @param int $pid Pid of the record
1512
     */
1513
    public function languageSwitch($table, $uid, $pid = null)
1514
    {
1515
        $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
1516
        $transOrigPointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
1517
        /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
1518
        $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
1519
1520
        // Table editable and activated for languages?
1521
        if ($this->getBackendUser()->check('tables_modify', $table)
1522
            && $languageField
1523
            && $transOrigPointerField
1524
        ) {
1525
            if (is_null($pid)) {
1526
                $row = BackendUtility::getRecord($table, $uid, 'pid');
1527
                $pid = $row['pid'];
1528
            }
1529
            // Get all available languages for the page
1530
            // If editing a page, the translations of the current UID need to be fetched
1531
            if ($table === 'pages') {
1532
                $row = BackendUtility::getRecord($table, $uid, $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']);
1533
                // Ensure the check is always done against the default language page
1534
                $langRows = $this->getLanguages($row[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']] ?: $uid);
1535
            } else {
1536
                $langRows = $this->getLanguages($pid);
1537
            }
1538
            // Page available in other languages than default language?
1539
            if (is_array($langRows) && count($langRows) > 1) {
1540
                $rowsByLang = [];
1541
                $fetchFields = 'uid,' . $languageField . ',' . $transOrigPointerField;
1542
                // Get record in current language
1543
                $rowCurrent = BackendUtility::getLiveVersionOfRecord($table, $uid, $fetchFields);
1544
                if (!is_array($rowCurrent)) {
1545
                    $rowCurrent = BackendUtility::getRecord($table, $uid, $fetchFields);
1546
                }
1547
                $currentLanguage = (int)$rowCurrent[$languageField];
1548
                // Disabled for records with [all] language!
1549
                if ($currentLanguage > -1) {
1550
                    // Get record in default language if needed
1551
                    if ($currentLanguage && $rowCurrent[$transOrigPointerField]) {
1552
                        $rowsByLang[0] = BackendUtility::getLiveVersionOfRecord(
1553
                            $table,
1554
                            $rowCurrent[$transOrigPointerField],
1555
                            $fetchFields
1556
                        );
1557
                        if (!is_array($rowsByLang[0])) {
1558
                            $rowsByLang[0] = BackendUtility::getRecord(
1559
                                $table,
1560
                                $rowCurrent[$transOrigPointerField],
1561
                                $fetchFields
1562
                            );
1563
                        }
1564
                    } else {
1565
                        $rowsByLang[$rowCurrent[$languageField]] = $rowCurrent;
1566
                    }
1567
                    if ($rowCurrent[$transOrigPointerField] || $currentLanguage === 0) {
1568
                        // Get record in other languages to see what's already available
1569
1570
                        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1571
                            ->getQueryBuilderForTable($table);
1572
1573
                        $queryBuilder->getRestrictions()
1574
                            ->removeAll()
1575
                            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1576
                            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1577
1578
                        $result = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fetchFields, true))
1579
                            ->from($table)
1580
                            ->where(
1581
                                $queryBuilder->expr()->eq(
1582
                                    'pid',
1583
                                    $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)
1584
                                ),
1585
                                $queryBuilder->expr()->gt(
1586
                                    $languageField,
1587
                                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1588
                                ),
1589
                                $queryBuilder->expr()->eq(
1590
                                    $transOrigPointerField,
1591
                                    $queryBuilder->createNamedParameter($rowsByLang[0]['uid'], \PDO::PARAM_INT)
1592
                                )
1593
                            )
1594
                            ->execute();
1595
1596
                        while ($row = $result->fetch()) {
1597
                            $rowsByLang[$row[$languageField]] = $row;
1598
                        }
1599
                    }
1600
                    $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
1601
                    $languageMenu->setIdentifier('_langSelector');
1602
                    foreach ($langRows as $lang) {
1603
                        if ($this->getBackendUser()->checkLanguageAccess($lang['uid'])) {
1604
                            $newTranslation = isset($rowsByLang[$lang['uid']]) ? '' : ' [' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.new')) . ']';
1605
                            // Create url for creating a localized record
1606
                            $addOption = true;
1607
                            if ($newTranslation) {
1608
                                $redirectUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', [
1609
                                    'justLocalized' => $table . ':' . $rowsByLang[0]['uid'] . ':' . $lang['uid'],
1610
                                    'returnUrl' => $this->retUrl
1611
                                ]);
1612
1613
                                if (array_key_exists(0, $rowsByLang)) {
1614
                                    $href = BackendUtility::getLinkToDataHandlerAction(
1615
                                        '&cmd[' . $table . '][' . $rowsByLang[0]['uid'] . '][localize]=' . $lang['uid'],
1616
                                        $redirectUrl
1617
                                    );
1618
                                } else {
1619
                                    $addOption = false;
1620
                                }
1621
                            } else {
1622
                                $href = (string)$uriBuilder->buildUriFromRoute('record_edit', [
1623
                                    'edit[' . $table . '][' . $rowsByLang[$lang['uid']]['uid'] . ']' => 'edit',
1624
                                    'returnUrl' => $this->retUrl
1625
                                ]);
1626
                            }
1627
                            if ($addOption) {
1628
                                $menuItem = $languageMenu->makeMenuItem()
1629
                                                         ->setTitle($lang['title'] . $newTranslation)
1630
                                                         ->setHref($href);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $href does not seem to be defined for all execution paths leading up to this point.
Loading history...
1631
                                if ((int)$lang['uid'] === $currentLanguage) {
1632
                                    $menuItem->setActive(true);
1633
                                }
1634
                                $languageMenu->addMenuItem($menuItem);
1635
                            }
1636
                        }
1637
                    }
1638
                    $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
1639
                }
1640
            }
1641
        }
1642
    }
1643
1644
    /**
1645
     * Redirects to FormEngine with new parameters to edit a just created localized record
1646
     *
1647
     * @param string $justLocalized String passed by GET &justLocalized=
1648
     */
1649
    public function localizationRedirect($justLocalized)
1650
    {
1651
        /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
1652
        $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
1653
1654
        list($table, $origUid, $language) = explode(':', $justLocalized);
1655
        if ($GLOBALS['TCA'][$table]
1656
            && $GLOBALS['TCA'][$table]['ctrl']['languageField']
1657
            && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
1658
        ) {
1659
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1660
                ->getQueryBuilderForTable($table);
1661
            $queryBuilder->getRestrictions()
1662
                ->removeAll()
1663
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1664
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1665
1666
            $localizedRecord = $queryBuilder->select('uid')
1667
                ->from($table)
1668
                ->where(
1669
                    $queryBuilder->expr()->eq(
1670
                        $GLOBALS['TCA'][$table]['ctrl']['languageField'],
1671
                        $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
1672
                    ),
1673
                    $queryBuilder->expr()->eq(
1674
                        $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
1675
                        $queryBuilder->createNamedParameter($origUid, \PDO::PARAM_INT)
1676
                    )
1677
                )
1678
                ->execute()
1679
                ->fetch();
1680
1681
            if (is_array($localizedRecord)) {
1682
                // Create parameters and finally run the classic page module for creating a new page translation
1683
                $location = (string)$uriBuilder->buildUriFromRoute('record_edit', [
1684
                    'edit[' . $table . '][' . $localizedRecord['uid'] . ']' => 'edit',
1685
                    'returnUrl' => GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'))
1686
                ]);
1687
                HttpUtility::redirect($location);
1688
            }
1689
        }
1690
    }
1691
1692
    /**
1693
     * Returns sys_language records available for record translations on given page.
1694
     *
1695
     * @param int $id Page id: If zero, the query will select all sys_language records from root level which are NOT
1696
     *                hidden. If set to another value, the query will select all sys_language records that has a
1697
     *                translation record on that page (and is not hidden, unless you are admin user)
1698
     * @return array Language records including faked record for default language
1699
     */
1700
    public function getLanguages($id)
1701
    {
1702
        $modSharedTSconfig = BackendUtility::getModTSconfig($id, 'mod.SHARED');
1703
        // Fallback non sprite-configuration
1704
        if (preg_match('/\\.gif$/', $modSharedTSconfig['properties']['defaultLanguageFlag'])) {
1705
            $modSharedTSconfig['properties']['defaultLanguageFlag'] = str_replace(
1706
                '.gif',
1707
                '',
1708
                $modSharedTSconfig['properties']['defaultLanguageFlag']
1709
            );
1710
        }
1711
        $languages = [
1712
            0 => [
1713
                'uid' => 0,
1714
                'pid' => 0,
1715
                'hidden' => 0,
1716
                'title' => $modSharedTSconfig['properties']['defaultLanguageLabel'] !== ''
1717
                        ? $modSharedTSconfig['properties']['defaultLanguageLabel'] . ' (' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage') . ')'
1718
                        : $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage'),
1719
                'flag' => $modSharedTSconfig['properties']['defaultLanguageFlag']
1720
            ]
1721
        ];
1722
1723
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1724
            ->getQueryBuilderForTable('sys_language');
1725
1726
        $queryBuilder->select('s.uid', 's.pid', 's.hidden', 's.title', 's.flag')
1727
            ->from('sys_language', 's')
1728
            ->groupBy('s.uid', 's.pid', 's.hidden', 's.title', 's.flag', 's.sorting')
1729
            ->orderBy('s.sorting');
1730
1731
        if ($id) {
1732
            $queryBuilder->getRestrictions()
1733
                ->removeAll()
1734
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1735
                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1736
1737
            if (!$this->getBackendUser()->isAdmin()) {
1738
                $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(HiddenRestriction::class));
1739
            }
1740
1741
            // Add join with pages translations to only show active languages
1742
            $queryBuilder->from('pages', 'o')
1743
                ->where(
1744
                    $queryBuilder->expr()->eq('o.' . $GLOBALS['TCA']['pages']['ctrl']['languageField'], $queryBuilder->quoteIdentifier('s.uid')),
1745
                    $queryBuilder->expr()->eq('o.' . $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'], $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT))
1746
                );
1747
        }
1748
1749
        $result = $queryBuilder->execute();
1750
        while ($row = $result->fetch()) {
1751
            $languages[$row['uid']] = $row;
1752
        }
1753
1754
        return $languages;
1755
    }
1756
1757
    /***************************
1758
     *
1759
     * Other functions
1760
     *
1761
     ***************************/
1762
    /**
1763
     * Fix $this->editconf if versioning applies to any of the records
1764
     *
1765
     * @param array|bool $mapArray Mapping between old and new ids if auto-versioning has been performed.
1766
     */
1767
    public function fixWSversioningInEditConf($mapArray = false)
1768
    {
1769
        // Traverse the editConf array
1770
        if (is_array($this->editconf)) {
1771
            // Tables:
1772
            foreach ($this->editconf as $table => $conf) {
1773
                if (is_array($conf) && $GLOBALS['TCA'][$table]) {
1774
                    // Traverse the keys/comments of each table (keys can be a commalist of uids)
1775
                    $newConf = [];
1776
                    foreach ($conf as $cKey => $cmd) {
1777
                        if ($cmd === 'edit') {
1778
                            // Traverse the ids:
1779
                            $ids = GeneralUtility::trimExplode(',', $cKey, true);
1780
                            foreach ($ids as $idKey => $theUid) {
1781
                                if (is_array($mapArray)) {
1782
                                    if ($mapArray[$table][$theUid]) {
1783
                                        $ids[$idKey] = $mapArray[$table][$theUid];
1784
                                    }
1785
                                } else {
1786
                                    // Default, look for versions in workspace for record:
1787
                                    $calcPRec = $this->getRecordForEdit($table, $theUid);
1788
                                    if (is_array($calcPRec)) {
1789
                                        // Setting UID again if it had changed, eg. due to workspace versioning.
1790
                                        $ids[$idKey] = $calcPRec['uid'];
1791
                                    }
1792
                                }
1793
                            }
1794
                            // Add the possibly manipulated IDs to the new-build newConf array:
1795
                            $newConf[implode(',', $ids)] = $cmd;
1796
                        } else {
1797
                            $newConf[$cKey] = $cmd;
1798
                        }
1799
                    }
1800
                    // Store the new conf array:
1801
                    $this->editconf[$table] = $newConf;
1802
                }
1803
            }
1804
        }
1805
    }
1806
1807
    /**
1808
     * Get record for editing.
1809
     *
1810
     * @param string $table Table name
1811
     * @param int $theUid Record UID
1812
     * @return array Returns record to edit, FALSE if none
1813
     */
1814
    public function getRecordForEdit($table, $theUid)
1815
    {
1816
        // Fetch requested record:
1817
        $reqRecord = BackendUtility::getRecord($table, $theUid, 'uid,pid');
1818
        if (is_array($reqRecord)) {
1819
            // If workspace is OFFLINE:
1820
            if ($this->getBackendUser()->workspace != 0) {
1821
                // Check for versioning support of the table:
1822
                if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1823
                    // If the record is already a version of "something" pass it by.
1824
                    if ($reqRecord['pid'] == -1) {
1825
                        // (If it turns out not to be a version of the current workspace there will be trouble, but
1826
                        // that is handled inside DataHandler then and in the interface it would clearly be an error of
1827
                        // links if the user accesses such a scenario)
1828
                        return $reqRecord;
1829
                    }
1830
                    // The input record was online and an offline version must be found or made:
1831
                    // Look for version of this workspace:
1832
                    $versionRec = BackendUtility::getWorkspaceVersionOfRecord(
1833
                            $this->getBackendUser()->workspace,
1834
                            $table,
1835
                            $reqRecord['uid'],
1836
                            'uid,pid,t3ver_oid'
1837
                        );
1838
                    return is_array($versionRec) ? $versionRec : $reqRecord;
1839
                }
1840
                // This means that editing cannot occur on this record because it was not supporting versioning
1841
                // which is required inside an offline workspace.
1842
                return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
1843
            }
1844
            // In ONLINE workspace, just return the originally requested record:
1845
            return $reqRecord;
1846
        }
1847
        // Return FALSE because the table/uid was not found anyway.
1848
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
1849
    }
1850
1851
    /**
1852
     * Populates the variables $this->storeArray, $this->storeUrl, $this->storeUrlMd5
1853
     *
1854
     * @see makeDocSel()
1855
     */
1856
    public function compileStoreDat()
1857
    {
1858
        $this->storeArray = GeneralUtility::compileSelectedGetVarsFromArray(
1859
            'edit,defVals,overrideVals,columnsOnly,noView,workspace',
1860
            $this->R_URL_getvars
1861
        );
1862
        $this->storeUrl = GeneralUtility::implodeArrayForUrl('', $this->storeArray);
1863
        $this->storeUrlMd5 = md5($this->storeUrl);
1864
    }
1865
1866
    /**
1867
     * Function used to look for configuration of buttons in the form: Fx. disabling buttons or showing them at various
1868
     * positions.
1869
     *
1870
     * @param string $table The table for which the configuration may be specific
1871
     * @param string $key The option for look for. Default is checking if the saveDocNew button should be displayed.
1872
     * @return string Return value fetched from USER TSconfig
1873
     */
1874
    public function getNewIconMode($table, $key = 'saveDocNew')
1875
    {
1876
        $TSconfig = $this->getBackendUser()->getTSConfig('options.' . $key);
1877
        $output = trim($TSconfig['properties'][$table] ?? $TSconfig['value']);
1878
        return $output;
1879
    }
1880
1881
    /**
1882
     * Handling the closing of a document
1883
     * The argument $mode can be one of this values:
1884
     * - 0/1 will redirect to $this->retUrl [self::DOCUMENT_CLOSE_MODE_DEFAULT || self::DOCUMENT_CLOSE_MODE_REDIRECT]
1885
     * - 3 will clear the docHandler (thus closing all documents) [self::DOCUMENT_CLOSE_MODE_CLEAR_ALL]
1886
     * - 4 will do no redirect [self::DOCUMENT_CLOSE_MODE_NO_REDIRECT]
1887
     * - other values will call setDocument with ->retUrl
1888
     *
1889
     * @param int $mode the close mode: one of self::DOCUMENT_CLOSE_MODE_*
1890
     */
1891
    public function closeDocument($mode = self::DOCUMENT_CLOSE_MODE_DEFAULT)
1892
    {
1893
        $mode = (int)$mode;
1894
        // If current document is found in docHandler,
1895
        // then unset it, possibly unset it ALL and finally, write it to the session data
1896
        if (isset($this->docHandler[$this->storeUrlMd5])) {
1897
            // add the closing document to the recent documents
1898
            $recentDocs = $this->getBackendUser()->getModuleData('opendocs::recent');
1899
            if (!is_array($recentDocs)) {
1900
                $recentDocs = [];
1901
            }
1902
            $closedDoc = $this->docHandler[$this->storeUrlMd5];
1903
            $recentDocs = array_merge([$this->storeUrlMd5 => $closedDoc], $recentDocs);
1904
            if (count($recentDocs) > 8) {
1905
                $recentDocs = array_slice($recentDocs, 0, 8);
1906
            }
1907
            // remove it from the list of the open documents
1908
            unset($this->docHandler[$this->storeUrlMd5]);
1909
            if ($mode === self::DOCUMENT_CLOSE_MODE_CLEAR_ALL) {
1910
                $recentDocs = array_merge($this->docHandler, $recentDocs);
1911
                $this->docHandler = [];
1912
            }
1913
            $this->getBackendUser()->pushModuleData('opendocs::recent', $recentDocs);
1914
            $this->getBackendUser()->pushModuleData('FormEngine', [$this->docHandler, $this->docDat[1]]);
1915
            BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
1916
        }
1917
        if ($mode !== self::DOCUMENT_CLOSE_MODE_NO_REDIRECT) {
1918
            /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
1919
            $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
1920
            // If ->returnEditConf is set, then add the current content of editconf to the ->retUrl variable: (used by
1921
            // other scripts, like wizard_add, to know which records was created or so...)
1922
            if ($this->returnEditConf && $this->retUrl != (string)$uriBuilder->buildUriFromRoute('dummy')) {
1923
                $this->retUrl .= '&returnEditConf=' . rawurlencode(json_encode($this->editconf));
1924
            }
1925
1926
            // If mode is NOT set (means 0) OR set to 1, then make a header location redirect to $this->retUrl
1927
            if ($mode === self::DOCUMENT_CLOSE_MODE_DEFAULT || $mode === self::DOCUMENT_CLOSE_MODE_REDIRECT) {
1928
                HttpUtility::redirect($this->retUrl);
1929
            } else {
1930
                $this->setDocument('', $this->retUrl);
1931
            }
1932
        }
1933
    }
1934
1935
    /**
1936
     * Redirects to the document pointed to by $currentDocFromHandlerMD5 OR $retUrl (depending on some internal
1937
     * calculations).
1938
     * Most likely you will get a header-location redirect from this function.
1939
     *
1940
     * @param string $currentDocFromHandlerMD5 Pointer to the document in the docHandler array
1941
     * @param string $retUrl Alternative/Default retUrl
1942
     */
1943
    public function setDocument($currentDocFromHandlerMD5 = '', $retUrl = '')
1944
    {
1945
        if ($retUrl === '') {
1946
            return;
1947
        }
1948
        if (!$this->modTSconfig['properties']['disableDocSelector']
1949
            && is_array($this->docHandler)
1950
            && !empty($this->docHandler)
1951
        ) {
1952
            if (isset($this->docHandler[$currentDocFromHandlerMD5])) {
1953
                $setupArr = $this->docHandler[$currentDocFromHandlerMD5];
1954
            } else {
1955
                $setupArr = reset($this->docHandler);
1956
            }
1957
            if ($setupArr[2]) {
1958
                $sParts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
1959
                $retUrl = $sParts['path'] . '?' . $setupArr[2] . '&returnUrl=' . rawurlencode($retUrl);
1960
            }
1961
        }
1962
        HttpUtility::redirect($retUrl);
1963
    }
1964
1965
    /**
1966
     * Injects the request object for the current request or subrequest
1967
     *
1968
     * @param ServerRequestInterface $request the current request
1969
     * @param ResponseInterface $response
1970
     * @return ResponseInterface the response with the content
1971
     */
1972
    public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
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

1972
    public function mainAction(/** @scrutinizer ignore-unused */ ServerRequestInterface $request, ResponseInterface $response)

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...
1973
    {
1974
        BackendUtility::lockRecords();
1975
1976
        // Preprocessing, storing data if submitted to
1977
        $this->preInit();
1978
1979
        // Checks, if a save button has been clicked (or the doSave variable is sent)
1980
        if ($this->doProcessData()) {
1981
            $this->processData();
1982
        }
1983
1984
        $this->init();
1985
        $this->main();
1986
1987
        $response->getBody()->write($this->moduleTemplate->renderContent());
1988
        return $response;
1989
    }
1990
1991
    /**
1992
     * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1993
     */
1994
    protected function getBackendUser()
1995
    {
1996
        return $GLOBALS['BE_USER'];
1997
    }
1998
1999
    /**
2000
     * Returns LanguageService
2001
     *
2002
     * @return \TYPO3\CMS\Core\Localization\LanguageService
2003
     */
2004
    protected function getLanguageService()
2005
    {
2006
        return $GLOBALS['LANG'];
2007
    }
2008
}
2009