PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / 6.9.1
Secure Custom Fields v6.9.1
6.9.1 6.9.0 6.8.9 6.8.7 6.8.8 6.8.6 6.8.4 6.8.5 trunk 6.4.0-beta1 6.4.0-beta2 6.4.1 6.4.1-beta3 6.4.1-beta4 6.4.1-beta5 6.4.1-beta6 6.4.1-beta7 6.4.2 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.6.0 6.7.0 6.7.1 6.8.0 6.8.1 6.8.2 6.8.3
secure-custom-fields / includes / fields / class-acf-repeater-table.php
secure-custom-fields / includes / fields Last commit date
FlexibleContent 2 months ago class-acf-field-accordion.php 2 months ago class-acf-field-button-group.php 2 months ago class-acf-field-checkbox.php 2 days ago class-acf-field-clone.php 2 months ago class-acf-field-color_picker.php 2 months ago class-acf-field-date_picker.php 2 months ago class-acf-field-date_time_picker.php 2 months ago class-acf-field-email.php 2 months ago class-acf-field-file.php 2 months ago class-acf-field-flexible-content.php 1 week ago class-acf-field-gallery.php 3 weeks ago class-acf-field-google-map.php 2 months ago class-acf-field-group.php 2 months ago class-acf-field-icon_picker.php 7 months ago class-acf-field-image.php 2 months ago class-acf-field-link.php 2 months ago class-acf-field-message.php 1 year ago class-acf-field-nav-menu.php 1 week ago class-acf-field-number.php 2 months ago class-acf-field-oembed.php 3 weeks ago class-acf-field-output.php 1 year ago class-acf-field-page_link.php 3 weeks ago class-acf-field-password.php 2 months ago class-acf-field-post_object.php 3 weeks ago class-acf-field-radio.php 2 days ago class-acf-field-range.php 2 months ago class-acf-field-relationship.php 3 weeks ago class-acf-field-repeater.php 3 weeks ago class-acf-field-select.php 2 days ago class-acf-field-separator.php 1 year ago class-acf-field-tab.php 1 year ago class-acf-field-taxonomy.php 3 weeks ago class-acf-field-text.php 3 weeks ago class-acf-field-textarea.php 3 weeks ago class-acf-field-time_picker.php 2 months ago class-acf-field-true_false.php 2 months ago class-acf-field-url.php 3 weeks ago class-acf-field-user.php 3 weeks ago class-acf-field-wysiwyg.php 2 months ago class-acf-field.php 2 months ago class-acf-repeater-table.php 1 year ago index.php 1 year ago
class-acf-repeater-table.php
493 lines
1 <?php
2 /**
3 * For rendering repeater tables.
4 *
5 * @package wordpress/secure-custom-fields
6 */
7
8 /**
9 * ACF_Repeater_Table
10 *
11 * Helper class for rendering repeater tables.
12 */
13 class ACF_Repeater_Table {
14
15 /**
16 * The main field array used to render the repeater.
17 *
18 * @var array
19 */
20 private $field;
21
22 /**
23 * An array containing the subfields used in the repeater.
24 *
25 * @var array
26 */
27 private $sub_fields;
28
29 /**
30 * The value(s) of the repeater field.
31 *
32 * @var array
33 */
34 private $value;
35
36 /**
37 * If we should show the "Add Row" button.
38 *
39 * @var boolean
40 */
41 private $show_add = true;
42
43 /**
44 * If we should show the "Remove Row" button.
45 *
46 * @var boolean
47 */
48 private $show_remove = true;
49
50 /**
51 * If we should show the order of the fields.
52 *
53 * @var boolean
54 */
55 private $show_order = true;
56
57 /**
58 * Constructs the ACF_Repeater_Table class.
59 *
60 * @param array $field The main field array for the repeater being rendered.
61 */
62 public function __construct( $field ) {
63 $this->field = $field;
64 $this->sub_fields = $field['sub_fields'];
65
66 // Default to non-paginated repeaters.
67 if ( empty( $this->field['pagination'] ) ) {
68 $this->field['pagination'] = false;
69 }
70
71 // We don't yet support pagination inside other repeaters or flexible content fields.
72 if ( ! empty( $this->field['parent_repeater'] ) || ! empty( $this->field['parent_layout'] ) ) {
73 $this->field['pagination'] = false;
74 }
75
76 // We don't yet support pagination in frontend forms or inside blocks.
77 if ( ! is_admin() || acf_get_data( 'acf_inside_rest_call' ) || doing_action( 'wp_ajax_acf/ajax/fetch-block' ) ) {
78 $this->field['pagination'] = false;
79 }
80
81 $this->setup();
82 }
83
84 /**
85 * Sets up the field for rendering.
86 *
87 * @since ACF 6.0.0
88 *
89 * @return void
90 */
91 private function setup() {
92 if ( $this->field['collapsed'] ) {
93 foreach ( $this->sub_fields as &$sub_field ) {
94 // Add target class.
95 if ( $sub_field['key'] === $this->field['collapsed'] ) {
96 $sub_field['wrapper']['class'] .= ' -collapsed-target';
97 }
98 }
99 }
100
101 if ( $this->field['max'] ) {
102 // If max 1 row, don't show order.
103 if ( 1 === (int) $this->field['max'] ) {
104 $this->show_order = false;
105 }
106
107 // If max == min, don't show add or remove buttons.
108 if ( $this->field['max'] <= $this->field['min'] ) {
109 $this->show_remove = false;
110 $this->show_add = false;
111 }
112 }
113
114 if ( empty( $this->field['rows_per_page'] ) ) {
115 $this->field['rows_per_page'] = 20;
116 }
117
118 if ( (int) $this->field['rows_per_page'] < 1 ) {
119 $this->field['rows_per_page'] = 20;
120 }
121
122 $this->value = $this->prepare_value();
123 }
124
125 /**
126 * Prepares the repeater values for rendering.
127 *
128 * @since ACF 6.0.0
129 *
130 * @return array
131 */
132 private function prepare_value() {
133 $value = is_array( $this->field['value'] ) ? $this->field['value'] : array();
134
135 if ( empty( $this->field['pagination'] ) ) {
136 // If there are fewer values than min, populate the extra values.
137 if ( $this->field['min'] ) {
138 $value = array_pad( $value, $this->field['min'], array() );
139 }
140
141 // If there are more values than max, remove some values.
142 if ( $this->field['max'] ) {
143 $value = array_slice( $value, 0, $this->field['max'] );
144 }
145 }
146
147 $value['acfcloneindex'] = array();
148
149 return $value;
150 }
151
152 /**
153 * Renders the full repeater table.
154 *
155 * @since ACF 6.0.0
156 *
157 * @return void
158 */
159 public function render() {
160 // Attributes for main wrapper div.
161 $div = array(
162 'class' => 'acf-repeater -' . $this->field['layout'],
163 'data-min' => $this->field['min'],
164 'data-max' => $this->field['max'],
165 'data-pagination' => ! empty( $this->field['pagination'] ),
166 'data-prefix' => $this->field['prefix'],
167 );
168
169 if ( $this->field['pagination'] ) {
170 $div['data-per_page'] = $this->field['rows_per_page'];
171 $div['data-total_rows'] = $this->field['total_rows'];
172 $div['data-orig_name'] = $this->field['orig_name'];
173 $div['data-nonce'] = wp_create_nonce( 'acf_field_' . $this->field['type'] . '_' . $this->field['key'] );
174 }
175
176 if ( empty( $this->value ) ) {
177 $div['class'] .= ' -empty';
178 }
179 ?>
180 <div <?php echo acf_esc_attrs( $div ); ?>>
181 <?php
182 acf_hidden_input(
183 array(
184 'name' => $this->field['name'],
185 'value' => '',
186 'class' => 'acf-repeater-hidden-input',
187 )
188 );
189 ?>
190 <table class="acf-table">
191 <?php $this->thead(); ?>
192 <tbody>
193 <?php $this->rows(); ?>
194 </tbody>
195 </table>
196 <?php $this->table_actions(); ?>
197 </div>
198 <?php
199 }
200
201 /**
202 * Renders the table head.
203 *
204 * @since ACF 6.0.0
205 *
206 * @return void
207 */
208 public function thead() {
209 if ( 'table' !== $this->field['layout'] ) {
210 return;
211 }
212 ?>
213 <thead>
214 <tr>
215 <?php if ( $this->show_order ) : ?>
216 <th class="acf-row-handle"></th>
217 <?php endif; ?>
218
219 <?php
220 foreach ( $this->sub_fields as $sub_field ) :
221 // Prepare field (allow sub fields to be removed).
222 $sub_field = acf_prepare_field( $sub_field );
223 if ( ! $sub_field ) {
224 continue;
225 }
226
227 // Define attrs.
228 $attrs = array(
229 'class' => 'acf-th',
230 'data-name' => $sub_field['_name'],
231 'data-type' => $sub_field['type'],
232 'data-key' => $sub_field['key'],
233 );
234
235 if ( $sub_field['wrapper']['width'] ) {
236 $attrs['data-width'] = $sub_field['wrapper']['width'];
237 $attrs['style'] = 'width: ' . $sub_field['wrapper']['width'] . '%;';
238 }
239
240 // Remove "id" to avoid "for" attribute on <label>.
241 $sub_field['id'] = '';
242 ?>
243 <th <?php echo acf_esc_attrs( $attrs ); ?>>
244 <?php acf_render_field_label( $sub_field ); ?>
245 <?php acf_render_field_instructions( $sub_field ); ?>
246 </th>
247 <?php endforeach; ?>
248
249 <?php if ( $this->show_remove ) : ?>
250 <th class="acf-row-handle"></th>
251 <?php endif; ?>
252 </tr>
253 </thead>
254 <?php
255 }
256
257 /**
258 * Renders or returns rows for the repeater field table.
259 *
260 * @since ACF 6.0.0
261 *
262 * @param boolean $should_return If we should return the rows or render them.
263 * @return array|void
264 */
265 public function rows( $should_return = false ) {
266 $rows = array();
267
268 // Don't include the clone when rendering via AJAX.
269 if ( $should_return && isset( $this->value['acfcloneindex'] ) ) {
270 unset( $this->value['acfcloneindex'] );
271 }
272
273 foreach ( $this->value as $i => $row ) {
274 $rows[ $i ] = $this->row( $i, $row, $should_return );
275 }
276
277 if ( $should_return ) {
278 return $rows;
279 }
280
281 echo implode( PHP_EOL, $rows ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- HTML already escaped by generating functions.
282 }
283
284 /**
285 * Renders an individual row.
286 *
287 * @since ACF 6.0.0
288 *
289 * @param integer $i The row number.
290 * @param array $row An array containing the row values.
291 * @param boolean $should_return If we should return the row or render it.
292 * @return string|void
293 */
294 public function row( $i, $row, $should_return = false ) {
295 if ( $should_return ) {
296 ob_start();
297 }
298
299 $id = "row-$i";
300 $class = 'acf-row';
301
302 if ( 'acfcloneindex' === $i ) {
303 $id = 'acfcloneindex';
304 $class .= ' acf-clone';
305 }
306
307 $el = 'td';
308 $before_fields = '';
309 $after_fields = '';
310
311 if ( 'row' === $this->field['layout'] ) {
312 $el = 'div';
313 $before_fields = '<td class="acf-fields -left">';
314 $after_fields = '</td>';
315 } elseif ( 'block' === $this->field['layout'] ) {
316 $el = 'div';
317 $before_fields = '<td class="acf-fields">';
318 $after_fields = '</td>';
319 }
320
321 printf(
322 '<tr class="%s" data-id="%s">',
323 esc_attr( $class ),
324 esc_attr( $id )
325 );
326
327 $this->row_handle( $i );
328
329 echo $before_fields; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- string only contains guaranteed safe HTML.
330
331 foreach ( $this->sub_fields as $sub_field ) {
332 if ( isset( $row[ $sub_field['key'] ] ) ) {
333 $sub_field['value'] = $row[ $sub_field['key'] ];
334 } elseif ( isset( $sub_field['default_value'] ) ) {
335 $sub_field['value'] = $sub_field['default_value'];
336 }
337
338 // Update prefix to allow for nested values.
339 $sub_field['prefix'] = $this->field['name'] . '[' . $id . ']';
340
341 acf_render_field_wrap( $sub_field, $el );
342 }
343
344 echo $after_fields; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- string only contains guaranteed safe HTML.
345
346 $this->row_actions();
347
348 echo '</tr>';
349
350 if ( $should_return ) {
351 return ob_get_clean();
352 }
353 }
354
355 /**
356 * Renders the row handle at the start of each row.
357 *
358 * @since ACF 6.0.0
359 *
360 * @param integer $i The current row number.
361 * @return void
362 */
363 public function row_handle( $i ) {
364 if ( ! $this->show_order ) {
365 return;
366 }
367
368 $hr_row_num = intval( $i ) + 1;
369 $classes = 'acf-row-handle order';
370 $title = __( 'Drag to reorder', 'secure-custom-fields' );
371 $row_num_html = sprintf(
372 '<span class="acf-row-number" title="%s">%d</span>',
373 esc_html__( 'Click to reorder', 'secure-custom-fields' ),
374 $hr_row_num
375 );
376
377 if ( ! empty( $this->field['pagination'] ) ) {
378 $classes .= ' pagination';
379 $title = '';
380 $input = sprintf( '<input type="number" class="acf-order-input" value="%d" style="display: none;" />', $hr_row_num );
381 $row_num_html = '<div class="acf-order-input-wrap">' . $input . $row_num_html . '</div>';
382 }
383 ?>
384 <td class="<?php echo esc_attr( $classes ); ?>" title="<?php echo esc_attr( $title ); ?>">
385 <?php if ( $this->field['collapsed'] ) : ?>
386 <a class="acf-icon -collapse small" href="#" data-event="collapse-row" title="<?php esc_attr_e( 'Click to toggle', 'secure-custom-fields' ); ?>"></a>
387 <?php endif; ?>
388 <?php echo $row_num_html; ?><?php //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped where necessary on generation. ?>
389 </td>
390 <?php
391 }
392
393 /**
394 * Renders the actions displayed at the end of each row.
395 *
396 * @since ACF 6.0.0
397 *
398 * @return void
399 */
400 public function row_actions() {
401 if ( ! $this->show_remove ) {
402 return;
403 }
404 ?>
405 <td class="acf-row-handle remove">
406 <a class="acf-icon -plus small acf-js-tooltip hide-on-shift" href="#" data-event="add-row" title="<?php esc_attr_e( 'Add row', 'secure-custom-fields' ); ?>"></a>
407 <a class="acf-icon -duplicate small acf-js-tooltip show-on-shift" href="#" data-event="duplicate-row" title="<?php esc_attr_e( 'Duplicate row', 'secure-custom-fields' ); ?>"></a>
408 <a class="acf-icon -minus small acf-js-tooltip" href="#" data-event="remove-row" title="<?php esc_attr_e( 'Remove row', 'secure-custom-fields' ); ?>"></a>
409 </td>
410 <?php
411 }
412
413 /**
414 * Renders the actions displayed underneath the table.
415 *
416 * @since ACF 6.0.0
417 *
418 * @return void
419 */
420 public function table_actions() {
421 if ( ! $this->show_add ) {
422 return;
423 }
424 ?>
425 <div class="acf-actions">
426 <a class="acf-button acf-repeater-add-row button button-primary" href="#" data-event="add-row"><?php echo acf_esc_html( $this->field['button_label'] ); ?></a>
427 <?php $this->pagination(); ?>
428 <div class="clear"></div>
429 </div>
430 <?php
431 }
432
433 /**
434 * Renders the table pagination.
435 * Mostly lifted from the WordPress core WP_List_Table class.
436 *
437 * @since ACF 6.0.0
438 *
439 * @return void
440 */
441 public function pagination() {
442 if ( empty( $this->field['pagination'] ) ) {
443 return;
444 }
445
446 $total_rows = isset( $this->field['total_rows'] ) ? (int) $this->field['total_rows'] : 0;
447 $total_pages = ceil( $total_rows / (int) $this->field['rows_per_page'] );
448 $total_pages = max( $total_pages, 1 );
449
450 $html_current_page = sprintf(
451 "%s<input class='current-page' id='current-page-selector' type='text' name='paged' value='%s' size='%d' aria-describedby='table-paging' />",
452 '<label for="current-page-selector" class="screen-reader-text">' . __( 'Current Page', 'secure-custom-fields' ) . '</label>',
453 1,
454 strlen( $total_pages )
455 );
456
457 $html_total_pages = sprintf( "<span class='acf-total-pages'>%s</span>", number_format_i18n( $total_pages ) );
458 ?>
459 <div class="acf-tablenav tablenav-pages">
460 <a class="first-page button acf-nav" aria-hidden="true" data-event="first-page" title="<?php esc_attr_e( 'First Page', 'secure-custom-fields' ); ?>">
461 <span class="screen-reader-text"><?php esc_html_e( 'First Page', 'secure-custom-fields' ); ?></span>
462 <span aria-hidden="true">&laquo;</span>
463 </a>
464 <a class="prev-page button acf-nav" aria-hidden="true" data-event="prev-page" title="<?php esc_attr_e( 'Previous Page', 'secure-custom-fields' ); ?>">
465 <span class="screen-reader-text"><?php esc_html_e( 'Previous Page', 'secure-custom-fields' ); ?></span>
466 <span aria-hidden="true">&lsaquo;</span>
467 </a>
468 <span class="paging-input">
469 <label for="current-page-selector" class="screen-reader-text"><?php esc_html_e( 'Current Page', 'secure-custom-fields' ); ?></label>
470 <span class="tablenav-paging-text" title="<?php esc_attr_e( 'Current Page', 'secure-custom-fields' ); ?>">
471 <?php
472 printf(
473 /* translators: 1: Current page, 2: Total pages. */
474 esc_html_x( '%1$s of %2$s', 'paging', 'secure-custom-fields' ),
475 $html_current_page, //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escape not necessary.
476 $html_total_pages //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escape not necessary.
477 );
478 ?>
479 </span>
480 </span>
481 <a class="next-page button acf-nav" data-event="next-page" title="<?php esc_attr_e( 'Next Page', 'secure-custom-fields' ); ?>">
482 <span class="screen-reader-text"><?php esc_html_e( 'Next Page', 'secure-custom-fields' ); ?></span>
483 <span aria-hidden="true">&rsaquo;</span>
484 </a>
485 <a class="last-page button acf-nav" data-event="last-page" title="<?php esc_attr_e( 'Last Page', 'secure-custom-fields' ); ?>">
486 <span class="screen-reader-text"><?php esc_html_e( 'Last Page', 'secure-custom-fields' ); ?></span>
487 <span aria-hidden="true">&raquo;</span>
488 </a>
489 </div>
490 <?php
491 }
492 }
493