1: <?php
2:
3: 4: 5: 6:
7: class Papi_Property_Flexible extends Papi_Property_Repeater {
8:
9: 10: 11: 12: 13:
14: public $convert_type = 'array';
15:
16: 17: 18: 19: 20:
21: protected $counter = 0;
22:
23: 24: 25: 26: 27:
28: public $default_value = [];
29:
30: 31: 32: 33: 34:
35: protected $exclude_properties = ['flexible', 'repeater'];
36:
37: 38: 39: 40: 41:
42: protected $layout_key = '_layout';
43:
44: 45: 46: 47: 48:
49: private $layout_prefix_regex = '/^\_flexible\_layout\_/';
50:
51: 52: 53: 54: 55: 56: 57: 58: 59:
60: public function delete_value( $slug, $post_id, $type ) {
61: $rows = intval( papi_get_property_meta_value( $post_id, $slug ) );
62: $value = $this->load_value( $rows, $slug, $post_id );
63: $value = papi_to_property_array_slugs( $value, $slug );
64: $result = true;
65:
66: foreach ( $value as $key => $value ) {
67: $out = papi_delete_property_meta_value( $post_id, $key, $type );
68: $result = $out ? $result : $out;
69: }
70:
71: return $result;
72: }
73:
74: 75: 76: 77: 78: 79: 80: 81: 82: 83:
84: public function format_value( $values, $repeater_slug, $post_id ) {
85: if ( ! is_array( $values ) ) {
86: return [];
87: }
88:
89: foreach ( $values as $index => $layout ) {
90: foreach ( $layout as $slug => $value ) {
91: if ( is_string( $value ) && preg_match( $this->layout_prefix_regex, $value ) ) {
92: if ( isset( $values[$index][$this->layout_key] ) ) {
93: unset( $values[$index][$slug] );
94: continue;
95: }
96:
97: $values[$index][$this->layout_key] = $value;
98: unset( $values[$index][$slug] );
99:
100: continue;
101: }
102:
103: if ( papi_is_property_type_key( $slug ) ) {
104: continue;
105: }
106:
107: $property_type_slug = papi_get_property_type_key_f( $slug );
108:
109: if ( ! isset( $values[$index][$property_type_slug] ) ) {
110: continue;
111: }
112:
113: $property_type_value = $values[$index][$property_type_slug];
114: $property_type = papi_get_property_type( $property_type_value );
115:
116: if ( ! is_object( $property_type ) ) {
117: continue;
118: }
119:
120:
121: $child_slug = $this->get_child_slug( $repeater_slug, $slug );
122:
123:
124: $values[$index][$slug] = $property_type->load_value(
125: $value,
126: $child_slug,
127: $post_id
128: );
129:
130: $values[$index][$slug] = papi_filter_load_value(
131: $property_type->type,
132: $values[$index][$slug],
133: $child_slug,
134: $post_id
135: );
136:
137:
138: $values[$index][$slug] = $property_type->format_value(
139: $values[$index][$slug],
140: $child_slug,
141: $post_id
142: );
143:
144: if ( ! is_admin() ) {
145: $values[$index][$slug] = papi_filter_format_value(
146: $property_type->type,
147: $values[$index][$slug],
148: $child_slug,
149: $post_id
150: );
151: }
152:
153: $values[$index][$property_type_slug] = $property_type_value;
154: }
155: }
156:
157: if ( ! is_admin() ) {
158: foreach ( $values as $index => $row ) {
159: foreach ( $row as $slug => $value ) {
160: if ( is_string( $value ) && preg_match( $this->layout_prefix_regex, $value ) ) {
161: $values[$index][$slug] = preg_replace(
162: $this->layout_prefix_regex,
163: '',
164: $value
165: );
166: }
167:
168: if ( papi_is_property_type_key( $slug ) ) {
169: unset( $values[$index][$slug] );
170: }
171: }
172: }
173: }
174:
175: return $values;
176: }
177:
178: 179: 180: 181: 182: 183: 184:
185: protected function is_layout_key( $key ) {
186: return is_string( $key ) && $this->layout_key === $key;
187: }
188:
189: 190: 191: 192: 193: 194: 195: 196:
197: protected function get_json_id( $key, $extra = '' ) {
198: return $this->get_slug() . '_' . papi_slugify( $key ) . (
199: empty( $extra ) ? '' : '_' . $extra
200: );
201: }
202:
203: 204: 205: 206: 207: 208: 209:
210: protected function get_layout( $slug ) {
211: $layouts = $this->get_settings_layouts();
212:
213: foreach ( $layouts as $layout ) {
214: if ( $layout['slug'] === $slug ) {
215: return $layout;
216: }
217: }
218: }
219:
220: 221: 222: 223: 224: 225: 226: 227:
228: protected function get_layout_value( $prefix, $name ) {
229: return sprintf( '_flexible_%s_%s', $prefix, $name );
230: }
231:
232: 233: 234: 235: 236: 237: 238: 239: 240:
241: protected function get_results( $value, $repeater_slug, $post_id ) {
242: global $wpdb;
243:
244: $option_page = $this->is_option_page();
245:
246: if ( $option_page ) {
247: $table = $wpdb->prefix . 'options';
248: $query = $wpdb->prepare(
249: "SELECT * FROM `$table` WHERE `option_name` LIKE '%s' ORDER BY `option_id` ASC",
250: $repeater_slug . '_%'
251: );
252: } else {
253: $table = $wpdb->prefix . 'postmeta';
254: $query = $wpdb->prepare(
255: "SELECT * FROM `$table` WHERE `meta_key` LIKE '%s' AND `post_id` = %s ORDER BY `meta_id` ASC", $repeater_slug . '_%',
256: $post_id
257: );
258: }
259:
260: $dbresults = $wpdb->get_results( $query );
261: $value = intval( $value );
262:
263:
264: if ( empty( $value ) ) {
265: return [[], []];
266: }
267:
268: $values = [];
269: $results = [];
270: $trash = [];
271:
272:
273: $rows = $this->get_row_results( $dbresults );
274:
275:
276: $columns = array_map( function ( $row ) {
277: return count( $row ) / 2;
278: }, $rows );
279:
280: $rows = array_values( $rows );
281:
282:
283: $values[$repeater_slug] = $value;
284:
285: for ( $i = 0; $i < $value; $i++ ) {
286:
287: $no_trash = [];
288:
289: if ( ! isset( $columns[$i] ) || ! isset( $rows[$i] ) ) {
290: continue;
291: }
292:
293: foreach ( $rows[$i] as $slug => $meta ) {
294: if ( ! is_string( $slug ) || ! isset( $rows[$i][$slug] ) ) {
295: continue;
296: }
297:
298:
299: if ( is_string( $meta->meta_value ) && preg_match( $this->layout_prefix_regex, $meta->meta_value ) ) {
300: if ( ! isset( $values[$slug] ) ) {
301: $values[$slug] = $meta->meta_value;
302: }
303:
304: continue;
305: }
306:
307:
308:
309: $no_trash[$slug] = $meta;
310:
311:
312: $property_type_key = papi_get_property_type_key_f(
313: $meta->meta_key
314: );
315: $property_type_value = papi_get_property_meta_value(
316: $post_id,
317: $property_type_key
318: );
319:
320:
321: $meta->meta_value = papi_maybe_json_decode(
322: maybe_unserialize( $meta->meta_value )
323: );
324:
325:
326: $values[$meta->meta_key] = $meta->meta_value;
327: $values[$property_type_key] = $property_type_value;
328:
329:
330: if ( ! preg_match( '/\_layout$/', $slug ) && is_string( $rows[$i][$slug]->meta_value ) && ! preg_match( $this->layout_prefix_regex, $rows[$i][$slug]->meta_value ) ) {
331: $slug .= '_layout';
332: }
333:
334: if ( isset( $rows[$i][$slug] ) ) {
335:
336: $values[$slug] = $rows[$i][$slug]->meta_value;
337: }
338: }
339:
340:
341: $trash_diff = array_diff(
342: array_keys( $rows[$i] ),
343: array_keys( $no_trash )
344: );
345:
346: if ( ! empty( $trash_diff ) ) {
347:
348: foreach ( $trash_diff as $slug ) {
349: if ( ! isset( $results[$i] ) || ! isset( $rows[$i][$slug] ) ) {
350: continue;
351: }
352:
353: $trash[$results[$i][$slug]->meta_key] = $rows[$i][$slug];
354: }
355: }
356: }
357:
358: $dblayouts = [];
359:
360:
361: foreach ( array_keys( $values ) as $slug ) {
362: if ( preg_match( '/slug\\' . $this->layout_key . '$/', $slug ) ) {
363: $num = str_replace( $repeater_slug . '_', '', $slug );
364: $num = intval( $num[0] );
365:
366: if ( ! isset( $dblayouts[$num] ) ) {
367: $dblayouts[$num] = $num . $values[$slug];
368: }
369: }
370: }
371:
372: $layouts = $this->get_settings_layouts();
373:
374:
375: for ( $i = 0; $i < $value; $i++ ) {
376: foreach ( $layouts as $layout ) {
377: if ( in_array( $i . $layout['slug'], $dblayouts ) ) {
378: foreach ( $layout['items'] as $prop ) {
379: $slug = sprintf( '%s_%d_%s', $repeater_slug, $i, papi_remove_papi( $prop->slug ) );
380:
381: if ( ! isset( $values[$slug] ) ) {
382: $values[$slug] = null;
383: }
384: }
385: }
386: }
387: }
388:
389: return [$values, $trash];
390: }
391:
392: 393: 394: 395: 396:
397: protected function get_settings_layouts() {
398: $settings = $this->get_settings();
399: return $this->prepare_properties( papi_to_array( $settings->items ) );
400: }
401:
402: 403: 404: 405: 406: 407: 408: 409: 410: 411:
412: public function load_value( $value, $repeater_slug, $post_id ) {
413: if ( is_array( $value ) ) {
414: return $value;
415: }
416:
417: list( $results, $trash ) = $this->get_results( $value, $repeater_slug, $post_id );
418:
419:
420: unset( $trash );
421:
422: $page = $this->get_page();
423: $results = papi_from_property_array_slugs(
424: $results,
425: papi_remove_papi( $repeater_slug )
426: );
427:
428: if ( is_null( $page ) ) {
429: return $this->default_value;
430: }
431:
432: foreach ( $results as $index => $row ) {
433: foreach ( $row as $slug => $value ) {
434: if ( papi_is_property_type_key( $slug ) ) {
435: continue;
436: }
437:
438: if ( $property = $page->get_property( $repeater_slug, $slug ) ) {
439: $type_key = papi_get_property_type_key_f( $slug );
440: $results[$index][$type_key] = $property;
441: }
442: }
443: }
444:
445: return $results;
446: }
447:
448: 449: 450: 451: 452: 453: 454: 455: 456: 457:
458: protected function prepare_properties( $layouts ) {
459: $layouts = array_map( function ( $layout ) {
460: return (array) $layout;
461: }, $layouts );
462:
463: foreach ( $layouts as $index => $layout ) {
464: if ( ! $this->valid_layout( $layout ) ) {
465: if ( is_array( $layout ) ) {
466: unset( $layout[$index] );
467: } else {
468: unset( $layouts[$index] );
469: }
470:
471: continue;
472: }
473:
474: if ( ! isset( $layout['slug'] ) ) {
475: $layout['slug'] = $layout['title'];
476: }
477:
478: $layouts[$index]['slug'] = papi_slugify( $layout['slug'] );
479: $layouts[$index]['slug'] = $this->get_layout_value(
480: 'layout',
481: $layouts[$index]['slug']
482: );
483: $layouts[$index]['items'] = parent::prepare_properties(
484: $layout['items']
485: );
486: }
487:
488: return array_filter( $layouts );
489: }
490:
491: 492: 493:
494: public function render_ajax_request() {
495: $items = null;
496: $layouts = $this->get_settings_layouts();
497:
498: if ( defined( 'DOING_PAPI_AJAX' ) && DOING_PAPI_AJAX ) {
499: $counter = papi_get_qs( 'counter' );
500: $this->counter = intval( $counter );
501: $flexible_layout = papi_get_qs( 'flexible_layout' );
502: foreach ( $layouts as $layout ) {
503: if ( $layout['slug'] === $flexible_layout ) {
504: $items = $layout;
505: break;
506: }
507: }
508: }
509:
510: if ( ! empty( $items ) ) {
511: $this->render_properties( $items, false );
512: }
513: }
514:
515: 516: 517: 518: 519:
520: protected function render_json_template( $slug ) {
521: $options = $this->get_options();
522:
523: foreach ( $options->settings->items as $key => $value ) {
524: if ( ! isset( $value['items'] ) ) {
525: continue;
526: }
527:
528: foreach ( $value['items'] as $index => $property ) {
529:
530: if ( $property->disabled() ) {
531: unset( $options->settings->items[$key]['items'][$index] );
532: continue;
533: }
534:
535: $options->settings->items[$key]['items'][$index] = clone $property->get_options();
536: }
537: }
538: ?>
539: <script type="application/json" data-papi-json="<?php echo $slug; ?>_repeater_json">
540: <?php echo json_encode( [$options] ); ?>
541: </script>
542: <?php
543: }
544:
545: 546: 547: 548: 549: 550:
551: protected function render_layout_input( $slug, $value ) {
552:
553: $slug = $this->html_name( papi_property( [
554: 'type' => 'hidden',
555: 'slug' => $slug . $this->layout_key
556: ] ), $this->counter );
557: ?>
558: <input type="hidden" name="<?php echo $slug; ?>" value="<?php echo $value; ?>" />
559: <?php
560: }
561:
562: 563: 564: 565: 566: 567:
568: protected function render_properties( $row, $value ) {
569: $has_value = $value !== false;
570: $layout_slug = isset( $row['slug'] ) ? $row['slug'] : $value['_layout'];
571: $render_layout = $this->get_setting( 'layout' );
572: $row = isset( $row['items'] ) ? $row['items'] : $row;
573: ?>
574: <td class="repeater-column flexible-column <?php echo $render_layout === 'table' ? 'flexible-layout-table' : 'flexible-layout-row'; ?>">
575: <div class="repeater-content-open">
576: <table class="<?php echo $render_layout === 'table' ? 'flexible-table' : 'papi-table'; ?>">
577: <?php
578: if ( $render_layout === 'table' ):
579: echo '<thead>';
580: for ( $i = 0, $l = count( $row ); $i < $l; $i++ ) {
581:
582: if ( $row[$i]->disabled() ) {
583: continue;
584: }
585:
586: echo '<th class="' . ( $row[$i]->display() ? '' : 'papi-hide' ) . '">';
587: echo sprintf(
588: '<label for="%s">%s</label>',
589: $this->html_id( $row[$i], $this->counter ),
590: $row[$i]->title
591: );
592: echo '</th>';
593: }
594: echo '</thead>';
595: endif;
596:
597: echo '<tbody>';
598:
599: if ( $render_layout === 'table' ):
600: echo '<tr>';
601: endif;
602:
603: for ( $i = 0, $l = count( $row ); $i < $l; $i++ ) {
604:
605: if ( $row[$i]->disabled() ) {
606: continue;
607: }
608:
609: $render_property = clone $row[$i]->get_options();
610: $value_slug = $row[$i]->get_slug( true );
611:
612: if ( $has_value ) {
613: if ( array_key_exists( $value_slug, $value ) ) {
614: $render_property->value = $value[$value_slug];
615: } else {
616: if ( array_key_exists( $row[$i]->get_slug(), $value ) ) {
617: $render_property->value = $row[$i]->default_value;
618: } else {
619: continue;
620: }
621: }
622: }
623:
624: $render_property->slug = $this->html_name(
625: $render_property,
626: $this->counter
627: );
628: $render_property->raw = $render_layout === 'table';
629:
630: if ( $render_layout === 'table' ) {
631: echo '<td class="' . ( $row[$i]->display() ? '' : 'papi-hide' ) . '">';
632: }
633:
634: $layout_value = isset( $layout_slug ) ?
635: $layout_slug : $value[$this->layout_key];
636:
637: $this->render_layout_input(
638: $value_slug,
639: $layout_value
640: );
641:
642: papi_render_property( $render_property );
643:
644: if ( $render_layout === 'table' ) {
645: echo '</td>';
646: }
647: }
648:
649: if ( $render_layout === 'table' ):
650: echo '</tr>';
651: endif;
652:
653: echo '</tbody>';
654: ?>
655: </table>
656: </div>
657: <div class="repeater-content-closed">
658: <?php
659: if ( $layout = $this->get_layout( $layout_slug ) ) {
660: echo $layout['title'];
661: }
662: ?>
663: </div>
664: </td>
665: <?php
666: }
667:
668: 669: 670: 671: 672:
673: protected function render_repeater( $options ) {
674: $layouts = $this->get_settings_layouts();
675: ?>
676: <div class="papi-property-flexible papi-property-repeater-top" data-limit="<?php echo $this->get_setting( 'limit' ); ?>">
677: <table class="papi-table">
678: <tbody class="repeater-tbody flexible-tbody">
679: <?php $this->render_repeater_row(); ?>
680: </tbody>
681: </table>
682:
683: <div class="bottom">
684: <div class="flexible-layouts-btn-wrap">
685: <div class="flexible-layouts papi-hide">
686: <div class="flexible-layouts-arrow"></div>
687: <ul>
688: <?php
689: foreach ( $layouts as $layout ) {
690: papi_render_html_tag( 'li', [
691: papi_html_tag( 'a', [
692: 'data-layout' => $layout['slug'],
693: 'data-papi-json' => sprintf( '%s_repeater_json', $options->slug ),
694: 'href' => '#',
695: 'role' => 'button',
696: 'tabindex' => 0,
697: $layout['title']
698: ] )
699: ] );
700: }
701: ?>
702: </ul>
703: </div>
704:
705: <?php
706: papi_render_html_tag( 'button', [
707: 'class' => 'button button-primary',
708: 'type' => 'button',
709: esc_html( $this->get_setting( 'add_new_label' ) )
710: ] );
711: ?>
712: </div>
713: </div>
714:
715: <?php ?>
716:
717: <input type="hidden" data-papi-rule="<?php echo $options->slug; ?>" name="<?php echo $this->get_slug(); ?>[]" />
718: </div>
719: <?php
720: }
721:
722: 723: 724:
725: protected function render_repeater_row() {
726: $layouts = $this->get_settings_layouts();
727: $values = $this->get_value();
728:
729:
730: $slugs = [];
731: foreach ( $layouts as $index => $layout ) {
732: foreach ( $layout['items'] as $item ) {
733: $slugs[] = papi_remove_papi( $item->slug );
734: }
735: }
736:
737:
738: foreach ( $values as $index => $row ) {
739: $keys = array_keys( $row );
740:
741: foreach ( $row as $slug => $value ) {
742: if ( in_array( $slug, $keys ) || papi_is_property_type_key( $slug ) || $this->is_layout_key( $slug ) ) {
743: continue;
744: }
745:
746: unset( $values[$index][$slug] );
747: }
748: }
749:
750: $values = array_filter( $values );
751: $closed_rows = $this->get_setting( 'closed_rows', true );
752:
753: foreach ( $values as $index => $row ):
754: ?>
755:
756: <tr <?php echo $closed_rows ? 'class="closed"' : ''; ?>>
757: <td class="handle">
758: <span class="toggle"></span>
759: <span class="count"><?php echo $this->counter + 1; ?></span>
760: </td>
761: <?php
762: foreach ( $layouts as $layout ) {
763:
764: if ( ! isset( $row[$this->layout_key] ) || $layout['slug'] !== $row[$this->layout_key] ) {
765: continue;
766: }
767:
768:
769: $this->render_properties( $layout['items'], $row );
770: }
771:
772: $this->counter++;
773: ?>
774: <td class="last">
775: <span>
776: <a title="<?php _e( 'Remove', 'papi' ); ?>" href="#" class="repeater-remove-item">x</a>
777: </span>
778: </td>
779: </tr>
780:
781: <?php
782: endforeach;
783: }
784:
785: 786: 787:
788: public function render_repeater_row_template() {
789: ?>
790: <script type="text/template" id="tmpl-papi-property-flexible-row">
791: <tr>
792: <td class="handle">
793: <span class="toggle"></span>
794: <span class="count"><%= counter + 1 %></span>
795: </td>
796: <%= columns %>
797: <td class="last">
798: <span>
799: <a title="<?php _e( 'Remove', 'papi' ); ?>" href="#" class="repeater-remove-item">x</a>
800: </span>
801: </td>
802: </tr>
803: </script>
804: <?php
805: }
806:
807: 808: 809:
810: protected function setup_actions() {
811: add_action( 'admin_head', [$this, 'render_repeater_row_template'] );
812: }
813:
814: 815: 816: 817: 818: 819: 820:
821: private function valid_layout( $layout ) {
822: return isset( $layout['title'] ) && isset( $layout['items'] );
823: }
824: }
825: