Save.php 43.9 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
namespace IMATHUZH\Qfq\Core;

use IMATHUZH\Qfq\Core\Database\Database;
12
use IMATHUZH\Qfq\Core\Helper\HelperFile;
Marc Egger's avatar
Marc Egger committed
13
14
use IMATHUZH\Qfq\Core\Helper\HelperFormElement;
use IMATHUZH\Qfq\Core\Helper\Logger;
15
use IMATHUZH\Qfq\Core\Helper\OnArray;
Marc Egger's avatar
Marc Egger committed
16
17
18
use IMATHUZH\Qfq\Core\Helper\Sanitize;
use IMATHUZH\Qfq\Core\Helper\Support;
use IMATHUZH\Qfq\Core\Store\Sip;
19
20
use IMATHUZH\Qfq\Core\Store\Store;
use IMATHUZH\Qfq\Core\Store\FillStoreForm;
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
56
        $this->formSpec = $formSpec;
        $this->feSpecAction = $feSpecAction;
        $this->feSpecNative = $feSpecNative;
57
        $this->feSpecNativeRaw = $feSpecNativeRaw;
58
        $this->store = Store::getInstance();
59
        $this->db = new Database($formSpec[F_DB_INDEX]);
60
        $this->evaluate = new Evaluate($this->store, $this->db);
61
62
63

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

64
65
66
    }

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

78
        if ($this->formSpec[F_MULTI_SQL] == '') {
79
80
            $recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_ZERO);
            $rc = $this->elements($recordId);
81
82
        } else {
            $rc = $this->saveMultiForm();
83
        }
84
85

        return $rc;
86
87
    }

88
89
90
91
92
93
94
95
96
97
98
    /**
     * @return int|string
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    private function saveMultiForm() {

        $parentRecords = $this->evaluate->parse($this->formSpec[F_MULTI_SQL], ROW_REGULAR);

99
        // No rows: This must be an error, cause MultiForms must have at least one record.
100
        if (empty($parentRecords)) {
101
102
103
104
            throw new \UserFormException(
                json_encode([ERROR_MESSAGE_TO_USER => $this->formSpec[F_MULTI_MSG_NO_RECORD],
                    ERROR_MESSAGE_TO_DEVELOPER => 'Query selects no records: ' . $this->formSpec[F_MULTI_SQL]]),
                ERROR_MISSING_EXPECT_RECORDS);
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
        }

        // Check for 'id' or '_id' as column name
        $idName = isset($parentRecords[0]['_' . F_MULTI_COL_ID]) ? '_' . F_MULTI_COL_ID : F_MULTI_COL_ID;

        // Check that an column 'id' is given
        if (!isset($parentRecords[0][$idName])) {
            throw new \UserFormException(
                json_encode([ERROR_MESSAGE_TO_USER => 'Missing column "_' . F_MULTI_COL_ID . '"', ERROR_MESSAGE_TO_DEVELOPER => $this->formSpec[F_MULTI_SQL]]),
                ERROR_INVALID_OR_MISSING_PARAMETER);
        }

        $fillStoreForm = new FillStoreForm();

        foreach ($parentRecords as $row) {
            $this->store->setStore($row, STORE_PARENT_RECORD, true);

            // Fake current recordId
            $this->store->setVar(SIP_RECORD_ID, $row[$idName], STORE_SIP);
            $fillStoreForm->process(FORM_SAVE);

            $rc = $this->elements($row[$idName]);
        }

        return $rc;
    }

132
    /**
Carsten  Rose's avatar
Carsten Rose committed
133
134
     * Create empty FormElements based on templateGroups, for those who not already exist.
     *
135
     * @param array $formValues
Carsten  Rose's avatar
Carsten Rose committed
136
     *
137
     * @return array
Marc Egger's avatar
Marc Egger committed
138
     * @throws \UserFormException
139
140
141
142
143
     */
    private function createEmptyTemplateGroupElements(array $formValues) {

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

144
145
146
147
148
149
150
151
            switch ($formElement[FE_TYPE]) {
//                case FE_TYPE_EXTRA:
                case FE_TYPE_NOTE:
                case FE_TYPE_SUBRECORD:
                    continue 2;
                default:
                    break;
            }
152
            $feName = $formElement[FE_NAME];
153
            if (!isset($formValues[$feName]) && $this->isMemberOfTemplateGroup($formElement)) {
154
155
156
                $formValues[$feName] = $formElement[FE_VALUE];
            }
        }
Carsten  Rose's avatar
Carsten Rose committed
157

158
159
160
        return $formValues;
    }

161
    /**
162
163
164
165
166
     * 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
167
     * @throws \UserFormException
168
169
170
171
172
     */
    private function isMemberOfTemplateGroup(array $formElement, $depth = 0) {
        $depth++;

        if ($depth > 15) {
Marc Egger's avatar
Marc Egger committed
173
            throw new \UserFormException('FormElement nested too much (in each other - endless?): stop recursion', ERROR_FE_NESTED_TOO_MUCH);
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
        }

        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,
    }

    /**
     *
195
     * @param $feName
Carsten  Rose's avatar
Carsten Rose committed
196
     *
197
198
199
200
201
202
203
204
205
206
207
     * @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;
    }

208
    /**
Carsten  Rose's avatar
Carsten Rose committed
209
210
     * 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
211
     *
212
     * @param $recordId
Carsten  Rose's avatar
Carsten Rose committed
213
     *
214
     * @return int   record id (in case of insert, it's different from $recordId)
Marc Egger's avatar
Marc Egger committed
215
216
217
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \DbException
218
     */
219
    private function elements($recordId) {
220
221
        $columnCreated = false;
        $columnModified = false;
Carsten  Rose's avatar
Carsten Rose committed
222

223
224
225
        $newValues = array();

        $tableColumns = array_keys($this->store->getStore(STORE_TABLE_COLUMN_TYPES));
226
        $formValues = $this->store->getStore(STORE_FORM);
227
        $formValues = $this->createEmptyTemplateGroupElements($formValues);
228
229
230

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

232
            // Never save a predefined 'id': autoincrement values will be given by database..
233
            if ($column === COLUMN_ID) {
234
                continue;
235
            }
236

Carsten  Rose's avatar
Upload:    
Carsten Rose committed
237
238
239
240
241
            // Skip Upload Elements: those will be processed later.
            if ($this->isColumnUploadField($column)) {
                continue;
            }

242
243
244
245
246
247
248
249
            if ($column === COLUMN_CREATED) {
                $columnCreated = true;
            }

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

250
251
            // Is there a value? Do not forget SIP values. Those do not have necessarily a FormElement.
            if (!isset($formValues[$column])) {
252
                continue;
253
254
            }

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

257
258
259
260
261
262
            // 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);
            }
263
            $newValues[$column] = $formValues[$column];
264

265
266
        }

267
268
269
270
        if ($columnModified && !isset($newValues[COLUMN_MODIFIED])) {
            $newValues[COLUMN_MODIFIED] = date('YmdHis');
        }

271
        if ($recordId == 0) {
272
273
274
            if ($columnCreated && !isset($newValues[COLUMN_CREATED])) {
                $newValues[COLUMN_CREATED] = date('YmdHis');
            }
275
            $rc = $this->insertRecord($this->formSpec[F_TABLE_NAME], $newValues);
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
276

277
        } else {
278
            $this->updateRecord($this->formSpec[F_TABLE_NAME], $newValues, $recordId, $this->formSpec[F_PRIMARY_KEY]);
279
280
281
282
            $rc = $recordId;
        }

        return $rc;
283
284
    }

Carsten  Rose's avatar
Upload:    
Carsten Rose committed
285
286
287
288
289
290
    /*
     * Checks if there is a formElement with name '$feName' of type 'upload'
     *
     * @param $feName
     * @return bool
     */
291
292
293
294
    /**
     * @param $feName
     * @return bool
     */
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
295
296
297
298
299
300
    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
301

Carsten  Rose's avatar
Upload:    
Carsten Rose committed
302
303
304
305
306
307
        return false;
    }

    /**
     * Insert new record in table $this->formSpec['tableName'].
     *
308
     * @param $tableName
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
309
     * @param array $values
Carsten  Rose's avatar
Carsten Rose committed
310
     *
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
311
     * @return int  last insert id
Marc Egger's avatar
Marc Egger committed
312
313
314
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
315
316
317
318
319
320
321
322
323
324
     */
    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)) . '`';

325
        $sql = "INSERT INTO $tableName ( " . $columnList . " ) VALUES ( " . $paramList . ' )';
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
326
327
328
329
330
331
332
333

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

        return $rc;
    }

    /**
     * @param string $tableName
Carsten  Rose's avatar
Carsten Rose committed
334
335
     * @param array $values
     * @param int $recordId
336
     * @param string $primaryKey
Carsten  Rose's avatar
Carsten Rose committed
337
     *
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
338
     * @return bool|int     false if $values is empty, else affectedrows
Marc Egger's avatar
Marc Egger committed
339
340
341
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
342
     */
343
    public function updateRecord($tableName, array $values, $recordId, $primaryKey = F_PRIMARY_KEY_DEFAULT) {
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
344
345
346
347

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

348
        if ($recordId === 0) {
Marc Egger's avatar
Marc Egger committed
349
            throw new \CodeException('RecordId=0 - this is not possible for update.', ERROR_RECORDID_0_FORBIDDEN);
350
        }
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
351
352
353
354
355
356
357
358

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

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

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

359
        $sql = substr($sql, 0, strlen($sql) - 2) . " WHERE $primaryKey = ?";
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
360
361
362
363
364
365
366
        $values[] = $recordId;

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

        return $rc;
    }

367
    /**
Carsten  Rose's avatar
Carsten Rose committed
368
369
     * Process all Upload Formelements for the given $recordId. After processing &$formValues will be updated with the
     * final filenames.
370
371
372
373
374
375
376
377
     *
     * Constellation: # FILE OLD   FILE NEW     FILESIZE
     *                1 none       none
     *                2 none       new
     *                3 exist      no change
     *                4 delete     none
     *                5 delete     new
     *
378
     * @param $recordId
Marc Egger's avatar
Marc Egger committed
379
380
381
382
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
383
384
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
385
     */
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
386
387
388
389
    public function processAllUploads($recordId) {

        $sip = new Sip(false);
        $newValues = array();
390
        $vars = array();
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
391
392

        $formValues = $this->store->getStore(STORE_FORM);
393
        $primaryRecord = $this->store->getStore(STORE_RECORD); // necessary to check if the current formElement exist as a column of the primary table.
394
395
396

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

401
402
403
404
            // 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);

405
            $formElement = HelperFormElement::initUploadFormElement($formElement);
406
            if (isset($formElement[FE_FILL_STORE_VAR])) {
407
                $formElement[FE_FILL_STORE_VAR] = $this->evaluate->parse($formElement[FE_FILL_STORE_VAR], ROW_EXPECT_0_1);
408
                $this->store->appendToStore($formElement[FE_FILL_STORE_VAR], STORE_VAR);
409
            }
410

411
            $column = $formElement[FE_NAME];
412
            $pathFileName = $this->doUpload($formElement, ($formValues[$column] ?? ''), $sip, $modeUpload);
413

414
415
            if ($modeUpload == UPLOAD_MODE_DELETEOLD && $pathFileName == '') {
                $pathFileNameTmp = '';  // see '4'
416
417
            } else {
                if (empty($pathFileName)) {
418
                    $pathFileNameTmp = $primaryRecord[$column] ?? ''; // see '3'. Attention: in case of Advanced Upload, $primaryRecord[$column] does not exist.
419
420
421
422
423
                } else {
                    $pathFileNameTmp = $pathFileName; // see '1,2,5'
                }
            }

424
            // Get latest file information
425
            if ($pathFileNameTmp == '') {
426
427
428
                // 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);
429
430
                $vars[VAR_FILE_SIZE] = 0;
                $vars[VAR_FILE_MIME_TYPE] = '';
431
432
433
434
435
            } else {
                $vars = HelperFile::getFileStat($pathFileNameTmp);
                $this->store->appendToStore($vars, STORE_VAR);
            }

436
437
438
            // If given: fire a sqlBefore query
            $this->evaluate->parse($formElement[FE_SQL_BEFORE]);

439
            // Upload Type: Simple or Advanced
440
441
442
            // 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.
443
444
                if ($pathFileName !== false) {
                    $newValues[$column] = $pathFileName;
445
446
447
448
449
450
451
452

                    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];
                    }
453
                }
454
455
            } elseif (isset($formElement[FE_IMPORT_TO_TABLE]) && !isset($formElement[FE_SLAVE_ID])) {
                // Excel import on nonexisting column -> no upload
456
457
            } else {
                // 'Advanced Upload'
458
                $this->doUploadSlave($formElement, $modeUpload);
459
            }
460
461
462
463

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

464
        }
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
465

466
        // Only used in 'Simple Upload'
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
467
        if (count($newValues) > 0) {
468
            $this->updateRecord($this->formSpec[F_TABLE_NAME], $newValues, $recordId, $this->formSpec[F_PRIMARY_KEY]);
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
469
        }
470
471
    }

472
    /**
473
474
     * Process all Upload FormElements for the given $recordId.
     * After processing, &$formValues will be updated with the final filename.
475
     *
Marc Egger's avatar
Marc Egger committed
476
477
     * @throws \CodeException
     * @throws \UserFormException
478
479
480
481
482
483
484
485
486
487
488
489
490
     */
    public function processAllImageCutFE() {

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

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

491
492
493
494
495
496
497
498

    /**
     * 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
499
500
501
502
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
503
504
505
506
507
508
509
510
511
     */
    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) {

512
513
514
515
516
            // Do not check retype slave FE.
            if (isset($formElement[FE_RETYPE_SOURCE_NAME])) {
                continue;
            }

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

519
520
521
522
523
524
525
            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;
            }

526
527
528
            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.
529
530
            }

531
            if (!$requiredOff && $mode == FE_MODE_REQUIRED && empty($clientValues[$formElement[FE_NAME]])) {
Marc Egger's avatar
Marc Egger committed
532
                throw new \UserFormException("Missing required value: " . $formElement[FE_LABEL], ERROR_REQUIRED_VALUE_EMPTY);
533
534
535
536
537
538
539
540
541
            }

            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);
            }
        }
    }

542
543
544
    /**
     *
     * @param array $formElement
Marc Egger's avatar
Marc Egger committed
545
546
     * @throws \CodeException
     * @throws \UserFormException
547
548
549
550
551
552
     */
    private function extractImageDataReplaceFile(array $formElement) {

        // Take care the necessary target directories exist.
        $cwd = getcwd();
        $sitePath = $this->store->getVar(SYSTEM_SITE_PATH, STORE_SYSTEM);
553
        if ($cwd === false || $sitePath === false || !HelperFile::chdir($sitePath)) {
Marc Egger's avatar
Marc Egger committed
554
            throw new \UserFormException(
Marc Egger's avatar
Marc Egger committed
555
                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."]),
556
                ERROR_IO_CHDIR);
557
558
        }

559
        // Get original pathFileName
560
561
562
        $field = HelperFormElement::AppendFormElementNameImageCut($formElement);
        $pathFileName = $this->store->getVar($field, STORE_SIP);
        if ($pathFileName == '' || !file_exists($pathFileName)) {
Marc Egger's avatar
Marc Egger committed
563
            throw new \UserFormException(
Marc Egger's avatar
Marc Egger committed
564
                json_encode([ERROR_MESSAGE_TO_USER => 'Empty file or file not found', ERROR_MESSAGE_TO_DEVELOPER => 'Empty file or file not found: ' . $pathFileName]),
565
                ERROR_IO_FILE_NOT_FOUND);
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
        }

        // '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)
            ) {
601
                HelperFile::rename($pathFileName, $pathFileName . $extSave);
602
603
604
605
606
607
608
609
            }
        }

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

        if (false === file_put_contents($pathFileName, base64_decode($imageData))) {
Marc Egger's avatar
Marc Egger committed
610
            throw new \UserFormException(
Marc Egger's avatar
Marc Egger committed
611
                json_encode([ERROR_MESSAGE_TO_USER => 'Write new image failed', ERROR_MESSAGE_TO_DEVELOPER => "Write new image failed: $pathFileName"]),
612
                ERROR_IO_WRITE);
613
614
615
616
617
        }

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

618
    /**
619
620
621
     * 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
622
623
     * Check also: doc/CODING.md
     *
Carsten  Rose's avatar
Carsten Rose committed
624
625
626
627
     * @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
628
629
     *                            UPLOAD_MODE_DELETEOLD_NEW
     *
630
     * @return false|string New pathFilename or false on error
Marc Egger's avatar
Marc Egger committed
631
632
633
634
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
635
636
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
637
638
     * @internal param $recordId
     */
639
640
641
    private function doUpload($formElement, $sipUpload, Sip $sip, &$modeUpload) {
        $flagDelete = false;
        $modeUpload = UPLOAD_MODE_UNCHANGED;
642

643
        // Status information about upload file
644
645
646
647
648
        $statusUpload = $this->store->getVar($sipUpload, STORE_EXTRA);
        if ($statusUpload === false) {
            return false;
        }

649
        if (isset($formElement[FE_IMPORT_TO_TABLE]) && isset($statusUpload[FILES_TMP_NAME])) {
650
            // Import
651
            $tmpFile = Support::extendFilename($statusUpload[FILES_TMP_NAME], UPLOAD_CACHED);
652
653
            $this->doImport($formElement, $tmpFile);
        }
654

655
        // Upload - Take care the necessary target directories exist.
656
657
        $cwd = getcwd();
        $sitePath = $this->store->getVar(SYSTEM_SITE_PATH, STORE_SYSTEM);
658
        if ($cwd === false || $sitePath === false || !HelperFile::chdir($sitePath)) {
Marc Egger's avatar
Marc Egger committed
659
            throw new \UserFormException(
Marc Egger's avatar
Marc Egger committed
660
                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."]),
661
                ERROR_IO_CHDIR);
662
        }
663

664
665
666
667
668
        // 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)) {
669
                //TODO: it might be possible to delete a file, which is referenced by another record - a check would be nice.
670
                HelperFile::unlink($oldFile, $this->qfqLogFilename);
671
672
673
674
675
676
677
678
679
680
681
            }
            $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;
        }

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

684
        // skip uploading the file, if this is an import without a specified file destination
685
686
        if (!isset($formElement[FE_IMPORT_TO_TABLE]) || isset($formElement[FE_FILE_DESTINATION])) {
            $pathFileName = $this->copyUploadFile($formElement, $statusUpload);
687
688
689
690

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

693
        HelperFile::chdir($cwd);
694
695
696
697
698
699
700
701
702

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

        return $pathFileName;
    }

    /**
     * @param $formElement
703
     * @param $fileName
Marc Egger's avatar
Marc Egger committed
704
705
706
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
707
708
709
710
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
     */
    private function doImport($formElement, $fileName) {
711
        $importNamedSheetsOnly = array();
712

713
        Support::setIfNotSet($formElement, FE_IMPORT_TYPE, FE_IMPORT_TYPE_AUTO);
714

715
716
717
718
        if (!empty($formElement[FE_IMPORT_NAMED_SHEETS_ONLY])) {
            $importNamedSheetsOnly = explode(',', $formElement[FE_IMPORT_NAMED_SHEETS_ONLY]);
        }

Carsten  Rose's avatar
Carsten Rose committed
719
        // Check for keywords which needs an explicit given document type.
720
721
722
723
724
        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
725
                    throw new \UserFormException('If ' . $token .
726
727
728
729
730
                        ' is given, an explicit document type (like ' . FE_IMPORT_TYPE . '=xlsx) should be set.', ERROR_IMPORT_MISSING_EXPLICIT_TYPE);
                }
            }
        }

731
732
733
734
735
736
737
738
739
740
741
        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);
742
743
744
745
746
747
748
749
750
751
752
753
754

                // 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
755
                    throw new \UserFormException("Worksheets: " . implode(', ', $sheetNames),
756
757
758
                        ERROR_IMPORT_LIST_SHEET_NAMES);
                }

759
760
761
762
                $spreadsheet = $reader->load($fileName);
                break;

            default:
Marc Egger's avatar
Marc Egger committed
763
                throw new \UserFormException("Unknown Excel import type: '" . $formElement[FE_IMPORT_TYPE] . "'.",
764
765
766
767
                    ERROR_UNKNOWN_EXCEL_IMPORT_TYPE);
        }

        $tableName = $formElement[FE_IMPORT_TO_TABLE];
768
        $regions = OnArray::trimArray(explode('|', $formElement[FE_IMPORT_REGION] ?? ''));
769
        $columnNames = OnArray::trimArray(explode(',', $formElement[FE_IMPORT_TO_COLUMNS] ?? ''));
770
771
772
773
        $importMode = $formElement[FE_IMPORT_MODE] ?? FE_IMPORT_MODE_APPEND;

        foreach ($regions as $region) {
            // region: tab, startColumn, startRow, endColumn, endRow
774
            $region = OnArray::trimArray(explode(',', $region));
775
776
777
778
779
780
781
782
783
784
785
786
787
788
            $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."
                        );
789
790
                    }
                }
791
            } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
Marc Egger's avatar
Marc Egger committed
792
                throw new \UserFormException($e->getMessage());
793
            }
794

795
796
797
798
799
800
801
802
803
            // 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];
804
805
                }
            }
806
807
808
809
810
            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];
                }
811
            }
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
            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);
830
                }
831
832
                $columnDefinitionArr[] = "`$columnName`   TEXT       NOT NULL  DEFAULT ''";
                $columnListArr[] = "$columnName";
833
834
            }

835
836
837
838
839
840
841
842
843
844
845
846
847
            // 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;
848
            }
849

850
851
852
853
854
855
856
            // 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);
            }
857
        }
858
859
860
    }

    /**
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
861
862
863
864
     * Copy uploaded file from temporary location to final location.
     *
     * Check also: doc/CODING.md
     *
865
866
867
     * @param array $formElement
     * @param array $statusUpload
     * @return array|mixed|null|string
Marc Egger's avatar
Marc Egger committed
868
869
870
871
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
872
     */
873
    private function copyUploadFile(array $formElement, array $statusUpload) {
874
875
        $pathFileName = '';

Carsten  Rose's avatar
Upload:    
Carsten Rose committed
876
        if (!isset($statusUpload[FILES_TMP_NAME]) || $statusUpload[FILES_TMP_NAME] === '') {
877
878
879
880
            // nothing to upload: e.g. user has deleted a previous uploaded file.
            return '';
        }

881
882
        $srcFile = Support::extendFilename($statusUpload[FILES_TMP_NAME], UPLOAD_CACHED);

883
        if (isset($formElement[FE_FILE_DESTINATION])) {
884

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

889
            $pathFileName = $this->evaluate->parse($formElement[FE_FILE_DESTINATION]);
890
            $pathFileName = Sanitize::safeFilename($pathFileName, false, true); // Dynamically calculated pathFileName might contain invalid characters.
891
892
893

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

896
        if ($pathFileName === '') {
Marc Egger's avatar
Marc Egger committed
897
            throw new \UserFormException("Upload failed, no target '" . FE_FILE_DESTINATION . "' specified.", ERROR_NO_TARGET_PATH_FILE_NAME);
898
899
        }

900
901
902
903
904
905
906
        // 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]);
        }

907
        $overwrite = isset($formElement[FE_FILE_REPLACE_MODE]) && $formElement[FE_FILE_REPLACE_MODE] == FE_FILE_REPLACE_MODE_ALWAYS;
908
        Support::moveFile($srcFile, $pathFileName, $overwrite, $chmodDir);
909

910
        // get chmodFile
911
912
        if (empty($formElement[FE_FILE_CHMOD_FILE])) {
            $chmodFile = false;
913
        } else {
914
            $chmodFile = octdec($formElement[FE_FILE_CHMOD_FILE]);
915
916
        }

917
        $this->autoOrient($formElement, $pathFileName);
918
        HelperFile::chmod($pathFileName, $chmodFile);
919

920
        $this->splitUpload($formElement, $pathFileName, $chmodFile, $statusUpload);
921

922
923
        return $pathFileName;
    }
924

925
926
    /**
     * If fe['autoOrient'] is given and the MimeType corresponds to fe['autoOrientMimeType']: the given {{pathFileName:V}} will be converted.
927
     * ImageMagick 'convert' seems to do a better job than GraficsMagick (Orientation is stable even if multiple times applied).
928
929
930
     *
     * @param array $formElement
     * @param $pathFileName
Marc Egger's avatar
Marc Egger committed
931
932
933
934
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
935
     */
936
    private function autoOrient(array $formElement, $pathFileName) {
937
938

        // 'autoOrient' wished?
939
        if (!isset($formElement[FE_FILE_AUTO_ORIENT]) || $formElement[FE_FILE_AUTO_ORIENT] == '0') {
940
941
942
943
            return; // No
        }

        // Upload has matching MimeType?
944
        $mimeTypeList = empty($formElement[FE_FILE_AUTO_ORIENT_MIME_TYPE]) ? 'image/jpeg,image/png,image/tiff' : $formElement[FE_FILE_AUTO_ORIENT_MIME_TYPE];
945
        if (!HelperFile::checkFileType($pathFileName, $pathFileName, $mimeTypeList)) {
946
947
948
949
950
951
952
953
            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
954
955
        $output = Support::qfqExec($cmd, $rc);
        if ($rc != 0) {
Marc Egger's avatar
Marc Egger committed
956
            throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => 'copy failed', ERROR_MESSAGE_TO_DEVELOPER => "[cmd=$cmd]$output"]), ERROR_IO_COPY);
957
        }
958
959
    }

960
    /**
Carsten  Rose's avatar
Carsten Rose committed
961
962
963
964
965
966
967
     * 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.
     *
968
     * @param array $formElement
969
970
     * @param string $pathFileName
     * @param int $chmod
971
     * @param array $statusUpload
Marc Egger's avatar
Marc Egger committed
972
973
974
975
     * @throws \CodeException
     * @throws \DbException
     * @throws \UserFormException
     * @throws \UserReportException
976
     */
977
    private function splitUpload(array $formElement, $pathFileName, $chmod, array $statusUpload) {
978

979

980
981
982
983
984
985
986
987
988
        // 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) {
989
990
991
            return;
        }

992
993
994
995
        $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] ?? '');
996
997
998
999
1000

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

1001
1002
1003
        if ($fileDestinationSplit == '') {
            $ext = ($fileSplitType == FE_FILE_SPLIT_SVG) ? '.%02d.svg' : '.jpg';
            $fileDestinationSplit = $pathFileName . '.split/split' . $ext;
1004
1005
        }

Carsten  Rose's avatar
Carsten Rose committed
1006
        HelperFile::mkDirParent($fileDestinationSplit);
1007
1008
1009
1010
1011

        // Save CWD
        $cwd = getcwd();

        // Create temporary directory
1012
        $tempDir = HelperFile::mktempdir();
1013
        $newSrc = $tempDir . DIRECTORY_SEPARATOR . QFQ_TEMP_SOURCE;
1014
        HelperFile::copy($pathFileName, $newSrc);
1015
1016
1017
1018

        // Split destination.
        $pathParts = pathinfo($fileDestinationSplit);
        if (empty($pathParts['filename']) || empty($pathParts['basename'])) {
Marc Egger's avatar
Marc Egger committed
1019
            throw new \UserFormException('Missing filename in ' . FE_FILE_DESTINATION_SPLIT, ERROR_MISSING_FILE_NAME);
1020
1021
1022
1023
1024
        }

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

1025
1026
1027
1028
1029
        switch ($fileSplitType) {
            case FE_FILE_SPLIT_SVG:
                $cmd = 'pdf2svg "' . $newSrc . '" "' . $fileNameDest . '" all';
                break;
            case FE_FILE_SPLIT_JPEG:
1030
1031
1032
1033
                if ($fileSplitTypeOptions == '') {
                    $fileSplitTypeOptions = FE_FILE_SPLIT_OPTIONS_JPEG;
                }
                $cmd = "convert $fileSplitTypeOptions '$newSrc' '$fileNameDest'";
1034
1035
                break;
            default:
Marc Egger's avatar
Marc Egger committed
1036
                throw new \UserFormException("Unknown 'fileSplit' type: " . $formElement[FE_FILE_SPLIT], ERROR_UNKNOWN_TOKEN);
1037
1038
        }

1039
        // Split PDF
1040
        HelperFile::chdir($tempDir);
1041
        $output = Support::qfqExec($cmd, $rc);
1042
1043
        HelperFile::chdir($cwd);

1044
        if ($rc != 0) {
Marc Egger's avatar
Marc Egger committed
1045
            throw new \UserFormException(
Marc Egger's avatar
Marc Egger committed
1046
                json_encode([ERROR_MESSAGE_TO_USER => 'pdf2svg failed', ERROR_MESSAGE_TO_DEVELOPER => "[$cwd][cmd=$cmd]$output"]),
1047
                ERROR_PDF2SVG);
1048
        }
1049

1050
        $files = Helperfile::getSplitFileNames($tempDir);
1051

1052
1053
1054
1055
1056
        $xId = $this->store->getVar(COLUMN_ID, STORE_RECORD);

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

1057
1058
        // IM 'convert' will produce files <file>-1.jpg <file>-10.jpg ... - bring them in natural sort order
        natsort($files);
1059
1060
1061

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

        // 1) Move split files to final location. 2) Created records to reference each split file.
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
        foreach ($files as $file) {

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

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

1077
            Support::moveFile($tempDir . DIRECTORY_SEPARATOR . $file, Support::joinPath($cwd, $fileDestination), true);
1078
            HelperFile::chmod($fileDestination, $chmod);
1079
1080

            // Insert records.
1081
            $this->db->sql($sql, ROW_REGULAR, [$fileSplitTableName, $xId, $fileDestination]);
1082
1083
1084
        }

        // Remove duplicated source
1085
1086
        HelperFile::unlink($newSrc);

1087
        // Remove empty directory
1088
        HelperFile::rmdir($tempDir);
1089
1090
    }