|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* YIKES Inc. Easy Forms. |
|
4
|
|
|
* |
|
5
|
|
|
* @package YIKES\EasyForms |
|
6
|
|
|
* @author Freddie Mixell |
|
7
|
|
|
* @license GPL2 |
|
8
|
|
|
*/ |
|
9
|
|
|
|
|
10
|
|
|
namespace YIKES\EasyForms\Form; |
|
11
|
|
|
|
|
12
|
|
|
use YIKES\EasyForms\Exception\InvalidClass; |
|
13
|
|
|
use YIKES\EasyForms\Exception\InvalidField; |
|
14
|
|
|
use YIKES\EasyForms\Field\Field; |
|
15
|
|
|
use YIKES\EasyForms\Field\Hidden; |
|
16
|
|
|
use YIKES\EasyForms\Field\Types; |
|
17
|
|
|
use YIKES\EasyForms\Renderable; |
|
18
|
|
|
use YIKES\EasyForms\Assets\AssetsAware; |
|
19
|
|
|
use YIKES\EasyForms\Assets\AssetsAwareness; |
|
20
|
|
|
use YIKES\EasyForms\Assets\ScriptAsset; |
|
21
|
|
|
use YIKES\EasyForms\Service; |
|
22
|
|
|
use YIKES\EasyForms\Model\OptinForm as EasyFormsModel; |
|
23
|
|
|
use YIKES\EasyForms\Model\Recaptcha as RecaptchaModel; |
|
24
|
|
|
use YIKES\EasyForms\Model\OptinMeta as Meta; |
|
25
|
|
|
|
|
26
|
|
|
/** |
|
27
|
|
|
* Class OptinForm |
|
28
|
|
|
* |
|
29
|
|
|
* @since %VERSION% |
|
30
|
|
|
* @package YIKES\EasyForms |
|
31
|
|
|
* |
|
32
|
|
|
* @property Field[] fields The array of field objects. |
|
33
|
|
|
* @property array field_classes The array of classes used for field objects. |
|
34
|
|
|
* @property array form_classes The array of classes used for the main form element. |
|
35
|
|
|
*/ |
|
36
|
|
|
final class OptinForm { |
|
37
|
|
|
|
|
38
|
|
|
use FormHelper; |
|
39
|
|
|
use FieldBuilder; |
|
40
|
|
|
use SubmitButton; |
|
41
|
|
|
/** |
|
42
|
|
|
* The Optin Form object. |
|
43
|
|
|
* |
|
44
|
|
|
* @since %VERSION% |
|
45
|
|
|
* @var EasyFormsModel |
|
46
|
|
|
*/ |
|
47
|
|
|
private $form_data; |
|
48
|
|
|
|
|
49
|
|
|
/** |
|
50
|
|
|
* Field Count |
|
51
|
|
|
*/ |
|
52
|
|
|
private $field_count = 0; |
|
53
|
|
|
|
|
54
|
|
|
/** |
|
55
|
|
|
* Hidden Label Count |
|
56
|
|
|
*/ |
|
57
|
|
|
private $hidden_label_count = 0; |
|
58
|
|
|
|
|
59
|
|
|
/** |
|
60
|
|
|
* Whether the form has any errors. |
|
61
|
|
|
* |
|
62
|
|
|
* @since %VERSION% |
|
63
|
|
|
* @var bool |
|
64
|
|
|
*/ |
|
65
|
|
|
private $has_errors = false; |
|
66
|
|
|
|
|
67
|
|
|
/** |
|
68
|
|
|
* Whether the form has been submitted. |
|
69
|
|
|
* |
|
70
|
|
|
* @since %VERSION% |
|
71
|
|
|
* @var bool |
|
72
|
|
|
*/ |
|
73
|
|
|
private $is_submitted = false; |
|
74
|
|
|
|
|
75
|
|
|
/** |
|
76
|
|
|
* The ID of the form. |
|
77
|
|
|
* |
|
78
|
|
|
* @since %VERSION% |
|
79
|
|
|
* @var int |
|
80
|
|
|
*/ |
|
81
|
|
|
private $form_id = 0; |
|
82
|
|
|
|
|
83
|
|
|
/** |
|
84
|
|
|
* The data submitted with this form. |
|
85
|
|
|
* |
|
86
|
|
|
* @since %VERSION% |
|
87
|
|
|
* @var array |
|
88
|
|
|
*/ |
|
89
|
|
|
private $submitted_data = []; |
|
90
|
|
|
|
|
91
|
|
|
/** |
|
92
|
|
|
* The validated data for this form. |
|
93
|
|
|
* |
|
94
|
|
|
* @since %VERSION% |
|
95
|
|
|
* @var array |
|
96
|
|
|
*/ |
|
97
|
|
|
private $valid_data = []; |
|
98
|
|
|
|
|
99
|
|
|
/** |
|
100
|
|
|
* Admin CSS Class |
|
101
|
|
|
* |
|
102
|
|
|
* @since %VERSION% |
|
103
|
|
|
* @var string |
|
104
|
|
|
*/ |
|
105
|
|
|
private $admin_class = ''; |
|
106
|
|
|
|
|
107
|
|
|
public $recaptcha; |
|
108
|
|
|
|
|
109
|
|
|
public $form_inline = false; |
|
110
|
|
|
|
|
111
|
|
|
/** |
|
112
|
|
|
* OptinForm constructor. |
|
113
|
|
|
* |
|
114
|
|
|
* @param int $form_id The ID the optin form is for. |
|
115
|
|
|
* @param EasyFormsModel $form_data The optin form object. |
|
116
|
|
|
*/ |
|
117
|
|
|
public function __construct( $form_id = 0, $form_data = array(), $attr = array() ) { |
|
118
|
|
|
$this->form_id = $form_id; |
|
119
|
|
|
$this->form_data = $form_data; |
|
|
|
|
|
|
120
|
|
|
$this->field_count = $this->set_field_count(); |
|
121
|
|
|
$this->form_inline = $form_data['form_settings']['yikes-easy-mc-inline-form']; |
|
122
|
|
|
$this->recaptcha = ( new RecaptchaModel() )->setup( $attr ); |
|
123
|
|
|
} |
|
124
|
|
|
|
|
125
|
|
|
/** |
|
126
|
|
|
* Admin CSS Class |
|
127
|
|
|
* |
|
128
|
|
|
* @return string |
|
129
|
|
|
*/ |
|
130
|
|
|
private function admin_class() { |
|
|
|
|
|
|
131
|
|
|
$is_admin = is_user_logged_in() && current_user_can( |
|
132
|
|
|
apply_filters( 'yikes-mailchimp-user-role-access' , 'manage_options' ) |
|
133
|
|
|
); |
|
134
|
|
|
return $is_admin ? ' admin-logged-in' : ''; |
|
135
|
|
|
} |
|
136
|
|
|
|
|
137
|
|
|
/** |
|
138
|
|
|
* Utilized for reading data from inaccessible members. |
|
139
|
|
|
* |
|
140
|
|
|
* @param string $name The property to retrieve. |
|
141
|
|
|
* |
|
142
|
|
|
* @return mixed |
|
143
|
|
|
*/ |
|
144
|
|
|
public function __get( $name ) { |
|
145
|
|
|
switch ( $name ) { |
|
146
|
|
|
case 'fields': |
|
147
|
|
|
$this->create_fields(); |
|
148
|
|
|
return $this->fields; |
|
149
|
|
|
|
|
150
|
|
|
default: |
|
151
|
|
|
$message = sprintf( 'Undefined property: %s::$%s', static::class, $name ); |
|
152
|
|
|
trigger_error( esc_html( $message ), E_USER_NOTICE ); |
|
153
|
|
|
|
|
154
|
|
|
return null; |
|
155
|
|
|
} |
|
156
|
|
|
} |
|
157
|
|
|
|
|
158
|
|
|
/** |
|
159
|
|
|
* Create the array of fields. |
|
160
|
|
|
* |
|
161
|
|
|
* @since %VERSION% |
|
162
|
|
|
*/ |
|
163
|
|
|
private function create_fields() { |
|
164
|
|
|
$fields = []; |
|
165
|
|
|
|
|
166
|
|
|
// // Manually add the hidden nonce and referrer fields. |
|
167
|
|
|
// $fields[] = new Hidden( "yikes_easy_mc_new_subscriber", wp_create_nonce( 'yikes_easy_mc_form_submit' ), $this->form_id ); |
|
168
|
|
|
// $fields[] = new Hidden( '_wp_http_referer', wp_unslash( $_SERVER['REQUEST_URI'] ), $this->form_id ); |
|
169
|
|
|
|
|
170
|
|
|
// // Honeypot Trap field. |
|
171
|
|
|
// $fields[] = new Hidden( 'yikes-mailchimp-honeypot', $this->form_data['list_id'], $this->form_id ); |
|
172
|
|
|
|
|
173
|
|
|
// // List ID field. |
|
174
|
|
|
// $fields[] = new Hidden( 'yikes-mailchimp-associated-list-id', $this->form_data['list_id'], $this->form_id ); |
|
175
|
|
|
|
|
176
|
|
|
// // The form that is being submitted! Used to display error/success messages above the correct form. |
|
177
|
|
|
// $fields[] = new Hidden( 'yikes-mailchimp-submitted-form', $this->form_id, $this->form_id ); |
|
178
|
|
|
|
|
179
|
|
|
// Add all of the active fields. |
|
180
|
|
|
foreach ( $this->form_data['fields'] as $field ) { |
|
181
|
|
|
if ( isset( $field['hide'] ) && (string) $field['hide'] === '1' ) { |
|
182
|
|
|
$this->reduce_field_count(); |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
|
$fields = array_merge( $fields, $this->instantiate_field( $field ) ); |
|
186
|
|
|
} |
|
187
|
|
|
|
|
188
|
|
|
$this->fields = $fields; |
|
189
|
|
|
} |
|
190
|
|
|
|
|
191
|
|
|
/** |
|
192
|
|
|
* Render the form fields. |
|
193
|
|
|
* |
|
194
|
|
|
* @since %VERSION% |
|
195
|
|
|
*/ |
|
196
|
|
|
public function render( array $context = [] ) { |
|
|
|
|
|
|
197
|
|
|
foreach ( $this->fields as $field ) { |
|
198
|
|
|
$field->render(); |
|
199
|
|
|
} |
|
200
|
|
|
} |
|
201
|
|
|
|
|
202
|
|
|
/** |
|
203
|
|
|
* Set the submission data. |
|
204
|
|
|
* |
|
205
|
|
|
* @since %VERSION% |
|
206
|
|
|
* |
|
207
|
|
|
* @param array $data Submitted data. |
|
208
|
|
|
*/ |
|
209
|
|
|
public function set_submission( array $data ) { |
|
210
|
|
|
$this->is_submitted = true; |
|
211
|
|
|
$this->submitted_data = $data; |
|
212
|
|
|
} |
|
213
|
|
|
|
|
214
|
|
|
/** |
|
215
|
|
|
* Determine whether the form has errors. |
|
216
|
|
|
* |
|
217
|
|
|
* @since %VERSION% |
|
218
|
|
|
* @return bool |
|
219
|
|
|
*/ |
|
220
|
|
|
public function has_errors() { |
|
221
|
|
|
return $this->is_submitted && $this->has_errors; |
|
222
|
|
|
} |
|
223
|
|
|
|
|
224
|
|
|
/** |
|
225
|
|
|
* Validate the submission. |
|
226
|
|
|
* |
|
227
|
|
|
* @since %VERSION% |
|
228
|
|
|
*/ |
|
229
|
|
|
public function validate_submission() { |
|
230
|
|
|
$valid = []; |
|
231
|
|
|
foreach ( $this->fields as $field ) { |
|
232
|
|
|
try { |
|
233
|
|
|
$submitted = array_key_exists( $field->get_id(), $this->submitted_data ) |
|
234
|
|
|
? $this->submitted_data[ $field->get_id() ] |
|
235
|
|
|
: ''; |
|
236
|
|
|
|
|
237
|
|
|
$field->set_submission( $submitted ); |
|
238
|
|
|
$valid[ $field->get_id() ] = $field->get_sanitized_value(); |
|
239
|
|
|
} catch ( InvalidField $e ) { |
|
240
|
|
|
$this->has_errors = true; |
|
241
|
|
|
} |
|
242
|
|
|
} |
|
243
|
|
|
|
|
244
|
|
|
$this->valid_data = $valid; |
|
245
|
|
|
} |
|
246
|
|
|
|
|
247
|
|
|
/** |
|
248
|
|
|
* Get the class type for a particular field. |
|
249
|
|
|
* |
|
250
|
|
|
* @since 1.0.0 |
|
251
|
|
|
* |
|
252
|
|
|
* @param string $field The field name. |
|
253
|
|
|
* |
|
254
|
|
|
* @return string The class name to instantiate that field. |
|
255
|
|
|
* @throws InvalidClass When a field type is returned to the filter that doesn't implement Field. |
|
256
|
|
|
*/ |
|
257
|
|
|
private function get_field_type( $field ) { |
|
258
|
|
|
|
|
259
|
|
|
$type = array_key_exists( $field['type'], Meta::FIELD_MAP ) ? Meta::FIELD_MAP[ $field['type'] ] : Types::TEXT; |
|
260
|
|
|
|
|
261
|
|
|
/** |
|
262
|
|
|
* Filter the class used to instantiate the field. |
|
263
|
|
|
* |
|
264
|
|
|
* @param string $type The field class name. Must extend implment the Field interface. |
|
265
|
|
|
* @param string $field The field name. |
|
266
|
|
|
*/ |
|
267
|
|
|
$type = apply_filters( 'easy_forms_field_type', $type, $field ); |
|
268
|
|
|
|
|
269
|
|
|
// Ensure that the field implements the Field interface.. |
|
270
|
|
|
$implements = class_implements( $type ); |
|
271
|
|
|
if ( ! isset( $implements[ Field::class ] ) ) { |
|
272
|
|
|
throw InvalidClass::from_interface( $type, Field::class ); |
|
273
|
|
|
} |
|
274
|
|
|
|
|
275
|
|
|
return $type; |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
/** |
|
279
|
|
|
* Instantiate a field. |
|
280
|
|
|
* |
|
281
|
|
|
* @since %SINCE% |
|
282
|
|
|
* |
|
283
|
|
|
* @param string $field The raw field name. |
|
284
|
|
|
* |
|
285
|
|
|
* @return Field[] Array of Field objects. |
|
286
|
|
|
*/ |
|
287
|
|
|
private function instantiate_field( $field ) { |
|
288
|
|
|
/** |
|
289
|
|
|
* Short-circuit the instantiation of a field object. |
|
290
|
|
|
* |
|
291
|
|
|
* To effectively short-circuit normal instantiation, an array of Field objects must be returned. |
|
292
|
|
|
* |
|
293
|
|
|
* @param array|null $pre Array of Field objects or null. |
|
294
|
|
|
* @param string $field The raw field name. |
|
295
|
|
|
* @param EasyFormsModel $form_data The form object. |
|
296
|
|
|
*/ |
|
297
|
|
|
$pre = apply_filters( 'yikes_easy_forms_instantiate_field', null, $field, $this->form_data ); |
|
298
|
|
|
if ( is_array( $pre ) ) { |
|
299
|
|
|
foreach ( $pre as $object ) { |
|
300
|
|
|
$this->validate_is_field( $object ); |
|
301
|
|
|
} |
|
302
|
|
|
return $pre; |
|
303
|
|
|
} |
|
304
|
|
|
|
|
305
|
|
|
$label = $this->get_label( $field ); |
|
306
|
|
|
$value = $this->get_value( $field ); |
|
307
|
|
|
$type = $this->get_field_type( $field ); |
|
308
|
|
|
$classes = $this->get_field_classes( $field ); |
|
309
|
|
|
$placeholder = $this->get_placeholder( $field ); |
|
310
|
|
|
$description = $this->get_description( $field ); |
|
311
|
|
|
$merge = $field['merge']; |
|
312
|
|
|
$hidden = $this->get_hidden( $field ); |
|
313
|
|
|
$required = isset( $field['require'] ) ? true : false; |
|
314
|
|
|
return [ |
|
315
|
|
|
new $type( |
|
316
|
|
|
$classes, |
|
317
|
|
|
$placeholder, |
|
318
|
|
|
$label, |
|
319
|
|
|
$value, |
|
320
|
|
|
$description, |
|
321
|
|
|
$merge, |
|
322
|
|
|
$this->form_id, |
|
323
|
|
|
$hidden, |
|
324
|
|
|
$required |
|
325
|
|
|
), |
|
326
|
|
|
]; |
|
327
|
|
|
} |
|
328
|
|
|
|
|
329
|
|
|
/** |
|
330
|
|
|
* Validate that the given object is a Field. |
|
331
|
|
|
* |
|
332
|
|
|
* @since %SINCE% |
|
333
|
|
|
* |
|
334
|
|
|
* @param object $maybe_field The object to validate. |
|
335
|
|
|
* |
|
336
|
|
|
* @throws InvalidClass When the object isn't a Field object. |
|
337
|
|
|
*/ |
|
338
|
|
|
private function validate_is_field( $maybe_field ) { |
|
339
|
|
|
if ( ! $maybe_field instanceof Field ) { |
|
340
|
|
|
throw InvalidClass::from_interface( get_class( $maybe_field ), Field::class ); |
|
341
|
|
|
} |
|
342
|
|
|
} |
|
343
|
|
|
} |
|
344
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.