Overview

Classes

  • Papi_Admin
  • Papi_Admin_Ajax
  • Papi_Admin_Assets
  • Papi_Admin_Management_Pages
  • Papi_Admin_Menu
  • Papi_Admin_Meta_Box
  • Papi_Admin_Meta_Box_Tabs
  • Papi_Admin_Option_Handler
  • Papi_Admin_Post_Handler
  • Papi_Admin_View
  • Papi_Attachment_Type
  • Papi_Conditional_Rules
  • Papi_Container
  • Papi_Core_Autoload
  • Papi_Core_Conditional
  • Papi_Core_Conditional_Rule
  • Papi_Core_Data_Handler
  • Papi_Core_Page
  • Papi_Core_Property
  • Papi_Core_Type
  • Papi_Loader
  • Papi_Option_Page
  • Papi_Option_Type
  • Papi_Page_Type
  • Papi_Page_Type_Meta
  • Papi_Porter
  • Papi_Porter_Driver
  • Papi_Porter_Driver_Core
  • Papi_Post_Page
  • Papi_Property
  • Papi_Property_Bool
  • Papi_Property_Checkbox
  • Papi_Property_Color
  • Papi_Property_Datetime
  • Papi_Property_Divider
  • Papi_Property_Dropdown
  • Papi_Property_Editor
  • Papi_Property_Email
  • Papi_Property_File
  • Papi_Property_Flexible
  • Papi_Property_Gallery
  • Papi_Property_Hidden
  • Papi_Property_Html
  • Papi_Property_Image
  • Papi_Property_Link
  • Papi_Property_Number
  • Papi_Property_Post
  • Papi_Property_Radio
  • Papi_Property_Reference
  • Papi_Property_Relationship
  • Papi_Property_Repeater
  • Papi_Property_String
  • Papi_Property_Term
  • Papi_Property_Text
  • Papi_Property_Url
  • Papi_Property_User

Functions

  • current_page
  • papi
  • papi_action_delete_value
  • papi_action_include
  • papi_append_post_type_query
  • papi_body_class
  • papi_cache_delete
  • papi_cache_get
  • papi_cache_key
  • papi_cache_set
  • papi_camel_case
  • papi_cast_string_value
  • papi_convert_to_string
  • papi_current_user_is_allowed
  • papi_dashify
  • papi_delete_field
  • papi_delete_option
  • papi_delete_property_meta_value
  • papi_display_page_type
  • papi_doing_ajax
  • papi_esc_html
  • papi_f
  • papi_field
  • papi_field_shortcode
  • papi_field_value
  • papi_fields
  • papi_filter_conditional_rule_allowed
  • papi_filter_core_load_one_type_on
  • papi_filter_format_value
  • papi_filter_load_value
  • papi_filter_settings_directories
  • papi_filter_settings_only_page_type
  • papi_filter_settings_page_type_column_title
  • papi_filter_settings_show_page_type
  • papi_filter_settings_show_standard_page_type
  • papi_filter_settings_show_standard_page_type_in_filter
  • papi_filter_settings_sort_order
  • papi_filter_settings_standard_page_description
  • papi_filter_settings_standard_page_name
  • papi_filter_settings_standard_page_thumbnail
  • papi_filter_update_value
  • papi_from_property_array_slugs
  • papi_get_all_files_in_directory
  • papi_get_all_page_type_files
  • papi_get_all_page_types
  • papi_get_box_property
  • papi_get_class_name
  • papi_get_field
  • papi_get_file_path
  • papi_get_number_of_pages
  • papi_get_only_objects
  • papi_get_option
  • papi_get_options_and_properties
  • papi_get_or_post
  • papi_get_page
  • papi_get_page_new_url
  • papi_get_page_query_strings
  • papi_get_page_type
  • papi_get_page_type_base_path
  • papi_get_page_type_by_id
  • papi_get_page_type_by_post_id
  • papi_get_page_type_id
  • papi_get_page_type_key
  • papi_get_page_type_meta_value
  • papi_get_page_type_name
  • papi_get_page_type_template
  • papi_get_parent_post_id
  • papi_get_post_id
  • papi_get_post_type
  • papi_get_post_type_label
  • papi_get_post_types
  • papi_get_property_class_name
  • papi_get_property_meta_value
  • papi_get_property_options
  • papi_get_property_type
  • papi_get_property_type_key
  • papi_get_property_type_key_f
  • papi_get_qs
  • papi_get_sanitized_post
  • papi_get_slugs
  • papi_get_tab_options
  • papi_html_name
  • papi_html_tag
  • papi_include_template
  • papi_is_empty
  • papi_is_json
  • papi_is_method
  • papi_is_option_page
  • papi_is_option_type
  • papi_is_page_type
  • papi_is_property
  • papi_is_property_type_key
  • papi_is_rule
  • papi_management_page_type_render_box
  • papi_maybe_convert_to_array
  • papi_maybe_convert_to_object
  • papi_maybe_get_callable_value
  • papi_maybe_json_decode
  • papi_maybe_json_encode
  • papi_nl2br
  • papi_option_shortcode
  • papi_option_type_exists
  • papi_page_type_exists
  • papi_populate_properties
  • papi_property
  • papi_remove_papi
  • papi_remove_trailing_quotes
  • papi_render_html_tag
  • papi_render_properties
  • papi_render_property
  • papi_require_text
  • papi_required_html
  • papi_rule
  • papi_santize_data
  • papi_set_page_type_id
  • papi_setup_tabs
  • papi_slugify
  • papi_sort_order
  • papi_tab
  • papi_template
  • papi_template_include
  • papi_to_array
  • papi_to_property_array_slugs
  • papi_translate_keys
  • papi_underscorify
  • papi_update_field
  • papi_update_option
  • papi_update_property_meta_value
  • papi_with
  • papify
  • the_papi_field
  • the_papi_option
  • the_papi_page_type_name
  • Overview
  • Class
  1: <?php
  2: 
  3: /**
  4:  * Repeater property that can repeat multiple properties.
  5:  */
  6: class Papi_Property_Repeater extends Papi_Property {
  7: 
  8:     /**
  9:      * The convert type.
 10:      *
 11:      * @var string
 12:      */
 13:     public $convert_type = 'array';
 14: 
 15:     /**
 16:      * Repeater counter number.
 17:      *
 18:      * @var int
 19:      */
 20:     protected $counter = 0;
 21: 
 22:     /**
 23:      * The default value.
 24:      *
 25:      * @var array
 26:      */
 27:     public $default_value = [];
 28: 
 29:     /**
 30:      * Exclude properties that is not allowed in a repeater.
 31:      *
 32:      * @var array
 33:      */
 34:     protected $exclude_properties = ['flexible', 'repeater'];
 35: 
 36:     /**
 37:      * Delete value from the database.
 38:      *
 39:      * @param  string $slug
 40:      * @param  int    $post_id
 41:      * @param  string $type
 42:      *
 43:      * @return bool
 44:      */
 45:     public function delete_value( $slug, $post_id, $type ) {
 46:         $rows   = intval( papi_get_property_meta_value( $post_id, $slug ) );
 47:         $value  = $this->load_value( $rows, $slug, $post_id );
 48:         $value  = papi_to_property_array_slugs( $value, $slug );
 49:         $result = true;
 50: 
 51:         foreach ( $value as $key => $value ) {
 52:             $out    = papi_delete_property_meta_value( $post_id, $key, $type );
 53:             $result = $out ? $result : $out;
 54:         }
 55: 
 56:         return $result;
 57:     }
 58: 
 59:     /**
 60:      * Format the value of the property before it's returned
 61:      * to WordPress admin or the site.
 62:      *
 63:      * @param  mixed  $values
 64:      * @param  string $repeater_slug
 65:      * @param  int    $post_id
 66:      *
 67:      * @return array
 68:      */
 69:     public function format_value( $values, $repeater_slug, $post_id ) {
 70:         if ( ! is_array( $values ) ) {
 71:             return [];
 72:         }
 73: 
 74:         $values = papi_to_property_array_slugs( $values, $repeater_slug );
 75: 
 76:         foreach ( $values as $slug => $value ) {
 77:             if ( papi_is_property_type_key( $slug ) ) {
 78:                 continue;
 79:             }
 80: 
 81:             $property_type_slug = papi_get_property_type_key_f( $slug );
 82: 
 83:             if ( ! isset( $values[$property_type_slug] ) ) {
 84:                 continue;
 85:             }
 86: 
 87:             // Get property type
 88:             $property_type_value = $values[$property_type_slug];
 89:             $property_type = papi_get_property_type( $property_type_value );
 90: 
 91:             if ( ! is_object( $property_type ) ) {
 92:                 continue;
 93:             }
 94: 
 95:             // Get property child slug.
 96:             $child_slug = $this->get_child_slug( $repeater_slug, $slug );
 97: 
 98:             // Load the value.
 99:             $values[$slug] = $property_type->load_value( $value, $child_slug, $post_id );
100:             $values[$slug] = papi_filter_load_value( $property_type->type, $values[$slug], $child_slug, $post_id );
101: 
102:             // Format the value from the property class.
103:             $values[$slug] = $property_type->format_value( $values[$slug], $child_slug, $post_id );
104: 
105:             if ( ! is_admin() ) {
106:                 $values[$slug] = papi_filter_format_value( $property_type->type, $values[$slug], $child_slug, $post_id );
107:             }
108: 
109:             $values[$property_type_slug] = $property_type_value;
110:         }
111: 
112:         if ( ! is_admin() ) {
113:             foreach ( $values as $slug => $value ) {
114:                 if ( papi_is_property_type_key( $slug ) ) {
115:                     unset( $values[$slug] );
116:                 }
117:             }
118:         }
119: 
120:         return papi_from_property_array_slugs( $values, $repeater_slug );
121:     }
122: 
123:     /**
124:      * Get child slug from the repeater slug.
125:      *
126:      * @param  string $repeater_slug
127:      * @param  string $child_slug
128:      *
129:      * @return string
130:      */
131:     protected function get_child_slug( $repeater_slug, $child_slug ) {
132:         return preg_replace( '/^\_/', '', preg_replace( '/\_\d+\_/', '', str_replace( $repeater_slug, '', $child_slug ) ) );
133:     }
134: 
135:     /**
136:      * Get default settings.
137:      *
138:      * @return array
139:      */
140:     public function get_default_settings() {
141:         return [
142:             'add_new_label' => __( 'Add new row', 'papi' ),
143:             'closed_rows'   => false,
144:             'items'         => [],
145:             'layout'        => 'table',
146:             'limit'         => -1
147:         ];
148:     }
149: 
150:     /**
151:      * Get import settings.
152:      *
153:      * @return array
154:      */
155:     public function get_import_settings() {
156:         return [
157:             'property_array_slugs' => true
158:         ];
159:     }
160: 
161:     /**
162:      * Get results from the database.
163:      *
164:      * @param  int    $value
165:      * @param  string $repeater_slug
166:      * @param  int    $post_id
167:      *
168:      * @return array
169:      */
170:     protected function get_results( $value, $repeater_slug, $post_id ) {
171:         global $wpdb;
172: 
173:         $option_page = $this->is_option_page();
174: 
175:         if ( $option_page ) {
176:             $table = $wpdb->prefix . 'options';
177:             $query = $wpdb->prepare(
178:                 "SELECT * FROM `$table` WHERE `option_name` LIKE '%s' ORDER BY `option_id` ASC",
179:                 $repeater_slug . '_%'
180:             );
181:         } else {
182:             $table = $wpdb->prefix . 'postmeta';
183:             $query = $wpdb->prepare(
184:                 "SELECT * FROM `$table` WHERE `meta_key` LIKE '%s' AND `post_id` = %s ORDER BY `meta_id` ASC", $repeater_slug . '_%',
185:                 $post_id
186:             );
187:         }
188: 
189:         $dbresults = $wpdb->get_results( $query );
190:         $value     = intval( $value );
191: 
192:         // Do not proceed with empty value.
193:         if ( empty( $value ) ) {
194:             return [[], []];
195:         }
196: 
197:         $values  = [];
198:         $results = [];
199:         $trash   = [];
200: 
201:         // Get row results.
202:         $rows = array_values( $this->get_row_results( $dbresults ) );
203: 
204:         // Add repeater slug with number of rows to the values array.
205:         $values[$repeater_slug] = $value;
206: 
207:         for ( $i = 0; $i < $value; $i++ ) {
208:             $no_trash = [];
209: 
210:             if ( ! isset( $no_trash[$i] ) ) {
211:                 $no_trash[$i] = [];
212:             }
213: 
214:             if ( ! isset( $rows[$i] ) ) {
215:                 continue;
216:             }
217: 
218:             foreach ( $rows[$i] as $slug => $meta ) {
219:                 if ( ! is_string( $slug ) || ! isset( $rows[$i][$slug] ) ) {
220:                     continue;
221:                 }
222: 
223:                 // Add meta object to the no trash array.
224:                 // so it won't be deleted.
225:                 $no_trash[$slug] = $meta;
226: 
227:                 // Add property value.
228:                 $values[$meta->meta_key] = papi_maybe_json_decode(
229:                     maybe_unserialize( $meta->meta_value )
230:                 );
231:             }
232: 
233:             // Get the meta keys to delete.
234:             $trash_diff = array_diff( array_keys( $rows[$i] ), array_keys( $no_trash[$i] ) );
235: 
236:             if ( ! empty( $trash_diff ) ) {
237:                 // Find all trash meta objects from results array.
238:                 foreach ( $trash_diff as $slug ) {
239:                     if ( ! isset( $results[$i] ) || ! isset( $rows[$i][$slug] ) ) {
240:                         continue;
241:                     }
242: 
243:                     $trash[$results[$i][$slug]->meta_key] = $rows[$i][$slug];
244:                 }
245:             }
246:         }
247: 
248:         $properties = $this->get_settings_properties();
249: 
250:         // Add empty rows that isn't saved to database.
251:         for ( $i = 0; $i < $value; $i++ ) {
252:             foreach ( $properties as $prop ) {
253:                 $slug = sprintf( '%s_%d_%s', $repeater_slug, $i, papi_remove_papi( $prop->slug ) );
254: 
255:                 if ( ! isset( $values[$slug] ) ) {
256:                     $values[$slug] = null;
257:                 }
258:             }
259:         }
260: 
261:         return [$values, $trash];
262:     }
263: 
264:     /**
265:      * Get row results.
266:      *
267:      * @param  array $dbresults
268:      *
269:      * @return array
270:      */
271:     protected function get_row_results( $dbresults ) {
272:         $results     = [];
273:         $option_page = $this->is_option_page();
274: 
275:         foreach ( $dbresults as $key => $meta ) {
276: 
277:             if ( $option_page ) {
278:                 preg_match( '/^[^\d]*(\d+)/', $meta->option_name, $matches );
279:             } else {
280:                 preg_match( '/^[^\d]*(\d+)/', $meta->meta_key, $matches );
281:             }
282: 
283:             if ( count( $matches ) < 2 ) {
284:                 continue;
285:             }
286:             $i = intval( $matches[1] );
287: 
288:             if ( ! isset( $results[$i] ) ) {
289:                 $results[$i] = [];
290:             }
291: 
292:             if ( $option_page ) {
293:                 $results[$i][$meta->option_name] = (object) [
294:                     'meta_key'   => $meta->option_name,
295:                     'meta_value' => $meta->option_value
296:                 ];
297:             } else {
298:                 $results[$i][$meta->meta_key] = $meta;
299:             }
300:         }
301: 
302:         return $results;
303:     }
304: 
305:     /**
306:      * Get settings properties.
307:      *
308:      * @return array
309:      */
310:     protected function get_settings_properties() {
311:         $settings = $this->get_settings();
312: 
313:         if ( is_null( $settings ) ) {
314:             return [];
315:         }
316: 
317:         return $this->prepare_properties( papi_to_array( $settings->items ) );
318:     }
319: 
320:     /**
321:      * Render property html.
322:      */
323:     public function html() {
324:         $options = $this->get_options();
325: 
326:         // Reset list counter number.
327:         $this->counter = 0;
328: 
329:         // Render repeater html.
330:         $this->render_repeater( $options );
331: 
332:         // Render JSON template that is used for Papi ajax.
333:         $this->render_json_template( $options->slug );
334:     }
335: 
336:     /**
337:      * Import value to the property.
338:      *
339:      * @param  mixed  $value
340:      * @param  string $slug
341:      * @param  int    $post_id
342:      *
343:      * @return array
344:      */
345:     public function import_value( $value, $slug, $post_id ) {
346:         if ( ! is_array( $value ) ) {
347:             return [];
348:         }
349: 
350:         // If the import value isn't array in array then fix it.
351:         $extras = array_filter( $value, function ( $value ) {
352:             return ! is_array( $value );
353:         } );
354: 
355:         if ( ! empty( $extras ) ) {
356:             $extra = [];
357: 
358:             foreach ( $extras as $key => $val ) {
359:                 if ( isset( $value[$key] ) ) {
360:                     unset( $value[$key] );
361:                 }
362: 
363:                 $extra[$key] = $val;
364:             }
365: 
366:             $value[] = $extra;
367:         }
368: 
369:         return $this->update_value( $value, $slug, $post_id );
370:     }
371: 
372:     /**
373:      * Check if the given layout is the layouted used.
374:      *
375:      * @param  string $layout
376:      *
377:      * @return bool
378:      */
379:     protected function layout( $layout ) {
380:         return $this->get_setting( 'layout' ) === $layout;
381:     }
382: 
383:     /**
384:      * Change value after it's loaded from the database
385:      * and populate every property in the repeater with the right property type.
386:      *
387:      * @param int    $value
388:      * @param string $repeater_slug
389:      * @param int    $post_id
390:      *
391:      * @return array
392:      */
393:     public function load_value( $value, $repeater_slug, $post_id ) {
394:         if ( is_array( $value ) ) {
395:             return $value;
396:         }
397: 
398:         list( $results, $trash ) = $this->get_results( $value, $repeater_slug, $post_id );
399: 
400:         // Will not need this array.
401:         unset( $trash );
402: 
403:         $page    = $this->get_page();
404:         $types   = [];
405:         $results = papi_from_property_array_slugs(
406:             $results,
407:             papi_remove_papi( $repeater_slug )
408:         );
409: 
410:         if ( empty( $page ) || empty( $results ) ) {
411:             return $this->default_value;
412:         }
413: 
414:         foreach ( $results[0] as $slug => $value ) {
415:             if ( $property = $page->get_property( $repeater_slug, $slug ) ) {
416:                 $types[$slug] = $property;
417:             }
418:         }
419: 
420:         foreach ( $results as $index => $row ) {
421:             foreach ( $row as $slug => $value ) {
422:                 if ( ! isset( $types[$slug] ) ) {
423:                     continue;
424:                 }
425: 
426:                 $type_key = papi_get_property_type_key_f( $slug );
427:                 $results[$index][$type_key] = $types[$slug];
428:             }
429:         }
430: 
431:         return $results;
432:     }
433: 
434:     /**
435:      * Prepare properties, get properties options object,
436:      * check which properties that are allowed to use.
437:      *
438:      * @param  array $items
439:      *
440:      * @return array
441:      */
442:     protected function prepare_properties( $items ) {
443:         $key   = isset( $this->layout_key ) &&
444:             $this->layout_key === '_layout' ?  'flexible' : 'repeater';
445:         $items = array_map( 'papi_get_property_options', $items );
446: 
447:         $exclude_properties = $this->exclude_properties;
448:         $exclude_properties = array_merge(
449:             $exclude_properties,
450:             apply_filters( 'papi/property/' . $key . '/exclude', [] )
451:         );
452: 
453:         return array_filter( $items, function ( $item ) use ( $exclude_properties ) {
454:             if ( ! is_object( $item ) ) {
455:                 return false;
456:             }
457: 
458:             if ( empty( $item->type ) ) {
459:                 return false;
460:             }
461: 
462:             return ! in_array( $item->type, $exclude_properties );
463:         } );
464:     }
465: 
466:     /**
467:      * Remove all repeater rows from the database.
468:      *
469:      * @param int    $post_id
470:      * @param string $repeater_slug
471:      */
472:     protected function remove_repeater_rows( $post_id, $repeater_slug ) {
473:         global $wpdb;
474: 
475:         $option_page   = $this->is_option_page();
476:         $repeater_slug = $repeater_slug . '_%';
477: 
478:         if ( $option_page ) {
479:             $table = $wpdb->prefix . 'options';
480:             $sql   = "SELECT * FROM $table WHERE (`option_name` LIKE %s OR `option_name` LIKE %s AND NOT `option_name` = %s)";
481:             $query = $wpdb->prepare(
482:                 $sql,
483:                 $repeater_slug,
484:                 papi_f( $repeater_slug ),
485:                 papi_get_property_type_key_f( $repeater_slug )
486:             );
487:         } else {
488:             $table = $wpdb->prefix . 'postmeta';
489:             $sql   = "SELECT * FROM $table WHERE `post_id` = %d AND (`meta_key` LIKE %s OR `meta_key` LIKE %s AND NOT `meta_key` = %s)";
490:             $query = $wpdb->prepare(
491:                 $sql,
492:                 $post_id,
493:                 $repeater_slug,
494:                 papi_f( $repeater_slug ),
495:                 papi_get_property_type_key_f( $repeater_slug )
496:             );
497:         }
498: 
499:         $results = $wpdb->get_results( $query );
500: 
501:         foreach ( $results as $res ) {
502:             if ( $option_page ) {
503:                 $key = $res->option_name;
504:             } else {
505:                 $key = $res->meta_key;
506:             }
507: 
508:             papi_delete_property_meta_value( $post_id, $key );
509:         }
510:     }
511: 
512:     /**
513:      * Render AJAX request.
514:      */
515:     public function render_ajax_request() {
516:         $items = $this->get_settings_properties();
517: 
518:         if ( papi_doing_ajax() ) {
519:             $counter = papi_get_qs( 'counter' );
520:             $this->counter = intval( $counter );
521:         }
522: 
523:         $this->render_properties( $items, false );
524:     }
525: 
526:     /**
527:      * Render property JSON template.
528:      *
529:      * @param string $slug
530:      */
531:     protected function render_json_template( $slug ) {
532:         $options = $this->get_options();
533: 
534:         foreach ( $options->settings->items as $key => $value ) {
535:             if ( ! papi_is_property( $value ) ) {
536:                 unset( $options->settings->items[$key] );
537:                 continue;
538:             }
539: 
540:             $options->settings->items[$key] = clone $value->get_options();
541:         }
542: 
543:         papi_render_html_tag( 'script', [
544:             'data-papi-json' => sprintf( '%s_repeater_json', $slug ),
545:             'type'           => 'application/json',
546:             json_encode( [$options] )
547:         ] );
548:     }
549: 
550:     /**
551:      * Render properties.
552:      *
553:      * @param array $row
554:      * @param array|bool $value
555:      */
556:     protected function render_properties( $row, $value ) {
557:         $layout = $this->get_setting( 'layout' );
558: 
559:         if ( $layout === 'row' ): ?>
560:         <td class="repeater-layout-row">
561:             <div class="repeater-content-open">
562:                 <table class="papi-table">
563:                     <tbody>
564:         <?php endif;
565: 
566:         $has_value = $value !== false;
567: 
568:         foreach ( $row as $property ) {
569:             // Don't show the property if it's disabled.
570:             if ( $property->disabled() ) {
571:                 continue;
572:             }
573: 
574:             $render_property = clone $property->get_options();
575:             $value_slug      = $property->get_slug( true );
576: 
577:             if ( $has_value ) {
578:                 if ( array_key_exists( $value_slug, $value ) ) {
579:                     $render_property->value = $value[$value_slug];
580:                 } else {
581:                     if ( array_key_exists( $property->get_slug(), $value ) ) {
582:                         $render_property->value = $property->default_value;
583:                     } else {
584:                         continue;
585:                     }
586:                 }
587:             }
588: 
589:             $render_property->slug = $this->html_name( $property, $this->counter );
590:             $render_property->raw  = $layout === 'table';
591: 
592:             if ( $layout === 'table' ) {
593:                 echo '<td class="repeater-column ' . ( $property->display() ? '' : 'papi-hide' ) . '">';
594:                     echo '<div class="repeater-content-open">';
595:                         echo sprintf(
596:                             '<label for="%s" class="papi-visually-hidden">%s</label>',
597:                             $this->html_id( $property, $this->counter ),
598:                             $property->title
599:                         );
600:             }
601: 
602:             papi_render_property( $render_property );
603: 
604:             if ( $layout === 'table' ) {
605:                     echo '</div>';
606:                 echo '</td>';
607:             }
608:         }
609: 
610:         if ( $layout === 'row' ): ?>
611:                     </tbody>
612:                 </table>
613:             </div>
614:         </td>
615:         <?php endif;
616:     }
617: 
618:     /**
619:      * Render repeater html.
620:      *
621:      * @param stdClass $options
622:      */
623:     protected function render_repeater( $options ) {
624:         ?>
625:         <div class="papi-property-repeater papi-property-repeater-top" data-limit="<?php echo $this->get_setting( 'limit' ); ?>">
626:             <table class="papi-table">
627:                 <?php $this->render_repeater_head(); ?>
628: 
629:                 <tbody class="repeater-tbody">
630:                     <?php $this->render_repeater_rows(); ?>
631:                 </tbody>
632:             </table>
633: 
634:             <div class="bottom">
635:                 <?php
636:                 papi_render_html_tag( 'button', [
637:                     'class'          => 'button button-primary',
638:                     'data-papi-json' => sprintf( '%s_repeater_json', $options->slug ),
639:                     'type'           => 'button',
640:                     esc_html( $this->get_setting( 'add_new_label' ) )
641:                 ] );
642:                 ?>
643:             </div>
644: 
645:             <?php /* Default repeater value */ ?>
646: 
647:             <input type="hidden" data-papi-rule="<?php echo $options->slug; ?>" name="<?php echo $options->slug; ?>[]" />
648:         </div>
649:         <?php
650:     }
651: 
652:     /**
653:      * Render repeater head.
654:      */
655:     protected function render_repeater_head() {
656:         $properties = $this->get_settings_properties();
657:         ?>
658:         <thead>
659:             <?php if ( ! $this->layout( 'row' ) ): ?>
660:             <tr>
661:                 <th></th>
662:                 <?php
663:                 foreach ( $properties as $property ):
664:                     // Don't show the property if it's disabled.
665:                     if ( $property->disabled() ) {
666:                         continue;
667:                     }
668:                 ?>
669:                     <th class="repeater-column <?php echo $property->display() ? '' : 'papi-hide'; ?>">
670:                         <?php echo $property->title; ?>
671:                     </th>
672:                 <?php endforeach; ?>
673:                 <th class="last"></th>
674:             </tr>
675:             <?php endif; ?>
676:         </thead>
677:         <?php
678:     }
679: 
680:     /**
681:      * Render repeater rows.
682:      */
683:     protected function render_repeater_rows() {
684:         $items  = $this->get_settings_properties();
685:         $values = $this->get_value();
686:         $slugs  = wp_list_pluck( $items, 'slug' );
687: 
688:         // Remove values that don't exists in the slugs array.
689:         foreach ( $values as $index => $value ) {
690:             $keys = array_keys( $value );
691: 
692:             foreach ( $slugs as $slug ) {
693:                 if ( in_array( $slug, $keys ) ) {
694:                     continue;
695:                 }
696: 
697:                 $values[$index][$slug] = '';
698:             }
699:         }
700: 
701:         $values = array_filter( $values );
702:         $closed_rows = $this->get_setting( 'closed_rows', true );
703: 
704:         foreach ( $values as $row ):
705:             ?>
706:             <tr <?php echo $closed_rows ? 'class="closed"' : ''; ?>>
707:                 <td class="handle">
708:                     <span class="toggle"></span>
709:                     <span class="count"><?php echo $this->counter + 1; ?></span>
710:                 </td>
711:                 <?php
712:                     $this->render_properties( $items, $row );
713:                     $this->counter++;
714:                 ?>
715:                 <td class="last">
716:                     <span>
717:                         <a title="<?php _e( 'Remove', 'papi' ); ?>" href="#" class="repeater-remove-item">x</a>
718:                     </span>
719:                 </td>
720:             </tr>
721:             <?php
722:         endforeach;
723:     }
724: 
725:     /**
726:      * Render repeater row template.
727:      */
728:     public function render_repeater_rows_template() {
729:         ?>
730:         <script type="text/template" id="tmpl-papi-property-repeater-row">
731:             <tr>
732:                 <td class="handle">
733:                     <span class="toggle"></span>
734:                     <span class="count"><%= counter + 1 %></span>
735:                 </td>
736:                 <%= columns %>
737:                 <td class="last">
738:                     <span>
739:                         <a title="<?php _e( 'Remove', 'papi' ); ?>" href="#" class="repeater-remove-item">x</a>
740:                     </span>
741:                 </td>
742:             </tr>
743:         </script>
744:         <?php
745:     }
746: 
747:     /**
748:      * Setup actions.
749:      */
750:     protected function setup_actions() {
751:         add_action( 'admin_head', [$this, 'render_repeater_rows_template'] );
752:     }
753: 
754:     /**
755:      * Update value before it's saved to the database.
756:      *
757:      * @param mixed  $values
758:      * @param string $repeater_slug
759:      * @param int    $post_id
760:      *
761:      * @return array
762:      */
763:     public function update_value( $values, $repeater_slug, $post_id ) {
764:         $rows = intval( papi_get_property_meta_value(
765:             $post_id,
766:             $repeater_slug
767:         ) );
768: 
769:         if ( ! is_array( $values ) ) {
770:             $values = [];
771:         }
772: 
773:         list( $results, $trash ) = $this->get_results( $rows, $repeater_slug, $post_id );
774: 
775:         // Delete trash values.
776:         foreach ( $trash as $index => $meta ) {
777:             papi_delete_property_meta_value( $post_id, $meta->meta_key );
778:         }
779: 
780:         $values = papi_to_property_array_slugs( $values, $repeater_slug );
781: 
782:         foreach ( $values as $slug => $value ) {
783:             if ( papi_is_property_type_key( $slug ) ) {
784:                 continue;
785:             }
786: 
787:             $property_type_slug = papi_get_property_type_key_f( $slug );
788: 
789:             if ( ! isset( $values[$property_type_slug] ) ) {
790:                 continue;
791:             }
792: 
793:             // Get real property slug
794:             $property_slug = $this->get_child_slug( $repeater_slug, $slug );
795: 
796:             // Get property type
797:             $property_type_value = $values[$property_type_slug]->type;
798:             $property_type = papi_get_property_type( $property_type_value );
799: 
800:             // Unserialize if needed.
801:             $value = papi_maybe_json_decode(
802:                 maybe_unserialize( $value )
803:             );
804: 
805:             // Run update value on each property type class.
806:             $value = $property_type->update_value(
807:                 $value,
808:                 $property_slug,
809:                 $post_id
810:             );
811: 
812:             // Run update value on each property type filter.
813:             $values[$slug] = papi_filter_update_value(
814:                 $property_type_value,
815:                 $value,
816:                 $property_slug,
817:                 $post_id
818:             );
819: 
820:             if ( is_array( $values[$slug] ) ) {
821:                 foreach ( $values[$slug] as $key => $val ) {
822:                     if ( ! is_string( $key ) ) {
823:                         continue;
824:                     }
825: 
826:                     unset( $values[$slug][$key] );
827:                     $key = preg_replace( '/^\_/', '', $key );
828:                     $values[$slug][$key] = $val;
829:                 }
830:             }
831: 
832:             if ( isset( $values[$property_type_slug] ) ) {
833:                 unset( $values[$property_type_slug] );
834:             }
835:         }
836: 
837:         $trash = array_diff(
838:             array_keys( papi_to_array( $results ) ),
839:             array_keys( papi_to_array( $values ) )
840:         );
841: 
842:         // Delete trash values.
843:         foreach ( $trash as $trash_key => $trash_value ) {
844:             papi_delete_property_meta_value( $post_id, $trash_key );
845:         }
846: 
847:         // Keep this method before the return statement.
848:         // It's safe to remove all rows in the database here.
849:         $this->remove_repeater_rows( $post_id, $repeater_slug );
850: 
851:         return $values;
852:     }
853: }
854: 
API documentation generated by ApiGen