Form.php 9.39 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;
13
14
15
use qfq\BuildFormPlain;
use qfq\BuildFormTable;
use qfq\BuildFormBootstrap;
16
17
18
use qfq\UserException;
use qfq\CodeException;
use qfq\DbException;
19
20
//use qfq\helper;
use qfq\Store;
Carsten  Rose's avatar
Carsten Rose committed
21

Carsten  Rose's avatar
Carsten Rose committed
22

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

36
37
38
39
40
41
42
43
44
45
46
47
/*
 * 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
48
49
50
51
/**
 * Class Form
 * @package qfq
 */
52
class Form {
Carsten  Rose's avatar
Carsten Rose committed
53

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

67
68
69
    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
70
71
72
73
74
75
76
77
78
79
80
81

    /*
     * 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'
     */
82
83


84
85
86
//    protected $formElements = null;
//    protected $userLog = null;

87
88
89
90
91
    /**
     * Construct the Form Class and Store too. This is the base initialization moment.
     *
     * As a result of instantiating of Form, the class Store will initially called the first time and therefore instantiated automatically.
     * Store might throw an exception, in case the URL-passed SIP is invalid.
92
     *
93
     * @param string $bodytext
94
     */
95
    public function __construct($bodytext = '') {
96

97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
        try {
            $this->store = Store::getInstance($bodytext);
            $this->db = new Database();
            $this->eval = new Evaluate($this->store, $this->db);
        } catch (UserException $e) {
            echo $e->formatMessage();
            exit;
        } catch (CodeException $e) {
            echo $e->formatMessage();
            exit;
        } catch (DbException $e) {
            echo $e->formatMessage();
            exit;
        } catch (\Exception $e) {
            echo "Generic Exception: " . $e->getMessage();
            exit;
        }


Carsten  Rose's avatar
Carsten Rose committed
116
117
    }

118
119
    /**
     * @return string
Carsten  Rose's avatar
Carsten Rose committed
120
     */
121
    public function process() {
122
123
        $html = '';
        $build = null;
124
        $master = null;
125

Carsten  Rose's avatar
Carsten Rose committed
126
127
        mb_internal_encoding("UTF-8");

128
129
130
131
132
133
//        render:
//        plain
//        multimode: none

        try {

134
            $formName = $this->loadFormSpecification();
135

Carsten  Rose's avatar
Carsten Rose committed
136
            $sipFound = $this->validateForm();
137
            if (!$sipFound) {
Carsten  Rose's avatar
Carsten Rose committed
138
                $this->store->createSipAfterFormLoad($formName);
139
            }
140

141
            $this->store->fillStoreTableDefaultColumnType($this->formSpec['tableName']);
142
143

            switch ($this->formSpec['render']) {
144
                case 'plain':
145
146
                    $build = new BuildFormPlain($this->formSpec, $this->feSpecAction, $this->feSpecNative);
                    break;
147
                case 'table':
148
                    $build = new BuildFormTable($this->formSpec, $this->feSpecAction, $this->feSpecNative);
149
150
                    break;
                case 'bootstrap':
151
                    $build = new BuildFormBootstrap($this->formSpec, $this->feSpecAction, $this->feSpecNative);
152
153
                    break;
                default:
154
                    throw new CodeException("This statement should never be reached", ERROR_CODE_SHOULD_NOT_HAPPEN);
155
156
            }

157
158
            // Form action: load or save?
            $mode = isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST' ? FORM_SAVE : FORM_LOAD;
159
160
            switch ($mode) {
                case FORM_LOAD:
161
                    $html .= $build->process();
162
163
164
165
                    break;
                case FORM_SAVE:
                    break;
                default:
166
                    throw new CodeException("This statement should never be reached", ERROR_CODE_SHOULD_NOT_HAPPEN);
167
168
169
170
171
172
173
174
175
176
177
178
            }
            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
179

180
        return $html;
181
182
    }

183
    /**
184
185
186
187
188
189
190
     * 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
     *
191
     * @return string formName
192
     * @throws DbException
193
     * @throws UserException
194
     */
195
    private function loadFormSpecification() {
Carsten  Rose's avatar
Carsten Rose committed
196

Carsten  Rose's avatar
Carsten Rose committed
197
        $formName = $this->getFormName();
198
        $this->store->setVar(SYSTEM_FORM, $formName, STORE_SYSTEM);
Carsten  Rose's avatar
Carsten Rose committed
199

200
        $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], 'Form not found or multiple forms with the same name.'));
201
        HelperFormElement::explodeFieldParameter($this->formSpec);
202

Carsten  Rose's avatar
Carsten Rose committed
203
        // Clear
204
205
        $this->store->setVar(SYSTEM_FORM_ELEMENT, '', STORE_SYSTEM);

206
207
208
209
210
        // "SELECT *, ? AS 'nestedInFieldSet' FROM FormElement AS fe WHERE fe.formId = ? AND fe.deleted = 'no' AND FIND_IN_SET(fe.class, ? ) AND fe.feIdContainer = ? AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
        $sql = SQL_FORM_ELEMENT;

        // FE: Action
        $this->feSpecAction = $this->eval->parseArray($this->db->sql($sql, ROW_REGULAR, ['no', $this->formSpec["id"], 'action', 0]));
211
        HelperFormElement::explodeFieldParameter($this->feSpecAction);
212
213
214

        // FE: Native & Container
        $this->feSpecNative = $this->db->sql($sql, ROW_REGULAR, ['no', $this->formSpec["id"], 'native,container', 0]);
215
        HelperFormElement::explodeFieldParameter($this->feSpecNative);
216
217

        return $formName;
Carsten  Rose's avatar
Carsten Rose committed
218
219
    }

Carsten  Rose's avatar
Carsten Rose committed
220
221
222
    /**
     * @return string
     * @throws UserException
223
     * @throws CodeException
Carsten  Rose's avatar
Carsten Rose committed
224
225
     */

Carsten  Rose's avatar
Carsten Rose committed
226
227
    private function getFormName() {

228
        $formName = $this->store->getVar(SIP_FORM, STORE_TYPO3 . STORE_SIP . STORE_CLIENT);
Carsten  Rose's avatar
Carsten Rose committed
229
230
231
        if ($formName !== false)
            return $formName;

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

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

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

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

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

282
        return $sipFound;
283
    }
Carsten  Rose's avatar
Carsten Rose committed
284

285
}