Save.php 11.2 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
222
223

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

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

231
            $column = $formElement['name'];
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
232
            $file = $this->doUpload($formElement, $formValues[$column], $sip);
233
            if ($file !== false) {
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
234
                $newValues[$column] = $file;
235
236
            }
        }
Carsten  Rose's avatar
Upload:    
Carsten Rose committed
237
238
239
240

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

    /**
244
245
246
     * 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
247
248
     * Check also: doc/CODING.md
     *
249
250
251
252
253
254
255
     * @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
256
    private function doUpload($formElement, $sipUpload, Sip $sip) {
257

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

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

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

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

312
        if (isset($formElement[FE_FILE_DESTINATION])) {
313

314
315
316
317
            // Provide variable '_filename'. Might be substituted in $formElement[FE_PATH_FILE_NAME].
            $origFilename = Sanitize::safeFilename($statusUpload[FILES_NAME]);
            $this->store->setVar(CLIENT_UPLOAD_FILENAME, $origFilename, STORE_FORM);

318
            $pathFileName = $this->evaluate->parse($formElement[FE_FILE_DESTINATION]);
319
320
        }

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

325
326
327
        if (file_exists($pathFileName)) {
            throw new UserFormException('Copy upload failed - file already exist: ' . $pathFileName, ERROR_IO_FILE_EXIST);
        }
328

329
        Support::mkDirParent($pathFileName);
330

331
332
333
334
        $srcFile = Support::extendFilename($statusUpload[FILES_TMP_NAME], UPLOAD_CACHED);
        if (!rename($srcFile, $pathFileName)) {
            throw new UserFormException("Rename file: '$srcFile' > '$pathFileName'", ERROR_IO_RENAME);
        }
335

336
337
        return $pathFileName;
    }
338

339
340
341
342
343
344
345
    /**
     * Get the complete FormElement for $name
     *
     * @param $name
     * @return bool|array if found the FormElement, else false.
     */
    private function getFormElementByName($name) {
346

347
348
349
350
        foreach ($this->feSpecNative as $formElement) {
            if ($formElement['name'] === $name)
                return $formElement;
        }
351

352
353
354
        return false;
    }

355
}