post_type ) ) { return; } // Bail if on PRO. if ( acf_is_pro() && acf_pro_is_license_active() ) { return; } acf_get_view( 'acf-field-group/pro-features' ); } /** * Add any menu items required for post types. * * @since 6.1 */ public function admin_menu() {} /** * Returns the admin URL for the current post type edit page. * * @date 27/3/20 * @since 5.9.0 * * @param string $params Extra URL params. * @return string */ public function get_admin_url( $params = '' ) { if ( isset( $_GET['paged'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- used as intval to return a page. $params .= '&paged=' . intval( $_GET['paged'] ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- used as intval to return a page. } return admin_url( "edit.php?post_type={$this->post_type}{$params}" ); } /** * Returns the post type admin URL taking into account the current view. * * @date 27/3/20 * @since 5.9.0 * * @param string $params Extra URL params. * @return string */ public function get_current_admin_url( $params = '' ) { return $this->get_admin_url( ( $this->view ? '&post_status=' . $this->view : '' ) . $params ); } /** * Constructor for all ACF internal post type admin list pages. * * @since 5.0.0 */ public function current_screen() { // Bail early if not the list admin page. if ( ! acf_is_screen( "edit-{$this->post_type}" ) ) { return; } // Get the current view. $this->view = acf_request_arg( 'post_status', '' ); // Setup and check for custom actions. $this->setup_sync(); $this->check_sync(); $this->check_duplicate(); $this->check_activate(); $this->check_deactivate(); // Modify publish post status text and order. global $wp_post_statuses; $wp_post_statuses['publish']->label_count = _n_noop( 'Active (%s)', 'Active (%s)', 'acf' ); $wp_post_statuses['trash'] = acf_extract_var( $wp_post_statuses, 'trash' ); // Add hooks. add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); add_action( 'admin_body_class', array( $this, 'admin_body_class' ) ); add_filter( "views_edit-{$this->post_type}", array( $this, 'admin_table_views' ), 10, 1 ); add_filter( "manage_{$this->post_type}_posts_columns", array( $this, 'admin_table_columns' ), 10, 1 ); add_action( "manage_{$this->post_type}_posts_custom_column", array( $this, 'admin_table_columns_html' ), 10, 2 ); add_filter( 'display_post_states', array( $this, 'display_post_states' ), 10, 2 ); add_filter( "bulk_actions-edit-{$this->post_type}", array( $this, 'admin_table_bulk_actions' ), 10, 1 ); add_action( 'admin_footer', array( $this, 'admin_footer' ), 1 ); if ( $this->view !== 'trash' ) { add_filter( 'page_row_actions', array( $this, 'page_row_actions' ), 10, 2 ); } // Add hooks for "sync" view. if ( $this->view === 'sync' ) { add_action( 'admin_footer', array( $this, 'admin_footer__sync' ), 1 ); } do_action( 'acf/internal_post_type_list/current_screen', $this->post_type ); } /** * Sets up the field groups ready for sync. * * @since 5.9.0 */ public function setup_sync() { // Review local json files. if ( acf_get_local_json_files( $this->post_type ) ) { // Get all posts in a single cached query to check if sync is available. $all_posts = acf_get_internal_post_type_posts( $this->post_type ); foreach ( $all_posts as $post ) { // Extract vars. $local = acf_maybe_get( $post, 'local' ); $modified = acf_maybe_get( $post, 'modified' ); $private = acf_maybe_get( $post, 'private' ); // Ignore if is private. if ( $private ) { continue; // Ignore not local "json". } elseif ( $local !== 'json' ) { continue; // Append to sync if not yet in database. } elseif ( ! $post['ID'] ) { $this->sync[ $post['key'] ] = $post; // Append to sync if "json" modified time is newer than database. } elseif ( $modified && $modified > get_post_modified_time( 'U', true, $post['ID'] ) ) { $this->sync[ $post['key'] ] = $post; } } } } /** * Enqueues admin scripts. * * @since 5.9.0 */ public function admin_enqueue_scripts() { acf_enqueue_script( 'acf' ); acf_localize_text( array( 'Review local JSON changes' => esc_html__( 'Review local JSON changes', 'acf' ), 'Loading diff' => esc_html__( 'Loading diff', 'acf' ), 'Sync changes' => esc_html__( 'Sync changes', 'acf' ), 'Please activate your ACF PRO license to edit this options page.' => esc_html__( 'Please activate your ACF PRO license to edit this options page.', 'acf' ), 'Please activate your ACF PRO license to edit field groups assigned to an ACF Block.' => esc_html__( 'Please activate your ACF PRO license to edit field groups assigned to an ACF Block.', 'acf' ), ) ); } /** * Modifies the admin body class. * * @date 18/4/20 * @since 5.9.0 * * @param string $classes Space-separated list of CSS classes. * @return string */ public function admin_body_class( $classes ) { $classes .= ' acf-admin-page acf-internal-post-type ' . esc_attr( $this->admin_body_class ); if ( $this->view ) { $classes .= ' view-' . esc_attr( $this->view ); } return apply_filters( 'acf/internal_post_type_list/admin_body_classes', $classes, $this->post_type ); } /** * Returns the disabled post state HTML. * * @since 5.9.0 * * @return string */ public function get_disabled_post_state() { return ' ' . _x( 'Inactive', 'post status', 'acf' ); } /** * Returns the registration error state. * * @since 6.1 * * @return string */ public function get_registration_error_state() { return ' ' . _x( 'Registration Failed', 'post status', 'acf' ); } /** * Adds the "disabled" post state for the admin table title. * * @date 1/4/20 * @since 5.9.0 * * @param array $post_states An array of post display states. * @param WP_Post $post The current post object. * @return array */ public function display_post_states( $post_states, $post ) { if ( $post->post_status === 'acf-disabled' ) { $post_states['acf-disabled'] = $this->get_disabled_post_state(); } // Check the post store to see if this failed registration. if ( ! empty( $this->store ) && ! empty( $post->ID ) ) { $store = acf_get_store( $this->store ); if ( $store ) { $store_item = $store->get( $post->ID ); if ( ! empty( $store_item ) && ! empty( $store_item['not_registered'] ) ) { $post_states['acf-registration-warning'] = $this->get_registration_error_state(); } } } return $post_states; } /** * Get the HTML for when there are no post objects found. * * @since 6.0.0 * * @return string */ public function get_not_found_html() { ob_start(); $view = $this->post_type . '/list-empty'; if ( $this->is_pro_feature ) { $view = ACF_PATH . 'pro/admin/views/' . $view . '.php'; } acf_get_view( $view ); return ob_get_clean(); } /** * Customizes the admin table columns. * * @date 1/4/20 * @since 5.9.0 * * @param array $_columns The columns array. * @return array */ public function admin_table_columns( $_columns ) { return $_columns; } /** * Renders the admin table column HTML * * @date 1/4/20 * @since 5.9.0 * * @param string $column_name The name of the column to display. * @param integer $post_id The current post ID. * @return void */ public function admin_table_columns_html( $column_name, $post_id ) { $post = acf_get_internal_post_type( $post_id, $this->post_type ); if ( $post ) { $this->render_admin_table_column( $column_name, $post ); } } /** * Renders a specific admin table column. * * @date 17/4/20 * @since 5.9.0 * * @param string $column_name The name of the column to display. * @param array $post The main ACF post array. * @return void */ public function render_admin_table_column( $column_name, $post ) {} /** * Returns a human-readable file location. * * @date 17/4/20 * @since 5.9.0 * * @param string $file The full file path. * @return string */ public function get_human_readable_file_location( $file ) { // Generate friendly file path. $theme_path = get_stylesheet_directory(); if ( strpos( $file, $theme_path ) !== false ) { $rel_file = str_replace( $theme_path, '', $file ); $located = sprintf( __( 'Located in theme: %s', 'acf' ), $rel_file ); } elseif ( strpos( $file, WP_PLUGIN_DIR ) !== false ) { $rel_file = str_replace( WP_PLUGIN_DIR, '', $file ); $located = sprintf( __( 'Located in plugin: %s', 'acf' ), $rel_file ); } else { $rel_file = str_replace( ABSPATH, '', $file ); $located = sprintf( __( 'Located in: %s', 'acf' ), $rel_file ); } return $located; } /** * Displays the local JSON status of an ACF post. * * @date 14/4/20 * @since 5.9.0 * * @param array $post The main ACF post array. * @return void */ public function render_admin_table_column_local_status( $post ) { $json = acf_get_local_json_files( $this->post_type ); if ( isset( $json[ $post['key'] ] ) ) { if ( isset( $this->sync[ $post['key'] ] ) ) { $url = $this->get_admin_url( '&acfsync=' . $post['key'] . '&_wpnonce=' . wp_create_nonce( 'bulk-posts' ) ); echo '' . esc_html__( 'Sync available', 'acf' ) . ''; if ( $post['ID'] ) { echo '
' . esc_html__( 'Sync', 'acf' ) . ' | ' . esc_html__( 'Review changes', 'acf' ) . '
'; } else { echo '
' . esc_html__( 'Import', 'acf' ) . '
'; } } else { echo esc_html__( 'Saved', 'acf' ); } } else { echo '' . esc_html__( 'Awaiting save', 'acf' ) . ''; } } /** * Customizes the page row actions visible on hover. * * @date 14/4/20 * @since 5.9.0 * * @param array $actions The array of actions HTML. * @param WP_Post $post The post. * @return array */ public function page_row_actions( $actions, $post ) { // Remove "Quick Edit" action. unset( $actions['inline'], $actions['inline hide-if-no-js'] ); $duplicate_action_url = ''; // Append "Duplicate" action. if ( 'acf-field-group' === $this->post_type ) { $duplicate_action_url = $this->get_admin_url( '&acfduplicate=' . $post->ID . '&_wpnonce=' . wp_create_nonce( 'bulk-posts' ) ); } elseif ( 'acf-post-type' === $this->post_type ) { $duplicate_action_url = wp_nonce_url( admin_url( 'post-new.php?post_type=acf-post-type&use_post_type=' . $post->ID ), 'acfduplicate-' . $post->ID ); } elseif ( 'acf-taxonomy' === $this->post_type ) { $duplicate_action_url = wp_nonce_url( admin_url( 'post-new.php?post_type=acf-taxonomy&use_taxonomy=' . $post->ID ), 'acfduplicate-' . $post->ID ); } elseif ( 'acf-ui-options-page' === $this->post_type ) { $duplicate_action_url = wp_nonce_url( admin_url( 'post-new.php?post_type=acf-ui-options-page&use_options_page=' . $post->ID ), 'acfduplicate-' . $post->ID ); } $actions['acfduplicate'] = '' . __( 'Duplicate', 'acf' ) . ''; // Append the "Activate" or "Deactivate" actions. if ( 'acf-disabled' === $post->post_status ) { $activate_deactivate_action = 'acfactivate'; $activate_action_url = $this->get_admin_url( '&acfactivate=' . $post->ID . '&_wpnonce=' . wp_create_nonce( 'bulk-posts' ) ); $actions['acfactivate'] = '' . __( 'Activate', 'acf' ) . ''; } else { $activate_deactivate_action = 'acfdeactivate'; $deactivate_action_url = $this->get_admin_url( '&acfdeactivate=' . $post->ID . '&_wpnonce=' . wp_create_nonce( 'bulk-posts' ) ); $actions['acfdeactivate'] = '' . __( 'Deactivate', 'acf' ) . ''; } // Return actions in custom order. $order = array( 'edit', 'acfduplicate', $activate_deactivate_action, 'trash' ); return array_merge( array_flip( $order ), $actions ); } /** * Modifies the admin table bulk actions dropdown. * * @date 15/4/20 * @since 5.9.0 * * @param array $actions The actions array. * @return array */ public function admin_table_bulk_actions( $actions ) { if ( ! in_array( $this->view, array( 'sync', 'trash' ), true ) ) { // TODO: We'll likely have to add support for CPTs/Taxonomies! if ( 'acf-field-group' === $this->post_type ) { $actions['acfduplicate'] = __( 'Duplicate', 'acf' ); } $actions['acfactivate'] = __( 'Activate', 'acf' ); $actions['acfdeactivate'] = __( 'Deactivate', 'acf' ); } if ( $this->sync ) { if ( $this->view === 'sync' ) { $actions = array(); } $actions['acfsync'] = __( 'Sync changes', 'acf' ); } return $actions; } /** * Gets the translated action notice text for list table actions (activate, deactivate, sync, etc.). * * @since 6.1 * * @param string $action The action being performed. * @param integer $count The number of items the action was performed on. * @return string */ public function get_action_notice_text( $action, $count = 1 ) { return ''; } /** * Checks for the custom "Activate" bulk action. * * @since 6.0 */ public function check_activate() { // Verify capability. if ( ! acf_current_user_can_admin() ) { return; } $args = acf_request_args( array( 'acfactivatecomplete' => '', 'acfactivate' => '', 'post' => '', 'action2' => '', ) ); if ( ! empty( $args['acfactivatecomplete'] ) ) { check_admin_referer( 'bulk-posts' ); $activated = array_map( 'intval', explode( ',', $args['acfactivatecomplete'] ) ); $text = $this->get_action_notice_text( 'acfactivatecomplete', count( $activated ) ); $links = array(); foreach ( $activated as $activated_id ) { $links[] = sprintf( '%2$s', esc_url( get_edit_post_link( $activated_id ) ), esc_html( get_the_title( $activated_id ) ) ); } $text .= ' ' . implode( ', ', $links ); // Add notice. acf_add_admin_notice( $text, 'success' ); return; } // Find items to activate. $to_activate = array(); if ( ! empty( $args['acfactivate'] ) ) { $to_activate[] = intval( $args['acfactivate'] ); } elseif ( ! empty( $args['post'] ) && 'acfactivate' === $args['action2'] ) { $to_activate = array_map( 'intval', $args['post'] ); } if ( ! empty( $to_activate ) ) { check_admin_referer( 'bulk-posts' ); $activated_ids = array(); $nonce = wp_create_nonce( 'bulk-posts' ); foreach ( $to_activate as $id ) { $post_type = get_post_type( $id ); if ( $post_type && acf_update_internal_post_type_active_status( $id, true, $post_type ) ) { $activated_ids[] = $id; } } wp_safe_redirect( $this->get_admin_url( '&_wpnonce=' . $nonce . '&acfactivatecomplete=' . implode( ',', $activated_ids ) ) ); exit; } } /** * Checks for the custom "Deactivate" bulk action. * * @since 6.0 */ public function check_deactivate() { // Verify capability. if ( ! acf_current_user_can_admin() ) { return; } $args = acf_request_args( array( 'acfdeactivatecomplete' => '', 'acfdeactivate' => '', 'post' => '', 'action2' => '', ) ); // Display notice on success redirect. if ( ! empty( $args['acfdeactivatecomplete'] ) ) { check_admin_referer( 'bulk-posts' ); $deactivated = array_map( 'intval', explode( ',', $args['acfdeactivatecomplete'] ) ); $text = $this->get_action_notice_text( 'acfdeactivatecomplete', count( $deactivated ) ); $links = array(); foreach ( $deactivated as $deactivated_id ) { $links[] = sprintf( '%2$s', esc_url( get_edit_post_link( $deactivated_id ) ), esc_html( get_the_title( $deactivated_id ) ) ); } $text .= ' ' . implode( ', ', $links ); // Add notice. acf_add_admin_notice( $text, 'success' ); return; } // Find items to deactivate. $to_deactivate = array(); if ( ! empty( $args['acfdeactivate'] ) ) { $to_deactivate[] = intval( $args['acfdeactivate'] ); } elseif ( ! empty( $args['post'] ) && 'acfdeactivate' === $args['action2'] ) { $to_deactivate = array_map( 'intval', $args['post'] ); } if ( ! empty( $to_deactivate ) ) { check_admin_referer( 'bulk-posts' ); $deactivated_ids = array(); $nonce = wp_create_nonce( 'bulk-posts' ); foreach ( $to_deactivate as $id ) { $post_type = get_post_type( $id ); if ( $post_type && acf_update_internal_post_type_active_status( $id, false, $post_type ) ) { $deactivated_ids[] = $id; } } wp_safe_redirect( $this->get_admin_url( '&_wpnonce=' . $nonce . '&acfdeactivatecomplete=' . implode( ',', $deactivated_ids ) ) ); exit; } } /** * Checks for the custom "duplicate" action. * * @since 5.9.0 */ public function check_duplicate() { // Verify capability. if ( ! acf_current_user_can_admin() ) { return; } $args = acf_request_args( array( 'acfduplicatecomplete' => '', 'acfduplicate' => '', 'post' => '', 'action2' => '', ) ); // Display notice on success redirect. if ( ! empty( $args['acfduplicatecomplete'] ) ) { check_admin_referer( 'bulk-posts' ); $duplicated = array_map( 'intval', explode( ',', $args['acfduplicatecomplete'] ) ); $text = $this->get_action_notice_text( 'acfduplicatecomplete', count( $duplicated ) ); $links = array(); foreach ( $duplicated as $duplicated_id ) { $links[] = sprintf( '%2$s', esc_url( get_edit_post_link( $duplicated_id ) ), esc_html( get_the_title( $duplicated_id ) ) ); } $text .= ' ' . implode( ', ', $links ); acf_add_admin_notice( $text, 'success' ); return; } // Find items to duplicate. $to_duplicate = array(); if ( ! empty( $args['acfduplicate'] ) ) { $to_duplicate[] = intval( $args['acfduplicate'] ); } elseif ( ! empty( $args['post'] ) && 'acfduplicate' === $args['action2'] ) { $to_duplicate = array_map( 'intval', $args['post'] ); } if ( ! empty( $to_duplicate ) ) { check_admin_referer( 'bulk-posts' ); $duplicated_ids = array(); $nonce = wp_create_nonce( 'bulk-posts' ); foreach ( $to_duplicate as $id ) { $field_group = acf_duplicate_field_group( $id ); $duplicated_ids[] = $field_group['ID']; } // Redirect. wp_safe_redirect( $this->get_admin_url( '&_wpnonce=' . $nonce . '&acfduplicatecomplete=' . implode( ',', $duplicated_ids ) ) ); exit; } } /** * Checks for the custom "acfsync" action. * * @since 5.9.0 */ public function check_sync() { // Verify capability. if ( ! acf_current_user_can_admin() ) { return; } $args = acf_request_args( array( 'acfsynccomplete' => '', 'acfsync' => '', 'post' => '', 'action2' => '', ) ); // Display notice on success redirect. if ( ! empty( $args['acfsynccomplete'] ) ) { check_admin_referer( 'bulk-posts' ); $synced = array_map( 'intval', explode( ',', $args['acfsynccomplete'] ) ); $text = $this->get_action_notice_text( 'acfsynccomplete', count( $synced ) ); $links = array(); foreach ( $synced as $synced_id ) { $links[] = sprintf( '%2$s', esc_url( get_edit_post_link( $synced_id ) ), esc_html( get_the_title( $synced_id ) ) ); } $text .= ' ' . implode( ', ', $links ); // Add notice. acf_add_admin_notice( $text, 'success' ); return; } // Find items to sync. $keys = array(); if ( ! empty( $args['acfsync'] ) ) { $keys[] = sanitize_text_field( $args['acfsync'] ); } elseif ( ! empty( $args['post'] ) && 'acfsync' === $args['action2'] ) { $keys = array_map( 'sanitize_text_field', $args['post'] ); } if ( $keys && $this->sync ) { check_admin_referer( 'bulk-posts' ); // Disable "Local JSON" controller to prevent the .json file from being modified during import. acf_update_setting( 'json', false ); // Sync the items and generate array of new IDs. $files = acf_get_local_json_files( $this->post_type ); $nonce = wp_create_nonce( 'bulk-posts' ); $synced = array(); foreach ( $this->sync as $key => $post ) { if ( $post['key'] && in_array( $post['key'], $keys ) ) { // Import. } elseif ( $post['ID'] && in_array( $post['ID'], $keys ) ) { // Import. } else { // Ignore. continue; } $local_post = json_decode( file_get_contents( $files[ $key ] ), true ); $local_post['ID'] = $post['ID']; // Local JSON comes from the server, so skip sanitization. $result = acf_skip_internal_post_sanitization( 'acf_import_internal_post_type', $local_post, $this->post_type ); $synced[] = $result['ID']; } // Redirect. wp_safe_redirect( $this->get_current_admin_url( '&_wpnonce=' . $nonce . '&acfsynccomplete=' . implode( ',', $synced ) ) ); exit; } } /** * Customizes the admin table subnav. * * @date 17/4/20 * @since 5.9.0 * * @param array $views The available views. * @return array */ public function admin_table_views( $views ) { global $wp_list_table, $wp_query; // Count items. $count = count( $this->sync ); // Append "sync" link to subnav. if ( $count ) { $views['sync'] = sprintf( '%s (%s)', ( $this->view === 'sync' ? 'class="current"' : '' ), esc_url( $this->get_admin_url( '&post_status=sync' ) ), esc_html( __( 'Sync available', 'acf' ) ), $count ); } // Modify table pagination args to match JSON data. if ( $this->view === 'sync' ) { $wp_list_table->set_pagination_args( array( 'total_items' => $count, 'total_pages' => 1, 'per_page' => $count, ) ); $wp_query->post_count = 1; // At least one post is needed to render bulk drop-down. } return $views; } /** * Prints scripts into the admin footer. * * @since 5.9.0 */ public function admin_footer() { ?> get_columns(); $hidden = get_hidden_columns( $wp_list_table->screen ); ?>
sync as $k => $field_group ) { echo ''; foreach ( $columns as $column_name => $column_label ) { $el = 'td'; if ( $column_name === 'cb' ) { $el = 'th'; $classes = 'check-column'; $column_label = ''; } elseif ( $column_name === 'title' ) { $classes = "$column_name column-$column_name column-primary"; } else { $classes = "$column_name column-$column_name"; } if ( in_array( $column_name, $hidden, true ) ) { $classes .= ' hidden'; } printf( '<%s class="%s" data-colname="%s">', esc_attr( $el ), esc_attr( $classes ), esc_attr( $column_label ) ); switch ( $column_name ) { // Checkbox. case 'cb': echo ''; echo ''; break; // Title. case 'title': $post_state = ''; if ( ! $field_group['active'] ) { $post_state = ' — ' . $this->get_disabled_post_state() . ''; } echo '' . esc_html( $field_group['title'] ) . '' . acf_esc_html( $post_state ) . ''; echo '
' . esc_html( $this->get_human_readable_file_location( $field_group['local_file'] ) ) . '
'; echo ''; break; // All other columns. default: $this->render_admin_table_column( $column_name, $field_group ); break; } printf( '', esc_attr( $el ) ); } echo ''; } ?>
post_type ) { acf_trash_internal_post_type( $post_id, $this->post_type ); } } /** * Fires when untrashing an internal post type. * * @date 8/01/2014 * @since 5.0.0 * * @param integer $post_id The post ID. * @return void */ public function untrashed_post( $post_id ) { if ( get_post_type( $post_id ) === $this->post_type ) { acf_untrash_internal_post_type( $post_id, $this->post_type ); } } /** * Fires when deleting an internal post type. * * @date 8/01/2014 * @since 5.0.0 * * @param integer $post_id The post ID. * @return void */ public function deleted_post( $post_id ) { if ( get_post_type( $post_id ) === $this->post_type ) { acf_delete_internal_post_type( $post_id, $this->post_type ); } } } endif; // Class exists check.