admin-field-groups.php
| 1 | <?php |
| 2 | |
| 3 | if( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly |
| 4 | |
| 5 | if( ! class_exists('ACF_Admin_Field_Groups') ) : |
| 6 | |
| 7 | class ACF_Admin_Field_Groups { |
| 8 | |
| 9 | /** |
| 10 | * Array of field groups availbale for sync. |
| 11 | * |
| 12 | * @since 5.9.0 |
| 13 | * @var array |
| 14 | */ |
| 15 | public $sync = array(); |
| 16 | |
| 17 | /** |
| 18 | * The current view (post_status). |
| 19 | * |
| 20 | * @since 5.9.0 |
| 21 | * @var string |
| 22 | */ |
| 23 | public $view = ''; |
| 24 | |
| 25 | /** |
| 26 | * Constructor. |
| 27 | * |
| 28 | * @date 5/03/2014 |
| 29 | * @since 5.0.0 |
| 30 | * |
| 31 | * @param void |
| 32 | * @return void |
| 33 | */ |
| 34 | public function __construct() { |
| 35 | |
| 36 | // Add hooks. |
| 37 | add_action( 'load-edit.php', array( $this, 'handle_redirection') ); |
| 38 | add_action( 'current_screen', array( $this, 'current_screen' ) ); |
| 39 | |
| 40 | // Handle post status change events. |
| 41 | add_action( 'trashed_post', array( $this, 'trashed_post') ); |
| 42 | add_action( 'untrashed_post', array( $this, 'untrashed_post') ); |
| 43 | add_action( 'deleted_post', array( $this, 'deleted_post') ); |
| 44 | } |
| 45 | |
| 46 | /** |
| 47 | * Returns the Field Groups admin URL. |
| 48 | * |
| 49 | * @date 27/3/20 |
| 50 | * @since 5.9.0 |
| 51 | * |
| 52 | * @param string $params Extra URL params. |
| 53 | * @return string |
| 54 | */ |
| 55 | public function get_admin_url( $params = '' ) { |
| 56 | return admin_url( "edit.php?post_type=acf-field-group{$params}" ); |
| 57 | } |
| 58 | |
| 59 | /** |
| 60 | * Returns the Field Groups admin URL taking into account the current view. |
| 61 | * |
| 62 | * @date 27/3/20 |
| 63 | * @since 5.9.0 |
| 64 | * |
| 65 | * @param string $params Extra URL params. |
| 66 | * @return string |
| 67 | */ |
| 68 | public function get_current_admin_url( $params = '' ) { |
| 69 | return $this->get_admin_url( ( $this->view ? '&post_status=' . $this->view : '' ) . $params ); |
| 70 | } |
| 71 | |
| 72 | /** |
| 73 | * Redirects users from ACF 4.0 admin page. |
| 74 | * |
| 75 | * @date 17/9/18 |
| 76 | * @since 5.7.6 |
| 77 | * |
| 78 | * @param void |
| 79 | * @return void |
| 80 | */ |
| 81 | public function handle_redirection() { |
| 82 | if( isset($_GET['post_type']) && $_GET['post_type'] === 'acf' ) { |
| 83 | wp_redirect( $this->get_admin_url() ); |
| 84 | exit; |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | /** |
| 89 | * Constructor for the Field Groups admin page. |
| 90 | * |
| 91 | * @date 21/07/2014 |
| 92 | * @since 5.0.0 |
| 93 | * |
| 94 | * @param void |
| 95 | * @return void |
| 96 | */ |
| 97 | public function current_screen() { |
| 98 | |
| 99 | // Bail early if not Field Groups admin page. |
| 100 | if( !acf_is_screen('edit-acf-field-group') ) { |
| 101 | return; |
| 102 | } |
| 103 | |
| 104 | // Get the current view. |
| 105 | $this->view = isset( $_GET['post_status'] ) ? sanitize_text_field( $_GET['post_status'] ) : ''; |
| 106 | |
| 107 | // Setup and check for custom actions.. |
| 108 | $this->setup_sync(); |
| 109 | $this->check_sync(); |
| 110 | $this->check_duplicate(); |
| 111 | |
| 112 | // Modify publish post status text and order. |
| 113 | global $wp_post_statuses; |
| 114 | $wp_post_statuses['publish']->label_count = _n_noop( 'Active <span class="count">(%s)</span>', 'Active <span class="count">(%s)</span>', 'acf' ); |
| 115 | $wp_post_statuses['trash'] = acf_extract_var( $wp_post_statuses, 'trash' ); |
| 116 | |
| 117 | // Add hooks. |
| 118 | add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts') ); |
| 119 | add_action( 'admin_body_class', array( $this, 'admin_body_class' ) ); |
| 120 | add_filter( 'views_edit-acf-field-group', array( $this, 'admin_table_views' ), 10, 1 ); |
| 121 | add_filter( 'manage_acf-field-group_posts_columns', array( $this, 'admin_table_columns' ), 10, 1 ); |
| 122 | add_action( 'manage_acf-field-group_posts_custom_column', array( $this, 'admin_table_columns_html' ), 10, 2 ); |
| 123 | add_filter( 'display_post_states', array( $this, 'display_post_states' ), 10, 2 ); |
| 124 | add_filter( 'bulk_actions-edit-acf-field-group', array( $this, 'admin_table_bulk_actions' ), 10, 1 ); |
| 125 | add_action( 'admin_footer', array( $this, 'admin_footer' ), 1 ); |
| 126 | if( $this->view !== 'trash' ) { |
| 127 | add_filter( 'page_row_actions', array( $this, 'page_row_actions' ), 10, 2 ); |
| 128 | } |
| 129 | |
| 130 | // Add hooks for "sync" view. |
| 131 | if( $this->view === 'sync' ) { |
| 132 | add_action( 'admin_footer', array( $this, 'admin_footer__sync' ), 1 ); |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | /** |
| 137 | * Sets up the field groups ready for sync. |
| 138 | * |
| 139 | * @date 17/4/20 |
| 140 | * @since 5.9.0 |
| 141 | * |
| 142 | * @param void |
| 143 | * @return void |
| 144 | */ |
| 145 | public function setup_sync() { |
| 146 | |
| 147 | // Review local json field groups. |
| 148 | if( acf_get_local_json_files() ) { |
| 149 | |
| 150 | // Get all groups in a single cached query to check if sync is available. |
| 151 | $all_field_groups = acf_get_field_groups(); |
| 152 | foreach( $all_field_groups as $field_group ) { |
| 153 | |
| 154 | // Extract vars. |
| 155 | $local = acf_maybe_get( $field_group, 'local' ); |
| 156 | $modified = acf_maybe_get( $field_group, 'modified' ); |
| 157 | $private = acf_maybe_get( $field_group, 'private' ); |
| 158 | |
| 159 | // Ignore if is private. |
| 160 | if( $private ) { |
| 161 | continue; |
| 162 | |
| 163 | // Ignore not local "json". |
| 164 | } elseif( $local !== 'json' ) { |
| 165 | continue; |
| 166 | |
| 167 | // Append to sync if not yet in database. |
| 168 | } elseif( !$field_group['ID'] ) { |
| 169 | $this->sync[ $field_group['key'] ] = $field_group; |
| 170 | |
| 171 | // Append to sync if "json" modified time is newer than database. |
| 172 | } elseif( $modified && $modified > get_post_modified_time('U', true, $field_group['ID']) ) { |
| 173 | $this->sync[ $field_group['key'] ] = $field_group; |
| 174 | } |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | /** |
| 180 | * Enqueues admin scripts. |
| 181 | * |
| 182 | * @date 18/4/20 |
| 183 | * @since 5.9.0 |
| 184 | * |
| 185 | * @param void |
| 186 | * @return void |
| 187 | */ |
| 188 | public function admin_enqueue_scripts() { |
| 189 | acf_enqueue_script( 'acf' ); |
| 190 | |
| 191 | // Localize text. |
| 192 | acf_localize_text(array( |
| 193 | 'Review local JSON changes' => __( 'Review local JSON changes', 'acf' ), |
| 194 | 'Loading diff' => __( 'Loading diff', 'acf' ), |
| 195 | 'Sync changes' => __( 'Sync changes', 'acf' ), |
| 196 | )); |
| 197 | } |
| 198 | |
| 199 | /** |
| 200 | * Modifies the admin body class. |
| 201 | * |
| 202 | * @date 18/4/20 |
| 203 | * @since 5.9.0 |
| 204 | * |
| 205 | * @param string $classes Space-separated list of CSS classes. |
| 206 | * @return string |
| 207 | */ |
| 208 | public function admin_body_class( $classes ) { |
| 209 | $classes .= ' acf-admin-field-groups'; |
| 210 | if( $this->view ) { |
| 211 | $classes .= " view-{$this->view}"; |
| 212 | } |
| 213 | return $classes; |
| 214 | } |
| 215 | |
| 216 | /** |
| 217 | * returns the disabled post state HTML. |
| 218 | * |
| 219 | * @date 17/4/20 |
| 220 | * @since 5.9.0 |
| 221 | * |
| 222 | * @param void |
| 223 | * @return string |
| 224 | */ |
| 225 | public function get_disabled_post_state() { |
| 226 | return '<span class="dashicons dashicons-hidden"></span> ' . _x( 'Disabled', 'post status', 'acf' ); |
| 227 | } |
| 228 | |
| 229 | /** |
| 230 | * Adds the "disabled" post state for the admin table title. |
| 231 | * |
| 232 | * @date 1/4/20 |
| 233 | * @since 5.9.0 |
| 234 | * |
| 235 | * @param array $post_states An array of post display states. |
| 236 | * @param WP_Post $post The current post object. |
| 237 | * @return array |
| 238 | */ |
| 239 | public function display_post_states( $post_states, $post ) { |
| 240 | if( $post->post_status === 'acf-disabled' ) { |
| 241 | $post_states['acf-disabled'] = $this->get_disabled_post_state(); |
| 242 | } |
| 243 | return $post_states; |
| 244 | } |
| 245 | |
| 246 | /** |
| 247 | * Customizes the admin table columns. |
| 248 | * |
| 249 | * @date 1/4/20 |
| 250 | * @since 5.9.0 |
| 251 | * |
| 252 | * @param array $columns The columns array. |
| 253 | * @return array |
| 254 | */ |
| 255 | public function admin_table_columns( $_columns ) { |
| 256 | $columns = array( |
| 257 | 'cb' => $_columns['cb'], |
| 258 | 'title' => $_columns['title'], |
| 259 | 'acf-description' => __('Description', 'acf'), |
| 260 | 'acf-key' => __('Key', 'acf'), |
| 261 | 'acf-location' => __('Location', 'acf'), |
| 262 | 'acf-count' => __('Fields', 'acf'), |
| 263 | ); |
| 264 | if( acf_get_local_json_files() ) { |
| 265 | $columns['acf-json'] = __('Local JSON', 'acf'); |
| 266 | } |
| 267 | return $columns; |
| 268 | } |
| 269 | |
| 270 | /** |
| 271 | * Renders the admin table column HTML |
| 272 | * |
| 273 | * @date 1/4/20 |
| 274 | * @since 5.9.0 |
| 275 | * |
| 276 | * @param string $column_name The name of the column to display. |
| 277 | * @param int $post_id The current post ID. |
| 278 | * @return void |
| 279 | */ |
| 280 | public function admin_table_columns_html( $column_name, $post_id ) { |
| 281 | $field_group = acf_get_field_group( $post_id ); |
| 282 | if( $field_group ) { |
| 283 | $this->render_admin_table_column( $column_name, $field_group ); |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | /** |
| 288 | * Renders a specific admin table column. |
| 289 | * |
| 290 | * @date 17/4/20 |
| 291 | * @since 5.9.0 |
| 292 | * |
| 293 | * @param string $column_name The name of the column to display. |
| 294 | * @param array $field_group The field group. |
| 295 | * @return void |
| 296 | */ |
| 297 | public function render_admin_table_column( $column_name, $field_group ) { |
| 298 | switch ( $column_name ) { |
| 299 | |
| 300 | // Key. |
| 301 | case 'acf-key': |
| 302 | echo esc_html( $field_group['key'] ); |
| 303 | break; |
| 304 | |
| 305 | // Description. |
| 306 | case 'acf-description': |
| 307 | if( $field_group['description'] ) { |
| 308 | echo '<span class="acf-description">' . acf_esc_html( $field_group['description'] ) . '</span>'; |
| 309 | } |
| 310 | break; |
| 311 | |
| 312 | // Location. |
| 313 | case 'acf-location': |
| 314 | $this->render_admin_table_column_locations( $field_group ); |
| 315 | break; |
| 316 | |
| 317 | // Count. |
| 318 | case 'acf-count': |
| 319 | echo esc_html( acf_get_field_count( $field_group ) ); |
| 320 | break; |
| 321 | |
| 322 | // Local JSON. |
| 323 | case 'acf-json': |
| 324 | $this->render_admin_table_column_local_status( $field_group ); |
| 325 | break; |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | /** |
| 330 | * Displays a visual representation of the field group's locations. |
| 331 | * |
| 332 | * @date 1/4/20 |
| 333 | * @since 5.9.0 |
| 334 | * |
| 335 | * @param array $field_group The field group. |
| 336 | * @return void |
| 337 | */ |
| 338 | public function render_admin_table_column_locations( $field_group ) { |
| 339 | $objects = array(); |
| 340 | |
| 341 | // Loop over location rules and determine connected object types. |
| 342 | if( $field_group['location'] ) { |
| 343 | foreach( $field_group['location'] as $i => $rules ) { |
| 344 | |
| 345 | // Determine object types for each rule. |
| 346 | foreach( $rules as $j => $rule ) { |
| 347 | |
| 348 | // Get location type and subtype for the current rule. |
| 349 | $location = acf_get_location_rule( $rule['param'] ); |
| 350 | $location_object_type = ''; |
| 351 | $location_object_subtype = ''; |
| 352 | if( $location ) { |
| 353 | $location_object_type = $location->get_object_type( $rule ); |
| 354 | $location_object_subtype = $location->get_object_subtype( $rule ); |
| 355 | } |
| 356 | $rules[ $j ]['object_type'] = $location_object_type; |
| 357 | $rules[ $j ]['object_subtype'] = $location_object_subtype; |
| 358 | } |
| 359 | |
| 360 | // Now that each $rule conains object type data... |
| 361 | $object_types = array_column( $rules, 'object_type' ); |
| 362 | $object_types = array_filter( $object_types ); |
| 363 | $object_types = array_values( $object_types ); |
| 364 | if( $object_types ) { |
| 365 | $object_type = $object_types[0]; |
| 366 | } else { |
| 367 | continue; |
| 368 | } |
| 369 | |
| 370 | $object_subtypes = array_column( $rules, 'object_subtype' ); |
| 371 | $object_subtypes = array_filter( $object_subtypes ); |
| 372 | $object_subtypes = array_values( $object_subtypes ); |
| 373 | $object_subtypes = array_map('acf_array', $object_subtypes); |
| 374 | if( count($object_subtypes) > 1 ) { |
| 375 | $object_subtypes = call_user_func_array('array_intersect', $object_subtypes); |
| 376 | $object_subtypes = array_values( $object_subtypes ); |
| 377 | } elseif( $object_subtypes ) { |
| 378 | $object_subtypes = $object_subtypes[0]; |
| 379 | } else { |
| 380 | $object_subtypes = array( '' ); |
| 381 | } |
| 382 | |
| 383 | // Append to objects. |
| 384 | foreach( $object_subtypes as $object_subtype ) { |
| 385 | $object = acf_get_object_type( $object_type, $object_subtype ); |
| 386 | if( $object ) { |
| 387 | $objects[ $object->name ] = $object; |
| 388 | } |
| 389 | } |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | // Reset keys. |
| 394 | $objects = array_values( $objects ); |
| 395 | |
| 396 | // Display. |
| 397 | $html = ''; |
| 398 | if( $objects ) { |
| 399 | $limit = 3; |
| 400 | $total = count( $objects ); |
| 401 | |
| 402 | // Icon. |
| 403 | $html .= '<span class="dashicons ' . $objects[0]->icon . ($total > 1 ? ' acf-multi-dashicon' : '') . '"></span> '; |
| 404 | |
| 405 | // Labels. |
| 406 | $labels = array_column( $objects, 'label' ); |
| 407 | $labels = array_slice( $labels, 0, 3 ); |
| 408 | $html .= implode(', ', $labels ); |
| 409 | |
| 410 | // More. |
| 411 | if( $total > $limit ) { |
| 412 | $html .= ', ...'; |
| 413 | } |
| 414 | } else { |
| 415 | $html = '<span class="dashicons dashicons-businesswoman"></span> ' . __( 'Various', 'acf' ); |
| 416 | } |
| 417 | |
| 418 | // Filter. |
| 419 | echo acf_esc_html( $html ); |
| 420 | } |
| 421 | |
| 422 | /** |
| 423 | * Returns a human readable file location. |
| 424 | * |
| 425 | * @date 17/4/20 |
| 426 | * @since 5.9.0 |
| 427 | * |
| 428 | * @param string $file The full file path. |
| 429 | * @return string |
| 430 | */ |
| 431 | public function get_human_readable_file_location( $file ) { |
| 432 | |
| 433 | // Generate friendly file path. |
| 434 | $theme_path = get_stylesheet_directory(); |
| 435 | if( strpos($file, $theme_path) !== false ) { |
| 436 | $rel_file = str_replace( $theme_path, '', $file ); |
| 437 | $located = sprintf( __('Located in theme: %s', 'acf'), $rel_file ); |
| 438 | |
| 439 | } elseif( strpos($file, WP_PLUGIN_DIR) !== false ) { |
| 440 | $rel_file = str_replace( WP_PLUGIN_DIR, '', $file ); |
| 441 | $located = sprintf( __('Located in plugin: %s', 'acf'), $rel_file ); |
| 442 | |
| 443 | } else { |
| 444 | $rel_file = str_replace( ABSPATH, '', $file ); |
| 445 | $located = sprintf( __('Located in: %s', 'acf'), $rel_file ); |
| 446 | } |
| 447 | return $located; |
| 448 | } |
| 449 | |
| 450 | /** |
| 451 | * Displays the local JSON status of a field group. |
| 452 | * |
| 453 | * @date 14/4/20 |
| 454 | * @since 5.9.0 |
| 455 | * |
| 456 | * @param type $var Description. Default. |
| 457 | * @return type Description. |
| 458 | */ |
| 459 | public function render_admin_table_column_local_status( $field_group ) { |
| 460 | $json = acf_get_local_json_files(); |
| 461 | if( isset( $json[ $field_group['key'] ] ) ) { |
| 462 | $file = $json[ $field_group['key'] ]; |
| 463 | if( isset($this->sync[ $field_group['key'] ]) ) { |
| 464 | $url = $this->get_admin_url( '&acfsync=' . $field_group['key'] . '&_wpnonce=' . wp_create_nonce('bulk-posts') ); |
| 465 | echo '<strong>' . __( 'Sync available', 'acf' ) . '</strong>'; |
| 466 | if( $field_group['ID'] ) { |
| 467 | echo '<div class="row-actions"> |
| 468 | <span class="sync"><a href="' . esc_url( $url ) . '">' . __( 'Sync', 'acf' ) . '</a> | </span> |
| 469 | <span class="review"><a href="#" data-event="review-sync" data-id="' . esc_attr($field_group['ID']) . '" data-href="' . esc_url( $url ) . '">' . __( 'Review changes', 'acf' ) . '</a></span> |
| 470 | </div>'; |
| 471 | } else { |
| 472 | echo '<div class="row-actions"> |
| 473 | <span class="sync"><a href="' . esc_url( $url ) . '">' . __( 'Import', 'acf' ) . '</a></span> |
| 474 | </div>'; |
| 475 | } |
| 476 | } else { |
| 477 | echo __( 'Saved', 'acf' ); |
| 478 | } |
| 479 | } else { |
| 480 | echo '<span class="acf-secondary-text">' . __( 'Awaiting save', 'acf' ) . '</span>'; |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | /** |
| 485 | * Customizes the page row actions visible on hover. |
| 486 | * |
| 487 | * @date 14/4/20 |
| 488 | * @since 5.9.0 |
| 489 | * |
| 490 | * @param array $actions The array of actions HTML. |
| 491 | * @param WP_Post $post The post. |
| 492 | * @return array |
| 493 | */ |
| 494 | public function page_row_actions( $actions, $post ) { |
| 495 | |
| 496 | // Remove "Quick Edit" action. |
| 497 | unset( $actions['inline'], $actions['inline hide-if-no-js'] ); |
| 498 | |
| 499 | // Append "Duplicate" action. |
| 500 | $duplicate_action_url = $this->get_admin_url( '&acfduplicate=' . $post->ID . '&_wpnonce=' . wp_create_nonce('bulk-posts') ); |
| 501 | $actions[ 'acfduplicate' ] = '<a href="' . esc_url( $duplicate_action_url ) . '" aria-label="' . esc_attr__( 'Duplicate this item', 'acf' ) . '">' . __( 'Duplicate', 'acf' ) . '</a>'; |
| 502 | |
| 503 | // Return actions in custom order. |
| 504 | $order = array( 'edit', 'acfduplicate', 'trash' ); |
| 505 | return array_merge( array_flip($order), $actions ); |
| 506 | } |
| 507 | |
| 508 | /** |
| 509 | * Modifies the admin table bulk actions dropdown. |
| 510 | * |
| 511 | * @date 15/4/20 |
| 512 | * @since 5.9.0 |
| 513 | * |
| 514 | * @param array $actions The actions array. |
| 515 | * @return array |
| 516 | */ |
| 517 | public function admin_table_bulk_actions( $actions ) { |
| 518 | |
| 519 | // Add "duplicate" action. |
| 520 | if( $this->view !== 'sync' ) { |
| 521 | $actions[ 'acfduplicate' ] = __( 'Duplicate', 'acf' ); |
| 522 | } |
| 523 | |
| 524 | // Add "Sync" action. |
| 525 | if( $this->sync ) { |
| 526 | if( $this->view === 'sync' ) { |
| 527 | $actions = array(); |
| 528 | } |
| 529 | $actions[ 'acfsync' ] = __( 'Sync changes', 'acf' ); |
| 530 | } |
| 531 | return $actions; |
| 532 | } |
| 533 | |
| 534 | /** |
| 535 | * Checks for the custom "duplicate" action. |
| 536 | * |
| 537 | * @date 15/4/20 |
| 538 | * @since 5.9.0 |
| 539 | * |
| 540 | * @param void |
| 541 | * @return void |
| 542 | */ |
| 543 | public function check_duplicate() { |
| 544 | |
| 545 | // Display notice on success redirect. |
| 546 | if( isset($_GET['acfduplicatecomplete']) ) { |
| 547 | $ids = array_map( 'intval', explode(',', $_GET['acfduplicatecomplete']) ); |
| 548 | |
| 549 | // Generate text. |
| 550 | $text = sprintf( |
| 551 | _n( 'Field group duplicated.', '%s field groups duplicated.', count($ids), 'acf' ), |
| 552 | count($ids) |
| 553 | ); |
| 554 | |
| 555 | // Append links to text. |
| 556 | $links = array(); |
| 557 | foreach( $ids as $id ) { |
| 558 | $links[] = '<a href="' . get_edit_post_link( $id ) . '">' . get_the_title( $id ) . '</a>'; |
| 559 | } |
| 560 | $text .= ' ' . implode( ', ', $links ); |
| 561 | |
| 562 | // Add notice. |
| 563 | acf_add_admin_notice( $text, 'success' ); |
| 564 | return; |
| 565 | } |
| 566 | |
| 567 | // Find items to duplicate. |
| 568 | $ids = array(); |
| 569 | if( isset($_GET['acfduplicate']) ) { |
| 570 | $ids[] = intval( $_GET['acfduplicate'] ); |
| 571 | } elseif( isset($_GET['post'], $_GET['action2']) && $_GET['action2'] === 'acfduplicate' ) { |
| 572 | $ids = array_map( 'intval', $_GET['post'] ); |
| 573 | } |
| 574 | |
| 575 | if( $ids ) { |
| 576 | check_admin_referer('bulk-posts'); |
| 577 | |
| 578 | // Duplicate field groups and generate array of new IDs. |
| 579 | $new_ids = array(); |
| 580 | foreach( $ids as $id ) { |
| 581 | $field_group = acf_duplicate_field_group( $id ); |
| 582 | $new_ids[] = $field_group['ID']; |
| 583 | } |
| 584 | |
| 585 | // Redirect. |
| 586 | wp_redirect( $this->get_admin_url( '&acfduplicatecomplete=' . implode(',', $new_ids) ) ); |
| 587 | exit; |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | /** |
| 592 | * Checks for the custom "acfsync" action. |
| 593 | * |
| 594 | * @date 15/4/20 |
| 595 | * @since 5.9.0 |
| 596 | * |
| 597 | * @param void |
| 598 | * @return void |
| 599 | */ |
| 600 | public function check_sync() { |
| 601 | |
| 602 | // Display notice on success redirect. |
| 603 | if( isset($_GET['acfsynccomplete']) ) { |
| 604 | $ids = array_map( 'intval', explode(',', $_GET['acfsynccomplete']) ); |
| 605 | |
| 606 | // Generate text. |
| 607 | $text = sprintf( |
| 608 | _n( 'Field group synchronised.', '%s field groups synchronised.', count($ids), 'acf' ), |
| 609 | count($ids) |
| 610 | ); |
| 611 | |
| 612 | // Append links to text. |
| 613 | $links = array(); |
| 614 | foreach( $ids as $id ) { |
| 615 | $links[] = '<a href="' . get_edit_post_link( $id ) . '">' . get_the_title( $id ) . '</a>'; |
| 616 | } |
| 617 | $text .= ' ' . implode( ', ', $links ); |
| 618 | |
| 619 | // Add notice. |
| 620 | acf_add_admin_notice( $text, 'success' ); |
| 621 | return; |
| 622 | } |
| 623 | |
| 624 | // Find items to sync. |
| 625 | $keys = array(); |
| 626 | if( isset($_GET['acfsync']) ) { |
| 627 | $keys[] = sanitize_text_field( $_GET['acfsync'] ); |
| 628 | } elseif( isset($_GET['post'], $_GET['action2']) && $_GET['action2'] === 'acfsync' ) { |
| 629 | $keys = array_map( 'sanitize_text_field', $_GET['post'] ); |
| 630 | } |
| 631 | |
| 632 | if( $keys && $this->sync ) { |
| 633 | check_admin_referer('bulk-posts'); |
| 634 | |
| 635 | // Disabled "Local JSON" controller to prevent the .json file from being modified during import. |
| 636 | acf_update_setting( 'json', false ); |
| 637 | |
| 638 | // Sync field groups and generate array of new IDs. |
| 639 | $files = acf_get_local_json_files(); |
| 640 | $new_ids = array(); |
| 641 | foreach( $this->sync as $key => $field_group ) { |
| 642 | if( $field_group['key'] && in_array($field_group['key'], $keys) ) { |
| 643 | // Import. |
| 644 | } elseif( $field_group['ID'] && in_array($field_group['ID'], $keys) ) { |
| 645 | // Import. |
| 646 | } else { |
| 647 | // Ignore. |
| 648 | continue; |
| 649 | } |
| 650 | $local_field_group = json_decode( file_get_contents( $files[ $key ] ), true ); |
| 651 | $local_field_group['ID'] = $field_group['ID']; |
| 652 | $result = acf_import_field_group( $local_field_group ); |
| 653 | $new_ids[] = $result['ID']; |
| 654 | } |
| 655 | |
| 656 | // Redirect. |
| 657 | wp_redirect( $this->get_current_admin_url( '&acfsynccomplete=' . implode(',', $new_ids) ) ); |
| 658 | exit; |
| 659 | } |
| 660 | } |
| 661 | |
| 662 | /** |
| 663 | * Customizes the admin table subnav. |
| 664 | * |
| 665 | * @date 17/4/20 |
| 666 | * @since 5.9.0 |
| 667 | * |
| 668 | * @param array $views The available views. |
| 669 | * @return array |
| 670 | */ |
| 671 | public function admin_table_views( $views ) { |
| 672 | global $wp_list_table, $wp_query; |
| 673 | |
| 674 | // Count items. |
| 675 | $count = count( $this->sync ); |
| 676 | |
| 677 | // Append "sync" link to subnav. |
| 678 | if( $count ) { |
| 679 | $views['sync'] = sprintf( |
| 680 | '<a %s href="%s">%s <span class="count">(%s)</span></a>', |
| 681 | ( $this->view === 'sync' ? 'class="current"' : '' ), |
| 682 | esc_url( $this->get_admin_url( '&post_status=sync' ) ), |
| 683 | esc_html( __('Sync available', 'acf') ), |
| 684 | $count |
| 685 | ); |
| 686 | } |
| 687 | |
| 688 | // Modify table pagination args to match JSON data. |
| 689 | if( $this->view === 'sync' ) { |
| 690 | $wp_list_table->set_pagination_args( array( |
| 691 | 'total_items' => $count, |
| 692 | 'total_pages' => 1, |
| 693 | 'per_page' => $count |
| 694 | )); |
| 695 | $wp_query->post_count = 1; // At least one post is needed to render bulk drop-down. |
| 696 | } |
| 697 | return $views; |
| 698 | } |
| 699 | |
| 700 | /** |
| 701 | * Prints scripts into the admin footer. |
| 702 | * |
| 703 | * @date 20/4/20 |
| 704 | * @since 5.9.0 |
| 705 | * |
| 706 | * @param void |
| 707 | * @return void |
| 708 | */ |
| 709 | function admin_footer() { |
| 710 | ?> |
| 711 | <script type="text/javascript"> |
| 712 | (function($){ |
| 713 | |
| 714 | // Displays a modal comparing local changes. |
| 715 | function reviewSync( props ) { |
| 716 | var modal = acf.newModal({ |
| 717 | title: acf.__('Review local JSON changes'), |
| 718 | content: '<p class="acf-modal-feedback"><i class="acf-loading"></i> ' + acf.__('Loading diff') + '</p>', |
| 719 | toolbar: '<a href="' + props.href + '" class="button button-primary button-sync-changes disabled">' + acf.__('Sync changes') + '</a>', |
| 720 | }); |
| 721 | |
| 722 | // Call AJAX. |
| 723 | var xhr = $.ajax({ |
| 724 | url: acf.get('ajaxurl'), |
| 725 | method: 'POST', |
| 726 | dataType: 'json', |
| 727 | data: acf.prepareForAjax({ |
| 728 | action: 'acf/ajax/local_json_diff', |
| 729 | id: props.id |
| 730 | }) |
| 731 | }) |
| 732 | .done(function( data, textStatus, jqXHR ) { |
| 733 | modal.content( data.html ); |
| 734 | modal.$('.button-sync-changes').removeClass('disabled'); |
| 735 | }) |
| 736 | .fail(function( jqXHR, textStatus, errorThrown ) { |
| 737 | if( error = acf.getXhrError(jqXHR) ) { |
| 738 | modal.content( '<p class="acf-modal-feedback error">' + error + '</p>' ); |
| 739 | } |
| 740 | }); |
| 741 | } |
| 742 | |
| 743 | // Add event listener. |
| 744 | $(document).on('click', 'a[data-event="review-sync"]', function( e ){ |
| 745 | e.preventDefault(); |
| 746 | reviewSync( $(this).data() ); |
| 747 | }); |
| 748 | })(jQuery); |
| 749 | </script> |
| 750 | <?php |
| 751 | } |
| 752 | |
| 753 | /** |
| 754 | * Customizes the admin table HTML when viewing "sync" post_status. |
| 755 | * |
| 756 | * @date 17/4/20 |
| 757 | * @since 5.9.0 |
| 758 | * |
| 759 | * @param array $views The available views. |
| 760 | * @return array |
| 761 | */ |
| 762 | public function admin_footer__sync() { |
| 763 | global $wp_list_table; |
| 764 | |
| 765 | // Get table columns. |
| 766 | $columns = $wp_list_table->get_columns(); |
| 767 | $hidden = get_hidden_columns( $wp_list_table->screen ); |
| 768 | ?> |
| 769 | <div style="display: none;"> |
| 770 | <table> |
| 771 | <tbody id="acf-the-list"> |
| 772 | <?php |
| 773 | foreach( $this->sync as $k => $field_group ) { |
| 774 | echo '<tr>'; |
| 775 | foreach( $columns as $column_name => $column_label ) { |
| 776 | $el = 'td'; |
| 777 | if( $column_name === 'cb' ) { |
| 778 | $el = 'th'; |
| 779 | $classes = 'check-column'; |
| 780 | $column_label = ''; |
| 781 | } elseif( $column_name === 'title' ) { |
| 782 | $classes = "$column_name column-$column_name column-primary"; |
| 783 | } else { |
| 784 | $classes = "$column_name column-$column_name"; |
| 785 | } |
| 786 | if( in_array( $column_name, $hidden, true ) ) { |
| 787 | $classes .= ' hidden'; |
| 788 | } |
| 789 | echo "<$el class=\"$classes\" data-colname=\"$column_label\">"; |
| 790 | switch ( $column_name ) { |
| 791 | |
| 792 | // Checkbox. |
| 793 | case 'cb': |
| 794 | echo '<label for="cb-select-' . esc_attr( $k ) .'" class="screen-reader-text">' .esc_html( sprintf( __( 'Select %s', 'acf' ), $field_group['title'] ) ) . '</label>'; |
| 795 | echo '<input id="cb-select-' . esc_attr( $k ) .'" type="checkbox" value="'. esc_attr( $k ) .'" name="post[]">'; |
| 796 | break; |
| 797 | |
| 798 | // Title. |
| 799 | case 'title': |
| 800 | $post_state = ''; |
| 801 | if( !$field_group['active'] ) { |
| 802 | $post_state = ' — <span class="post-state">' . $this->get_disabled_post_state() . '</span>'; |
| 803 | } |
| 804 | echo '<strong><span class="row-title">' . esc_html( $field_group['title']) . '</span>' . $post_state . '</strong>'; |
| 805 | echo '<div class="row-actions"><span class="file acf-secondary-text">' . $this->get_human_readable_file_location( $field_group['local_file'] ) . '</span></div>'; |
| 806 | echo '<button type="button" class="toggle-row"><span class="screen-reader-text">Show more details</span></button>'; |
| 807 | break; |
| 808 | |
| 809 | // All other columns. |
| 810 | default: |
| 811 | $this->render_admin_table_column( $column_name, $field_group ); |
| 812 | break; |
| 813 | } |
| 814 | echo "</$el>"; |
| 815 | } |
| 816 | echo '</tr>'; |
| 817 | } |
| 818 | ?> |
| 819 | </tbody> |
| 820 | </table> |
| 821 | </div> |
| 822 | <script type="text/javascript"> |
| 823 | (function($){ |
| 824 | $('#the-list').html( $('#acf-the-list').children() ); |
| 825 | })(jQuery); |
| 826 | </script> |
| 827 | <?php |
| 828 | } |
| 829 | |
| 830 | /** |
| 831 | * Fires when trashing a field group post. |
| 832 | * |
| 833 | * @date 8/01/2014 |
| 834 | * @since 5.0.0 |
| 835 | * |
| 836 | * @param int $post_id The post ID. |
| 837 | * @return void |
| 838 | */ |
| 839 | public function trashed_post( $post_id ) { |
| 840 | if( get_post_type($post_id) === 'acf-field-group' ) { |
| 841 | acf_trash_field_group( $post_id ); |
| 842 | } |
| 843 | } |
| 844 | |
| 845 | /** |
| 846 | * Fires when untrashing a field group post. |
| 847 | * |
| 848 | * @date 8/01/2014 |
| 849 | * @since 5.0.0 |
| 850 | * |
| 851 | * @param int $post_id The post ID. |
| 852 | * @return void |
| 853 | */ |
| 854 | public function untrashed_post( $post_id ) { |
| 855 | if( get_post_type($post_id) === 'acf-field-group' ) { |
| 856 | acf_untrash_field_group( $post_id ); |
| 857 | } |
| 858 | } |
| 859 | |
| 860 | /** |
| 861 | * Fires when deleting a field group post. |
| 862 | * |
| 863 | * @date 8/01/2014 |
| 864 | * @since 5.0.0 |
| 865 | * |
| 866 | * @param int $post_id The post ID. |
| 867 | * @return void |
| 868 | */ |
| 869 | public function deleted_post( $post_id ) { |
| 870 | if( get_post_type($post_id) === 'acf-field-group' ) { |
| 871 | acf_delete_field_group( $post_id ); |
| 872 | } |
| 873 | } |
| 874 | } |
| 875 | |
| 876 | // Instantiate. |
| 877 | acf_new_instance('ACF_Admin_Field_Groups'); |
| 878 | |
| 879 | endif; // class_exists check |
| 880 |