Save.php 42.4 KB
Newer Older
1
2
3
4
5
6
7
8
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 1/30/16
 * Time: 7:59 PM
 */

Marc Egger's avatar
Marc Egger committed
9
10
11
12
13
14
15
16
17
18
19
20
namespace IMATHUZH\Qfq\Core;

use IMATHUZH\Qfq\Core\Database\Database;
 
use IMATHUZH\Qfq\Core\Helper\HelperFormElement;
use IMATHUZH\Qfq\Core\Helper\Logger;
use IMATHUZH\Qfq\Core\Helper\Sanitize;
use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Store\Store;
use IMATHUZH\Qfq\Core\Store\Sip;
use IMATHUZH\Qfq\Core\Helper\HelperFile;
use IMATHUZH\Qfq\Core\Helper\OnArray;
21

22
23
24
25
/**
 * Class Save
 * @package qfq
 */
26
27
28
29
30
class Save {

    private $formSpec = array();  // copy of the loaded form
    private $feSpecAction = array(); // copy of all formElement.class='action' of the loaded form
    private $feSpecNative = array(); // copy of all formElement.class='native' of the loaded form
31
    private $feSpecNativeRaw = array(); // copy of all formElement.class='native' of the loaded form
32
33
34
35
36
37
38
39
    /**
     * @var null|Store
     */
    private $store = null;
    private $db = null;

    private $evaluate = null;

40
41
    private $qfqLogFilename = '';

42
43
44
45
    /**
     * @param array $formSpec
     * @param array $feSpecAction
     * @param array $feSpecNative
46
     * @param array $feSpecNativeRaw
Marc Egger's avatar
Marc Egger committed
47
48
49
50
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
51
     */
52
    public function __construct(array $formSpec, array $feSpecAction, array $feSpecNative, array $feSpecNativeRaw) {
53
54
55
        $this->formSpec = $formSpec;
        $this->feSpecAction = $feSpecAction;
        $this->feSpecNative = $feSpecNative;
56
        $this->feSpecNativeRaw = $feSpecNativeRaw;
57
        $this->store = Store::getInstance();
58
        $this->db = new Database($formSpec[F_DB_INDEX]);
59
        $this->evaluate = new Evaluate($this->store, $this->db);
60
61
62

        $this->qfqLogFilename = $this->store->getVar(SYSTEM_QFQ_LOG, STORE_SYSTEM);

63
64
65
    }

    /**
Carsten  Rose's avatar
Carsten Rose committed
66
     * Starts save process. Returns recordId.
67
     *
68
     * @return int
Marc Egger's avatar
Marc Egger committed
69
70
71
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
72
73
     */
    public function process() {
74
        $rc = 0;
75
76
77
78
79

        if ($this->formSpec['multiMode'] !== 'none') {

            $parentRecords = $this->db->sql($this->formSpec['multiSql']);
            foreach ($parentRecords as $row) {
80
                $this->store->setStore($row, STORE_PARENT_RECORD, true);
81
                $rc = $this->elements($row['_id']);
82
83
            }
        } else {
84
85
            $recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_ZERO);
            $rc = $this->elements($recordId);
86
        }
87
88

        return $rc;
89
90
    }

91
    /**
Carsten  Rose's avatar
Carsten Rose committed
92
93
     * Create empty FormElements based on templateGroups, for those who not already exist.
     *
94
     * @param array $formValues
Carsten  Rose's avatar
Carsten Rose committed
95
     *
96
     * @return array
Marc Egger's avatar
Marc Egger committed
97
     * @throws \UserFormException
98
99
100
101
102
     */
    private function createEmptyTemplateGroupElements(array $formValues) {

        foreach ($this->feSpecNative as $formElement) {

103
104
105
106
107
108
109
110
            switch ($formElement[FE_TYPE]) {
//                case FE_TYPE_EXTRA:
                case FE_TYPE_NOTE:
                case FE_TYPE_SUBRECORD:
                    continue 2;
                default:
                    break;
            }
111
            $feName = $formElement[FE_NAME];
112
            if (!isset($formValues[$feName]) && $this->isMemberOfTemplateGroup($formElement)) {
113
114
115
                $formValues[$feName] = $formElement[FE_VALUE];
            }
        }
Carsten  Rose's avatar
Carsten Rose committed
116

117
118
119
        return $formValues;
    }

120
    /**
121
122
123
124
125
     * Check if the current $formElement is member of a templateGroup.
     *
     * @param array $formElement
     * @param int $depth
     * @return bool
Marc Egger's avatar
Marc Egger committed
126
     * @throws \UserFormException
127
128
129
130
131
     */
    private function isMemberOfTemplateGroup(array $formElement, $depth = 0) {
        $depth++;

        if ($depth > 15) {
Marc Egger's avatar
Marc Egger committed
132
            throw new \UserFormException('FormElement nested too much (in each other - endless?): stop recursion', ERROR_FE_NESTED_TOO_MUCH);
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
        }

        if ($formElement[FE_TYPE] == FE_TYPE_TEMPLATE_GROUP) {
            return true;
        }

        if ($formElement[FE_ID_CONTAINER] == 0) {
            return false;
        }

        // Get the parent element
        $formElementArr = OnArray::filter($this->feSpecNativeRaw, FE_ID, $formElement[FE_ID_CONTAINER]);
        if (isset($formElementArr[0])) {
            return $this->isMemberOfTemplateGroup($formElementArr[0], $depth);
        }

        return false; // This should not be reached,
    }

    /**
     *
154
     * @param $feName
Carsten  Rose's avatar
Carsten Rose committed
155
     *
156
157
158
159
160
161
162
163
164
165
166
     * @return bool
     */
    private function isSetEmptyMeansNull($feName) {

        $fe = OnArray::filter($this->feSpecNative, FE_NAME, $feName);

        $flag = isset($fe[0][FE_EMPTY_MEANS_NULL]) && $fe[0][FE_EMPTY_MEANS_NULL] != '0';

        return $flag;
    }

167
    /**
Carsten  Rose's avatar
Carsten Rose committed
168
169
     * Build an array of all values which should be saved. Values must exist as a 'form value' as well as a regular
     * 'table column'.
Carsten  Rose's avatar
Carsten Rose committed
170
     *
171
     * @param $recordId
Carsten  Rose's avatar
Carsten Rose committed
172
     *
173
     * @return int   record id (in case of insert, it's different from $recordId)
Marc Egger's avatar
Marc Egger committed
174
175
176
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \DbException
177
178
     */
    public function elements($recordId) {
179
180
        $columnCreated = false;
        $columnModified = false;
Carsten  Rose's avatar
Carsten Rose committed
181

182
183
184
        $newValues = array();

        $tableColumns = array_keys($this->store->getStore(STORE_TABLE_COLUMN_TYPES));
185
        $formValues = $this->store->getStore(STORE_FORM);
186
        $formValues = $this->createEmptyTemplateGroupElements($formValues);
187
188
189

        // Iterate over all table.columns. Built an assoc array $newValues.
        foreach ($tableColumns AS $column) {
190

191
            // Never save a predefined 'id': autoincrement values will be given by database..
192
            if ($column === COLUMN_ID) {
193
                continue;
194
            }
195

Carsten  Rose's avatar
Upload:    
Carsten Rose committed
196
197
198
199
200
            // Skip Upload Elements: those will be processed later.
            if ($this->isColumnUploadField($column)) {
                continue;
            }

201
202
203
204
205
206
207
208
            if ($column === COLUMN_CREATED) {
                $columnCreated = true;
            }

            if ($column === COLUMN_MODIFIED) {
                $columnModified = true;
            }

209
210
            // Is there a value? Do not forget SIP values. Those do not have necessarily a FormElement.
            if (!isset($formValues[$column])) {
211
                continue;
212
213
            }

214
            $this->store->setVar(SYSTEM_FORM_ELEMENT, "Column: $column", STORE_SYSTEM);
215

216
217
218
219
220
221
            // Check if an empty string has to be converted to null.
            if (isset($formValues[$column]) && $formValues[$column] == '' && $this->isSetEmptyMeansNull($column)) {
                $formValues[$column] = null;
            } else {
                Support::setIfNotSet($formValues, $column);
            }
222
            $newValues[$column] = $formValues[$column];
223

224
225
        }

226
227
228
229
        if ($columnModified && !isset($newValues[COLUMN_MODIFIED])) {
            $newValues[COLUMN_MODIFIED] = date('YmdHis');
        }

230
        if ($recordId == 0) {
231
232
233
            if ($columnCreated && !isset($newValues[COLUMN_CREATED])) {
                $newValues[COLUMN_CREATED] = date('YmdHis');
            }
234
            $rc = $this->insertRecord($this->formSpec[F_TABLE_NAME], $newValues);
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
235

236
        } else {
237
            $this->updateRecord($this->formSpec[F_TABLE_NAME], $newValues, $recordId, $this->formSpec[F_PRIMARY_KEY]);
238
239
240
241
            $rc = $recordId;
        }

        return $rc;
242
243
    }

Carsten  Rose's avatar
Upload:    
Carsten Rose committed
244
245
246
247
248
249
    /*
     * Checks if there is a formElement with name '$feName' of type 'upload'
     *
     * @param $feName
     * @return bool
     */
250
251
252
253
    /**
     * @param $feName
     * @return bool
     */
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
254
255
256
257
258
259
    private function isColumnUploadField($feName) {

        foreach ($this->feSpecNative AS $formElement) {
            if ($formElement[FE_NAME] === $feName && $formElement[FE_TYPE] == 'upload')
                return true;
        }
Carsten  Rose's avatar
Carsten Rose committed
260

Carsten  Rose's avatar
Upload:    
Carsten Rose committed
261
262
263
264
265
266
        return false;
    }

    /**
     * Insert new record in table $this->formSpec['tableName'].
     *
267
     * @param $tableName
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
268
     * @param array $values
Carsten  Rose's avatar
Carsten Rose committed
269
     *
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
270
     * @return int  last insert id
Marc Egger's avatar
Marc Egger committed
271
272
273
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
274
275
276
277
278
279
280
281
282
283
     */
    public function insertRecord($tableName, array $values) {

        if (count($values) === 0)
            return 0; // nothing to write, last insert id=0

        $paramList = str_repeat('?, ', count($values));
        $paramList = substr($paramList, 0, strlen($paramList) - 2);
        $columnList = '`' . implode('`, `', array_keys($values)) . '`';

284
        $sql = "INSERT INTO $tableName ( " . $columnList . " ) VALUES ( " . $paramList . ' )';
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
285
286
287
288
289
290
291
292

        $rc = $this->db->sql($sql, ROW_REGULAR, array_values($values));

        return $rc;
    }

    /**
     * @param string $tableName
Carsten  Rose's avatar
Carsten Rose committed
293
294
     * @param array $values
     * @param int $recordId
295
     * @param string $primaryKey
Carsten  Rose's avatar
Carsten Rose committed
296
     *
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
297
     * @return bool|int     false if $values is empty, else affectedrows
Marc Egger's avatar
Marc Egger committed
298
299
300
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
301
     */
302
    public function updateRecord($tableName, array $values, $recordId, $primaryKey = F_PRIMARY_KEY_DEFAULT) {
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
303
304
305
306

        if (count($values) === 0)
            return 0; // nothing to write, 0 rows affected

307
        if ($recordId === 0) {
Marc Egger's avatar
Marc Egger committed
308
            throw new \CodeException('RecordId=0 - this is not possible for update.', ERROR_RECORDID_0_FORBIDDEN);
309
        }
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
310
311
312
313
314
315
316
317

        $sql = 'UPDATE `' . $tableName . '` SET ';

        foreach ($values as $column => $value) {

            $sql .= '`' . $column . '` = ?, ';
        }

318
        $sql = substr($sql, 0, strlen($sql) - 2) . " WHERE $primaryKey = ?";
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
319
320
321
322
323
324
325
        $values[] = $recordId;

        $rc = $this->db->sql($sql, ROW_REGULAR, array_values($values));

        return $rc;
    }

326
    /**
Carsten  Rose's avatar
Carsten Rose committed
327
328
     * Process all Upload Formelements for the given $recordId. After processing &$formValues will be updated with the
     * final filenames.
329
330
331
332
333
334
335
336
     *
     * Constellation: # FILE OLD   FILE NEW     FILESIZE
     *                1 none       none
     *                2 none       new
     *                3 exist      no change
     *                4 delete     none
     *                5 delete     new
     *
337
     * @param $recordId
Marc Egger's avatar
Marc Egger committed
338
339
340
341
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
342
343
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
344
     */
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
345
346
347
348
    public function processAllUploads($recordId) {

        $sip = new Sip(false);
        $newValues = array();
349
        $vars = array();
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
350
351

        $formValues = $this->store->getStore(STORE_FORM);
352
        $primaryRecord = $this->store->getStore(STORE_RECORD); // necessary to check if the current formElement exist as a column of the primary table.
353
354
355

        foreach ($this->feSpecNative AS $formElement) {
            // skip non upload formElements
356
            if ($formElement[FE_TYPE] != FE_TYPE_UPLOAD) {
357
358
359
                continue;
            }

360
361
362
363
            // Preparation for Log, Debug
            $this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($formElement), STORE_SYSTEM);
            $this->store->setVar(SYSTEM_FORM_ELEMENT_ID, $formElement[FE_ID], STORE_SYSTEM);

364
            $formElement = HelperFormElement::initUploadFormElement($formElement);
365
            if (isset($formElement[FE_FILL_STORE_VAR])) {
366
                $formElement[FE_FILL_STORE_VAR] = $this->evaluate->parse($formElement[FE_FILL_STORE_VAR], ROW_EXPECT_0_1);
367
                $this->store->appendToStore($formElement[FE_FILL_STORE_VAR], STORE_VAR);
368
            }
369

370
            $column = $formElement[FE_NAME];
371
            $pathFileName = $this->doUpload($formElement, ($formValues[$column] ?? ''), $sip, $modeUpload);
372

373
374
            if ($modeUpload == UPLOAD_MODE_DELETEOLD && $pathFileName == '') {
                $pathFileNameTmp = '';  // see '4'
375
376
            } else {
                if (empty($pathFileName)) {
377
                    $pathFileNameTmp = $primaryRecord[$column] ?? ''; // see '3'. Attention: in case of Advanced Upload, $primaryRecord[$column] does not exist.
378
379
380
381
382
                } else {
                    $pathFileNameTmp = $pathFileName; // see '1,2,5'
                }
            }

383
            // Get latest file information
384
            if ($pathFileNameTmp == '') {
385
386
387
                // No new upload and no existing: take care to remove previous upload file statistics.
                $this->store->unsetVar(VAR_FILE_MIME_TYPE, STORE_VAR);
                $this->store->unsetVar(VAR_FILE_SIZE, STORE_VAR);
388
389
                $vars[VAR_FILE_SIZE] = 0;
                $vars[VAR_FILE_MIME_TYPE] = '';
390
391
392
393
394
            } else {
                $vars = HelperFile::getFileStat($pathFileNameTmp);
                $this->store->appendToStore($vars, STORE_VAR);
            }

395
396
397
            // If given: fire a sqlBefore query
            $this->evaluate->parse($formElement[FE_SQL_BEFORE]);

398
            // Upload Type: Simple or Advanced
399
400
401
            // If (isset($primaryRecord[$column])) { - see #5048 - isset does not deal correctly with NULL!
            if (array_key_exists($column, $primaryRecord)) {
                // 'Simple Upload': no special action needed, just process the current (maybe modified) value.
402
403
                if ($pathFileName !== false) {
                    $newValues[$column] = $pathFileName;
404
405
406
407
408
409
410
411

                    if (isset($primaryRecord[COLUMN_FILE_SIZE])) {
                        $newValues[COLUMN_FILE_SIZE] = $vars[VAR_FILE_SIZE];
                    }

                    if (isset($primaryRecord[COLUMN_MIME_TYPE])) {
                        $newValues[COLUMN_MIME_TYPE] = $vars[VAR_FILE_MIME_TYPE];
                    }
412
                }
413
414
            } elseif (isset($formElement[FE_IMPORT_TO_TABLE]) && !isset($formElement[FE_SLAVE_ID])) {
                // Excel import on nonexisting column -> no upload
415
416
            } else {
                // 'Advanced Upload'
417
                $this->doUploadSlave($formElement, $modeUpload);
418
            }
419
420
421
422

            // If given: fire a sqlAfter query
            $this->evaluate->parse($formElement[FE_SQL_AFTER]);

423
        }
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
424

425
        // Only used in 'Simple Upload'
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
426
        if (count($newValues) > 0) {
427
            $this->updateRecord($this->formSpec[F_TABLE_NAME], $newValues, $recordId, $this->formSpec[F_PRIMARY_KEY]);
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
428
        }
429
430
    }

431
    /**
432
433
     * Process all Upload FormElements for the given $recordId.
     * After processing, &$formValues will be updated with the final filename.
434
     *
Marc Egger's avatar
Marc Egger committed
435
436
     * @throws \CodeException
     * @throws \UserFormException
437
438
439
440
441
442
443
444
445
446
447
448
449
     */
    public function processAllImageCutFE() {

        foreach ($this->feSpecNative AS $formElement) {
            // skip non upload formElements
            if ($formElement[FE_TYPE] != FE_TYPE_IMAGE_CUT) {
                continue;
            }

            $this->extractImageDataReplaceFile($formElement);
        }
    }

450
451
452
453
454
455
456
457

    /**
     * Iterates over all FE and checks all 'required' (mode & modeSql) FE.
     * If a required FE is empty, throw an exception.
     * Take care to remove all FE with modeSql='hidden'.
     *
     * Typically, the browser does not allow a submit if a required field is empty.
     *
Marc Egger's avatar
Marc Egger committed
458
459
460
461
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
462
463
464
465
466
467
468
469
470
     */
    public function checkRequiredHidden() {

        $requiredOff = ($this->store->getVar(F_MODE_GLOBAL, STORE_SIP) == F_MODE_REQUIRED_OFF);

        $clientValues = $this->store::getStore(STORE_FORM);

        foreach ($this->feSpecNative AS $key => $formElement) {

471
472
473
474
475
            // Do not check retype slave FE.
            if (isset($formElement[FE_RETYPE_SOURCE_NAME])) {
                continue;
            }

476
477
            $this->store->setVar(SYSTEM_FORM_ELEMENT, "Column: " . $formElement[FE_NAME], STORE_SYSTEM);

478
479
480
481
482
483
484
            if (empty($formElement[FE_MODE_SQL])) {
                $mode = $formElement[FE_MODE];
            } else {
                $mode = $this->evaluate->parse($formElement[FE_MODE_SQL]);
                $this->feSpecNative[$key][FE_MODE_SQL] = $mode;
            }

485
486
487
            if (isset($formElement[FE_ACCEPT_ZERO_AS_REQUIRED]) && $formElement[FE_ACCEPT_ZERO_AS_REQUIRED] != '0' &&
                isset($clientValues[$formElement[FE_NAME]]) && $clientValues[$formElement[FE_NAME]] == '0') {
                $mode = 'fake'; // The next if() should never be true.
488
489
            }

490
            if (!$requiredOff && $mode == FE_MODE_REQUIRED && empty($clientValues[$formElement[FE_NAME]])) {
Marc Egger's avatar
Marc Egger committed
491
                throw new \UserFormException("Missing required value: " . $formElement[FE_LABEL], ERROR_REQUIRED_VALUE_EMPTY);
492
493
494
495
496
497
498
499
500
            }

            if ($mode == FE_MODE_HIDDEN) {
                // Removing the value from the store, forces that the value won't be stored.
                $this->store::unsetVar($formElement[FE_NAME], STORE_FORM);
            }
        }
    }

501
502
503
    /**
     *
     * @param array $formElement
Marc Egger's avatar
Marc Egger committed
504
505
     * @throws \CodeException
     * @throws \UserFormException
506
507
508
509
510
511
     */
    private function extractImageDataReplaceFile(array $formElement) {

        // Take care the necessary target directories exist.
        $cwd = getcwd();
        $sitePath = $this->store->getVar(SYSTEM_SITE_PATH, STORE_SYSTEM);
512
        if ($cwd === false || $sitePath === false || !HelperFile::chdir($sitePath)) {
Marc Egger's avatar
Marc Egger committed
513
            throw new \UserFormException(
Marc Egger's avatar
Marc Egger committed
514
                json_encode([ERROR_MESSAGE_TO_USER => 'getcwd() failed or SITE_PATH undefined or chdir() failed', ERROR_MESSAGE_TO_DEVELOPER => "getcwd() failed or SITE_PATH undefined or chdir('$sitePath') failed."]),
515
                ERROR_IO_CHDIR);
516
517
        }

518
        // Get original pathFileName
519
520
521
        $field = HelperFormElement::AppendFormElementNameImageCut($formElement);
        $pathFileName = $this->store->getVar($field, STORE_SIP);
        if ($pathFileName == '' || !file_exists($pathFileName)) {
Marc Egger's avatar
Marc Egger committed
522
            throw new \UserFormException(
Marc Egger's avatar
Marc Egger committed
523
                json_encode([ERROR_MESSAGE_TO_USER => 'Empty file or file not found', ERROR_MESSAGE_TO_DEVELOPER => 'Empty file or file not found: ' . $pathFileName]),
524
                ERROR_IO_FILE_NOT_FOUND);
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
        }

        // 'data:image/png;base64,AAAFBfj42Pj4...';
        $data = $this->store->getVar($formElement[FE_NAME], STORE_FORM, SANITIZE_ALLOW_ALLBUT);
        // Replace data by pathFileName (that is stored in DB).
        $this->store->setVar($formElement[FE_NAME], $pathFileName, STORE_FORM, true);

        if ($data == '') {
            return; // Nothing to do
        }

        // Split base64 encoded image: 'data:image/png;base64,AAAFBfj42Pj4...'
        list($type, $imageData) = explode(';', $data, 2); // $type= 'data:image/png;', $imageData='base64,AAAFBfj42Pj4...'
        list(, $extension) = explode('/', $type); // $type='png'
        list(, $imageData) = explode(',', $imageData); // $imageData='AAAFBfj42Pj4...'

        // If undefined: set default. BTW: Defined and empty means "no original".
        if (!isset($formElement[FE_IMAGE_CUT_KEEP_ORIGINAL])) {
            $formElement[FE_IMAGE_CUT_KEEP_ORIGINAL] = FE_IMAGE_CUT_ORIGINAL_EXTENSION;
        }
        $extSave = $formElement[FE_IMAGE_CUT_KEEP_ORIGINAL];

        $pathParts = pathinfo($pathFileName);
        // Keep the original file?
        if ($extSave != '') {

            // In case the leading '.' is missing.
            if ($extSave[0] != ".") {
                $extSave = '.' . $extSave;
            }

            // Check if there is already an original - don't create an additional one.
            if (!file_exists($pathFileName . $extSave) &&
                !file_exists($pathParts['dirname'] . $pathParts['filename'] . $extSave)
            ) {
560
                HelperFile::rename($pathFileName, $pathFileName . $extSave);
561
562
563
564
565
566
567
568
            }
        }

        if ($extension != $pathParts['extension']) {
            $pathFileName .= "." . $extension;
        }

        if (false === file_put_contents($pathFileName, base64_decode($imageData))) {
Marc Egger's avatar
Marc Egger committed
569
            throw new \UserFormException(
Marc Egger's avatar
Marc Egger committed
570
                json_encode([ERROR_MESSAGE_TO_USER => 'Write new image failed', ERROR_MESSAGE_TO_DEVELOPER => "Write new image failed: $pathFileName"]),
571
                ERROR_IO_WRITE);
572
573
574
575
576
        }

        $this->store->setVar($formElement[FE_NAME], $pathFileName, STORE_FORM, true);
    }

577
    /**
578
579
580
     * Process upload for the given Formelement. If necessary, delete a previous uploaded file.
     * Calculate the final path/filename and move the file to the new location.
     *
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
581
582
     * Check also: doc/CODING.md
     *
Carsten  Rose's avatar
Carsten Rose committed
583
584
585
586
     * @param array $formElement FormElement 'upload'
     * @param string $sipUpload SIP
     * @param Sip $sip
     * @param string $modeUpload UPLOAD_MODE_UNCHANGED | UPLOAD_MODE_NEW | UPLOAD_MODE_DELETEOLD |
Carsten  Rose's avatar
Carsten Rose committed
587
588
     *                            UPLOAD_MODE_DELETEOLD_NEW
     *
589
     * @return false|string New pathFilename or false on error
Marc Egger's avatar
Marc Egger committed
590
591
592
593
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
594
595
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
596
597
     * @internal param $recordId
     */
598
599
600
    private function doUpload($formElement, $sipUpload, Sip $sip, &$modeUpload) {
        $flagDelete = false;
        $modeUpload = UPLOAD_MODE_UNCHANGED;
601

602
        // Status information about upload file
603
604
605
606
607
        $statusUpload = $this->store->getVar($sipUpload, STORE_EXTRA);
        if ($statusUpload === false) {
            return false;
        }

608
        if (isset($formElement[FE_IMPORT_TO_TABLE]) && isset($statusUpload[FILES_TMP_NAME])) {
609
            // Import
610
            $tmpFile = Support::extendFilename($statusUpload[FILES_TMP_NAME], UPLOAD_CACHED);
611
612
            $this->doImport($formElement, $tmpFile);
        }
613

614
        // Upload - Take care the necessary target directories exist.
615
616
        $cwd = getcwd();
        $sitePath = $this->store->getVar(SYSTEM_SITE_PATH, STORE_SYSTEM);
617
        if ($cwd === false || $sitePath === false || !HelperFile::chdir($sitePath)) {
Marc Egger's avatar
Marc Egger committed
618
            throw new \UserFormException(
Marc Egger's avatar
Marc Egger committed
619
                json_encode([ERROR_MESSAGE_TO_USER => 'getcwd() failed or SITE_PATH undefined or chdir() failed', ERROR_MESSAGE_TO_DEVELOPER => "getcwd() failed or SITE_PATH undefined or chdir('$sitePath') failed."]),
620
                ERROR_IO_CHDIR);
621
        }
622

623
624
625
626
627
        // Delete existing old file.
        if (isset($statusUpload[FILES_FLAG_DELETE]) && $statusUpload[FILES_FLAG_DELETE] == '1') {
            $arr = $sip->getVarsFromSip($sipUpload);
            $oldFile = $arr[EXISTING_PATH_FILE_NAME];
            if (file_exists($oldFile)) {
628
                //TODO: it might be possible to delete a file, which is referenced by another record - a check would be nice.
629
                HelperFile::unlink($oldFile, $this->qfqLogFilename);
630
631
632
633
634
635
636
637
638
639
640
            }
            $flagDelete = ($oldFile != '');
        }

        // Set $modeUpload
        if (isset($statusUpload[FILES_TMP_NAME]) && $statusUpload[FILES_TMP_NAME] != '') {
            $modeUpload = $flagDelete ? UPLOAD_MODE_DELETEOLD_NEW : UPLOAD_MODE_NEW;
        } else {
            $modeUpload = $flagDelete ? UPLOAD_MODE_DELETEOLD : UPLOAD_MODE_UNCHANGED;
        }

641
642
        Logger::logMessageWithPrefix(UPLOAD_LOG_PREFIX . ': modeUpload= ' . $modeUpload, $this->qfqLogFilename);

643
        // skip uploading the file, if this is an import without a specified file destination
644
645
        if (!isset($formElement[FE_IMPORT_TO_TABLE]) || isset($formElement[FE_FILE_DESTINATION])) {
            $pathFileName = $this->copyUploadFile($formElement, $statusUpload);
646
647
648
649

            $msg = UPLOAD_LOG_PREFIX . ': ';
            $msg .= ($pathFileName == '') ? 'Remove old upload / no new upload' : 'File "' . $statusUpload[FILES_TMP_NAME] . '" >> "' . $pathFileName . '"';
            Logger::logMessageWithPrefix($msg, $this->qfqLogFilename);
650
651
        }

652
        HelperFile::chdir($cwd);
653
654
655
656
657
658
659
660
661

        // Delete current used uniq SIP
        $this->store->setVar($sipUpload, array(), STORE_EXTRA);

        return $pathFileName;
    }

    /**
     * @param $formElement
662
     * @param $fileName
Marc Egger's avatar
Marc Egger committed
663
664
665
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
666
667
668
669
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     */
    private function doImport($formElement, $fileName) {
670
        $importNamedSheetsOnly = array();
671

672
        Support::setIfNotSet($formElement, FE_IMPORT_TYPE, FE_IMPORT_TYPE_AUTO);
673

674
675
676
677
        if (!empty($formElement[FE_IMPORT_NAMED_SHEETS_ONLY])) {
            $importNamedSheetsOnly = explode(',', $formElement[FE_IMPORT_NAMED_SHEETS_ONLY]);
        }

Carsten  Rose's avatar
Carsten Rose committed
678
        // Check for keywords which needs an explicit given document type.
679
680
681
682
683
        if ($formElement[FE_IMPORT_TYPE] === FE_IMPORT_TYPE_AUTO) {

            $list = [FE_IMPORT_LIST_SHEET_NAMES, FE_IMPORT_READ_DATA_ONLY, FE_IMPORT_LIST_SHEET_NAMES];
            foreach ($list as $token) {
                if (isset($formElement[$token])) {
Marc Egger's avatar
Marc Egger committed
684
                    throw new \UserFormException('If ' . $token .
685
686
687
688
689
                        ' is given, an explicit document type (like ' . FE_IMPORT_TYPE . '=xlsx) should be set.', ERROR_IMPORT_MISSING_EXPLICIT_TYPE);
                }
            }
        }

690
691
692
693
694
695
696
697
698
699
700
        switch ($formElement[FE_IMPORT_TYPE]) {
            case FE_IMPORT_TYPE_AUTO:
                $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($fileName);
                break;

            case FE_IMPORT_TYPE_XLS:
            case FE_IMPORT_TYPE_XLSX:
            case FE_IMPORT_TYPE_CSV:
            case FE_IMPORT_TYPE_ODS:
                $inputFileType = ucfirst($formElement[FE_IMPORT_TYPE]);
                $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType);
701
702
703
704
705
706
707
708
709
710
711
712
713

                // setReadDataOnly
                if (($formElement[FE_IMPORT_READ_DATA_ONLY] ?? '0') != '0') {
                    $reader->setReadDataOnly(true);
                }

                // setLoadSheetsOnly
                if (!empty ($importNamedSheetsOnly)) {
                    $reader->setLoadSheetsOnly($importNamedSheetsOnly);
                }

                if (($formElement[FE_IMPORT_LIST_SHEET_NAMES] ?? '0') != '0') {
                    $sheetNames = $reader->listWorksheetNames($fileName);
Marc Egger's avatar
Marc Egger committed
714
                    throw new \UserFormException("Worksheets: " . implode(', ', $sheetNames),
715
716
717
                        ERROR_IMPORT_LIST_SHEET_NAMES);
                }

718
719
720
721
                $spreadsheet = $reader->load($fileName);
                break;

            default:
Marc Egger's avatar
Marc Egger committed
722
                throw new \UserFormException("Unknown Excel import type: '" . $formElement[FE_IMPORT_TYPE] . "'.",
723
724
725
726
                    ERROR_UNKNOWN_EXCEL_IMPORT_TYPE);
        }

        $tableName = $formElement[FE_IMPORT_TO_TABLE];
727
        $regions = OnArray::trimArray(explode('|', $formElement[FE_IMPORT_REGION] ?? ''));
728
        $columnNames = OnArray::trimArray(explode(',', $formElement[FE_IMPORT_TO_COLUMNS] ?? ''));
729
730
731
732
        $importMode = $formElement[FE_IMPORT_MODE] ?? FE_IMPORT_MODE_APPEND;

        foreach ($regions as $region) {
            // region: tab, startColumn, startRow, endColumn, endRow
733
            $region = OnArray::trimArray(explode(',', $region));
734
735
736
737
738
739
740
741
742
743
744
745
746
747
            $tab = 1;
            if (!empty($region[0])) {
                $tab = $region[0];
            }

            try {
                if (is_numeric($tab)) {
                    $worksheet = $spreadsheet->getSheet($tab - 1); // 0-based
                } else {
                    $worksheet = $spreadsheet->getSheetByName($tab);
                    if ($worksheet === null) {
                        throw new \PhpOffice\PhpSpreadsheet\Exception(
                            "No sheet with the name '$tab' could be found."
                        );
748
749
                    }
                }
750
            } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
Marc Egger's avatar
Marc Egger committed
751
                throw new \UserFormException($e->getMessage());
752
            }
753

754
755
756
757
758
759
760
761
762
            // Set up requested region
            $columnStart = '1';
            $columnEnd = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($worksheet->getHighestColumn());
            $rowStart = 1;
            $rowEnd = $worksheet->getHighestRow();
            if (!empty($region[1])) { // startColumn
                if (!is_numeric($region[1])) $region[1] = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($region[1]);
                if ($region[1] >= $columnStart && $region[1] <= $columnEnd) {
                    $columnStart = $region[1];
763
764
                }
            }
765
766
767
768
769
            if (!empty($region[3])) { // endColumn
                if (!is_numeric($region[3])) $region[3] = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($region[3]);
                if ($region[3] >= $columnStart && $region[3] <= $columnEnd) {
                    $columnEnd = $region[3];
                }
770
            }
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
            if (!empty($region[2]) && $region[2] >= $rowStart && $region[2] <= $rowEnd) {
                $rowStart = $region[2];
            }
            if (!empty($region[4]) && $region[4] >= $rowStart && $region[4] <= $rowEnd) {
                $rowEnd = $region[4];
            }
            // Read the specified region
            $rangeStr = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($columnStart) . $rowStart . ':' .
                \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($columnEnd) . $rowEnd;
            $worksheetData = $worksheet->rangeToArray($rangeStr, '', true, false);

            $columnDefinitionArr = [];
            $columnListArr = [];
            for ($column = $columnStart; $column <= $columnEnd; ++$column) {
                if (!empty($columnNames[$column - $columnStart])) {
                    $columnName = $columnNames[$column - $columnStart];
                } else {
                    $columnName = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($column);
789
                }
790
791
                $columnDefinitionArr[] = "`$columnName`   TEXT       NOT NULL  DEFAULT ''";
                $columnListArr[] = "$columnName";
792
793
            }

794
795
796
797
798
799
800
801
802
803
804
805
806
            // SQL time!
            $createTableSql = "CREATE TABLE IF NOT EXISTS `$tableName` (" .
                "`id`        INT(11)    NOT NULL  AUTO_INCREMENT," .
                implode(', ', $columnDefinitionArr) . ',' .
                "`modified`  TIMESTAMP  NOT NULL  DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," .
                "`created`   DATETIME   NOT NULL  DEFAULT CURRENT_TIMESTAMP," .
                "PRIMARY KEY (`id`) )" .
                "ENGINE = InnoDB  DEFAULT CHARSET = utf8 AUTO_INCREMENT = 0;";
            $this->db->sql($createTableSql);

            if ($importMode === FE_IMPORT_MODE_REPLACE) {
                $this->db->sql("TRUNCATE $tableName");
                $importMode = FE_IMPORT_MODE_APPEND;
807
            }
808

809
810
811
812
813
814
815
            // Import the data
            foreach ($worksheetData AS $rowIndex => $row) {
                $columnList = implode(',', $columnListArr);
                $paramPlaceholders = str_repeat('?,', count($worksheetData[0]) - 1) . '?';
                $insertSql = "INSERT INTO `$tableName` ($columnList) VALUES ($paramPlaceholders)";
                $this->db->sql($insertSql, ROW_REGULAR, $row);
            }
816
        }
817
818
819
    }

    /**
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
820
821
822
823
     * Copy uploaded file from temporary location to final location.
     *
     * Check also: doc/CODING.md
     *
824
825
826
     * @param array $formElement
     * @param array $statusUpload
     * @return array|mixed|null|string
Marc Egger's avatar
Marc Egger committed
827
828
829
830
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
831
     */
832
    private function copyUploadFile(array $formElement, array $statusUpload) {
833
834
        $pathFileName = '';

Carsten  Rose's avatar
Upload:    
Carsten Rose committed
835
        if (!isset($statusUpload[FILES_TMP_NAME]) || $statusUpload[FILES_TMP_NAME] === '') {
836
837
838
839
            // nothing to upload: e.g. user has deleted a previous uploaded file.
            return '';
        }

840
841
        $srcFile = Support::extendFilename($statusUpload[FILES_TMP_NAME], UPLOAD_CACHED);

842
        if (isset($formElement[FE_FILE_DESTINATION])) {
843

844
            // Provide variable 'filename'. Might be substituted in $formElement[FE_PATH_FILE_NAME].
845
            $origFilename = Sanitize::safeFilename($statusUpload[FILES_NAME]);
846
            $this->store->appendToStore(HelperFile::pathinfo($origFilename), STORE_VAR);
847

848
            $pathFileName = $this->evaluate->parse($formElement[FE_FILE_DESTINATION]);
849
            $pathFileName = Sanitize::safeFilename($pathFileName, false, true); // Dynamically calculated pathFileName might contain invalid characters.
850
851
852

            // Saved in store for later use during 'Advanced Upload'-post processing
            $this->store->setVar(VAR_FILE_DESTINATION, $pathFileName, STORE_VAR);
853
854
        }

855
        if ($pathFileName === '') {
Marc Egger's avatar
Marc Egger committed
856
            throw new \UserFormException("Upload failed, no target '" . FE_FILE_DESTINATION . "' specified.", ERROR_NO_TARGET_PATH_FILE_NAME);
857
858
        }

859
860
861
862
863
864
865
        // If given, get chmodDir. Needs to be prefixed with a 0 (=octal) - it should not be quoted! Symbolic mode is not allowed. E.g.: 0660, or 01777
        if (empty($formElement[FE_FILE_CHMOD_DIR])) {
            $chmodDir = false;
        } else {
            $chmodDir = octdec($formElement[FE_FILE_CHMOD_DIR]);
        }

866
        $overwrite = isset($formElement[FE_FILE_REPLACE_MODE]) && $formElement[FE_FILE_REPLACE_MODE] == FE_FILE_REPLACE_MODE_ALWAYS;
867
        Support::moveFile($srcFile, $pathFileName, $overwrite, $chmodDir);
868

869
        // get chmodFile
870
871
        if (empty($formElement[FE_FILE_CHMOD_FILE])) {
            $chmodFile = false;
872
        } else {
873
            $chmodFile = octdec($formElement[FE_FILE_CHMOD_FILE]);
874
875
        }

876
        $this->autoOrient($formElement, $pathFileName);
877
        HelperFile::chmod($pathFileName, $chmodFile);
878

879
        $this->splitUpload($formElement, $pathFileName, $chmodFile, $statusUpload);
880

881
882
        return $pathFileName;
    }
883

884
885
    /**
     * If fe['autoOrient'] is given and the MimeType corresponds to fe['autoOrientMimeType']: the given {{pathFileName:V}} will be converted.
886
     * ImageMagick 'convert' seems to do a better job than GraficsMagick (Orientation is stable even if multiple times applied).
887
888
889
     *
     * @param array $formElement
     * @param $pathFileName
Marc Egger's avatar
Marc Egger committed
890
891
892
893
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
894
     */
895
    private function autoOrient(array $formElement, $pathFileName) {
896
897

        // 'autoOrient' wished?
898
        if (!isset($formElement[FE_FILE_AUTO_ORIENT]) || $formElement[FE_FILE_AUTO_ORIENT] == '0') {
899
900
901
902
            return; // No
        }

        // Upload has matching MimeType?
903
        $mimeTypeList = empty($formElement[FE_FILE_AUTO_ORIENT_MIME_TYPE]) ? 'image/jpeg,image/png,image/tiff' : $formElement[FE_FILE_AUTO_ORIENT_MIME_TYPE];
904
        if (!HelperFile::checkFileType($pathFileName, $pathFileName, $mimeTypeList)) {
905
906
907
908
909
910
911
912
            return;
        }

        // Get 'autoOrient' command
        $cmd = empty($formElement[FE_FILE_AUTO_ORIENT_CMD]) ? FE_FILE_AUTO_ORIENT_CMD_DEFAULT : $formElement[FE_FILE_AUTO_ORIENT_CMD];
        $cmd = $this->evaluate->parse($cmd);

        // Do 'autoOrient' command
913
914
        $output = Support::qfqExec($cmd, $rc);
        if ($rc != 0) {
Marc Egger's avatar
Marc Egger committed
915
            throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => 'copy failed', ERROR_MESSAGE_TO_DEVELOPER => "[cmd=$cmd]$output"]), ERROR_IO_COPY);
916
        }
917
918
    }

919
    /**
Carsten  Rose's avatar
Carsten Rose committed
920
921
922
923
924
925
926
     * Check's if the file $pathFileName should be splitted in one file per page. If no: do nothing and return.
     * The only possible split target file format is 'svg': fileSplit=svg.
     * The splitted files will be saved under fileDestinationSplit=some/path/to/file.%02d.svg. A printf style token,
     * like '%02d', is needed to create distinguished filename's. See 'man pdf2svg' for further details.
     * For every created file, a record in table 'Split' is created (see splitSvg() ), storing the pathFileName of the
     * current page/file.
     *
927
     * @param array $formElement
928
929
     * @param string $pathFileName
     * @param int $chmod
930
     * @param array $statusUpload
Marc Egger's avatar
Marc Egger committed
931
932
933
934
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
935
     */
936
    private function splitUpload(array $formElement, $pathFileName, $chmod, array $statusUpload) {
937

938

939
940
941
942
943
944
945
946
947
        // Should the file be split?
        if (empty($formElement[FE_FILE_SPLIT])) {
            return;
        }

        // Is it technically possible to split the file?
        // BTW: the $statusUpload['type'] delivers 'octetstream' for PDF files who do not have the extension '.pdf' - therefore get mimetype again.
        $mime = HelperFile::getMimeType($pathFileName);
        if (false === strpos($mime, MIME_TYPE_SPLIT_CAPABLE) && $statusUpload[FILES_TYPE] != MIME_TYPE_SPLIT_CAPABLE) {
948
949
950
            return;
        }

951
952
953
954
        $fileDestinationSplit = $this->evaluate->parse($formElement[FE_FILE_DESTINATION_SPLIT] ?? '');
        $fileSplitType = $this->evaluate->parse($formElement[FE_FILE_SPLIT] ?? '');
        $fileSplitTypeOptions = $this->evaluate->parse($formElement[FE_FILE_SPLIT_OPTIONS] ?? '');
        $fileSplitTableName = $this->evaluate->parse($formElement[FE_FILE_SPLIT_TABLE_NAME] ?? '');
955
956
957
958
959

        if (empty($fileSplitTableName)) {
            $fileSplitTableName = $this->formSpec[F_TABLE_NAME];
        }

960
961
962
        if ($fileDestinationSplit == '') {
            $ext = ($fileSplitType == FE_FILE_SPLIT_SVG) ? '.%02d.svg' : '.jpg';
            $fileDestinationSplit = $pathFileName . '.split/split' . $ext;
963
964
        }

Carsten  Rose's avatar
Carsten Rose committed
965
        HelperFile::mkDirParent($fileDestinationSplit);
966
967
968
969
970

        // Save CWD
        $cwd = getcwd();

        // Create temporary directory
971
        $tempDir = HelperFile::mktempdir();
972
        $newSrc = $tempDir . DIRECTORY_SEPARATOR . QFQ_TEMP_SOURCE;
973
        HelperFile::copy($pathFileName, $newSrc);
974
975
976
977

        // Split destination.
        $pathParts = pathinfo($fileDestinationSplit);
        if (empty($pathParts['filename']) || empty($pathParts['basename'])) {
Marc Egger's avatar
Marc Egger committed
978
            throw new \UserFormException('Missing filename in ' . FE_FILE_DESTINATION_SPLIT, ERROR_MISSING_FILE_NAME);
979
980
981
982
983
        }

        // Extract filename from destination directory.
        $fileNameDest = $pathParts['basename'];

984
985
986
987
988
        switch ($fileSplitType) {
            case FE_FILE_SPLIT_SVG:
                $cmd = 'pdf2svg "' . $newSrc . '" "' . $fileNameDest . '" all';
                break;
            case FE_FILE_SPLIT_JPEG:
989
990
991
992
                if ($fileSplitTypeOptions == '') {
                    $fileSplitTypeOptions = FE_FILE_SPLIT_OPTIONS_JPEG;
                }
                $cmd = "convert $fileSplitTypeOptions '$newSrc' '$fileNameDest'";
993
994
                break;
            default:
Marc Egger's avatar
Marc Egger committed
995
                throw new \UserFormException("Unknown 'fileSplit' type: " . $formElement[FE_FILE_SPLIT], ERROR_UNKNOWN_TOKEN);
996
997
        }

998
        // Split PDF
999
        HelperFile::chdir($tempDir);
1000
        $output = Support::qfqExec($cmd, $rc);
1001
1002
        HelperFile::chdir($cwd);

1003
        if ($rc != 0) {
Marc Egger's avatar
Marc Egger committed
1004
            throw new \UserFormException(
Marc Egger's avatar
Marc Egger committed
1005
                json_encode([ERROR_MESSAGE_TO_USER => 'pdf2svg failed', ERROR_MESSAGE_TO_DEVELOPER => "[$cwd][cmd=$cmd]$output"]),
1006
                ERROR_PDF2SVG);
1007
        }
1008

1009
        $files = Helperfile::getSplitFileNames($tempDir);
1010

1011
1012
1013
1014
1015
        $xId = $this->store->getVar(COLUMN_ID, STORE_RECORD);

        // Clean optional existing old split records and files from further uploads.
        $this->db->deleteSplitFileAndRecord($xId, $fileSplitTableName);

1016
1017
        // IM 'convert' will produce files <file>-1.jpg <file>-10.jpg ... - bring them in natural sort order
        natsort($files);
1018
1019
1020

        // Create DB records according to the extracted filenames.
        $tableName = TABLE_NAME_SPLIT;
1021
1022
1023
        $sql = "INSERT INTO $tableName (`tableName`, `xId`, `pathFilename`) VALUES (?,?,?)";

        // 1) Move split files to final location. 2) Created records to reference each split file.
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
        foreach ($files as $file) {

            if ($file == '.' || $file == '..' || $file == QFQ_TEMP_SOURCE) {
                continue;
            }

            if (!empty($pathParts['dirname'])) {
                $fileDestination = $pathParts['dirname'] . '/' . $file;
            } else {
                $fileDestination = $file;
            }

1036
            Support::moveFile($tempDir . DIRECTORY_SEPARATOR . $file, Support::joinPath($cwd, $fileDestination), true);
1037
            HelperFile::chmod($fileDestination, $chmod);
1038
1039

            // Insert records.
1040
            $this->db->sql($sql, ROW_REGULAR, [$fileSplitTableName, $xId, $fileDestination]);
1041
1042
1043
        }

        // Remove duplicated source
1044
1045
        HelperFile::unlink($newSrc);

1046
        // Remove empty directory
1047
        HelperFile::rmdir($tempDir);
1048
1049
    }

1050
    /**
1051
     * Create/update or delete the slave record.
1052
1053
     *
     * @param array $fe
1054
     * @param $modeUpload
1055
     * @return int
Marc Egger's avatar
Marc Egger committed
1056
1057
1058
1059
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
1060
     */
1061
    private function doUploadSlave(array $fe, $modeUpload) {
1062
1063
        $sql = '';
        $flagUpdateSlaveId = false;
1064
        $flagSlaveDeleted = false;
1065