1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Carbon_Fields\Field; |
4
|
|
|
|
5
|
|
|
use Carbon_Fields\Datastore\Datastore_Interface; |
6
|
|
|
use Carbon_Fields\Helper\Helper; |
7
|
|
|
use Carbon_Fields\Field\Field; |
8
|
|
|
use Carbon_Fields\Field\Group_Field; |
9
|
|
|
use Carbon_Fields\Exception\Incorrect_Syntax_Exception; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Complex field class. |
13
|
|
|
* Allows nested repeaters with multiple field groups to be created. |
14
|
|
|
*/ |
15
|
|
|
class Complex_Field extends Field { |
16
|
|
|
const LAYOUT_TABLE = 'table'; |
17
|
|
|
const LAYOUT_LIST = 'list'; |
18
|
|
|
|
19
|
|
|
protected $fields = array(); |
20
|
|
|
protected $values = array(); |
21
|
|
|
protected $groups = array(); |
22
|
|
|
|
23
|
|
|
protected $layout = self::LAYOUT_TABLE; |
24
|
|
|
protected $values_min = -1; |
25
|
|
|
protected $values_max = -1; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Defines how complex field data is saved: |
29
|
|
|
* - multiple_fields - default. All sub-fields are stored as seperated postmeta fields. |
30
|
|
|
* - single_field - All sub-fields are stored serialized in a single postmeta field. |
31
|
|
|
*/ |
32
|
|
|
protected $save_mode = 'multiple_fields'; |
33
|
|
|
|
34
|
|
|
public $labels = array( |
35
|
|
|
'singular_name' => 'Entry', |
36
|
|
|
'plural_name' => 'Entries', |
37
|
|
|
); |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Initialization tasks |
41
|
|
|
*/ |
42
|
|
|
public function init() { |
43
|
|
|
$this->labels = array( |
44
|
|
|
'singular_name' => __( 'Entry', 'carbon-fields' ), |
45
|
|
|
'plural_name' => __( 'Entries', 'carbon-fields' ), |
46
|
|
|
); |
47
|
|
|
|
48
|
|
|
// Include the complex group Underscore template |
49
|
|
|
$this->add_template( 'Complex-Group', array( $this, 'template_group' ) ); |
50
|
|
|
|
51
|
|
|
parent::init(); |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Add a set/group of fields. |
56
|
|
|
* |
57
|
|
|
* @return $this |
58
|
|
|
*/ |
59
|
|
|
public function add_fields() { |
60
|
|
|
$argv = func_get_args(); |
61
|
|
|
$argc = count( $argv ); |
62
|
|
|
|
63
|
|
|
if ( $argc == 1 ) { |
64
|
|
|
$fields = $argv[0]; |
65
|
|
|
$name = ''; |
66
|
|
|
$label = null; |
67
|
|
|
} else if ( $argc == 2 ) { |
68
|
|
View Code Duplication |
if ( is_array( $argv[0] ) ) { |
|
|
|
|
69
|
|
|
list( $fields, $name ) = $argv; |
70
|
|
|
} else { |
71
|
|
|
list( $name, $fields ) = $argv; |
72
|
|
|
} |
73
|
|
|
$label = null; |
74
|
|
|
} else if ( $argc == 3 ) { |
75
|
|
View Code Duplication |
if ( is_array( $argv[0] ) ) { |
|
|
|
|
76
|
|
|
list( $fields, $name, $label ) = $argv; |
77
|
|
|
} else { |
78
|
|
|
list( $name, $label, $fields ) = $argv; |
79
|
|
|
} |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
if ( array_key_exists( '_' . $name, $this->groups ) ) { |
83
|
|
|
Incorrect_Syntax_Exception::raise( 'Group with name "' . $name . '" in Complex Field "' . $this->get_label() . '" already exists.' ); |
|
|
|
|
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
$group = new Group_Field($name, $label, $fields); |
|
|
|
|
87
|
|
|
|
88
|
|
|
$this->groups[ $group->get_name() ] = $group; |
89
|
|
|
|
90
|
|
|
return $this; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Set the group label Underscore template. |
95
|
|
|
* |
96
|
|
|
* @param string|callable $template |
97
|
|
|
* @return $this |
98
|
|
|
*/ |
99
|
|
|
public function set_header_template( $template ) { |
100
|
|
|
if ( count($this->groups) === 0 ) { |
|
|
|
|
101
|
|
|
Incorrect_Syntax_Exception::raise( "Can't set group label template. There are no present groups for Complex Field " . $this->get_label() . "." ); |
|
|
|
|
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
$template = is_callable( $template ) ? call_user_func( $template ) : $template; |
105
|
|
|
|
106
|
|
|
// Assign the template to the group that was added last |
107
|
|
|
$group = end( array_values( $this->groups ) ); |
108
|
|
|
$group->set_label_template( $template ); |
109
|
|
|
|
110
|
|
|
// Include the group label Underscore template |
111
|
|
|
$this->add_template( $group->get_group_id(), array( $group, 'template_label' ) ); |
112
|
|
|
|
113
|
|
|
$this->groups[ $group->get_name() ] = $group; |
114
|
|
|
|
115
|
|
|
return $this; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Retrieve all groups of fields. |
120
|
|
|
* |
121
|
|
|
* @return array $fields |
122
|
|
|
*/ |
123
|
|
|
public function get_fields() { |
124
|
|
|
$fields = array(); |
125
|
|
|
|
126
|
|
|
foreach ( $this->groups as $group ) { |
127
|
|
|
$group_fields = $group->get_fields(); |
128
|
|
|
|
129
|
|
|
$fields = array_merge( $fields, $group_fields ); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
return $fields; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Set the field labels. |
137
|
|
|
* Currently supported values: |
138
|
|
|
* - singular_name - the singular entry label |
139
|
|
|
* - plural_name - the plural entries label |
140
|
|
|
* |
141
|
|
|
* @param array $labels Labels |
142
|
|
|
*/ |
143
|
|
|
public function setup_labels( $labels ) { |
144
|
|
|
$this->labels = array_merge( $this->labels, $labels ); |
145
|
|
|
return $this; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Set the datastore of this field. |
150
|
|
|
* |
151
|
|
|
* @param Datastore_Interface $store |
152
|
|
|
*/ |
153
|
|
|
public function set_datastore( Datastore_Interface $store ) { |
154
|
|
|
$this->store = $store; |
155
|
|
|
|
156
|
|
|
foreach ( $this->groups as $group ) { |
157
|
|
|
$group->set_datastore( $this->store ); |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Restructures data in single field mode so it emulates the data retrieved and saved via multiple fields. |
163
|
|
|
* |
164
|
|
|
* @param array $data input data to be restructured |
165
|
|
|
* @param string $mode Defines type of transformation: "db_to_process" (transforms database data to a linear key-value-array to be used to display the fields in the backend), "db_save" (transforms input data to the database save-format) |
166
|
|
|
* @param array $args Arguments mainly used for recursion metadata. Initially only "complex_field_name" is required for mode "db_to_process" |
167
|
|
|
* @return array restructured data |
168
|
|
|
*/ |
169
|
|
|
private function get_single_field_restructured_data( array $data = null, $mode, array $args = array() ) { |
170
|
|
|
if ( empty($data) ) return $data; |
|
|
|
|
171
|
|
|
|
172
|
|
|
switch ( $mode ) { |
173
|
|
|
case 'db_to_process': |
174
|
|
|
$complex_field_name = isset( $args[ 'complex_field_name' ] ) ? $args[ 'complex_field_name' ] : null; |
|
|
|
|
175
|
|
|
if ( ! $complex_field_name ) Incorrect_Syntax_Exception::raise( "complex_field_name missing" ); |
|
|
|
|
176
|
|
|
$level = isset( $args[ 'level' ] ) ? $args[ 'level' ] : 0; |
|
|
|
|
177
|
|
|
$prefix = isset( $args[ 'prefix' ] ) ? $args[ 'prefix' ] : $complex_field_name; |
|
|
|
|
178
|
|
|
|
179
|
|
|
$output = array(); |
180
|
|
|
$i = -1; |
181
|
|
|
foreach ( $data as $index => $item ) { |
182
|
|
|
$i++; |
183
|
|
|
|
184
|
|
|
if ( ! isset( $item[ '_type' ] ) || ! is_array( $item ) || empty( $item ) ) { |
|
|
|
|
185
|
|
|
continue; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
$type = $item[ '_type' ]; |
|
|
|
|
189
|
|
|
|
190
|
|
|
foreach ( $item as $key => $val ) { |
191
|
|
|
if ( $key === '_type' ) continue; |
|
|
|
|
192
|
|
|
|
193
|
|
|
$field_key = $prefix . $type . '-_' . $key . '_' . $i; |
194
|
|
|
$field_value = null; |
195
|
|
|
|
196
|
|
|
if ( is_array( $val ) ) { |
197
|
|
|
if ( isset( $val[ 0 ][ '_type' ] ) ) { |
|
|
|
|
198
|
|
|
$outputInner = $this->get_single_field_restructured_data( $val, $mode, array_merge( $args, array( |
199
|
|
|
'level' => $level + 1, |
200
|
|
|
'prefix' => $field_key, |
201
|
|
|
) ) ); |
202
|
|
|
$output = array_merge( $output, $outputInner ); |
203
|
|
|
} |
204
|
|
|
else { |
205
|
|
|
$field_value = serialize( $val ); |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
else if ( $val !== null ) { |
209
|
|
|
$field_value = (string) $val; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
if ( $field_value !== null ) { |
213
|
|
|
$output[] = array( |
214
|
|
|
'field_key' => $field_key, |
215
|
|
|
'field_value' => $field_value, |
216
|
|
|
); |
217
|
|
|
} |
218
|
|
|
} |
219
|
|
|
} |
220
|
|
|
$data = $output; |
221
|
|
|
|
222
|
|
|
break; |
223
|
|
|
case 'db_save': |
224
|
|
|
$indices = array_keys( $data ); |
225
|
|
|
foreach ( $indices as $index ) { |
226
|
|
|
if ( ! isset( $data[ $index ][ 'group' ] ) || ! is_array( $data[ $index ] ) || empty( $data[ $index ] ) ) { |
|
|
|
|
227
|
|
|
continue; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
$keys = array_keys( $data[ $index ] ); |
231
|
|
|
foreach ( $keys as $key ) { |
232
|
|
|
// rename key "group" to "_type" |
233
|
|
|
if ( $key === 'group' ) { |
234
|
|
|
$new_key = '_type'; |
235
|
|
|
$data[ $index ][ $new_key ] = $data[ $index ][ $key ]; |
236
|
|
|
unset( $data[ $index ][ $key ] ); |
237
|
|
|
$key = $new_key; |
238
|
|
|
} |
239
|
|
|
// remove underline-prefix from keys |
240
|
|
|
else if ( preg_match( '/^_/', $key ) ) { |
241
|
|
|
$new_key = substr( $key, 1 ); |
242
|
|
|
$data[ $index ][ $new_key ] = $data[ $index ][ $key ]; |
243
|
|
|
unset( $data[ $index ][ $key ] ); |
244
|
|
|
$key = $new_key; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
if ( is_array( $data[ $index ][ $key ] ) ) { |
248
|
|
|
$data[ $index ][ $key ] = $this->get_single_field_restructured_data( $data[ $index ][ $key ], $mode ); |
249
|
|
|
} |
250
|
|
|
} |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
break; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
return $data; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Load the field value from an input array based on it's name |
261
|
|
|
* |
262
|
|
|
* @param array $input (optional) Array of field names and values. Defaults to $_POST |
263
|
|
|
**/ |
264
|
|
|
public function set_value_from_input( $input = null ) { |
265
|
|
|
$this->values = array(); |
266
|
|
|
|
267
|
|
|
if ( is_null( $input ) ) { |
268
|
|
|
$input = $_POST; |
|
|
|
|
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
if ( ! isset( $input[ $this->get_name() ] ) ) { |
272
|
|
|
return; |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
$input_groups = $input[ $this->get_name() ]; |
276
|
|
|
$index = 0; |
277
|
|
|
|
278
|
|
|
// transform data |
279
|
|
|
if ( $this->save_mode === 'single_field' ) { |
280
|
|
|
$input_groups = stripslashes_deep( $input_groups ); |
281
|
|
|
$input_groups = $this->get_single_field_restructured_data( $input_groups, 'db_save' ); |
282
|
|
|
$this->set_value( $input_groups ); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
foreach ( $input_groups as $values ) { |
286
|
|
|
$value_group = array(); |
287
|
|
|
if ( ! isset( $values['group'] ) || ! isset( $this->groups[ $values['group'] ] ) ) { |
288
|
|
|
continue; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
$group = $this->groups[ $values['group'] ]; |
292
|
|
|
unset( $values['group'] ); |
293
|
|
|
|
294
|
|
|
$group_fields = $group->get_fields(); |
295
|
|
|
|
296
|
|
|
// trim input values to those used by the field |
297
|
|
|
$group_field_names = array_flip( $group->get_field_names() ); |
298
|
|
|
$values = array_intersect_key( $values, $group_field_names ); |
299
|
|
|
|
300
|
|
|
foreach ( $group_fields as $field ) { |
301
|
|
|
// set value from the group |
302
|
|
|
$tmp_field = clone $field; |
303
|
|
|
if ( is_a( $tmp_field, __NAMESPACE__ . '\\Complex_Field' ) ) { |
304
|
|
|
if ( ! isset( $values[ $tmp_field->get_name() ] ) ) { |
305
|
|
|
continue; // bail if the complex field is empty |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
$new_name = $this->get_name() . $group->get_name() . '-' . $field->get_name() . '_' . $index; |
309
|
|
|
$new_values = array( $new_name => $values[ $tmp_field->get_name() ] ); |
310
|
|
|
|
311
|
|
|
$tmp_field->set_name( $new_name ); |
312
|
|
|
$tmp_field->set_value_from_input( $new_values ); |
313
|
|
|
} else { |
314
|
|
|
$tmp_field->set_value_from_input( $values ); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
// update name to group name |
318
|
|
|
$tmp_field->set_name( $this->get_name() . $group->get_name() . '-' . $field->get_name() . '_' . $index ); |
319
|
|
|
$value_group[] = $tmp_field; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
$this->values[] = $value_group; |
323
|
|
|
$index++; |
324
|
|
|
} |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** |
328
|
|
|
* Load all groups of fields and their data. |
329
|
|
|
*/ |
330
|
|
|
public function load() { |
331
|
|
|
// load existing groups |
332
|
|
|
$this->load_values(); |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* Save all contained groups of fields. |
337
|
|
|
*/ |
338
|
|
|
public function save() { |
339
|
|
|
if ( $this->save_mode === 'single_field' ) { |
340
|
|
|
if ( $this->value !== null) { |
|
|
|
|
341
|
|
|
return $this->store->save( $this ); |
342
|
|
|
} |
343
|
|
|
else { |
344
|
|
|
return $this->delete(); |
345
|
|
|
} |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
$this->delete(); |
349
|
|
|
|
350
|
|
|
foreach ( $this->values as $value ) { |
351
|
|
|
foreach ( $value as $field ) { |
352
|
|
|
$field->save(); |
353
|
|
|
} |
354
|
|
|
} |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
/** |
358
|
|
|
* Delete the values of all contained fields. |
359
|
|
|
*/ |
360
|
|
|
public function delete() { |
361
|
|
|
if ( $this->save_mode === 'single_field' ) { |
362
|
|
|
return $this->store->delete( $this ); |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
return $this->store->delete_values( $this ); |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
/** |
369
|
|
|
* Load and parse the field data |
370
|
|
|
*/ |
371
|
|
|
public function load_values() { |
372
|
|
|
if ( $this->save_mode === 'single_field' ) { |
373
|
|
|
$tmp_value = $this->value; |
374
|
|
|
$this->store->load( $this ); |
375
|
|
|
$data = maybe_unserialize( $this->value ); |
376
|
|
|
$this->value = $tmp_value; |
377
|
|
|
|
378
|
|
|
// transform data |
379
|
|
|
if ( is_array($data) ) { |
|
|
|
|
380
|
|
|
$data = $this->get_single_field_restructured_data( $data, 'db_to_process', array( |
381
|
|
|
'complex_field_name' => $this->get_name(), |
382
|
|
|
) ); |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
return $this->process_loaded_values($data); |
|
|
|
|
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
return $this->load_values_from_db(); |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* Load and parse the field data from the database. |
393
|
|
|
*/ |
394
|
|
|
public function load_values_from_db() { |
395
|
|
|
$this->values = array(); |
396
|
|
|
|
397
|
|
|
$group_rows = $this->store->load_values( $this ); |
398
|
|
|
|
399
|
|
|
return $this->process_loaded_values( $group_rows ); |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
/** |
403
|
|
|
* Load and parse a raw set of field data. |
404
|
|
|
* |
405
|
|
|
* @param array $values Raw data entries |
406
|
|
|
* @return array Processed data entries |
407
|
|
|
*/ |
408
|
|
|
public function load_values_from_array( $values ) { |
409
|
|
|
$this->values = array(); |
410
|
|
|
|
411
|
|
|
$group_rows = array(); |
412
|
|
|
|
413
|
|
|
$meta_key = $this->get_name(); |
414
|
|
|
|
415
|
|
|
foreach ( $values as $key => $value ) { |
416
|
|
|
if ( strpos( $key, $meta_key ) !== 0 ) { |
417
|
|
|
continue; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
$group_rows[] = array( |
421
|
|
|
'field_key' => preg_replace( '~^(' . preg_quote( $this->name, '~' ) . ')_\d+_~', '$1_', $key ), |
422
|
|
|
'field_value' => $value, |
423
|
|
|
); |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
return $this->process_loaded_values( $group_rows ); |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
/** |
430
|
|
|
* Parse groups of raw field data into the actual field hierarchy. |
431
|
|
|
* |
432
|
|
|
* @param array $group_rows Group rows |
433
|
|
|
*/ |
434
|
|
|
public function process_loaded_values( $group_rows ) { |
435
|
|
|
$input_groups = array(); |
436
|
|
|
|
437
|
|
|
// Set default values |
438
|
|
|
$field_names = array(); |
439
|
|
|
foreach ( $this->groups as $group ) { |
440
|
|
|
$group_fields = $group->get_fields(); |
441
|
|
|
foreach ( $group_fields as $field ) { |
442
|
|
|
$field_names[] = $field->get_name(); |
443
|
|
|
$field->set_value( $field->get_default_value() ); |
444
|
|
|
} |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
if ( empty( $group_rows ) ) { |
448
|
|
|
return; |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
// load and parse values and group type |
452
|
|
|
foreach ( $group_rows as $row ) { |
453
|
|
|
if ( ! preg_match( Helper::get_complex_field_regex( $this->name, array_keys( $this->groups ), $field_names ), $row['field_key'], $field_name ) ) { |
454
|
|
|
continue; |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
$row['field_value'] = maybe_unserialize( $row['field_value'] ); |
458
|
|
|
$input_groups[ $field_name['index'] ]['type'] = $field_name['group']; |
459
|
|
|
|
460
|
|
|
if ( ! empty( $field_name['trailing'] ) ) { |
461
|
|
|
$input_groups[ $field_name['index'] ][ $field_name['key'] . '_' . $field_name['sub'] . '-' . $field_name['trailing'] ] = $row['field_value']; |
462
|
|
View Code Duplication |
} else if ( ! empty( $field_name['sub'] ) ) { |
|
|
|
|
463
|
|
|
$input_groups[ $field_name['index'] ][ $field_name['key'] ][ $field_name['sub'] ] = $row['field_value']; |
464
|
|
|
} else { |
465
|
|
|
$input_groups[ $field_name['index'] ][ $field_name['key'] ] = $row['field_value']; |
466
|
|
|
} |
467
|
|
|
} |
468
|
|
|
|
469
|
|
|
// create groups list with loaded fields |
470
|
|
|
ksort( $input_groups ); |
471
|
|
|
|
472
|
|
|
foreach ( $input_groups as $index => $values ) { |
473
|
|
|
$value_group = array( 'type' => $values['type'] ); |
474
|
|
|
$group_fields = $this->groups[ $values['type'] ]->get_fields(); |
475
|
|
|
unset( $values['type'] ); |
476
|
|
|
|
477
|
|
|
foreach ( $group_fields as $field ) { |
478
|
|
|
// set value from the group |
479
|
|
|
$tmp_field = clone $field; |
480
|
|
|
|
481
|
|
|
if ( is_a( $field, __NAMESPACE__ . '\\Complex_Field' ) ) { |
482
|
|
|
$tmp_field->load_values_from_array( $values ); |
483
|
|
|
} else { |
484
|
|
|
$tmp_field->set_value_from_input( $values ); |
485
|
|
|
} |
486
|
|
|
|
487
|
|
|
$value_group[] = $tmp_field; |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
$this->values[] = $value_group; |
491
|
|
|
} |
492
|
|
|
} |
493
|
|
|
|
494
|
|
|
/** |
495
|
|
|
* Retrieve the field values |
496
|
|
|
* @return array |
497
|
|
|
*/ |
498
|
|
|
public function get_values() { |
499
|
|
|
return $this->values; |
500
|
|
|
} |
501
|
|
|
|
502
|
|
|
/** |
503
|
|
|
* Generate and set the field prefix. |
504
|
|
|
* @param string $prefix |
505
|
|
|
*/ |
506
|
|
|
public function set_prefix( $prefix ) { |
507
|
|
|
parent::set_prefix( $prefix ); |
508
|
|
|
|
509
|
|
|
foreach ( $this->groups as $group ) { |
510
|
|
|
$group->set_prefix( $prefix ); |
511
|
|
|
} |
512
|
|
|
} |
513
|
|
|
|
514
|
|
|
/** |
515
|
|
|
* Returns an array that holds the field data, suitable for JSON representation. |
516
|
|
|
* This data will be available in the Underscore template and the Backbone Model. |
517
|
|
|
* |
518
|
|
|
* @param bool $load Should the value be loaded from the database or use the value from the current instance. |
519
|
|
|
* @return array |
520
|
|
|
*/ |
521
|
|
|
public function to_json( $load ) { |
522
|
|
|
$complex_data = parent::to_json( $load ); |
523
|
|
|
|
524
|
|
|
$groups_data = array(); |
525
|
|
|
$values_data = array(); |
526
|
|
|
|
527
|
|
|
foreach ( $this->groups as $group ) { |
528
|
|
|
$groups_data[] = $group->to_json( false ); |
529
|
|
|
} |
530
|
|
|
|
531
|
|
|
foreach ( $this->values as $fields ) { |
532
|
|
|
$group = $this->get_group_by_name( $fields['type'] ); |
533
|
|
|
unset( $fields['type'] ); |
534
|
|
|
|
535
|
|
|
$data = array( |
536
|
|
|
'name' => $group->get_name(), |
537
|
|
|
'label' => $group->get_label(), |
538
|
|
|
'group_id' => $group->get_group_id(), |
539
|
|
|
'fields' => array(), |
540
|
|
|
); |
541
|
|
|
|
542
|
|
|
foreach ( $fields as $index => $field ) { |
543
|
|
|
$data['fields'][] = $field->to_json( false ); |
544
|
|
|
} |
545
|
|
|
|
546
|
|
|
$values_data[] = $data; |
547
|
|
|
} |
548
|
|
|
|
549
|
|
|
$complex_data = array_merge( $complex_data, array( |
550
|
|
|
'layout' => $this->layout, |
551
|
|
|
'labels' => $this->labels, |
552
|
|
|
'min' => $this->get_min(), |
553
|
|
|
'max' => $this->get_max(), |
554
|
|
|
'multiple_groups' => count( $groups_data ) > 1, |
555
|
|
|
'groups' => $groups_data, |
556
|
|
|
'value' => $values_data, |
557
|
|
|
) ); |
558
|
|
|
|
559
|
|
|
return $complex_data; |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
/** |
563
|
|
|
* The main Underscore template |
564
|
|
|
*/ |
565
|
|
|
public function template() { |
566
|
|
|
?> |
567
|
|
|
<div class="carbon-subcontainer carbon-grid {{ multiple_groups ? 'multiple-groups' : '' }}"> |
568
|
|
|
|
569
|
|
|
<div class="carbon-empty-row"> |
570
|
|
|
{{{ crbl10n.complex_no_rows.replace('%s', labels.plural_name) }}} |
571
|
|
|
</div> |
572
|
|
|
|
573
|
|
|
<div class="carbon-groups-holder layout-{{ layout }}"></div> |
574
|
|
|
|
575
|
|
|
<div class="carbon-actions"> |
576
|
|
|
<div class="carbon-button"> |
577
|
|
|
<a href="#" class="button" data-group="{{{ multiple_groups ? '' : groups[0].name }}}"> |
578
|
|
|
{{{ crbl10n.complex_add_button.replace('%s', labels.singular_name) }}} |
579
|
|
|
{{{ multiple_groups ? '⇩' : '' }}} |
580
|
|
|
</a> |
581
|
|
|
|
582
|
|
|
<# if (multiple_groups) { #> |
583
|
|
|
<ul> |
584
|
|
|
<# _.each(groups, function(group) { #> |
585
|
|
|
<li><a href="#" data-group="{{{ group.name }}}">{{{ group.label }}}</a></li> |
586
|
|
|
<# }); #> |
587
|
|
|
</ul> |
588
|
|
|
<# } #> |
589
|
|
|
</div> |
590
|
|
|
</div> |
591
|
|
|
</div> |
592
|
|
|
<?php |
593
|
|
|
} |
594
|
|
|
|
595
|
|
|
/** |
596
|
|
|
* The Underscore template for a complex field group |
597
|
|
|
*/ |
598
|
|
|
public function template_group() { |
599
|
|
|
?> |
600
|
|
|
<div id="carbon-{{{ complex_name }}}-complex-container" class="carbon-row carbon-group-row" data-group-id="{{ id }}"> |
601
|
|
|
<input type="hidden" name="{{{ complex_name + '[' + index + ']' }}}[group]" value="{{ name }}" /> |
602
|
|
|
|
603
|
|
|
<div class="carbon-drag-handle"> |
604
|
|
|
<span class="group-number">{{{ order + 1 }}}</span><span class="group-name">{{{ label_template || label }}}</span> |
605
|
|
|
</div> |
606
|
|
|
<div class="carbon-group-actions"> |
607
|
|
|
<a class="carbon-btn-collapse" href="#" title="<?php esc_attr_e( 'Collapse/Expand', 'carbon-fields' ); ?>"><?php _e( 'Collapse/Expand', 'carbon-fields' ); ?></a> |
608
|
|
|
<a class="carbon-btn-duplicate" href="#" title="<?php esc_attr_e( 'Clone', 'carbon-fields' ); ?>"><?php _e( 'Clone', 'carbon-fields' ); ?></a> |
609
|
|
|
<a class="carbon-btn-remove" href="#" title="<?php esc_attr_e( 'Remove', 'carbon-fields' ); ?>"><?php _e( 'Remove', 'carbon-fields' ); ?></a> |
610
|
|
|
</div> |
611
|
|
|
|
612
|
|
|
<div class="fields-container"> |
613
|
|
|
<# _.each(fields, function(field) { #> |
614
|
|
|
<div class="carbon-row carbon-subrow subrow-{{{ field.type }}} {{{ field.classes.join(' ') }}}"> |
615
|
|
|
<label for="{{{ complex_id + '-' + field.id + '-' + index }}}"> |
616
|
|
|
{{ field.label }} |
617
|
|
|
|
618
|
|
|
<# if (field.required) { #> |
619
|
|
|
<span class="carbon-required">*</span> |
620
|
|
|
<# } #> |
621
|
|
|
</label> |
622
|
|
|
|
623
|
|
|
<div class="field-holder {{{ complex_id + '-' + field.id + '-' + index }}}"></div> |
624
|
|
|
|
625
|
|
|
<# if (field.help_text) { #> |
626
|
|
|
<em class="help-text"> |
627
|
|
|
{{{ field.help_text }}} |
628
|
|
|
</em> |
629
|
|
|
<# } #> |
630
|
|
|
|
631
|
|
|
<em class="carbon-error"></em> |
632
|
|
|
</div> |
633
|
|
|
<# }) #> |
634
|
|
|
</div> |
635
|
|
|
</div> |
636
|
|
|
<?php |
637
|
|
|
} |
638
|
|
|
|
639
|
|
|
/** |
640
|
|
|
* Modify the layout of this field. |
641
|
|
|
* Deprecated in favor of set_width(). |
642
|
|
|
* |
643
|
|
|
* @deprecated |
644
|
|
|
* |
645
|
|
|
* @param string $layout |
646
|
|
|
*/ |
647
|
|
|
public function set_layout( $layout ) { |
648
|
|
|
_doing_it_wrong( __METHOD__, __( 'Complex field layouts are deprecated, please use <code>set_width()</code> instead.', 'carbon-fields' ), null ); |
649
|
|
|
|
650
|
|
|
if ( ! in_array( $layout, array( self::LAYOUT_TABLE, self::LAYOUT_LIST ) ) ) { |
651
|
|
|
Incorrect_Syntax_Exception::raise( 'Incorrect layout specifier. Available values are "<code>' . self::LAYOUT_TABLE . '</code>" and "<code>' . self::LAYOUT_LIST . '</code>"' ); |
652
|
|
|
} |
653
|
|
|
|
654
|
|
|
$this->layout = $layout; |
655
|
|
|
|
656
|
|
|
return $this; |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
/** |
660
|
|
|
* Set the minimum number of entries. |
661
|
|
|
* |
662
|
|
|
* @param int $min |
663
|
|
|
*/ |
664
|
|
|
public function set_min( $min ) { |
665
|
|
|
$this->values_min = intval( $min ); |
666
|
|
|
return $this; |
667
|
|
|
} |
668
|
|
|
|
669
|
|
|
/** |
670
|
|
|
* Get the minimum number of entries. |
671
|
|
|
* |
672
|
|
|
* @return int $min |
673
|
|
|
*/ |
674
|
|
|
public function get_min() { |
675
|
|
|
return $this->values_min; |
676
|
|
|
} |
677
|
|
|
|
678
|
|
|
/** |
679
|
|
|
* Set the maximum number of entries. |
680
|
|
|
* |
681
|
|
|
* @param int $max |
682
|
|
|
*/ |
683
|
|
|
public function set_max( $max ) { |
684
|
|
|
$this->values_max = intval( $max ); |
685
|
|
|
return $this; |
686
|
|
|
} |
687
|
|
|
|
688
|
|
|
/** |
689
|
|
|
* Get the maximum number of entries. |
690
|
|
|
* |
691
|
|
|
* @return int $max |
692
|
|
|
*/ |
693
|
|
|
public function get_max() { |
694
|
|
|
return $this->values_max; |
695
|
|
|
} |
696
|
|
|
|
697
|
|
|
/** |
698
|
|
|
* Retrieve the groups of this field. |
699
|
|
|
* |
700
|
|
|
* @return array |
701
|
|
|
*/ |
702
|
|
|
public function get_group_names() { |
703
|
|
|
return array_keys( $this->groups ); |
704
|
|
|
} |
705
|
|
|
|
706
|
|
|
/** |
707
|
|
|
* Retrieve a group by its name |
708
|
|
|
* @param string $group_name Group name |
709
|
|
|
* @return Group_Field $group_object Group object |
710
|
|
|
*/ |
711
|
|
|
public function get_group_by_name( $group_name ) { |
712
|
|
|
$group_object = null; |
713
|
|
|
|
714
|
|
|
foreach ( $this->groups as $group ) { |
715
|
|
|
if ( $group->get_name() == $group_name ) { |
716
|
|
|
$group_object = $group; |
717
|
|
|
} |
718
|
|
|
} |
719
|
|
|
|
720
|
|
|
return $group_object; |
721
|
|
|
} |
722
|
|
|
|
723
|
|
|
public function set_save_mode( $save_mode ) { |
724
|
|
|
$this->save_mode = $save_mode; |
725
|
|
|
|
726
|
|
|
return $this; |
727
|
|
|
} |
728
|
|
|
} |
729
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.