|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace SilverStripe\Forms\Schema; |
|
4
|
|
|
|
|
5
|
|
|
use InvalidArgumentException; |
|
6
|
|
|
use SilverStripe\Control\HTTPRequest; |
|
7
|
|
|
use SilverStripe\Forms\CompositeField; |
|
8
|
|
|
use SilverStripe\Forms\Form; |
|
9
|
|
|
use SilverStripe\Forms\FormField; |
|
10
|
|
|
use SilverStripe\ORM\ValidationResult; |
|
11
|
|
|
|
|
12
|
|
|
/** |
|
13
|
|
|
* Represents a {@link Form} as structured data which allows a frontend library to render it. |
|
14
|
|
|
* Includes information about the form as well as its fields. |
|
15
|
|
|
* Can create a "schema" (structure only) as well as "state" (data only). |
|
16
|
|
|
*/ |
|
17
|
|
|
class FormSchema |
|
18
|
|
|
{ |
|
19
|
|
|
/** |
|
20
|
|
|
* Request the schema part |
|
21
|
|
|
*/ |
|
22
|
|
|
const PART_SCHEMA = 'schema'; |
|
23
|
|
|
|
|
24
|
|
|
/** |
|
25
|
|
|
* Request the state part |
|
26
|
|
|
*/ |
|
27
|
|
|
const PART_STATE = 'state'; |
|
28
|
|
|
|
|
29
|
|
|
/** |
|
30
|
|
|
* Request the errors from a {@see ValidationResult} |
|
31
|
|
|
*/ |
|
32
|
|
|
const PART_ERRORS = 'errors'; |
|
33
|
|
|
|
|
34
|
|
|
/** |
|
35
|
|
|
* Request errors if invalid, or state if valid |
|
36
|
|
|
*/ |
|
37
|
|
|
const PART_AUTO = 'auto'; |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* Returns a representation of the provided {@link Form} as structured data, |
|
41
|
|
|
* based on the request data. |
|
42
|
|
|
* |
|
43
|
|
|
* @param array|string $schemaParts Array or list of requested parts. |
|
44
|
|
|
* @param string $schemaID ID for this schema. Required. |
|
45
|
|
|
* @param Form $form Required for 'state' or 'schema' response |
|
46
|
|
|
* @param ValidationResult $result Required for 'error' response |
|
47
|
|
|
* @return array |
|
48
|
|
|
*/ |
|
49
|
|
|
public function getMultipartSchema($schemaParts, $schemaID, Form $form = null, ValidationResult $result = null) |
|
50
|
|
|
{ |
|
51
|
|
|
if (!is_array($schemaParts)) { |
|
52
|
|
|
$schemaParts = preg_split('#\s*,\s*#', $schemaParts) ?: []; |
|
53
|
|
|
} |
|
54
|
|
|
$wantSchema = in_array('schema', $schemaParts); |
|
55
|
|
|
$wantState = in_array('state', $schemaParts); |
|
56
|
|
|
$wantErrors = in_array('errors', $schemaParts); |
|
57
|
|
|
$auto = in_array('auto', $schemaParts); |
|
58
|
|
|
|
|
59
|
|
|
// Require ID |
|
60
|
|
|
if (empty($schemaID)) { |
|
61
|
|
|
throw new InvalidArgumentException("schemaID is required"); |
|
62
|
|
|
} |
|
63
|
|
|
$return = ['id' => $schemaID]; |
|
64
|
|
|
|
|
65
|
|
|
// Default to schema if not set |
|
66
|
|
|
if ($form && ($wantSchema || empty($schemaParts))) { |
|
67
|
|
|
$return['schema'] = $this->getSchema($form); |
|
68
|
|
|
} |
|
69
|
|
|
|
|
70
|
|
|
// Return 'state' if requested, or if there are errors and 'auto' |
|
71
|
|
|
if ($form && ($wantState || ($auto && !$result))) { |
|
72
|
|
|
$return['state'] = $this->getState($form); |
|
73
|
|
|
} |
|
74
|
|
|
|
|
75
|
|
|
// Return errors if 'errors' or 'auto' |
|
|
|
|
|
|
76
|
|
|
if ($result && ($wantErrors || $auto)) { |
|
77
|
|
|
$return['errors'] = $this->getErrors($result); |
|
78
|
|
|
} |
|
79
|
|
|
|
|
80
|
|
|
return $return; |
|
81
|
|
|
} |
|
82
|
|
|
|
|
83
|
|
|
/** |
|
84
|
|
|
* Gets the schema for this form as a nested array. |
|
85
|
|
|
* |
|
86
|
|
|
* @param Form $form |
|
87
|
|
|
* @return array |
|
88
|
|
|
*/ |
|
89
|
|
|
public function getSchema(Form $form) |
|
90
|
|
|
{ |
|
91
|
|
|
$schema = [ |
|
92
|
|
|
'name' => $form->getName(), |
|
93
|
|
|
'id' => $form->FormName(), |
|
94
|
|
|
'action' => $form->FormAction(), |
|
95
|
|
|
'method' => $form->FormMethod(), |
|
96
|
|
|
'attributes' => $form->getAttributes(), |
|
97
|
|
|
'data' => [], |
|
98
|
|
|
'fields' => [], |
|
99
|
|
|
'actions' => [] |
|
100
|
|
|
]; |
|
101
|
|
|
|
|
102
|
|
|
/** @var FormField $action */ |
|
103
|
|
|
foreach ($form->Actions() as $action) { |
|
104
|
|
|
$schema['actions'][] = $action->getSchemaData(); |
|
105
|
|
|
} |
|
106
|
|
|
|
|
107
|
|
|
/** @var FormField $field */ |
|
108
|
|
|
foreach ($form->Fields() as $field) { |
|
109
|
|
|
$schema['fields'][] = $field->getSchemaData(); |
|
110
|
|
|
} |
|
111
|
|
|
|
|
112
|
|
|
return $schema; |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
|
|
/** |
|
116
|
|
|
* Gets the current state of this form as a nested array. |
|
117
|
|
|
* |
|
118
|
|
|
* @param Form $form |
|
119
|
|
|
* @return array |
|
120
|
|
|
*/ |
|
121
|
|
|
public function getState(Form $form) |
|
122
|
|
|
{ |
|
123
|
|
|
$state = [ |
|
124
|
|
|
'id' => $form->FormName(), |
|
125
|
|
|
'fields' => [], |
|
126
|
|
|
'messages' => [], |
|
127
|
|
|
]; |
|
128
|
|
|
|
|
129
|
|
|
// flattened nested fields are returned, rather than only top level fields. |
|
130
|
|
|
$state['fields'] = array_merge( |
|
131
|
|
|
$this->getFieldStates($form->Fields()), |
|
132
|
|
|
$this->getFieldStates($form->Actions()) |
|
133
|
|
|
); |
|
134
|
|
|
|
|
135
|
|
|
if ($message = $form->getSchemaMessage()) { |
|
136
|
|
|
$state['messages'][] = $message; |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
return $state; |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
/** |
|
143
|
|
|
* @param ValidationResult $result |
|
144
|
|
|
* @return array List of errors |
|
145
|
|
|
*/ |
|
146
|
|
|
public function getErrors(ValidationResult $result) |
|
147
|
|
|
{ |
|
148
|
|
|
$messages = []; |
|
149
|
|
|
foreach ($result->getMessages() as $message) { |
|
150
|
|
|
$messages[] = $this->getSchemaForMessage($message); |
|
151
|
|
|
} |
|
152
|
|
|
return $messages; |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
/** |
|
156
|
|
|
* Return form schema for encoded validation message |
|
157
|
|
|
* |
|
158
|
|
|
* @param array $message Internal ValidationResult format for this message |
|
159
|
|
|
* @return array Form schema format for this message |
|
160
|
|
|
*/ |
|
161
|
|
|
protected function getSchemaForMessage($message) |
|
162
|
|
|
{ |
|
163
|
|
|
// Form schema messages treat simple strings as plain text, so nest for html messages |
|
164
|
|
|
$value = $message['message']; |
|
165
|
|
|
if ($message['messageCast'] === ValidationResult::CAST_HTML) { |
|
166
|
|
|
$value = ['html' => $message]; |
|
167
|
|
|
} |
|
168
|
|
|
return [ |
|
169
|
|
|
'value' => $value, |
|
170
|
|
|
'type' => $message['messageType'], |
|
171
|
|
|
'field' => empty($message['fieldName']) ? null : $message['fieldName'], |
|
172
|
|
|
]; |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
protected function getFieldStates($fields) |
|
176
|
|
|
{ |
|
177
|
|
|
$states = []; |
|
178
|
|
|
/** @var FormField $field */ |
|
179
|
|
|
foreach ($fields as $field) { |
|
180
|
|
|
$states[] = $field->getSchemaState(); |
|
181
|
|
|
|
|
182
|
|
|
if ($field instanceof CompositeField) { |
|
183
|
|
|
$subFields = $field->FieldList(); |
|
184
|
|
|
$states = array_merge($states, $this->getFieldStates($subFields)); |
|
185
|
|
|
} |
|
186
|
|
|
} |
|
187
|
|
|
return $states; |
|
188
|
|
|
} |
|
189
|
|
|
} |
|
190
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.