1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Carbon_Fields\Container; |
4
|
|
|
|
5
|
|
|
use Carbon_Fields\App; |
6
|
|
|
use Carbon_Fields\Field\Field; |
7
|
|
|
use Carbon_Fields\Field\Group_Field; |
8
|
|
|
use Carbon_Fields\Datastore\Datastore_Interface; |
9
|
|
|
use Carbon_Fields\Datastore\Datastore_Holder_Interface; |
10
|
|
|
use Carbon_Fields\Exception\Incorrect_Syntax_Exception; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Base container class. |
14
|
|
|
* Defines the key container methods and their default implementations. |
15
|
|
|
*/ |
16
|
|
|
abstract class Container implements Datastore_Holder_Interface { |
17
|
|
|
/** |
18
|
|
|
* Where to put a particular tab -- at the head or the tail. Tail by default |
19
|
|
|
*/ |
20
|
|
|
const TABS_TAIL = 1; |
21
|
|
|
const TABS_HEAD = 2; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Separator signifying field hierarchy relation |
25
|
|
|
* Used when searching for fields in a specific complex field |
26
|
|
|
*/ |
27
|
|
|
const HIERARCHY_FIELD_SEPARATOR = '/'; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Separator signifying complex_field->group relation |
31
|
|
|
* Used when searching for fields in a specific complex field group |
32
|
|
|
*/ |
33
|
|
|
const HIERARCHY_GROUP_SEPARATOR = ':'; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Stores if the container is active on the current page |
37
|
|
|
* |
38
|
|
|
* @see activate() |
39
|
|
|
* @var bool |
40
|
|
|
*/ |
41
|
|
|
protected $active = false; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* List of registered unique field names for this container instance |
45
|
|
|
* |
46
|
|
|
* @see verify_unique_field_name() |
47
|
|
|
* @var array |
48
|
|
|
*/ |
49
|
|
|
protected $registered_field_names = array(); |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Stores all the container Backbone templates |
53
|
|
|
* |
54
|
|
|
* @see factory() |
55
|
|
|
* @see add_template() |
56
|
|
|
* @var array |
57
|
|
|
*/ |
58
|
|
|
protected $templates = array(); |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Tabs available |
62
|
|
|
*/ |
63
|
|
|
protected $tabs = array(); |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* List of default container settings |
67
|
|
|
* |
68
|
|
|
* @see init() |
69
|
|
|
* @var array |
70
|
|
|
*/ |
71
|
|
|
public $settings = array(); |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Title of the container |
75
|
|
|
* |
76
|
|
|
* @var string |
77
|
|
|
*/ |
78
|
|
|
public $title = ''; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* List of notification messages to be displayed on the front-end |
82
|
|
|
* |
83
|
|
|
* @var array |
84
|
|
|
*/ |
85
|
|
|
protected $notifications = array(); |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* List of error messages to be displayed on the front-end |
89
|
|
|
* |
90
|
|
|
* @var array |
91
|
|
|
*/ |
92
|
|
|
protected $errors = array(); |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* List of container fields |
96
|
|
|
* |
97
|
|
|
* @see add_fields() |
98
|
|
|
* @var array |
99
|
|
|
*/ |
100
|
|
|
protected $fields = array(); |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Container datastores. Propagated to all container fields |
104
|
|
|
* |
105
|
|
|
* @see set_datastore() |
106
|
|
|
* @see get_datastore() |
107
|
|
|
* @var object |
108
|
|
|
*/ |
109
|
|
|
protected $datastore; |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* Flag whether the datastore is the default one or replaced with a custom one |
113
|
|
|
* |
114
|
|
|
* @see set_datastore() |
115
|
|
|
* @see get_datastore() |
116
|
|
|
* @var boolean |
117
|
|
|
*/ |
118
|
|
|
protected $has_default_datastore = true; |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Normalizes a container type string to an expected format |
122
|
|
|
* |
123
|
|
|
* @param string $type |
124
|
|
|
* @return string $normalized_type |
125
|
|
|
**/ |
126
|
|
|
protected static function normalize_container_type( $type ) { |
127
|
|
|
// backward compatibility: post_meta container used to be called custom_fields |
128
|
|
|
if ( $type === 'custom_fields' ) { |
129
|
|
|
$type = 'post_meta'; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
$normalized_type = str_replace( ' ', '_', ucwords( str_replace( '_', ' ', $type ) ) ); |
133
|
|
|
return $normalized_type; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Resolves a string-based type to a fully qualified container class name |
138
|
|
|
* |
139
|
|
|
* @param string $type |
140
|
|
|
* @return string $class_name |
141
|
|
|
**/ |
142
|
|
|
protected static function container_type_to_class( $type ) { |
143
|
|
|
$class = __NAMESPACE__ . '\\' . $type . '_Container'; |
144
|
|
View Code Duplication |
if ( ! class_exists( $class ) ) { |
|
|
|
|
145
|
|
|
Incorrect_Syntax_Exception::raise( 'Unknown container "' . $type . '".' ); |
146
|
|
|
$class = __NAMESPACE__ . '\\Broken_Container'; |
147
|
|
|
} |
148
|
|
|
return $class; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Create a new container of type $type and name $name. |
153
|
|
|
* |
154
|
|
|
* @param string $type |
155
|
|
|
* @param string $name Human-readable name of the container |
156
|
|
|
* @return object $container |
157
|
|
|
**/ |
158
|
9 |
|
public static function factory( $type, $name ) { |
159
|
9 |
|
$repository = App::resolve( 'container_repository' ); |
160
|
9 |
|
$unique_id = $repository->get_unique_panel_id( $name ); |
161
|
|
|
|
162
|
9 |
|
$normalized_type = static::normalize_container_type( $type ); |
163
|
9 |
|
$class = static::container_type_to_class( $normalized_type ); |
164
|
7 |
|
$container = new $class( $unique_id, $name, $normalized_type ); |
165
|
7 |
|
$repository->register_container( $container ); |
166
|
|
|
|
167
|
7 |
|
return $container; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* An alias of factory(). |
172
|
|
|
* |
173
|
|
|
* @see Container::factory() |
174
|
|
|
**/ |
175
|
|
|
public static function make( $type, $name ) { |
176
|
|
|
return static::factory( $type, $name ); |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Create a new container |
181
|
|
|
* |
182
|
|
|
* @param string $unique_id Unique id of the container |
183
|
|
|
* @param string $title title of the container |
184
|
|
|
* @param string $type Type of the container |
185
|
|
|
**/ |
186
|
2 |
|
public function __construct( $unique_id, $title, $type ) { |
187
|
2 |
|
App::verify_boot(); |
188
|
|
|
|
189
|
2 |
|
if ( empty( $title ) ) { |
190
|
1 |
|
Incorrect_Syntax_Exception::raise( 'Empty container title is not supported' ); |
191
|
|
|
} |
192
|
|
|
|
193
|
1 |
|
$this->id = $unique_id; |
194
|
1 |
|
$this->title = $title; |
195
|
1 |
|
$this->type = $type; |
196
|
1 |
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Return whether the container is active |
200
|
|
|
**/ |
201
|
|
|
public function active() { |
202
|
|
|
return $this->active; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Activate the container and trigger an action |
207
|
|
|
**/ |
208
|
|
|
protected function activate() { |
209
|
|
|
$this->active = true; |
210
|
|
|
$this->boot(); |
211
|
|
|
do_action( 'crb_container_activated', $this ); |
212
|
|
|
|
213
|
|
|
$fields = $this->get_fields(); |
214
|
|
|
foreach ( $fields as $field ) { |
215
|
|
|
$field->activate(); |
216
|
|
|
} |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* Perform instance initialization |
221
|
|
|
**/ |
222
|
|
|
abstract public function init(); |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Prints the container Underscore template |
226
|
|
|
**/ |
227
|
|
|
public function template() { |
228
|
|
|
?> |
229
|
|
|
<div class="{{{ classes.join(' ') }}}"> |
230
|
|
|
<# _.each(fields, function(field) { #> |
231
|
|
|
<div class="{{{ field.classes.join(' ') }}}"> |
232
|
|
|
<label for="{{{ field.id }}}"> |
233
|
|
|
{{ field.label }} |
234
|
|
|
|
235
|
|
|
<# if (field.required) { #> |
236
|
|
|
<span class="carbon-required">*</span> |
237
|
|
|
<# } #> |
238
|
|
|
</label> |
239
|
|
|
|
240
|
|
|
<div class="field-holder {{{ field.id }}}"></div> |
241
|
|
|
|
242
|
|
|
<# if (field.help_text) { #> |
243
|
|
|
<em class="help-text"> |
244
|
|
|
{{{ field.help_text }}} |
245
|
|
|
</em> |
246
|
|
|
<# } #> |
247
|
|
|
|
248
|
|
|
<em class="carbon-error"></em> |
249
|
|
|
</div> |
250
|
|
|
<# }); #> |
251
|
|
|
</div> |
252
|
|
|
<?php |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* Boot the container once it's attached. |
257
|
|
|
**/ |
258
|
|
|
protected function boot() { |
259
|
|
|
$this->add_template( $this->type, array( $this, 'template' ) ); |
260
|
|
|
|
261
|
|
|
add_action( 'admin_footer', array( get_class(), 'admin_hook_scripts' ), 5 ); |
262
|
|
|
add_action( 'admin_footer', array( get_class(), 'admin_hook_styles' ), 5 ); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Load the value for each field in the container. |
267
|
|
|
* Could be used internally during container rendering |
268
|
|
|
**/ |
269
|
|
|
public function load() { |
270
|
|
|
foreach ( $this->fields as $field ) { |
271
|
|
|
$field->load(); |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* Called first as part of the container save procedure. |
277
|
|
|
* Responsible for checking the request validity and |
278
|
|
|
* calling the container-specific save() method |
279
|
|
|
* |
280
|
|
|
* @see save() |
281
|
|
|
* @see is_valid_save() |
282
|
|
|
**/ |
283
|
|
|
public function _save() { |
284
|
|
|
$param = func_get_args(); |
285
|
|
|
if ( call_user_func_array( array( $this, '_is_valid_save' ), $param ) ) { |
286
|
|
|
call_user_func_array( array( $this, 'save' ), $param ); |
287
|
|
|
} |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* Load submitted data and save each field in the container |
292
|
|
|
* |
293
|
|
|
* @see is_valid_save() |
294
|
|
|
**/ |
295
|
|
|
public function save( $data = null ) { |
296
|
|
|
foreach ( $this->fields as $field ) { |
297
|
|
|
$field->set_value_from_input( stripslashes_deep( $_POST ) ); |
298
|
|
|
$field->save(); |
299
|
|
|
} |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Checks whether the current save request is valid |
304
|
|
|
* |
305
|
|
|
* @return bool |
306
|
|
|
**/ |
307
|
|
|
final protected function _is_valid_save() { |
308
|
|
|
$param = func_get_args(); |
309
|
|
|
$is_valid_save = call_user_func_array( array( $this, 'is_valid_save' ), $param ); |
310
|
|
|
return apply_filters( 'carbon_fields_container_is_valid_save', $is_valid_save, $this ); |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* Checks whether the current save request is valid |
315
|
|
|
* |
316
|
|
|
* @return bool |
317
|
|
|
**/ |
318
|
|
|
abstract protected function is_valid_save(); |
319
|
|
|
|
320
|
|
|
/** |
321
|
|
|
* Called first as part of the container attachment procedure. |
322
|
|
|
* Responsible for checking it's OK to attach the container |
323
|
|
|
* and if it is, calling the container-specific attach() method |
324
|
|
|
* |
325
|
|
|
* @see attach() |
326
|
|
|
* @see is_valid_attach() |
327
|
|
|
**/ |
328
|
|
|
public function _attach() { |
329
|
|
|
$param = func_get_args(); |
330
|
|
|
if ( $this->is_valid_attach() ) { |
331
|
|
|
call_user_func_array( array( $this, 'attach' ), $param ); |
332
|
|
|
|
333
|
|
|
// Allow containers to activate but not load (useful in cases such as theme options) |
334
|
|
|
if ( $this->should_activate() ) { |
335
|
|
|
$this->activate(); |
336
|
|
|
} |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
/** |
341
|
|
|
* Attach the container rendering and helping methods |
342
|
|
|
* to concrete WordPress Action hooks |
343
|
|
|
**/ |
344
|
|
|
public function attach() {} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* Perform checks whether the container should be attached during the current request |
348
|
|
|
* |
349
|
|
|
* @return bool True if the container is allowed to be attached |
350
|
|
|
**/ |
351
|
|
|
final public function is_valid_attach() { |
352
|
|
|
$is_valid_attach = $this->is_valid_attach_for_request(); |
353
|
|
|
return apply_filters( 'carbon_fields_container_is_valid_attach', $is_valid_attach, $this ); |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
/** |
357
|
|
|
* Check container attachment rules against current page request (in admin) |
358
|
|
|
* |
359
|
|
|
* @return bool |
360
|
|
|
**/ |
361
|
|
|
abstract protected function is_valid_attach_for_request(); |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* Check container attachment rules against object id |
365
|
|
|
* |
366
|
|
|
* @param int $object_id |
367
|
|
|
* @return bool |
368
|
|
|
**/ |
369
|
|
|
abstract public function is_valid_attach_for_object( $object_id = null ); |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* Whether this container is currently viewed. |
373
|
|
|
**/ |
374
|
|
|
public function should_activate() { |
375
|
|
|
return $this->is_valid_attach(); |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
/** |
379
|
|
|
* Returns all the Backbone templates |
380
|
|
|
* |
381
|
|
|
* @return array |
382
|
|
|
**/ |
383
|
|
|
public function get_templates() { |
384
|
|
|
return $this->templates; |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
/** |
388
|
|
|
* Adds a new Backbone template |
389
|
|
|
**/ |
390
|
|
|
protected function add_template( $name, $callback ) { |
391
|
|
|
$this->templates[ $name ] = $callback; |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* Perform a check whether the current container has fields |
396
|
|
|
* |
397
|
|
|
* @return bool |
398
|
|
|
**/ |
399
|
|
|
public function has_fields() { |
400
|
|
|
return (bool) $this->fields; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
/** |
404
|
|
|
* Returns the private container array of fields. |
405
|
|
|
* Use only if you are completely aware of what you are doing. |
406
|
|
|
* |
407
|
|
|
* @return array |
408
|
|
|
**/ |
409
|
|
|
public function get_fields() { |
410
|
|
|
return $this->fields; |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
/** |
414
|
|
|
* Return root field from container with specified name |
415
|
|
|
* |
416
|
|
|
* @example crb_complex |
417
|
|
|
* |
418
|
|
|
* @param string $field_name |
419
|
|
|
* @return Field |
420
|
|
|
**/ |
421
|
|
|
public function get_root_field_by_name( $field_name ) { |
422
|
|
|
$fields = $this->get_fields(); |
423
|
|
|
foreach ( $fields as $field ) { |
424
|
|
|
if ( $field->get_base_name() === $field_name ) { |
425
|
|
|
return $field; |
426
|
|
|
} |
427
|
|
|
} |
428
|
|
|
return null; |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
/** |
432
|
|
|
* Get a regex to match field name patterns used to fetch specific fields |
433
|
|
|
* |
434
|
|
|
* @return string |
435
|
|
|
*/ |
436
|
|
|
protected function get_field_pattern_regex() { |
437
|
|
|
// matches: |
438
|
|
|
// field_name |
439
|
|
|
// field_name[0] |
440
|
|
|
// field_name[0]:group_name |
441
|
|
|
// field_name:group_name |
442
|
|
|
$regex = '/ |
443
|
|
|
\A |
444
|
|
|
(?P<field_name>[a-z0-9_]+) |
445
|
|
|
(?:\[(?P<group_index>\d+)\])? |
446
|
|
|
(?:' . preg_quote( static::HIERARCHY_GROUP_SEPARATOR, '/' ). '(?P<group_name>[a-z0-9_]+))? |
447
|
|
|
\z |
448
|
|
|
/x'; |
449
|
|
|
return $regex; |
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
/** |
453
|
|
|
* Return field from container with specified name |
454
|
|
|
* |
455
|
|
|
* @example crb_complex/text_field |
456
|
|
|
* @example crb_complex/complex_2 |
457
|
|
|
* @example crb_complex/complex_2:text_group/text_field |
458
|
|
|
* |
459
|
|
|
* @param string $field_name Can specify a field inside a complex with a / (slash) separator |
460
|
|
|
* @return Field |
461
|
|
|
**/ |
462
|
|
|
public function get_field_by_name( $field_name ) { |
463
|
|
|
$hierarchy = array_filter( explode( static::HIERARCHY_FIELD_SEPARATOR, $field_name ) ); |
464
|
|
|
$field = null; |
465
|
|
|
|
466
|
|
|
$field_group = $this->get_fields(); |
467
|
|
|
$hierarchy_left = $hierarchy; |
468
|
|
|
$field_pattern_regex = $this->get_field_pattern_regex(); |
469
|
|
|
$hierarchy_index = array(); |
470
|
|
|
|
471
|
|
|
while ( ! empty( $hierarchy_left ) ) { |
472
|
|
|
$segment = array_shift( $hierarchy_left ); |
473
|
|
|
$segment_pieces = array(); |
474
|
|
|
if ( ! preg_match( $field_pattern_regex, $segment, $segment_pieces ) ) { |
475
|
|
|
Incorrect_Syntax_Exception::raise( 'Invalid field name pattern used: ' . $field_name ); |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
$segment_field_name = $segment_pieces['field_name']; |
479
|
|
|
$segment_group_index = isset( $segment_pieces['group_index'] ) ? $segment_pieces['group_index'] : 0; |
480
|
|
|
$segment_group_name = isset( $segment_pieces['group_name'] ) ? $segment_pieces['group_name'] : Group_Field::DEFAULT_GROUP_NAME; |
481
|
|
|
|
482
|
|
|
foreach ( $field_group as $f ) { |
483
|
|
|
if ( $f->get_base_name() === $segment_field_name ) { |
484
|
|
|
if ( empty( $hierarchy_left ) ) { |
485
|
|
|
$field = clone $f; |
486
|
|
|
$field->set_hierarchy_index( $hierarchy_index ); |
487
|
|
|
} else { |
488
|
|
|
if ( is_a( $f, 'Carbon_Fields\\Field\\Complex_Field' ) ) { |
489
|
|
|
$group = $f->get_group_by_name( $segment_group_name ); |
490
|
|
|
if ( ! $group ) { |
491
|
|
|
Incorrect_Syntax_Exception::raise( 'Unknown group name specified when fetching a value inside a complex field: "' . $segment_group_name . '".' ); |
492
|
|
|
} |
493
|
|
|
$field_group = $group->get_fields(); |
494
|
|
|
$hierarchy_index[] = $segment_group_index; |
495
|
|
|
} else { |
496
|
|
|
Incorrect_Syntax_Exception::raise( 'Attempted to look for a nested field inside a non-complex field.' ); |
497
|
|
|
} |
498
|
|
|
} |
499
|
|
|
break; |
500
|
|
|
} |
501
|
|
|
} |
502
|
|
|
} |
503
|
|
|
|
504
|
|
|
return $field; |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
/** |
508
|
|
|
* Perform checks whether there is a field registered with the name $name. |
509
|
|
|
* If not, the field name is recorded. |
510
|
|
|
* |
511
|
|
|
* @param string $name |
512
|
|
|
**/ |
513
|
|
View Code Duplication |
public function verify_unique_field_name( $name ) { |
514
|
|
|
if ( in_array( $name, $this->registered_field_names ) ) { |
515
|
|
|
Incorrect_Syntax_Exception::raise( 'Field name "' . $name . '" already registered' ); |
516
|
|
|
} |
517
|
|
|
|
518
|
|
|
$this->registered_field_names[] = $name; |
519
|
|
|
} |
520
|
|
|
|
521
|
|
|
/** |
522
|
|
|
* Remove field name $name from the list of unique field names |
523
|
|
|
* |
524
|
|
|
* @param string $name |
525
|
|
|
**/ |
526
|
|
|
public function drop_unique_field_name( $name ) { |
527
|
|
|
$index = array_search( $name, $this->registered_field_names ); |
528
|
|
|
|
529
|
|
|
if ( $index !== false ) { |
530
|
|
|
unset( $this->registered_field_names[ $index ] ); |
531
|
|
|
} |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
/** |
535
|
|
|
* Return whether the datastore instance is the default one or has been overriden |
536
|
|
|
* |
537
|
|
|
* @return boolean |
538
|
|
|
**/ |
539
|
6 |
|
public function has_default_datastore() { |
540
|
6 |
|
return $this->has_default_datastore; |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
/** |
544
|
|
|
* Set datastore instance |
545
|
|
|
* |
546
|
|
|
* @param Datastore_Interface $datastore |
547
|
|
|
* @return object $this |
548
|
|
|
**/ |
549
|
6 |
View Code Duplication |
public function set_datastore( Datastore_Interface $datastore, $set_as_default = false ) { |
550
|
6 |
|
if ( $set_as_default && ! $this->has_default_datastore() ) { |
551
|
1 |
|
return $this; // datastore has been overriden with a custom one - abort changing to a default one |
552
|
|
|
} |
553
|
6 |
|
$this->datastore = $datastore; |
554
|
6 |
|
$this->has_default_datastore = $set_as_default; |
555
|
|
|
|
556
|
6 |
|
foreach ( $this->fields as $field ) { |
557
|
|
|
$field->set_datastore( $this->get_datastore(), true ); |
558
|
6 |
|
} |
559
|
6 |
|
return $this; |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
/** |
563
|
|
|
* Get the DataStore instance |
564
|
|
|
* |
565
|
|
|
* @return Datastore_Interface $datastore |
566
|
|
|
**/ |
567
|
6 |
|
public function get_datastore() { |
568
|
6 |
|
return $this->datastore; |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
/** |
572
|
|
|
* Return WordPress nonce name used to identify the current container instance |
573
|
|
|
* |
574
|
|
|
* @return string |
575
|
|
|
**/ |
576
|
|
|
public function get_nonce_name() { |
577
|
|
|
return 'carbon_panel_' . $this->id . '_nonce'; |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
/** |
581
|
|
|
* Return WordPress nonce field |
582
|
|
|
* |
583
|
|
|
* @return string |
584
|
|
|
**/ |
585
|
|
|
public function get_nonce_field() { |
586
|
|
|
return wp_nonce_field( $this->get_nonce_name(), $this->get_nonce_name(), /*referer?*/ false, /*echo?*/ false ); |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
/** |
590
|
|
|
* Check if the nonce is present in the request and that it is verified |
591
|
|
|
* |
592
|
|
|
* @return bool |
593
|
|
|
**/ |
594
|
|
|
protected function verified_nonce_in_request() { |
595
|
|
|
$nonce_name = $this->get_nonce_name(); |
596
|
|
|
$nonce_value = isset( $_REQUEST[ $nonce_name ] ) ? $_REQUEST[ $nonce_name ] : ''; |
|
|
|
|
597
|
|
|
return wp_verify_nonce( $nonce_value, $nonce_name ); |
598
|
|
|
} |
599
|
|
|
|
600
|
|
|
/** |
601
|
|
|
* Internal function that creates the tab and associates it with particular field set |
602
|
|
|
* |
603
|
|
|
* @param string $tab_name |
604
|
|
|
* @param array $fields |
605
|
|
|
* @param int $queue_end |
606
|
|
|
* @return object $this |
607
|
|
|
*/ |
608
|
|
|
private function create_tab( $tab_name, $fields, $queue_end = self::TABS_TAIL ) { |
609
|
|
|
if ( isset( $this->tabs[ $tab_name ] ) ) { |
610
|
|
|
Incorrect_Syntax_Exception::raise( "Tab name duplication for $tab_name" ); |
611
|
|
|
} |
612
|
|
|
|
613
|
|
|
if ( $queue_end === static::TABS_TAIL ) { |
614
|
|
|
$this->tabs[ $tab_name ] = array(); |
615
|
|
|
} else if ( $queue_end === static::TABS_HEAD ) { |
616
|
|
|
$this->tabs = array_merge( |
617
|
|
|
array( $tab_name => array() ), |
618
|
|
|
$this->tabs |
619
|
|
|
); |
620
|
|
|
} |
621
|
|
|
|
622
|
|
|
foreach ( $fields as $field ) { |
623
|
|
|
$field_name = $field->get_name(); |
624
|
|
|
$this->tabs[ $tab_name ][ $field_name ] = $field; |
625
|
|
|
} |
626
|
|
|
|
627
|
|
|
$this->settings['tabs'] = $this->get_tabs_json(); |
628
|
|
|
} |
629
|
|
|
|
630
|
|
|
/** |
631
|
|
|
* Whether the container is tabbed or not |
632
|
|
|
* |
633
|
|
|
* @return bool |
634
|
|
|
*/ |
635
|
|
|
public function is_tabbed() { |
636
|
|
|
return (bool) $this->tabs; |
637
|
|
|
} |
638
|
|
|
|
639
|
|
|
/** |
640
|
|
|
* Retrieve all fields that are not defined under a specific tab |
641
|
|
|
* |
642
|
|
|
* @return array |
643
|
|
|
*/ |
644
|
|
|
protected function get_untabbed_fields() { |
645
|
|
|
$tabbed_fields_names = array(); |
646
|
|
|
foreach ( $this->tabs as $tab_fields ) { |
647
|
|
|
$tabbed_fields_names = array_merge( $tabbed_fields_names, array_keys( $tab_fields ) ); |
648
|
|
|
} |
649
|
|
|
|
650
|
|
|
$all_fields_names = array(); |
651
|
|
|
foreach ( $this->fields as $field ) { |
652
|
|
|
$all_fields_names[] = $field->get_name(); |
653
|
|
|
} |
654
|
|
|
|
655
|
|
|
$fields_not_in_tabs = array_diff( $all_fields_names, $tabbed_fields_names ); |
656
|
|
|
|
657
|
|
|
$untabbed_fields = array(); |
658
|
|
|
foreach ( $this->fields as $field ) { |
659
|
|
|
if ( in_array( $field->get_name(), $fields_not_in_tabs ) ) { |
660
|
|
|
$untabbed_fields[] = $field; |
661
|
|
|
} |
662
|
|
|
} |
663
|
|
|
|
664
|
|
|
return $untabbed_fields; |
665
|
|
|
} |
666
|
|
|
|
667
|
|
|
/** |
668
|
|
|
* Retrieve all tabs. |
669
|
|
|
* Create a default tab if there are any untabbed fields. |
670
|
|
|
* |
671
|
|
|
* @return array |
672
|
|
|
*/ |
673
|
|
|
protected function get_tabs() { |
674
|
|
|
$untabbed_fields = $this->get_untabbed_fields(); |
675
|
|
|
|
676
|
|
|
if ( ! empty( $untabbed_fields ) ) { |
677
|
|
|
$this->create_tab( __( 'General', \Carbon_Fields\TEXT_DOMAIN ), $untabbed_fields, static::TABS_HEAD ); |
678
|
|
|
} |
679
|
|
|
|
680
|
|
|
return $this->tabs; |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
/** |
684
|
|
|
* Build the tabs JSON |
685
|
|
|
* |
686
|
|
|
* @return array |
687
|
|
|
*/ |
688
|
|
|
protected function get_tabs_json() { |
689
|
|
|
$tabs_json = array(); |
690
|
|
|
$tabs = $this->get_tabs(); |
691
|
|
|
|
692
|
|
|
foreach ( $tabs as $tab_name => $fields ) { |
693
|
|
|
foreach ( $fields as $field_name => $field ) { |
694
|
|
|
$tabs_json[ $tab_name ][] = $field_name; |
695
|
|
|
} |
696
|
|
|
} |
697
|
|
|
|
698
|
|
|
return $tabs_json; |
699
|
|
|
} |
700
|
|
|
|
701
|
|
|
/** |
702
|
|
|
* Underscore template for tabs |
703
|
|
|
*/ |
704
|
|
|
public function template_tabs() { |
705
|
|
|
?> |
706
|
|
|
<div class="carbon-tabs"> |
707
|
|
|
<ul class="carbon-tabs-nav"> |
708
|
|
|
<# _.each(tabs, function (tab, tabName) { #> |
709
|
|
|
<li><a href="#" data-id="{{{ tab.id }}}">{{{ tabName }}}</a></li> |
710
|
|
|
<# }); #> |
711
|
|
|
</ul> |
712
|
|
|
|
713
|
|
|
<div class="carbon-tabs-body"> |
714
|
|
|
<# _.each(tabs, function (tab) { #> |
715
|
|
|
<div class="carbon-fields-collection carbon-tab"> |
716
|
|
|
{{{ tab.html }}} |
717
|
|
|
</div> |
718
|
|
|
<# }); #> |
719
|
|
|
</div> |
720
|
|
|
</div> |
721
|
|
|
<?php |
722
|
|
|
} |
723
|
|
|
|
724
|
|
|
/** |
725
|
|
|
* Returns an array that holds the container data, suitable for JSON representation. |
726
|
|
|
* This data will be available in the Underscore template and the Backbone Model. |
727
|
|
|
* |
728
|
|
|
* @param bool $load Should the value be loaded from the database or use the value from the current instance. |
729
|
|
|
* @return array |
730
|
|
|
*/ |
731
|
|
|
public function to_json( $load ) { |
732
|
|
|
$container_data = array( |
733
|
|
|
'id' => $this->id, |
734
|
|
|
'type' => $this->type, |
735
|
|
|
'title' => $this->title, |
736
|
|
|
'settings' => $this->settings, |
737
|
|
|
'fields' => array(), |
738
|
|
|
); |
739
|
|
|
|
740
|
|
|
$fields = $this->get_fields(); |
741
|
|
|
foreach ( $fields as $field ) { |
742
|
|
|
$field_data = $field->to_json( $load ); |
743
|
|
|
$container_data['fields'][] = $field_data; |
744
|
|
|
} |
745
|
|
|
|
746
|
|
|
return $container_data; |
747
|
|
|
} |
748
|
|
|
|
749
|
|
|
/** |
750
|
|
|
* Enqueue admin scripts |
751
|
|
|
*/ |
752
|
|
|
public static function admin_hook_scripts() { |
753
|
|
|
wp_enqueue_script( 'carbon-containers', \Carbon_Fields\URL . '/assets/js/containers.js', array( 'carbon-app' ), \Carbon_Fields\VERSION ); |
754
|
|
|
|
755
|
|
|
wp_localize_script( 'carbon-containers', 'carbon_containers_l10n', |
756
|
|
|
array( |
757
|
|
|
'please_fill_the_required_fields' => __( 'Please fill out all required fields highlighted below.', \Carbon_Fields\TEXT_DOMAIN ), |
758
|
|
|
'changes_made_save_alert' => __( 'The changes you made will be lost if you navigate away from this page.', \Carbon_Fields\TEXT_DOMAIN ), |
759
|
|
|
) |
760
|
|
|
); |
761
|
|
|
} |
762
|
|
|
|
763
|
|
|
/** |
764
|
|
|
* Enqueue admin styles |
765
|
|
|
*/ |
766
|
|
|
public static function admin_hook_styles() { |
767
|
|
|
wp_enqueue_style( 'carbon-main', \Carbon_Fields\URL . '/assets/bundle.css', array(), \Carbon_Fields\VERSION ); |
768
|
|
|
} |
769
|
|
|
|
770
|
|
|
/** |
771
|
|
|
* COMMON USAGE METHODS |
772
|
|
|
*/ |
773
|
|
|
|
774
|
|
|
/** |
775
|
|
|
* Append array of fields to the current fields set. All items of the array |
776
|
|
|
* must be instances of Field and their names should be unique for all |
777
|
|
|
* Carbon containers. |
778
|
|
|
* If a field does not have DataStore already, the container datastore is |
779
|
|
|
* assigned to them instead. |
780
|
|
|
* |
781
|
|
|
* @param array $fields |
782
|
|
|
* @return object $this |
783
|
|
|
**/ |
784
|
|
|
public function add_fields( $fields ) { |
785
|
|
|
foreach ( $fields as $field ) { |
786
|
|
|
if ( ! is_a( $field, 'Carbon_Fields\\Field\\Field' ) ) { |
787
|
|
|
Incorrect_Syntax_Exception::raise( 'Object must be of type Carbon_Fields\\Field\\Field' ); |
788
|
|
|
} |
789
|
|
|
|
790
|
|
|
$this->verify_unique_field_name( $field->get_name() ); |
791
|
|
|
|
792
|
|
|
$field->set_context( $this->type ); |
793
|
|
|
if ( ! $field->get_datastore() ) { |
794
|
|
|
$field->set_datastore( $this->get_datastore(), $this->has_default_datastore() ); |
795
|
|
|
} |
796
|
|
|
} |
797
|
|
|
|
798
|
|
|
$this->fields = array_merge( $this->fields, $fields ); |
799
|
|
|
|
800
|
|
|
return $this; |
801
|
|
|
} |
802
|
|
|
|
803
|
|
|
/** |
804
|
|
|
* Configuration function for adding tab with fields |
805
|
|
|
* |
806
|
|
|
* @param string $tab_name |
807
|
|
|
* @param array $fields |
808
|
|
|
* @return object $this |
809
|
|
|
*/ |
810
|
|
|
public function add_tab( $tab_name, $fields ) { |
811
|
|
|
$this->add_template( 'tabs', array( $this, 'template_tabs' ) ); |
812
|
|
|
|
813
|
|
|
$this->add_fields( $fields ); |
814
|
|
|
$this->create_tab( $tab_name, $fields ); |
815
|
|
|
|
816
|
|
|
return $this; |
817
|
|
|
} |
818
|
|
|
} |
819
|
|
|
|
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.