1 <?php
2
3 4 5 6 7 8 9 10 11
12 class Fieldmanager_Group extends Fieldmanager_Field {
13
14 15 16 17
18 public $children = array();
19
20 21 22 23
24 public $field_class = 'group';
25
26 27 28 29
30 public $label_element = 'h4';
31
32 33 34 35
36 public $collapsible = FALSE;
37
38 39 40 41
42 public $collapsed = FALSE;
43
44 45 46 47 48 49
50 public $tabbed = false;
51
52 53 54 55
56 public $tab_limit = 0;
57
58 59 60 61 62
63 public $persist_active_tab = true;
64
65 66 67 68 69 70 71 72
73 public $label_macro = Null;
74
75 76 77 78 79
80 public $label_format = Null;
81
82 83 84 85
86 public $label_token = Null;
87
88 89 90 91
92 public $group_is_empty = Null;
93
94 95 96 97 98 99 100 101 102
103 public $add_to_prefix = true;
104
105 106 107 108
109 protected $child_count = 0;
110
111 112 113 114 115 116 117 118 119 120
121 public $has_unserialized_descendants = false;
122
123 124 125
126 public function __construct( $label = '', $options = array() ) {
127
128 parent::__construct( $label, $options );
129
130
131 $is_repeatable = ( 1 != $this->limit );
132 if ( ! $this->serialize_data && $is_repeatable ) {
133 throw new FM_Developer_Exception( esc_html__( 'You cannot use `"serialize_data" => false` with repeating groups', 'fieldmanager' ) );
134 }
135
136
137 if ( $this->collapsed ) {
138 $this->collapsible = True;
139 }
140
141
142 foreach ( $this->children as $name => $element ) {
143
144 if ( $element->name && !is_int( $name ) && $element->name != $name ) {
145 throw new FM_Developer_Exception( esc_html__( 'Group child name conflict: ', 'fieldmanager' ) . $name . ' / ' . $element->name );
146 } elseif ( ! $element->name ) {
147 $element->name = $name;
148 }
149
150
151 if ( ! $this->serialize_data && $element->index ) {
152 throw new FM_Developer_Exception( esc_html__( 'You cannot use `serialize_data => false` with `index => true`', 'fieldmanager' ) );
153 }
154
155
156
157 if ( $element->datasource && ! empty( $element->datasource->save_to_post_parent ) && $this->is_repeatable() ) {
158 _doing_it_wrong( 'Fieldmanager_Datasource_Post::$save_to_post_parent', __( 'A post can only have one parent, therefore you cannot store to post_parent in repeatable fields.', 'fieldmanager' ), '1.0.0' );
159 $element->datasource->save_to_post_parent = false;
160 $element->datasource->only_save_to_post_parent = false;
161 }
162
163
164 if ( ! $this->has_unserialized_descendants && ( ! $element->serialize_data || ( $element->is_group() && $element->has_unserialized_descendants ) ) ) {
165 $this->has_unserialized_descendants = true;
166 }
167
168
169 $element->parent = $this;
170 }
171
172
173 if ( $is_repeatable && $this->has_unserialized_descendants ) {
174 throw new FM_Developer_Exception( esc_html__( 'You cannot use `serialize_data => false` with repeating groups', 'fieldmanager' ) );
175 }
176
177
178 if ( $this->tabbed ) {
179 fm_add_script( 'jquery-hoverintent', 'js/jquery.hoverIntent.js', array( 'jquery' ), '1.8.0' );
180 fm_add_script( 'fm_group_tabs_js', 'js/fieldmanager-group-tabs.js', array( 'jquery', 'jquery-hoverintent' ), '1.0.3' );
181 fm_add_style( 'fm_group_tabs_css', 'css/fieldmanager-group-tabs.css', array(), '1.0.4' );
182 }
183 }
184
185 186 187 188
189 public function form_element( $value = NULL ) {
190 $out = '';
191 $tab_group = '';
192 $tab_group_submenu = '';
193
194
195 if ( isset( $this->label ) && !empty( $this->label ) ) {
196 $out .= '<div class="fm-group-inner">';
197 }
198
199
200 if ( $this->tabbed ) {
201 $tab_group = sprintf( '<ul class="fm-tab-bar wp-tab-bar %s" id="%s-tabs">',
202 $this->persist_active_tab ? 'fm-persist-active-tab' : '',
203 esc_attr( $this->get_element_id() ) );
204 }
205
206
207 foreach ( $this->children as $element ) {
208
209 $element->parent = $this;
210
211
212 if ( $this->tabbed ) {
213
214
215 $tab_classes = array( 'fm-tab' );
216 $tab_classes[] = ( $this->child_count == 0 ) ? "wp-tab-active" : "hide-if-no-js";
217
218
219 if ( $this->tab_limit == 0 || $this->child_count < $this->tab_limit ) {
220 $tab_group .= sprintf( '<li class="%s"><a href="#%s-tab">%s</a></li>',
221 esc_attr( implode( " ", $tab_classes ) ),
222 esc_attr( $element->get_element_id() ),
223 $element->escape( 'label' )
224 );
225 } else if ( $this->tab_limit != 0 && $this->child_count >= $this->tab_limit ) {
226 $submenu_item_classes = array( 'fm-submenu-item' );
227 $submenu_item_link_class = "";
228
229
230 if ( $this->child_count == $this->tab_limit ) {
231
232 $tab_group_submenu .= sprintf( '<li class="fm-tab fm-has-submenu"><a href="#%s-tab">%s</a>',
233 esc_attr( $element->get_element_id() ),
234 esc_html__( 'More...', 'fieldmanager' )
235 );
236
237
238 $tab_group_submenu .= sprintf(
239 '<div class="fm-submenu" id="%s-submenu"><div class="fm-submenu-wrap fm-submenu-wrap"><ul>',
240 esc_attr( $this->get_element_id() )
241 );
242
243
244 $submenu_item_classes[] = 'fm-first-item';
245 $submenu_item_link_class = 'class="fm-first-item"';
246 }
247
248
249 $tab_group_submenu .= sprintf( '<li class="%s"><a href="#%s-tab" %s>%s</a></li>',
250 esc_attr( implode( ' ', $submenu_item_classes ) ),
251 esc_attr( $element->get_element_id() ),
252 $submenu_item_link_class,
253 $element->escape( 'label' )
254 );
255 }
256
257
258 $element->is_tab = TRUE;
259 }
260
261
262 $child_value = isset( $value[ $element->name ] ) ? $value[ $element->name ] : null;
263
264
265 if ( $this->data_type ) $element->data_type = $this->data_type;
266 if ( $this->data_id ) $element->data_id = $this->data_id;
267
268 $out .= $element->element_markup( $child_value );
269
270 $this->child_count++;
271
272 }
273
274
275 if ( isset( $this->label ) && !empty( $this->label ) ) $out .= '</div>';
276
277
278 if ( $this->tab_limit != 0 && $this->child_count >= $this->tab_limit ) $tab_group_submenu .= '</ul></div></div></li>';
279 if ( $this->tabbed ) $tab_group .= $tab_group_submenu . '</ul>';
280
281
282
283 return $tab_group . $out;
284 }
285
286 287 288 289 290
291 public function add_child( Fieldmanager_Field $child ) {
292 $child->parent = $this;
293 $this->children[ $child->name ] = $child;
294
295
296 if ( ! $this->serialize_data && $child->index ) {
297 throw new FM_Developer_Exception( esc_html__( 'You cannot use `serialize_data => false` with `index => true`', 'fieldmanager' ) );
298 }
299 }
300
301 302 303 304 305
306 public function presave( $values, $current_values = array() ) {
307
308 if( isset( $values ) && !empty( $values ) ) {
309 foreach ( array_keys( $values ) as $key ) {
310 if ( !isset( $this->children[$key] ) ) {
311
312
313 $this->_unauthorized_access( sprintf( __( 'Found "%1$s" in data but not in children', 'fieldmanager' ), $key ) );
314 }
315 }
316 }
317
318
319 $skip_save_all = true;
320 foreach ( $this->children as $k => $element ) {
321 $element->data_id = $this->data_id;
322 $element->data_type = $this->data_type;
323 if ( ! isset( $values[ $element->name ] ) ) {
324 $values[ $element->name ] = NULL;
325 }
326
327 if ( $element->skip_save ) {
328 unset( $values[ $element->name ] );
329 continue;
330 }
331
332 $child_value = empty( $values[ $element->name ] ) ? Null : $values[ $element->name ];
333 $current_child_value = ! isset( $current_values[ $element->name ] ) ? array() : $current_values[ $element->name ];
334 $values[ $element->name ] = $element->presave_all( $values[ $element->name ], $current_child_value );
335 if ( ! $this->save_empty && $this->limit != 1 ) {
336 if ( is_array( $values[ $element->name ] ) && empty( $values[ $element->name ] ) ) unset( $values[ $element->name ] );
337 elseif ( empty( $values[ $element->name ] ) ) unset( $values[ $element->name ] );
338 }
339
340 if ( ! empty( $element->datasource->only_save_to_taxonomy ) || ! empty( $element->datasource->only_save_to_post_parent ) ) {
341 unset( $values[ $element->name ] );
342 continue;
343 }
344
345 $skip_save_all = false;
346 }
347
348 if ( $skip_save_all ) {
349 $this->skip_save = true;
350 }
351
352 if ( is_callable( $this->group_is_empty ) ) {
353 if ( call_user_func( $this->group_is_empty, $values ) ) {
354 $values = array();
355 }
356 }
357
358 return $values;
359 }
360
361 362 363 364 365
366 public function get_element_label( $classes = array() ) {
367 $classes[] = 'fm-label';
368 $classes[] = 'fm-label-' . $this->name;
369
370 $wrapper_classes = array( 'fm-group-label-wrapper' );
371
372 if ( $this->sortable ) {
373 $wrapper_classes[] = 'fmjs-drag';
374 $wrapper_classes[] = 'fmjs-drag-header';
375 }
376
377 $collapse_handle = '';
378 if ( $this->collapsible ) {
379 $wrapper_classes[] = 'fmjs-collapsible-handle';
380 $collapse_handle = $this->get_collapse_handle();
381 }
382
383 $extra_attrs = '';
384 if ( $this->label_macro ) {
385 $this->label_format = $this->label_macro[0];
386 $this->label_token = sprintf( '.fm-%s .fm-element:input', $this->label_macro[1] );
387 }
388
389 if ( $this->label_format && $this->label_token ) {
390 $extra_attrs = sprintf(
391 'data-label-format="%1$s" data-label-token="%2$s"',
392 esc_attr( $this->label_format ),
393 esc_attr( $this->label_token )
394 );
395 $classes[] = 'fm-label-with-macro';
396 }
397
398 $remove = '';
399 if ( $this->one_label_per_item && ( $this->limit == 0 || ( $this->limit > 1 && $this->limit > $this->minimum_count ) ) ) {
400 $remove = $this->get_remove_handle();
401 }
402
403 return sprintf(
404 '<div class="%1$s"><%2$s class="%3$s"%4$s>%5$s</%2$s>%6$s%7$s</div>',
405 esc_attr( implode( ' ', $wrapper_classes ) ),
406 $this->label_element,
407 esc_attr( implode( ' ', $classes ) ),
408 $extra_attrs,
409 $this->escape( 'label' ),
410 $collapse_handle,
411 $remove
412 );
413 }
414
415 416 417 418 419
420 public function wrap_with_multi_tools( $html, $classes = array() ) {
421 if ( empty( $this->label ) || ! $this->one_label_per_item ) {
422 return parent::wrap_with_multi_tools( $html, $classes );
423 }
424 return $html;
425 }
426
427 428 429 430
431 public function () {
432 $classes = array();
433 if ( $this->collapsible ) {
434 $classes[] = 'fm-collapsible';
435 }
436 if ( $this->collapsed ) {
437 $classes[] = 'fm-collapsed';
438 }
439 return $classes;
440 }
441
442 443 444 445 446 447 448
449 protected function add_meta_boxes_to_remove( &$meta_boxes_to_remove ) {
450 foreach( $this->children as $child ) {
451
452 if ( $this->remove_default_meta_boxes ) {
453 $child->remove_default_meta_boxes = true;
454 }
455
456 $child->add_meta_boxes_to_remove( $meta_boxes_to_remove );
457 }
458 }
459
460 }
461