Form.php 9.57 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>
 */

Carsten  Rose's avatar
Carsten Rose committed
44
45
46
47
/**
 * Class Form
 * @package qfq
 */
48
class Form {
Carsten  Rose's avatar
Carsten Rose committed
49

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

63
64
65
    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
66
67
68
69
70
71
72
73
74
75
76
77

    /*
     * 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'
     */
78
79


80
81
82
83
84
85
//    protected $formElements = null;
//    protected $userLog = null;

    /*
     *
     */
86
    public function __construct($bodytext = '') {
87

Carsten  Rose's avatar
Carsten Rose committed
88
        $this->store = Store::getInstance($bodytext);
89
        $this->db = new Database();
90
        $this->eval = new Evaluate($this->store, $this->db);
Carsten  Rose's avatar
Carsten Rose committed
91
92
    }

93
94
    /**
     * @return string
Carsten  Rose's avatar
Carsten Rose committed
95
     */
96
    public function process() {
97
98
        $html = '';
        $build = null;
99
        $master = null;
100

Carsten  Rose's avatar
Carsten Rose committed
101
102
        mb_internal_encoding("UTF-8");

103
104
105
106
107
108
109
110
//        render:
//        plain
//        multimode: none

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

111
            $formName = $this->loadFormSpecification();
112

Carsten  Rose's avatar
Carsten Rose committed
113
            $sipFound = $this->validateForm();
114
            if (!$sipFound) {
Carsten  Rose's avatar
Carsten Rose committed
115
                $this->store->createSipAfterFormLoad($formName);
116
            }
117

118
            $this->store->fillStoreTableDefaultColumnType($this->formSpec['tableName']);
119
120

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

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

155
        return $html;
156
157
    }

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

Carsten  Rose's avatar
Carsten Rose committed
172
        $formName = $this->getFormName();
173
        $this->store->setVar(SYSTEM_FORM, $formName, STORE_SYSTEM);
Carsten  Rose's avatar
Carsten Rose committed
174

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

        return $formName;
Carsten  Rose's avatar
Carsten Rose committed
187
188
    }

Carsten  Rose's avatar
Carsten Rose committed
189
190
191
192
    /**
     * @return string
     * @throws UserException
     * @throws exceptions\CodeException
Carsten  Rose's avatar
Carsten Rose committed
193
194
     */

Carsten  Rose's avatar
Carsten Rose committed
195
196
    private function getFormName() {

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

201
        // TODO: phpunit tests all - missing formname always fires this exception
Carsten  Rose's avatar
Carsten Rose committed
202
        throw new UserException("Missing form name. Not found in T3_BODYTEXT / SIP / GET", ERROR_MISSING_FORM_NAME);
203
    }
Carsten  Rose's avatar
Carsten Rose committed
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
230
231
232
    /**
     * 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);
            }
        }
    }

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

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

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

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

279
        return $sipFound;
280
    }
Carsten  Rose's avatar
Carsten Rose committed
281

282
}