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

namespace qfq;

require_once(__DIR__ . '/../qfq/store/Store.php');
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
12
require_once(__DIR__ . '/../qfq/store/Sip.php');
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
require_once(__DIR__ . '/../qfq/Constants.php');
require_once(__DIR__ . '/../qfq/Evaluate.php');
//require_once(__DIR__ . '/../qfq/exceptions/UserException.php');
//require_once(__DIR__ . '/../qfq/exceptions/CodeException.php');
//require_once(__DIR__ . '/../qfq/exceptions/DbException.php');
//require_once(__DIR__ . '/../qfq/Evaluate.php');


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
    /**
     * @var null|Store
     */
    private $store = null;
    private $db = null;

    private $evaluate = null;

    /**
     * @param array $formSpec
     * @param array $feSpecAction
     * @param array $feSpecNative
     */
    public function __construct(array $formSpec, array $feSpecAction, array $feSpecNative) {
        $this->formSpec = $formSpec;
        $this->feSpecAction = $feSpecAction;
        $this->feSpecNative = $feSpecNative;
        $this->store = Store::getInstance();
        $this->db = new Database();
        $this->evaluate = new Evaluate($this->store, $this->db);
    }

    /**
49
50
     * Starts save process. On succcess, returns forwardmode/page.
     *
51
     * @return int
52
53
     * @throws CodeException
     * @throws DbException
54
     * @throws UserFormException
55
56
     */
    public function process() {
57
        $rc = 0;
58
59
60
61
62
63

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

            $parentRecords = $this->db->sql($this->formSpec['multiSql']);
            foreach ($parentRecords as $row) {
                $this->store->setVarArray($row, STORE_PARENT_RECORD, true);
64
                $rc = $this->elements($row['_id']);
65
66
            }
        } else {
67
68
            $recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_ZERO);
            $rc = $this->elements($recordId);
69
        }
70
71

        return $rc;
72
73
74
75
    }

    /**
     * @param $recordId
76
     * @return int   record id (in case of insert, it's different from $recordId)
77
78
     * @throws CodeException
     * @throws DbException
79
     * @throws UserFormException
80
81
     */
    public function elements($recordId) {
82
83
        $columnCreated = false;
        $columnModified = false;
Carsten  Rose's avatar
Carsten Rose committed
84

85
86
87
        $newValues = array();

        $tableColumns = array_keys($this->store->getStore(STORE_TABLE_COLUMN_TYPES));
88
        $formValues = $this->store->getStore(STORE_FORM);
89
90
91

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

93
            // Never save a predefined 'id': autoincrement values will be given by database..
94
            if ($column === 'id') {
95
                continue;
96
            }
97

Carsten  Rose's avatar
Upload:    
Carsten Rose committed
98
99
100
101
102
            // Skip Upload Elements: those will be processed later.
            if ($this->isColumnUploadField($column)) {
                continue;
            }

103
104
105
106
107
108
109
110
            if ($column === COLUMN_CREATED) {
                $columnCreated = true;
            }

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

111
112
            // Is there a value? Do not forget SIP values. Those do not have necessarily a FormElement.
            if (!isset($formValues[$column])) {
113
                continue;
114
115
            }

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

118
119
            Support::setIfNotSet($formValues, $column);
            $newValues[$column] = $formValues[$column];
120
121
        }

122
123
124
125
        if ($columnModified && !isset($newValues[COLUMN_MODIFIED])) {
            $newValues[COLUMN_MODIFIED] = date('YmdHis');
        }

126
        if ($recordId == 0) {
127
128
129
            if ($columnCreated && !isset($newValues[COLUMN_CREATED])) {
                $newValues[COLUMN_CREATED] = date('YmdHis');
            }
130
            $rc = $this->insertRecord($this->formSpec[F_TABLE_NAME], $newValues);
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
131

132
        } else {
133
            $this->updateRecord($this->formSpec[F_TABLE_NAME], $newValues, $recordId);
134
135
136
137
            $rc = $recordId;
        }

        return $rc;
138
139
    }

Carsten  Rose's avatar
Upload:    
Carsten Rose committed
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
    /*
     * Checks if there is a formElement with name '$feName' of type 'upload'
     *
     * @param $feName
     * @return bool
     */
    private function isColumnUploadField($feName) {

        foreach ($this->feSpecNative AS $formElement) {
            if ($formElement[FE_NAME] === $feName && $formElement[FE_TYPE] == 'upload')
                return true;
        }
        return false;
    }

    /**
     * Insert new record in table $this->formSpec['tableName'].
     *
     * @param array $values
     * @return int  last insert id
     * @throws DbException
     */
    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)) . '`';

        $sql = 'INSERT INTO ' . $tableName . ' ( ' . $columnList . ' ) VALUES ( ' . $paramList . ' )';

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

        return $rc;
    }

    /**
     * @param string $tableName
     * @param array $values
     * @param int $recordId
     * @return bool|int     false if $values is empty, else affectedrows
     * @throws CodeException
     * @throws DbException
     */
    public function updateRecord($tableName, array $values, $recordId) {

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

        if ($recordId === 0)
            throw new CodeException('RecordId=0 - this is not possible for update.', ERROR_RECORDID_0_FORBIDDEN);

//        $paramList = str_repeat('?, ', count($values));
//        $paramList = substr($paramList, 0, strlen($paramList) - 2);

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

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

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

        $sql = substr($sql, 0, strlen($sql) - 2) . ' WHERE id = ?';
        $values[] = $recordId;

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

        return $rc;
    }

212
    /**
213
     * Process all Upload Formelements for the given $recordId. After processing &$formValues will be updated with the final filenames.
214
215
     *
     */
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
216
217
218
219
220
221
    public function processAllUploads($recordId) {

        $sip = new Sip(false);
        $newValues = array();

        $formValues = $this->store->getStore(STORE_FORM);
222
        $primaryRecord = $this->store->getStore(STORE_RECORD); // necessary to check if the current formElement exist as a column of the primary table.
223
224
225

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

230
231
            $formElement = HelperFormElement::initUploadFormElement($formElement);

232
233
234
            // Preparation for Log, Debug
            $this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($formElement), STORE_SYSTEM);

235
            $column = $formElement['name'];
236
            $pathFileName = $this->doUpload($formElement, $formValues[$column], $sip);
237
238
239
240
241
242
243
244
245
246

            // Upload Type: Simple or Advanced
            if (isset($primaryRecord[$column])) {
                // 'Simple Upload': no special action needed, just process the current (maybe modifired) value.
                if ($pathFileName !== false) {
                    $newValues[$column] = $pathFileName;
                }
            } else {
                // 'Advanced Upload'
                $this->doUploadSlave($formElement, $recordId, $pathFileName);
247
248
            }
        }
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
249

250
        // Only used in 'Simple Upload'
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
251
252
253
        if (count($newValues) > 0) {
            $this->updateRecord($this->formSpec[F_TABLE_NAME], $newValues, $recordId);
        }
254
255
256
    }

    /**
257
258
259
     * 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
260
261
     * Check also: doc/CODING.md
     *
262
263
264
     * @param array $formElement FormElement 'upload'
     * @param string $sipUpload SIP
     * @return string|false         New pathFilename or false on error
265
266
267
268
     * @throws CodeException
     * @throws UserFormException
     * @internal param $recordId
     */
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
269
    private function doUpload($formElement, $sipUpload, Sip $sip) {
270

271
        // Status information about upload file
272
273
274
275
276
        $statusUpload = $this->store->getVar($sipUpload, STORE_EXTRA);
        if ($statusUpload === false) {
            return false;
        }

277
278
279
280
281
282
283
        // Take care the necessary target directories exist.
        $cwd = getcwd();
        $sitePath = $this->store->getVar(SYSTEM_SITE_PATH, STORE_SYSTEM);
        if ($cwd === false || $sitePath === false || !chdir($sitePath)) {
            throw new UserFormException("getcwd() failed or SITE_PATH undefined or chdir('$sitePath') failed.", ERROR_IO_CHDIR);
        }

284
285
        // Delete existing old file.
        if (isset($statusUpload[FILES_FLAG_DELETE]) && $statusUpload[FILES_FLAG_DELETE] == '1') {
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
286
287
            $arr = $sip->getVarsFromSip($sipUpload);
            $oldFile = $arr[EXISTING_PATH_FILE_NAME];
288
289
            if (file_exists($oldFile)) {
                if (!unlink($oldFile)) {
290
                    throw new UserFormException('Unlink file failed: ' . $oldFile, ERROR_IO_UNLINK);
291
292
293
294
                }
            }
        }

295
296
297
298
299
300
301
302
303
304
305
306
        $pathFileName = $this->copyUploadFile($formElement, $statusUpload);

        chdir($cwd);

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

        return $pathFileName;

    }

    /**
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
307
308
309
310
     * Copy uploaded file from temporary location to final location.
     *
     * Check also: doc/CODING.md
     *
311
312
313
314
315
316
317
318
319
     * @param array $formElement
     * @param array $statusUpload
     * @return array|mixed|null|string
     * @throws CodeException
     * @throws UserFormException
     */
    private function copyUploadFile(array $formElement, array $statusUpload) {
        $pathFileName = '';

Carsten  Rose's avatar
Upload:    
Carsten Rose committed
320
        if (!isset($statusUpload[FILES_TMP_NAME]) || $statusUpload[FILES_TMP_NAME] === '') {
321
322
323
324
            // nothing to upload: e.g. user has deleted a previous uploaded file.
            return '';
        }

325
        if (isset($formElement[FE_FILE_DESTINATION])) {
326

327
            // Provide variable 'filename'. Might be substituted in $formElement[FE_PATH_FILE_NAME].
328
            $origFilename = Sanitize::safeFilename($statusUpload[FILES_NAME]);
329
            $this->store->setVar(VAR_FILENAME, $origFilename, STORE_VAR);
330

331
            $pathFileName = $this->evaluate->parse($formElement[FE_FILE_DESTINATION]);
332
333
334

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

337
        if ($pathFileName === '') {
338
            throw new UserFormException("Upload failed, no target '" . FE_FILE_DESTINATION . "' specified.", ERROR_NO_TARGET_PATH_FILE_NAME);
339
340
        }

341
        if (file_exists($pathFileName)) {
342
343
344
345
346
347
348
            if (isset($formElement[FE_FILE_REPLACE_MODE]) && $formElement[FE_FILE_REPLACE_MODE] == FE_FILE_REPLACE_MODE_ALWAYS) {
                if (!unlink($pathFileName)) {
                    throw new UserFormException('Copy upload failed - file exist and unlink() failed: ' . $pathFileName, ERROR_IO_UNLINK);
                }
            } else {
                throw new UserFormException('Copy upload failed - file already exist: ' . $pathFileName, ERROR_IO_FILE_EXIST);
            }
349
        }
350

351
        Support::mkDirParent($pathFileName);
352

353
354
355
356
        $srcFile = Support::extendFilename($statusUpload[FILES_TMP_NAME], UPLOAD_CACHED);
        if (!rename($srcFile, $pathFileName)) {
            throw new UserFormException("Rename file: '$srcFile' > '$pathFileName'", ERROR_IO_RENAME);
        }
357

358
359
        return $pathFileName;
    }
360

361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
    /**
     * Create the slave record. First try to evaluate a slaveId. Depending if the slaveId > 0 choose `sqlUpdate` or `sqlInsert`
     *
     * @param array $fe
     * @return int
     * @throws CodeException
     * @throws UserFormException
     */
    private function doUploadSlave(array $fe, $recordId, $pathFileName) {
        $sql = '';
        $flagUpdateSlaveId = false;

        // Get the slaveId
        $slaveId = $this->evaluate->parse($fe[FE_SLAVE_ID]);

        if ($slaveId === '' || $slaveId === false) {
            $slaveId = 0;
        }

        // Store the slaveId: it's used and replaced in the update statement.
        $this->store->setVar(VAR_SLAVE_ID, $slaveId, STORE_VAR, true);

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

        if ($slaveId == 0 && $pathFileName != '') {
            $sql = $fe[FE_SQL_INSERT];
            $flagUpdateSlaveId = true;
        }

        if ($slaveId > 0 && $pathFileName != '') {
            $sql = $fe[FE_SQL_UPDATE];
        }

        if ($slaveId > 0 && $pathFileName == '') {
            $sql = $fe[FE_SQL_DELETE];
        }

        $rc = $this->evaluate->parse($sql);

        if ($flagUpdateSlaveId) {
            // Store the slaveId: it's used and replaced in the update statement.
            $this->store->setVar(VAR_SLAVE_ID, $rc, STORE_VAR, true);
            $slaveId = $rc;
        }

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

        return $slaveId;
    }

413
414
415
416
417
418
419
    /**
     * Get the complete FormElement for $name
     *
     * @param $name
     * @return bool|array if found the FormElement, else false.
     */
    private function getFormElementByName($name) {
420

421
422
423
424
        foreach ($this->feSpecNative as $formElement) {
            if ($formElement['name'] === $name)
                return $formElement;
        }
425

426
427
428
        return false;
    }

429
}