Form.php 9.53 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;
Carsten  Rose's avatar
Carsten Rose committed
17
18
use qfq\store\Store;

Carsten  Rose's avatar
Carsten Rose committed
19

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

32
33
34
35
36
37
38
39
40
41
42
43
/*
 * 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>
 */

44
class Form {
Carsten  Rose's avatar
Carsten Rose committed
45

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

59
60
61
    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
62
63
64
65
66
67
68
69
70
71
72
73

    /*
     * 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'
     */
74
75


76
77
78
79
80
81
//    protected $formElements = null;
//    protected $userLog = null;

    /*
     *
     */
82
    public function __construct($bodytext = '') {
83

Carsten  Rose's avatar
Carsten Rose committed
84
        $this->store = Store::getInstance($bodytext);
85
        $this->db = new Database();
86
        $this->eval = new Evaluate($this->store, $this->db);
Carsten  Rose's avatar
Carsten Rose committed
87
88
    }

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

Carsten  Rose's avatar
Carsten Rose committed
97
98
        mb_internal_encoding("UTF-8");

99
100
101
102
103
104
105
106
//        render:
//        plain
//        multimode: none

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

107
            $formName = $this->loadFormSpecification();
108

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

114
            $this->store->fillStoreTableDefaultColumnType($this->formSpec['tableName']);
115
116

            switch ($this->formSpec['render']) {
117
                case 'plain':
118
119
                    $build = new BuildFormPlain($this->formSpec, $this->feSpecAction, $this->feSpecNative);
                    break;
120
                case 'table':
121
                    $build = new BuildFormTable($this->formSpec, $this->feSpecAction, $this->feSpecNative);
122
123
                    break;
                case 'bootstrap':
124
                    $build = new BuildFormBootstrap($this->formSpec, $this->feSpecAction, $this->feSpecNative);
125
126
                    break;
                default:
127
                    throw new CodeException("This statement should never be reached", ERROR_CODE_SHOULD_NOT_HAPPEN);
128
129
130
131
            }

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

151
        return $html;
152
153
    }

154
    /**
155
156
157
158
159
160
161
     * 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
     *
162
     * @return string formName
163
     * @throws DbException
164
     * @throws UserException
165
     */
166
    private function loadFormSpecification() {
Carsten  Rose's avatar
Carsten Rose committed
167

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

171
172
173
174
175
176
177
178
        $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
179
        $this->feSpecNative = $this->db->sql($sql, ROW_REGULAR, [$this->formSpec["id"], 'native']);
180
        $this->explodeFormElementParameter($this->feSpecNative);
181
182

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

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

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

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

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

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
    /**
     * 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);
            }
        }
    }

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

        // 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
240
        $mode = ($r > 0) ? $this->formSpec['permitEdit'] : $this->formSpec['permitNew'];
241
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

        $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
269

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

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

278
}