Form.php 9.36 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
require_once(__DIR__ . '/../qfq/BuildForm.php');
29

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

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

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

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

    /*
     * 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'
     */
72
73


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

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

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

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

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

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

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

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

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

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

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

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

148
        return $html;
149
150
    }

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

Carsten  Rose's avatar
Carsten Rose committed
165
        $formName = $this->getFormName();
166
        $this->store->setVar(SYSTEM_FORM, $formName, STORE_SYSTEM);
Carsten  Rose's avatar
Carsten Rose committed
167

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

        return $formName;
Carsten  Rose's avatar
Carsten Rose committed
180
181
    }

Carsten  Rose's avatar
Carsten Rose committed
182
183
184
185
    /**
     * @return string
     * @throws UserException
     * @throws exceptions\CodeException
Carsten  Rose's avatar
Carsten Rose committed
186
187
     */

Carsten  Rose's avatar
Carsten Rose committed
188
189
    private function getFormName() {

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

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

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

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

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

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

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

272
        return $sipFound;
273
    }
Carsten  Rose's avatar
Carsten Rose committed
274

275
}