Form.php 9.67 KB
Newer Older
1
2
3
4
5
6
7
8
<?php
/**
 * Created by PhpStorm.
 * User: ep
 * Date: 12/23/15
 * Time: 6:33 PM
 */

Carsten  Rose's avatar
Carsten Rose committed
9

10
11
namespace qfq;

Carsten  Rose's avatar
Carsten Rose committed
12
use qfq;
Carsten  Rose's avatar
Carsten Rose committed
13
use qfq\exceptions\UserException;
14
15
use qfq\exceptions\CodeException;
use qfq\exceptions\DbException;
16
use qfq\helper;
17
use qfq\store;
Carsten  Rose's avatar
Carsten Rose committed
18

19
require_once(__DIR__ . '/../qfq/store/Store.php');
Carsten  Rose's avatar
Carsten Rose committed
20
require_once(__DIR__ . '/../qfq/Constants.php');
Carsten  Rose's avatar
Carsten Rose committed
21
require_once(__DIR__ . '/../qfq/helper/KeyValueStringParser.php');
Carsten  Rose's avatar
Carsten Rose committed
22
require_once(__DIR__ . '/../qfq/exceptions/UserException.php');
23
24
require_once(__DIR__ . '/../qfq/exceptions/CodeException.php');
require_once(__DIR__ . '/../qfq/exceptions/DbException.php');
25
require_once(__DIR__ . '/../qfq/Database.php');
Carsten  Rose's avatar
Carsten Rose committed
26
require_once(__DIR__ . '/../qfq/Evaluate.php');
27
require_once(__DIR__ . '/../qfq/FormBuildPlain.php');
28

29
30
31
32
33
34
35
36
37
38
39
40
/*
 * Form will be called
 * a) with a SIP identifier, or
 * b) without a SIP identifier (form setting has to allow this) and will create on the fly a new SIP.
 *
 * The SIP-Store stores:
 *  form=<formname>
 *  r=<record id>  (table.id for a single record form)
 *  keySemId,keySemIduser
 *  <further individual variables>
 */

41
class Form {
Carsten  Rose's avatar
Carsten Rose committed
42

43
44
45
    /**
     * @var Store instantiated class
     */
Carsten  Rose's avatar
Carsten Rose committed
46
    protected $store = null;
47
48
49
    /**
     * @var Database instantiated class
     */
50
    protected $db = null;
51
52
53
54
55
    /**
     * @var Evaluate instantiated class
     */
    protected $eval = null;

56
57
58
    protected $formSpec = array();  // Form Definition: copy of the loaded form
    protected $feSpecAction = array(); // FormEelement Definition: all formElement.class='action' of the loaded form
    protected $feSpecNative = array(); // FormEelement Definition: all formElement.class='native' of the loaded form
59
60
61
62
63
64
65
66
67
68
69
70

    /*
     * TODO:
     *  Preparation: setup logging, database access, record locking
     *  fill stores
     *  Check permission_create / permission_update
     *  Multi: iterate over all records, Single: activate record
     *      Check mode: Load | Save
     *      doActions 'Before'
     *      Do all FormElements
     *      doActions 'After'
     */
71
72


73
74
75
76
77
78
//    protected $formElements = null;
//    protected $userLog = null;

    /*
     *
     */
79
    public function __construct($bodytext = '') {
80

81
        $this->store = \qfq\store\Store::getInstance($bodytext);
82
        $this->db = new Database();
83
        $this->eval = new Evaluate($this->store, $this->db);
Carsten  Rose's avatar
Carsten Rose committed
84
85
    }

86
87
    /**
     * @return string
Carsten  Rose's avatar
Carsten Rose committed
88
     */
89
    public function process() {
90
91
        $html = '';
        $build = null;
92
        $master = null;
93

Carsten  Rose's avatar
Carsten Rose committed
94
95
        mb_internal_encoding("UTF-8");

96
97
98
99
100
101
102
103
//        render:
//        plain
//        multimode: none

        try {
            // Form action: load or save?
            $mode = ($this->store->getVar(CLIENT_POST_SIP, STORE_CLIENT) === false) ? FORM_LOAD : FORM_SAVE;

104
            $formName = $this->loadFormSpecification();
105

Carsten  Rose's avatar
Carsten Rose committed
106
            $sipFound = $this->validateForm();
107
            if (!$sipFound) {
Carsten  Rose's avatar
Carsten Rose committed
108
                $this->store->createSipAfterFormLoad($formName);
109
            }
110

111
            $this->store->fillStoreTableDefaultColumnType($this->formSpec['tableName']);
112
113

            switch ($this->formSpec['render']) {
114
                case 'plain':
115
                    $build = new FormBuildPlain($this->formSpec, $this->feSpecAction, $this->feSpecNative);
116
                    break;
117
118
119
120
121
                case 'table':
                    //TODO: implement bootstrap rendering: FormBuildBootstrap
//                    $build = new FormBuildBootstrap($this->formDef, $this->feDefAction, $this->feDefNative);
                    break;
                case 'bootstrap':
122
                    //TODO: implement bootstrap rendering: FormBuildBootstrap
123
//                    $build = new FormBuildBootstrap($this->formDef, $this->feDefAction, $this->feDefNative);
124
125
                    break;
                default:
126
                    throw new CodeException("This statement should never be reached", ERROR_CODE_SHOULD_NOT_HAPPEN);
127
128
129
130
131
            }

            switch ($mode) {
                case FORM_LOAD:
//                    $this->formActionBefore();
132
                    $html .= $build->process();
133
134
135
136
137
//                    $this->formActionAfter();
                    break;
                case FORM_SAVE:
                    break;
                default:
138
                    throw new CodeException("This statement should never be reached", ERROR_CODE_SHOULD_NOT_HAPPEN);
139
140
141
142
143
144
145
146
147
148
149
150
            }
            return $html;

        } catch (UserException $e) {
            echo $e->formatMessage();
        } catch (CodeException $e) {
            echo $e->formatMessage();
        } catch (DbException $e) {
            echo $e->formatMessage();
        } catch (\Exception $e) {
            echo "Generic Exception: " . $e->getMessage();
        }
Carsten  Rose's avatar
Carsten Rose committed
151

152
        return $html;
153
154
    }

155
    /**
156
157
158
159
160
161
162
     * Loads specification of recent form.
     * Evaluates Form and all FormElements.
     *
     * Loaded Form is in  $this->formSpec
     * Loaded 'action' FormElements are in $this->feSpecAction
     * Loaded 'native' FormElements are in $this->feSpecNative
     *
163
     * @return string formName
164
     * @throws DbException
165
     * @throws UserException
166
     */
167
    private function loadFormSpecification() {
Carsten  Rose's avatar
Carsten Rose committed
168

Carsten  Rose's avatar
Carsten Rose committed
169
        $formName = $this->getFormName();
170
        $this->store->setVar(SYSTEM_FORM, $formName, STORE_SYSTEM);
Carsten  Rose's avatar
Carsten Rose committed
171

172
173
174
175
176
177
178
179
        $this->formSpec = $this->eval->parseArray($this->db->sql("SELECT * FROM Form AS f WHERE f.name LIKE ? AND f.deleted='no'", ROW_EXACT_1, [$formName]));

        $this->store->setVar(SYSTEM_FORM_ELEMENT, '', STORE_SYSTEM);
        $sql = "SELECT * FROM FormElement AS fe WHERE fe.formId = ? AND fe.deleted='no' AND fe.class = ? AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
        $this->feSpecAction = $this->eval->parseArray($this->db->sql($sql, ROW_REGULAR, [$this->formSpec["id"], 'action']));
        $this->explodeFormElementParameter($this->feSpecAction);

        // Native FormElements will be evaluated later
180
        $this->feSpecNative = $this->db->sql($sql, ROW_REGULAR, [$this->formSpec["id"], 'native']);
181
        $this->explodeFormElementParameter($this->feSpecNative);
182
183

        return $formName;
Carsten  Rose's avatar
Carsten Rose committed
184
185
    }

Carsten  Rose's avatar
Carsten Rose committed
186
187
188
189
    /**
     * @return string
     * @throws UserException
     * @throws exceptions\CodeException
Carsten  Rose's avatar
Carsten Rose committed
190
191
     */

Carsten  Rose's avatar
Carsten Rose committed
192
193
    private function getFormName() {

194
        $formName = $this->store->getVar(SIP_FORM, STORE_TYPO3 . STORE_SIP . STORE_CLIENT);
Carsten  Rose's avatar
Carsten Rose committed
195
196
197
        if ($formName !== false)
            return $formName;

198
        // TODO: phpunit tests all - missing formname always fires this exception
Carsten  Rose's avatar
Carsten Rose committed
199
        throw new UserException("Missing form name. Not found in T3_BODYTEXT / SIP / GET", ERROR_MISSING_FORM_NAME);
200
    }
Carsten  Rose's avatar
Carsten Rose committed
201

202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
    /**
     * Iterates over all FormElements in $elements.
     * If $element['parameter'] exist and is filled: explode the content and apend them to the current FormElement Array.
     *
     * @param array $elements
     * @throws UserException
     */
    private function explodeFormElementParameter(array &$elements) {
        // Iterate all FormElements
        foreach ($elements AS $key => $element) {
            // Something to explode?
            if (isset($element['parameter']) && $element['parameter'] !== '') {
                // Explode
                $arr = helper\KeyValueStringParser::parse($element['parameter'], "=", "\n");
                // Check if some of the exploded keys conflict with existing keys
                $checkKeys = array_keys($arr);
                foreach ($checkKeys AS $checkKey) {
                    if (isset($element[$checkKey])) {
                        $this->store->setVar(SYSTEM_FORM_ELEMENT, $element['name'] . ' / ' . $element['id'], STORE_SYSTEM);
                        $this->store->setVar(SYSTEM_FORM_ELEMENT_COLUMN, 'parameter', STORE_SYSTEM);
                        throw new UserException("Found reserved keyname '$checkKey'");
                    }
                }
                $elements[$key] = array_merge($element, $arr);
            }
        }
    }

230
231
232
233
234
    /**
     * @return bool - 'true' if SIP exists, else 'false'
     * @throws CodeException
     * @throws UserException
     */
Carsten  Rose's avatar
Carsten Rose committed
235
    private function validateForm() {
236
237
238
239
240

        // Retrieve record_id either from SIP (prefered) or via URL
        $r = $this->store->getVar(SIP_RECORD_ID, STORE_SIP . STORE_CLIENT);

        // If there is a record_id>0: EDIT else NEW
241
        $mode = ($r > 0) ? $this->formSpec['permitEdit'] : $this->formSpec['permitNew'];
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269

        $feUserLoggedIn = isset($GLOBALS["TSFE"]->fe_user->user["uid"]) && $GLOBALS["TSFE"]->fe_user->user["uid"] > 0;

        $sipFound = $this->store->getVar(SIP_SIP, STORE_SIP) !== false;

        switch ($mode) {
            case  FORM_PERMISSION_SIP:
                if (!$sipFound) {
                    throw new UserException("SIP Parameter needed for this form.", ERROR_SIP_NEEDED_FOR_THIS_FORM);
                }
                break;
            case  FORM_PERMISSION_LOGGED_IN:
                if (!$feUserLoggedIn) {
                    throw new UserException("User not logged in.", ERROR_USER_NOT_LOGGED_IN);
                }
                break;
            case FORM_PERMISSION_LOGGED_OUT:
                if ($feUserLoggedIn) {
                    throw new UserException("User logged in.", ERROR_USER_LOGGED_IN);
                }
                break;
            case FORM_PERMISSION_ALWAYS:
                break;
            case FORM_PERMISSION_NEVER:
                throw new UserException("Loading form forbidden.", ERROR_FORM_FORBIDDEN);
            default:
                throw new CodeException("Unknown permission mode: '" . $mode . "'", ERROR_FORM_UNKNOWN_PERMISSION_MODE);
        }
Carsten  Rose's avatar
Carsten Rose committed
270

Carsten  Rose's avatar
Carsten Rose committed
271
        // Form Definition valid?
272
        if ($this->formSpec['multiMode'] !== 'none' && $this->formSpec['multiSql'] === '') {
Carsten  Rose's avatar
Carsten Rose committed
273
274
275
            throw new UserException("MultiMode selected, but MultiSQL missing", ERROR_MULTI_SQL_MISSING);
        }

276
        return $sipFound;
277
    }
Carsten  Rose's avatar
Carsten Rose committed
278

279
}