FormAction.php 14.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 5/29/16
 * Time: 5:24 PM
 */

namespace qfq;

require_once(__DIR__ . '/../Constants.php');
12
require_once(__DIR__ . '/../database/Database.php');
13
14
require_once(__DIR__ . '/../store/Store.php');
require_once(__DIR__ . '/../Evaluate.php');
15
require_once(__DIR__ . '/../report/Sendmail.php');
16
require_once(__DIR__ . '/../helper/HelperFormElement.php');
17
require_once(__DIR__ . '/../exceptions/UserFormException.php');
18
19
20
21
22

/**
 * Class formAction
 * @package qfq
 */
23
class FormAction {
24
25

//    private $feSpecNative = array(); // copy of all formElement.class='native' of the loaded form
26
27
28
29
30
    /**
     * @var Evaluate instantiated class
     */
    protected $evaluate = null;  // copy of the loaded form
    private $formSpec = array();
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
    private $primaryTableName = '';
    /**
     * @var Database
     */
    private $db = null;
    /**
     * @var Store
     */
    private $store = null;

    /**
     * @param array $formSpec
     * @param Database $db
     * @param bool|false $phpUnit
     */
    public function __construct(array $formSpec, Database $db, $phpUnit = false) {
        $this->formSpec = $formSpec;
48
        $this->primaryTableName = Support::setIfNotSet($formSpec, F_TABLE_NAME);
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

        $this->db = $db;

        $this->store = Store::getInstance('', $phpUnit);

        $this->evaluate = new Evaluate($this->store, $this->db);

    }

    /**
     * @param integer $recordId
     * @param array $feSpecAction
     * @param string $feTypeList
     *         On FormLoad: FE_TYPE_BEFORE_LOAD, FE_TYPE_AFTER_LOAD
     *         Before Save: FE_TYPE_BEFORE_SAVE, FE_TYPE_BEFORE_INSERT, FE_TYPE_BEFORE_UPDATE, FE_TYPE_BEFORE_DELETE
     *         After Save: FE_TYPE_AFTER_SAVE, FE_TYPE_AFTER_INSERT, FE_TYPE_AFTER_UPDATE, FE_TYPE_AFTER_DELETE
65
     * @return bool: true if there are potential changes on the DB like fired SQL statements, else false.
66
67
68
69
     * @throws CodeException
     * @throws DbException
     * @throws UserFormException
     */
70
    public function elements($recordId, array $feSpecAction, $feTypeList) {
71

72
73
        $flagModified = false;

74
75
76
        // Iterate over all Action FormElements
        foreach ($feSpecAction as $fe) {

77
78
79
            // Preparation for Log, Debug
            $this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($fe), STORE_SYSTEM);

80
            $fe = HelperFormElement::initActionFormElement($fe);
81

82
            // Only process FE elements of types listed in $feTypeList. Skip all other
83
84
85
86
            if (false === Support::findInSet($fe[FE_TYPE], $feTypeList)) {
                continue;
            }

87
88
            // Process templateGroup action elements
            if (isset($fe[FE_ID_CONTAINER]) && $fe[FE_ID_CONTAINER] > 0) {
Carsten  Rose's avatar
TODO    
Carsten Rose committed
89
//TODO: hier muss ein FE_TG_DEFAULT gesetzt werden, im Fall es ist keine maxlength angegeben.
90
                // Get native 'templateGroup'-FE - to retrieve MAX_LENGTH
91
                $feTemplateGroup = $this->db->sql(SQL_FORM_ELEMENT_TEMPLATE_GROUP_FE_ID, ROW_REGULAR, [$fe[FE_ID_CONTAINER]]);
92
93
94
95
96
97
98
99
100
                if (isset($feTemplateGroup[0][FE_TYPE]) && $feTemplateGroup[0][FE_TYPE] == FE_TYPE_TEMPLATE_GROUP) {
                    if (count($feTemplateGroup) == 1) {
                        $fe[FE_ID_CONTAINER] = 0;
                        for ($ii = 1; $ii <= $feTemplateGroup[0][FE_MAX_LENGTH]; $ii++) {
                            $feNew = OnArray::arrayValueReplace($fe, FE_TEMPLATE_GROUP_NAME_PATTERN, $ii);
                            $feNew = OnArray::arrayValueReplace($feNew, FE_TEMPLATE_GROUP_NAME_PATTERN_0, $ii - 1);
                            if ($this->elements($recordId, [$feNew], $feTypeList)) {
                                $flagModified = true;
                            }
101
                        }
102
103
104
                    } else {
                        // At the moment 'action' elements have to point to a templateGroup - nothing else is defined. Break if there is something else
                        throw new UserFormException("Expect a 'templateGroup' record in FormElement.id=" . $fe[FE_ID_CONTAINER], ERROR_RECORD_NOT_FOUND);
105
                    }
106
                    continue; // skip to next FormElement
107
108
                }
            }
109

110
111
112
113
114
115
116
117
            switch ($fe[FE_TYPE]) {
                case FE_TYPE_BEFORE_LOAD:
                case FE_TYPE_AFTER_LOAD:
                case FE_TYPE_AFTER_DELETE:  # Main record is already deleted. Do not try to load it again.
                    break;
                default:
                    // Always work on recent data: previous actions might have modified the data.
                    $this->fillStoreRecord($this->primaryTableName, $recordId);
118
119
            }

120
121
122
123
            if (!$this->checkRequiredList($fe)) {
                continue;
            }

124
125
126
127
128
            if (isset($fe[FE_FILL_STORE_LDAP])) {
                $keyNames = [F_LDAP_SERVER, F_LDAP_BASE_DN, F_LDAP_ATTRIBUTES, F_LDAP_SEARCH, F_LDAP_TIME_LIMIT];
                $fe = OnArray::copyArrayItemsIfNotAlreadyExist($this->formSpec, $fe, $keyNames);

                // Extract necessary elements
129
                $config = OnArray::getArrayItems($fe, [FE_LDAP_SERVER, FE_LDAP_BASE_DN, FE_LDAP_SEARCH, FE_LDAP_ATTRIBUTES, FE_LDAP_USE_BIND_CREDENTIALS]);
130
131
                $config = $this->evaluate->parseArray($config);

132
                if ($fe[FE_LDAP_USE_BIND_CREDENTIALS] == 1) {
133
134
135
136
                    $config[SYSTEM_LDAP_1_RDN] = $this->store->getVar(SYSTEM_LDAP_1_RDN, STORE_SYSTEM);
                    $config[SYSTEM_LDAP_1_PASSWORD] = $this->store->getVar(SYSTEM_LDAP_1_PASSWORD, STORE_SYSTEM);
                }

137
138
139
140
141
                $ldap = new Ldap();
                $arr = $ldap->process($config, '', MODE_LDAP_SINGLE);
                $this->store->setStore($arr, STORE_LDAP, true);
            }

142
143
144
145
146
            if ($fe[FE_TYPE] === FE_TYPE_SENDMAIL) {
                $this->sendMail($fe);
                //no further processing of current element necessary.
                continue;
            }
147
148
149

            $this->validate($fe);

150
            $this->doSlave($fe, $recordId);
151
152

            $flagModified = true;
153
        }
154
155

        return $flagModified;
156
157
158
    }

    /**
159
160
     * Copy the current primary record to STORE_RECORD
     *
161
162
     * @param $table
     * @param $recordId
163
     * @throws CodeException
164
     * @throws DbException
165
166
     * @throws UserFormException
     */
167
    private function fillStoreRecord($table, $recordId) {
168

169
170
        if (!is_string($table) || $table === '') {
            throw new UserFormException("");
171
        }
172

173
174
        if ($recordId !== false && $recordId > 0) {
            $record = $this->db->sql("SELECT * FROM $table WHERE id = ?", ROW_EXPECT_1, [$recordId]);
175
            $this->store->setStore($record, STORE_RECORD, true);
176
        }
177
    }
178

179
    /**
180
     * Process all FormElements given in the `requiredList` identified by their name.
181
182
183
184
185
186
187
     * If none is empty in STORE_FORM return true, else false.
     * If none FormElement is specified, return true.
     *
     * @param array $fe
     * @return bool  true if none FE is specified or all specified are non empty.
     */
    private function checkRequiredList(array $fe) {
188

189
190
        if (!isset($fe[FE_REQUIRED_LIST]) || $fe[FE_REQUIRED_LIST] === '') {
            return true;
191
192
        }

193
194
195
196
        $arr = explode(',', $fe[FE_REQUIRED_LIST]);
        foreach ($arr as $key) {

            $key = trim($key);
197
            $val = $this->store->getVar($key, STORE_FORM, SANITIZE_ALLOW_ALL);
198
199
200
201

            if ($val === false || $val === '' || $val === '0') {
                return false;
            }
202
203
        }

204
        return true;
205
206
    }

207
208
209
210
211
212
213
214
215
216
    /**
     * @param array $feSpecAction
     */
    private function sendMail(array $feSpecAction) {

        $mail[SENDMAIL_IDX_RECEIVER] = $this->evaluate->parse($feSpecAction[FE_SENDMAIL_TO]);
        $mail[SENDMAIL_IDX_SENDER] = $this->evaluate->parse($feSpecAction[FE_SENDMAIL_FROM]);
        $mail[SENDMAIL_IDX_SUBJECT] = $this->evaluate->parse($feSpecAction[FE_SENDMAIL_SUBJECT]);
        $mail[SENDMAIL_IDX_BODY] = $this->evaluate->parse($feSpecAction[FE_VALUE]);
        $mail[SENDMAIL_IDX_REPLY_TO] = $this->evaluate->parse($feSpecAction[FE_SENDMAIL_REPLY_TO]);
217
        $mail[SENDMAIL_IDX_FLAG_AUTO_SUBMIT] = $this->evaluate->parse($feSpecAction[FE_SENDMAIL_FLAG_AUTO_SUBMIT]) === 'off' ? 'off' : 'on';
218
219
220
221
        $mail[SENDMAIL_IDX_GR_ID] = $this->evaluate->parse($feSpecAction[FE_SENDMAIL_GR_ID]);
        $mail[SENDMAIL_IDX_X_ID] = $this->evaluate->parse($feSpecAction[FE_SENDMAIL_X_ID]);
        $mail[SENDMAIL_IDX_RECEIVER_CC] = $this->evaluate->parse($feSpecAction[FE_SENDMAIL_CC]);
        $mail[SENDMAIL_IDX_RECEIVER_BCC] = $this->evaluate->parse($feSpecAction[FE_SENDMAIL_BCC]);
Carsten  Rose's avatar
Carsten Rose committed
222
        $mail[SENDMAIL_IDX_SRC] = "FormId: " . $feSpecAction[FE_FORM_ID] . ", FormElementId: " . $feSpecAction['id'];
223
224
225
226
227

        // Mail: send
        new Sendmail($mail);
    }

228
229
230
231
    /**
     * If there is a query defined in fe.parameter.FE_SQL_VALIDATE: fire them.
     * Count the selected records and compare them with fe.parameter.FE_EXPECT_RECORDS.
     * If match: everything is fine, do nothing.
232
     * Else throw UserFormException with error message of fe.parameter.FE_MESSAGE_FAIL
233
234
235
236
237
238
239
     *
     * @param array $fe
     * @throws UserFormException
     */
    private function validate(array $fe) {

        // Is there something to check?
240
        if ($fe[FE_SQL_VALIDATE] === '') {
241
242
243
            return;
        }

244
        $expect = $this->evaluate->parse($fe[FE_EXPECT_RECORDS]);
245

246
        if ($fe[FE_MESSAGE_FAIL] === '') {
247
248
249
250
251
252
253
254
255
            throw new UserFormException("Missing error message. Column: " . FE_MESSAGE_FAIL, ERROR_MISSING_MESSAGE_FAIL);
        }

        // Do the check
        $result = $this->evaluate->parse($fe[FE_SQL_VALIDATE]);
        if (!is_array($result)) {
            throw new UserFormException("Expected an array for '" . FE_SQL_VALIDATE . "', got a scalar. Please check for {{!...", ERROR_EXPECTED_ARRAY);
        }

256
257
258
259
260
261
        // If there is at least one record count given, who matches: return 'check succeeded'
        $countRecordsArr = explode(',', $expect);
        foreach ($countRecordsArr AS $count) {
            if (count($result) == $count) {
                return; // check succesfully passed
            }
262
263
        }

264
        $msg = $this->evaluate->parse($fe[FE_MESSAGE_FAIL]); // Replace possible dynamic parts
265
266
267
268
269

        // Throw user defineable error message
        throw new UserFormException($msg, ERROR_REPORT_FAILED_ACTION);
    }

270
271
272
273
    /**
     * Create the slave record. First try to evaluate a slaveId. Depending if the slaveId > 0 choose `sqlUpdate` or `sqlInsert`
     *
     * @param array $fe
274
     * @param int $recordId
275
276
     * @return int
     * @throws CodeException
277
     * @throws DbException
278
279
     * @throws UserFormException
     */
280
    private function doSlave(array $fe, $recordId) {
281
282

        // Get the slaveId
283
        $slaveId = $this->evaluate->parse($fe[FE_SLAVE_ID]);
284
285
286
287
288
289
290
291
292
293
294

        if ($slaveId === '' && $fe[FE_NAME] !== '') {
            // if the current action element has the same name as a real master record column: take that value as an id
            $slaveId = $this->store->getVar($fe[FE_NAME], STORE_RECORD);
        }

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

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

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

300
301
        $doInsert = ($slaveId == 0);
        $doUpdate = ($slaveId != 0);
302
        $doDelete = ($slaveId != 0) && $fe[FE_SQL_DELETE] != '';
303
304
305
306
307
308
309
310
311

        $flagHonor = isset($fe[FE_SQL_HONOR_FORM_ELEMENTS]) && $fe[FE_SQL_HONOR_FORM_ELEMENTS] != '';
        if ($flagHonor) {
            $filled = $this->checkFormElements($fe[FE_SQL_HONOR_FORM_ELEMENTS]);
            $doInsert = $filled && $doInsert;
            $doUpdate = $filled && $doUpdate;
            $doDelete = !$filled && $doDelete;
        }

312
        // Fire slave query
313
        if ($doInsert) {
314
            $slaveId = $this->evaluate->parse($fe[FE_SQL_INSERT]);
315
316
            // Store the slaveId: might be used later
            $this->store->setVar(VAR_SLAVE_ID, $slaveId, STORE_VAR, true);
317
318
319
        }

        if ($doUpdate) {
320
321
322
            $this->evaluate->parse($fe[FE_SQL_UPDATE]);
        }

323
324
325
326
327
328
        // Fire a delete query
        if ($doDelete) {
            $this->evaluate->parse($fe[FE_SQL_DELETE]);
            $slaveId = 0;
        }

329
330
        // Check if there is a column with the same name as the 'action'-FormElement.
        if (false !== $this->store->getVar($fe[FE_NAME], STORE_RECORD)) {
331
            // After an insert or update, propagate the (new) slave id to the master record.
332
333
334
            $this->db->sql("UPDATE " . $this->primaryTableName . " SET " . $fe[FE_NAME] . " = $slaveId WHERE id = ? LIMIT 1", ROW_REGULAR, [$recordId]);
        }

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


339
340
        return $slaveId;
    }
341

342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
    /**
     * Iterates over list of FormElement-names and check STORE_FORM if there is a corresponding value. If at least one
     * of the give elements is non empty, return true. If all elements are empty, return false.
     *
     * @param string $listOfFormElementNames E.g.: 'city, street, number'
     * @return bool true if at lease one of the named elements is non empty on STORE_FORM (use SANATIZE_ALLOW_ALL to perform the check)
     */
    private function checkFormElements($listOfFormElementNames) {
        $arr = explode(',', $listOfFormElementNames);

        foreach ($arr as $key) {
            $value = $this->store->getVar(trim($key), STORE_FORM . STORE_EMPTY, SANITIZE_ALLOW_ALL);
            if ($value != '') {
                return true;
            }
        }
        return false;
    }
360

361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
    /**
     * Set all necessary keys - subsequent 'isset()' are not necessary anymore.
     *
     * @param array $fe
     * @return array
     */
    private function initActionFormElement(array $fe) {

        $list = [FE_TYPE, FE_SLAVE_ID, FE_SQL_VALIDATE, FE_SQL_BEFORE, FE_SQL_INSERT, FE_SQL_UPDATE, FE_SQL_DELETE,
            FE_SQL_AFTER, FE_EXPECT_RECORDS, FE_REQUIRED_LIST, FE_MESSAGE_FAIL, FE_SENDMAIL_TO, FE_SENDMAIL_CC,
            FE_SENDMAIL_BCC, FE_SENDMAIL_FROM, FE_SENDMAIL_SUBJECT, FE_SENDMAIL_REPLY_TO, FE_SENDMAIL_FLAG_AUTO_SUBMIT,
            FE_SENDMAIL_GR_ID, FE_SENDMAIL_X_ID];

        foreach ($list as $key) {
            Support::setIfNotSet($fe, $key);
        }

        return $fe;
    }
380
}