<?php
/**
 * @package ACF
 * @author  WP Engine
 *
 * © 2026 Advanced Custom Fields (ACF®). All rights reserved.
 * "ACF" is a trademark of WP Engine.
 * Licensed under the GNU General Public License v2 or later.
 * https://www.gnu.org/licenses/gpl-2.0.html
 */

/**
 * Gets an instance of an ACF_Internal_Post_Type.
 *
 * @param string $post_type The ACF internal post type to get the instance for.
 * @return ACF_Internal_Post_Type|boolean The internal post type class instance, or false on failure.
 */
function acf_get_internal_post_type_instance( $post_type = 'acf-field-group' ) {
	$store = acf_get_store( 'internal-post-types' );
	if ( ! $store ) {
		return false;
	}

	$instance = $store->get( $post_type );
	if ( ! $instance ) {
		return false;
	}

	return acf_get_instance( $instance );
}

/**
 * Get an ACF CPT object as an array
 *
 * @param integer $id        The post ID being queried.
 * @param string  $post_type The post type being queried.
 * @return array|false The post type object.
 */
function acf_get_internal_post_type( $id, $post_type ) {
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( ! $instance ) {
		return false;
	}

	return $instance->get_post( $id );
}

/**
 * Retrieves raw internal post type data for the given identifier.
 *
 * @since   6.1
 *
 * @param   integer|string $id        The post ID.
 * @param   string         $post_type The post type name.
 * @return  array|false The internal post type array.
 */
function acf_get_raw_internal_post_type( $id, $post_type ) {
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( ! $instance ) {
		return false;
	}

	return $instance->get_raw_post( $id );
}

/**
 * Gets a post object from an ACF internal post type.
 *
 * @since 6.1
 *
 * @param integer|string $id        The post ID, key, or name.
 * @param string         $post_type The post type name.
 * @return object|boolean The post object, or false on failure.
 */
function acf_get_internal_post_type_post( $id, $post_type ) {
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( ! $instance ) {
		return false;
	}

	return $instance->get_post_object( $id );
}

/**
 * Returns true if the given identifier is a ACF internal post type key.
 *
 * @since 6.1
 *
 * @param string $id        The identifier.
 * @param string $post_type The ACF post type the key is for.
 * @return boolean
 */
function acf_is_internal_post_type_key( $id = '', $post_type = 'acf-field-group' ) {
	$instance = acf_get_internal_post_type_instance( $post_type );
	if ( ! $instance ) {
		return false;
	}

	return $instance->is_post_key( $id );
}

/**
 * Validates an ACF internal post type.
 *
 * @since 6.1
 *
 * @param array  $internal_post_type The internal post type array.
 * @param string $post_type_name     The post type name.
 * @return array|boolean
 */
function acf_validate_internal_post_type( $internal_post_type, $post_type_name ) {
	$instance = acf_get_internal_post_type_instance( $post_type_name );

	if ( ! $instance ) {
		return false; // TODO: Should this return an empty array instead?
	}

	return $instance->validate_post( $internal_post_type );
}

/**
 * Translates the settings for an ACF internal post type.
 *
 * @since 6.1
 *
 * @param array  $internal_post_type The ACF post array.
 * @param string $post_type          The post type name.
 * @return array
 */
function acf_translate_internal_post_type( $internal_post_type, $post_type ) {
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( ! $instance ) {
		return $internal_post_type;
	}

	return $instance->translate_post( $internal_post_type );
}

/**
 * Returns and array of ACF posts for the given $filter.
 *
 * @since 6.1
 *
 * @param string $post_type The ACF post type to get posts for.
 * @param array  $filter    An array of args to filter results by.
 * @return array
 */
function acf_get_internal_post_type_posts( $post_type = 'acf-field-group', $filter = array() ) {
	$posts    = array();
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( $instance ) {
		$posts = $instance->get_posts( $filter );
	}

	return $posts;
}

/**
 * Returns an array of raw/unvalidated ACF post data.
 *
 * @since 6.1
 *
 * @param string $post_type The ACF post type to get post data for.
 * @return array
 */
function acf_get_raw_internal_post_type_posts( $post_type ) {
	$raw_posts = array();
	$instance  = acf_get_internal_post_type_instance( $post_type );

	if ( $instance ) {
		$raw_posts = $instance->get_raw_posts();
	}

	return $raw_posts;
}

/**
 * Returns a filtered array of ACF posts based on the given $args.
 *
 * @since 6.1
 *
 * @param array  $posts     An array of ACF posts.
 * @param array  $args      An array of args to filter by.
 * @param string $post_type The ACF post type of the posts being filtered.
 * @return array
 */
function acf_filter_internal_post_type_posts( $posts, $args = array(), $post_type = 'acf-field-group' ) {
	$filtered = array();
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( $instance ) {
		$filtered = $instance->filter_posts( $posts, $args );
	}

	return $filtered;
}

/**
 * Updates a internal post type in the database.
 *
 * @since   6.1
 *
 * @param array  $internal_post_type Array of data to be saved.
 * @param string $post_type_name     The internal post type being updated.
 * @return array
 */
function acf_update_internal_post_type( $internal_post_type, $post_type_name ) {
	$instance = acf_get_internal_post_type_instance( $post_type_name );

	if ( $instance ) {
		$internal_post_type = $instance->update_post( $internal_post_type );
	}

	return $internal_post_type;
}

/**
 * Deletes all caches for the provided ACF post.
 *
 * @since 6.1
 *
 * @param array  $post      The ACF post array.
 * @param string $post_type The ACF post type the cache is being cleared for.
 * @return void
 */
function acf_flush_internal_post_type_cache( $post, $post_type ) {
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( $instance ) {
		$instance->flush_post_cache( $post );
	}
}

/**
 * Deletes an internal post type from the database.
 *
 * @since 6.1
 *
 * @param integer|string $id             The internal post type ID, key or name.
 * @param string         $post_type_name The post type to be deleted.
 * @return boolean True if field group was deleted.
 */
function acf_delete_internal_post_type( $id = 0, $post_type_name = '' ) {
	$instance = acf_get_internal_post_type_instance( $post_type_name );

	if ( $instance ) {
		return $instance->delete_post( $id );
	}

	return false;
}

/**
 * Trashes an internal post type.
 *
 * @since 6.1
 *
 * @param integer|string $id             The internal post type ID, key, or name.
 * @param string         $post_type_name The post type being trashed.
 * @return boolean True if post was trashed.
 */
function acf_trash_internal_post_type( $id = 0, $post_type_name = '' ) {
	$instance = acf_get_internal_post_type_instance( $post_type_name );

	if ( $instance ) {
		return $instance->trash_post( $id );
	}

	return false;
}

/**
 * Restores an ACF post from the trash.
 *
 * @since 6.1
 *
 * @param integer|string $id             The internal post type ID, key, or name.
 * @param string         $post_type_name The post type being untrashed.
 * @return boolean True if post was untrashed.
 */
function acf_untrash_internal_post_type( $id = 0, $post_type_name = '' ) {
	$instance = acf_get_internal_post_type_instance( $post_type_name );

	if ( $instance ) {
		return $instance->untrash_post( $id );
	}

	return false;
}

/**
 * Returns true if the given params match an ACF post.
 *
 * @since 6.1
 *
 * @param array  $post      The ACF post array.
 * @param string $post_type The ACF post type.
 * @return boolean
 */
function acf_is_internal_post_type( $post, $post_type ) {
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( $instance ) {
		return $instance->is_post( $post );
	}

	return false;
}

/**
 * Duplicates an ACF post.
 *
 * @since 6.1
 *
 * @param integer|string $id          The field_group ID, key or name.
 * @param integer        $new_post_id Optional ID to override.
 * @param string         $post_type   The post type of the post being duplicated.
 * @return array|boolean The new ACF post, or false on failure.
 */
function acf_duplicate_internal_post_type( $id = 0, $new_post_id = 0, $post_type = 'acf-field-group' ) {
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( $instance ) {
		return $instance->duplicate_post( $id, $new_post_id );
	}

	return false;
}

/**
 * Activates or deactivates an ACF post.
 *
 * @param integer|string $id        The field_group ID, key or name.
 * @param boolean        $activate  True if the post should be activated.
 * @param string         $post_type The post type being activated/deactivated.
 * @return boolean
 */
function acf_update_internal_post_type_active_status( $id, $activate = true, $post_type = 'acf-field-group' ) {
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( $instance ) {
		return $instance->update_post_active_status( $id, $activate );
	}

	return false;
}

/**
 * Checks if the current user can edit the field group and returns the edit url.
 *
 * @since 6.1
 *
 * @param integer $post_id   The ACF post ID.
 * @param string  $post_type The ACF post type to get the edit link for.
 * @return string
 */
function acf_get_internal_post_type_edit_link( $post_id, $post_type ) {
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( $instance ) {
		return $instance->get_post_edit_link( $post_id );
	}

	return '';
}

/**
 * Returns a modified field group ready for export.
 *
 * @since 6.1
 *
 * @param array  $post      The ACF post array.
 * @param string $post_type The post type of the ACF post being exported.
 * @return array
 */
function acf_prepare_internal_post_type_for_export( $post = array(), $post_type = 'acf-field-group' ) {
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( $instance ) {
		$post = $instance->prepare_post_for_export( $post );
	}

	return $post;
}

/**
 * Exports an ACF post as PHP.
 *
 * @since 6.1
 *
 * @param array  $post      The ACF post array.
 * @param string $post_type The post type of the ACF post being exported.
 * @return string|boolean
 */
function acf_export_internal_post_type_as_php( $post, $post_type = 'acf-field-group' ) {
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( $instance ) {
		return $instance->export_post_as_php( $post );
	}

	return false;
}

/**
 * Prepares an ACF post for the import process.
 *
 * @since 6.1
 *
 * @param array  $post      The ACF post array.
 * @param string $post_type The post type of the ACF post being imported.
 * @return  array
 */
function acf_prepare_internal_post_type_for_import( $post = array(), $post_type = 'acf-field-group' ) {
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( $instance ) {
		$post = $instance->prepare_post_for_import( $post );
	}

	return $post;
}

/**
 * Imports an ACF post into the database.
 *
 * @since 6.1
 *
 * @param array  $post      The ACF post array.
 * @param string $post_type The post type of the ACF post being imported.
 * @return array The imported post.
 */
function acf_import_internal_post_type( $post, $post_type ) {
	$instance = acf_get_internal_post_type_instance( $post_type );

	if ( $instance ) {
		$post = $instance->import_post( $post );
	}

	return $post;
}

/**
 * Tries to determine the ACF post type for the provided key.
 *
 * @param string $key The key to check.
 * @return string|boolean
 */
function acf_determine_internal_post_type( $key ) {
	$store      = acf_get_store( 'internal-post-types' );
	$post_types = array();

	if ( $store ) {
		$post_types = $store->get();
		$post_types = array_keys( $post_types );
	}

	foreach ( $post_types as $post_type ) {
		if ( acf_is_internal_post_type_key( $key, $post_type ) ) {
			return $post_type;
		}
	}

	return false;
}

/**
 * Check if the provided key is an identifiable ACF post type.
 *
 * @since 6.2.8
 *
 * @param string $key The key to check.
 * @return boolean
 */
function acf_is_valid_internal_post_type_key( string $key ) {
	return (bool) acf_determine_internal_post_type( $key );
}

/**
 * Check if the provided post type object contains a valid internal post type key.
 *
 * @since 6.2.8
 *
 * @param array $internal_post_type The post type object array to check it's key.
 * @return boolean
 */
function acf_internal_post_object_contains_valid_key( array $internal_post_type ) {
	if ( ! is_array( $internal_post_type ) || empty( $internal_post_type['key'] ) || ! is_string( $internal_post_type['key'] ) ) {
		return false;
	}
	return acf_is_valid_internal_post_type_key( $internal_post_type['key'] );
}

/**
 * Returns an array of tabs for the post type advanced settings.
 *
 * @since 6.1
 *
 * @return array
 */
function acf_get_combined_post_type_settings_tabs() {
	$default_post_type_tabs = array(
		'general'     => __( 'General', 'acf' ),
		'labels'      => __( 'Labels', 'acf' ),
		'visibility'  => __( 'Visibility', 'acf' ),
		'urls'        => __( 'URLs', 'acf' ),
		'permissions' => __( 'Permissions', 'acf' ),
		'rest_api'    => __( 'REST API', 'acf' ),
	);

	$additional_post_type_tabs = (array) apply_filters( 'acf/post_type/additional_settings_tabs', array() );

	// Remove any default tab values from the filtered tabs.
	foreach ( $additional_post_type_tabs as $key => $tab ) {
		if ( isset( $default_post_type_tabs[ $key ] ) ) {
			unset( $additional_post_type_tabs[ $key ] );
		}
	}

	return array_merge( $default_post_type_tabs, $additional_post_type_tabs );
}

/**
 * Returns an array of tabs for the taxonomy advanced settings.
 *
 * @since 6.1
 *
 * @return array
 */
function acf_get_combined_taxonomy_settings_tabs() {
	$default_taxonomy_tabs = array(
		'general'     => __( 'General', 'acf' ),
		'labels'      => __( 'Labels', 'acf' ),
		'visibility'  => __( 'Visibility', 'acf' ),
		'urls'        => __( 'URLs', 'acf' ),
		'permissions' => __( 'Permissions', 'acf' ),
		'rest_api'    => __( 'REST API', 'acf' ),
	);

	$additional_taxonomy_tabs = (array) apply_filters( 'acf/taxonomy/additional_settings_tabs', array() );

	// Remove any default tab values from the filtered tabs.
	foreach ( $additional_taxonomy_tabs as $key => $tab ) {
		if ( isset( $default_taxonomy_tabs[ $key ] ) ) {
			unset( $additional_taxonomy_tabs[ $key ] );
		}
	}

	return array_merge( $default_taxonomy_tabs, $additional_taxonomy_tabs );
}

/**
 * Returns an array of tabs for the options page advanced settings
 *
 * @since 6.2
 *
 * @return array
 */
function acf_get_combined_options_page_settings_tabs() {
	$default_options_page_tabs = array(
		'visibility'  => __( 'Visibility', 'acf' ),
		'labels'      => __( 'Labels', 'acf' ),
		'permissions' => __( 'Permissions', 'acf' ),
	);

	$additional_options_page_tabs = (array) apply_filters( 'acf/ui_options_page/additional_settings_tabs', array() );

	// Remove any default tab values from the filtered tabs.
	foreach ( $additional_options_page_tabs as $key => $tab ) {
		if ( isset( $default_options_page_tabs[ $key ] ) ) {
			unset( $additional_options_page_tabs[ $key ] );
		}
	}

	return array_merge( $default_options_page_tabs, $additional_options_page_tabs );
}

/**
 * Converts an _acf_screen or hook value into a post type.
 *
 * @since 6.1
 *
 * @param string $screen The ACF screen being viewed.
 * @return string The post type matching the screen or hook value.
 */
function acf_get_post_type_from_screen_value( $screen ) {
	switch ( $screen ) {
		case 'post_type':
			return 'acf-post-type';
		case 'taxonomy':
			return 'acf-taxonomy';
		case 'field_group':
			return 'acf-field-group';
		case 'ui_options_page':
			return 'acf-ui-options-page';
		default:
			return false;
	}
}

/**
 * Calls the ajax validator for a post type
 *
 * @since 6.1
 *
 * @param string $post_type The post type being validated.
 * @return mixed
 */
function acf_validate_internal_post_type_values( $post_type ) {
	if ( $post_type ) {
		$instance = acf_get_internal_post_type_instance( $post_type );
		return $instance->ajax_validate_values();
	}
	return false;
}

/**
 * Adds a validation error for ACF internal post types.
 *
 * @since 6.1
 *
 * @param string $name      The name of the input.
 * @param string $message   An optional error message to display.
 * @param string $post_type Optional post type the error message is for.
 * @return void
 */
function acf_add_internal_post_type_validation_error( $name, $message = '', $post_type = '' ) {
	if ( empty( $post_type ) ) {
		$screen    = isset( $_POST['_acf_screen'] ) ? (string) $_POST['_acf_screen'] : 'post_type'; // phpcs:ignore WordPress.Security -- Nonce verified upstream, value only used for comparison.
		$post_type = acf_get_post_type_from_screen_value( $screen );

		if ( ! $post_type ) {
			return;
		}
	}

	$input_prefix = str_replace( '-', '_', $post_type );

	if ( substr( $name, 0, strlen( $input_prefix ) ) !== $input_prefix ) {
		$name = "{$input_prefix}[$name]";
	}

	return acf_add_validation_error( $name, $message );
}

/**
 * Gets an ACF post type from request args and verifies nonce based on action.
 *
 * @since 6.1.5
 *
 * @param string $action The action being performed.
 * @return array|boolean
 */
function acf_get_post_type_from_request_args( $action = '' ) {
	$acf_use_post_type = (int) acf_request_arg( 'use_post_type', 0 );

	if ( ! $acf_use_post_type || ! wp_verify_nonce( acf_request_arg( '_wpnonce' ), $action . '-' . $acf_use_post_type ) ) {
		return false;
	}

	return acf_get_internal_post_type( $acf_use_post_type, 'acf-post-type' );
}

/**
 * Gets an ACF taxonomy from request args and verifies nonce based on action.
 *
 * @since 6.1.5
 *
 * @param string $action The action being performed.
 * @return array|boolean
 */
function acf_get_taxonomy_from_request_args( $action = '' ) {
	$acf_use_taxonomy = (int) acf_request_arg( 'use_taxonomy', 0 );

	if ( ! $acf_use_taxonomy || ! wp_verify_nonce( acf_request_arg( '_wpnonce' ), $action . '-' . $acf_use_taxonomy ) ) {
		return false;
	}

	return acf_get_internal_post_type( $acf_use_taxonomy, 'acf-taxonomy' );
}

/**
 * Gets an ACF options page from request args and verifies nonce based on action.
 *
 * @since 6.2
 *
 * @param string $action The action being performed.
 * @return array|boolean
 */
function acf_get_ui_options_page_from_request_args( $action = '' ) {
	$acf_use_options_page = (int) acf_request_arg( 'use_options_page', 0 );

	if ( ! $acf_use_options_page || ! wp_verify_nonce( acf_request_arg( '_wpnonce' ), $action . '-' . $acf_use_options_page ) ) {
		return false;
	}

	return acf_get_internal_post_type( $acf_use_options_page, 'acf-ui-options-page' );
}
