Save.php 12 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
52
     * @throws CodeException
     * @throws DbException
53
     * @throws UserFormException
54
55
     */
    public function process() {
56
        $rc = 0;
57
58
59
60
61
62

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

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

        return $rc;
71
72
73
74
    }

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

84
85
86
        $newValues = array();

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

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

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

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

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

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

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

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

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

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

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

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

        return $rc;
137
138
    }

Carsten  Rose's avatar
Upload:    
Carsten Rose committed
139
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
    /*
     * 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;
    }

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

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

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

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

229
230
231
            // Preparation for Log, Debug
            $this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($formElement), STORE_SYSTEM);

232
            $column = $formElement['name'];
233
234
235
236
            $pathFileName = $this->doUpload($formElement, $formValues[$column], $sip);
            // Only update (save) the new pathFileName to the primaryRecord if the corresponding column exist.
            if ($pathFileName !== false && isset($primaryRecord[$column])) {
                $newValues[$column] = $pathFileName;
237
238
            }
        }
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
239
240
241
242

        if (count($newValues) > 0) {
            $this->updateRecord($this->formSpec[F_TABLE_NAME], $newValues, $recordId);
        }
243
244
245
    }

    /**
246
247
248
     * 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
249
250
     * Check also: doc/CODING.md
     *
251
252
253
254
255
256
257
     * @param $formElement
     * @param $sipUpload
     * @return string|false  New filename or false on error
     * @throws CodeException
     * @throws UserFormException
     * @internal param $recordId
     */
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
258
    private function doUpload($formElement, $sipUpload, Sip $sip) {
259

260
        // Status information about upload file
261
262
263
264
265
        $statusUpload = $this->store->getVar($sipUpload, STORE_EXTRA);
        if ($statusUpload === false) {
            return false;
        }

266
267
268
269
270
271
272
        // 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);
        }

273
274
        // Delete existing old file.
        if (isset($statusUpload[FILES_FLAG_DELETE]) && $statusUpload[FILES_FLAG_DELETE] == '1') {
Carsten  Rose's avatar
Carsten Rose committed
275
            $this->store->setVar(CLIENT_FILE_DELETED . $formElement[FE_NAME], '1', STORE_FORM);
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
276
277
            $arr = $sip->getVarsFromSip($sipUpload);
            $oldFile = $arr[EXISTING_PATH_FILE_NAME];
278
279
            if (file_exists($oldFile)) {
                if (!unlink($oldFile)) {
280
                    throw new UserFormException('Unlink file failed: ' . $oldFile, ERROR_IO_UNLINK);
281
282
283
284
                }
            }
        }

285
286
287
288
289
290
291
292
293
294
295
296
        $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
297
298
299
300
     * Copy uploaded file from temporary location to final location.
     *
     * Check also: doc/CODING.md
     *
301
302
303
304
305
306
307
308
309
     * @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
310
        if (!isset($statusUpload[FILES_TMP_NAME]) || $statusUpload[FILES_TMP_NAME] === '') {
311
312
313
314
            // nothing to upload: e.g. user has deleted a previous uploaded file.
            return '';
        }

315
        if (isset($formElement[FE_FILE_DESTINATION])) {
316

317
318
            // Provide variable '_filename'. Might be substituted in $formElement[FE_PATH_FILE_NAME].
            $origFilename = Sanitize::safeFilename($statusUpload[FILES_NAME]);
Carsten  Rose's avatar
Carsten Rose committed
319
            $this->store->setVar(CLIENT_UPLOAD_FILENAME . $formElement[FE_NAME], $origFilename, STORE_FORM);
320

321
            $pathFileName = $this->evaluate->parse($formElement[FE_FILE_DESTINATION]);
322
323
        }

324
        if ($pathFileName === '') {
325
            throw new UserFormException("Upload failed, no target '" . FE_FILE_DESTINATION . "' specified.", ERROR_NO_TARGET_PATH_FILE_NAME);
326
327
        }

328
        if (file_exists($pathFileName)) {
329
330
331
332
333
334
335
            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);
            }
336
        }
337

338
        Support::mkDirParent($pathFileName);
339

340
341
342
343
        $srcFile = Support::extendFilename($statusUpload[FILES_TMP_NAME], UPLOAD_CACHED);
        if (!rename($srcFile, $pathFileName)) {
            throw new UserFormException("Rename file: '$srcFile' > '$pathFileName'", ERROR_IO_RENAME);
        }
344

345
346
        return $pathFileName;
    }
347

348
349
350
351
352
353
354
    /**
     * Get the complete FormElement for $name
     *
     * @param $name
     * @return bool|array if found the FormElement, else false.
     */
    private function getFormElementByName($name) {
355

356
357
358
359
        foreach ($this->feSpecNative as $formElement) {
            if ($formElement['name'] === $name)
                return $formElement;
        }
360

361
362
363
        return false;
    }

364
}