<?php

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

if ( ! class_exists( 'ACF_Form_Post' ) ) :

	/**
	 * Class ACF_Form_Post
	 *
	 * Handles the functionality for adding custom fields to post edit screens.
	 *
	 * @package ACF
	 * @since ACF 5.0.0
	 */
	class ACF_Form_Post {

		/**
		 * Style.
		 *
		 * @var string The first field groups style CSS.
		 */
		public $style = '';

		/**
		 * __construct
		 *
		 * Sets up the class functionality.
		 *
		 * @date    5/03/2014
		 * @since   ACF 5.0.0.0.0
		 *
		 * @return  void
		 */
		public function __construct() {

			// initialize on post edit screens
			add_action( 'load-post.php', array( $this, 'initialize' ) );
			add_action( 'load-post-new.php', array( $this, 'initialize' ) );

			// save
			add_filter( 'wp_insert_post_empty_content', array( $this, 'wp_insert_post_empty_content' ), 10, 1 );
			add_action( 'save_post', array( $this, 'save_post' ), 10, 2 );
		}


		/**
		 * Initialize.
		 *
		 * Sets up Form functionality.
		 *
		 * @date    19/9/18
		 * @since   ACF 5.7.6.7.6
		 *
		 * @return  void
		 */
		public function initialize() {

			// globals
			global $typenow;

			$acf_post_types = acf_get_internal_post_types();

			foreach ( $acf_post_types as $post_type ) {
				remove_meta_box( 'submitdiv', $post_type, 'side' );
			}

			// restrict specific post types
			$restricted = array_merge( $acf_post_types, array( 'acf-taxonomy', 'attachment' ) );
			if ( in_array( $typenow, $restricted ) ) {
				return;
			}

			// enqueue scripts
			acf_enqueue_scripts(
				array(
					'uploader' => true,
				)
			);

			// actions
			add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 10, 2 );
		}

		/**
		 * Checks if a field group is assigned to blocks.
		 *
		 * Block field groups should not be rendered as metaboxes because:
		 * 1. They are managed by the block editor (blocks v3)
		 * 2. They have their own validation system via AJAX
		 * 3. Rendering them as metaboxes causes duplicate validation on post save
		 *
		 * @since   SCF 6.6.0
		 *
		 * @param   array $field_group The field group array.
		 * @return  bool True if field group is for blocks, false otherwise.
		 */
		public function is_block_field_group( $field_group ) {
			if ( empty( $field_group['location'] ) ) {
				return false;
			}

			// Check each location group
			foreach ( $field_group['location'] as $location_group ) {
				foreach ( $location_group as $rule ) {
					if ( isset( $rule['param'] ) && 'block' === $rule['param'] ) {
						return true;
					}
				}
			}

			return false;
		}

		/**
		 *
		 * Adds ACF metaboxes for the given $post_type and $post.
		 *
		 * @date    19/9/18
		 * @since   ACF 5.7.6.7.6
		 *
		 * @param   string  $post_type The post type.
		 * @param   WP_Post $post      The post being edited.
		 * @return  void
		 */
		public function add_meta_boxes( $post_type, $post ) {

			// Storage for localized postboxes.
			$postboxes = array();

			// Get field groups for this screen.
			$field_groups = acf_get_field_groups(
				array(
					'post_id'   => $post->ID,
					'post_type' => $post_type,
				)
			);

			// Loop over field groups.
			if ( $field_groups ) {
				foreach ( $field_groups as $field_group ) {
					// Skip block field groups - they are managed by the block editor
					if ( $this->is_block_field_group( $field_group ) ) {
						continue;
					}

					$id       = esc_attr( "acf-{$field_group['key']}" );
					$context  = esc_attr( $field_group['position'] );
					$priority = 'high';

					// Reduce priority for sidebar metaboxes for best position.
					if ( 'side' === $context ) {
						$priority = 'core';
					}

					/**
					 * Filters the metabox priority.
					 *
					 * @date    23/06/12
					 * @since   ACF 3.1.8.1.8
					 *
					 * @param   string $priority The metabox priority (high, core, default, low).
					 * @param   array $field_group The field group array.
					 */
					$priority = apply_filters( 'acf/input/meta_box_priority', $priority, $field_group );

					// Localize data
					$postboxes[] = array(
						'id'    => $id,
						'key'   => esc_attr( $field_group['key'] ),
						'style' => esc_attr( $field_group['style'] ),
						'label' => esc_attr( $field_group['label_placement'] ),
						'edit'  => esc_url( acf_get_field_group_edit_link( $field_group['ID'] ) ),
					);

					// Add the meta box.
					add_meta_box(
						$id,
						acf_esc_html( acf_get_field_group_title( $field_group ) ),
						array( $this, 'render_meta_box' ),
						$post_type,
						$context,
						$priority,
						array( 'field_group' => $field_group )
					);
				}

				// Set style from first field group.
				$this->style = acf_get_field_group_style( $field_groups[0] );

				// Localize postboxes.
				acf_localize_data(
					array(
						'postboxes' => $postboxes,
					)
				);
			}

			// remove postcustom metabox (removes expensive SQL query)
			if ( acf_get_setting( 'remove_wp_meta_box' ) ) {
				remove_meta_box( 'postcustom', false, 'normal' );
			}

			// Add hidden input fields.
			add_action( 'edit_form_after_title', array( $this, 'edit_form_after_title' ) );

			/**
			* Fires after metaboxes have been added.
			*
			* @date    13/12/18
			* @since   ACF 5.8.0.8.0
			*
			* @param   string $post_type The post type.
			* @param   WP_Post $post The post being edited.
			* @param   array $field_groups The field groups added.
			*/
			do_action( 'acf/add_meta_boxes', $post_type, $post, $field_groups );
		}

		/**
		 * Called after the title and before the content editor to render the after title metaboxes.
		 * Also renders the CSS required to hide the "hide-on-screen" elements on the page based on the field group settings.
		 *
		 * @since ACF 5.7.6.7.6
		 */
		public function edit_form_after_title() {

			// globals
			global $post;

			// render post data
			acf_form_data(
				array(
					'screen'  => 'post',
					'post_id' => $post->ID,
				)
			);

			// render 'acf_after_title' metaboxes
			do_meta_boxes( get_current_screen(), 'acf_after_title', $post );

			$style = '';
			if ( is_string( $this->style ) ) {
				$style = $this->style;
			}

			// Render dynamic field group style, using wp_strip_all_tags as this is filterable, but should only contain valid styles and no html.
			echo '<style type="text/css" id="acf-style">' . wp_strip_all_tags( $style ) . '</style>'; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CSS only, escaped by wp_strip_all_tags.
		}

		/**
		 * Renders the ACF metabox HTML.
		 *
		 * @date    19/9/18
		 * @since   ACF 5.7.6.7.6
		 *
		 * @param   WP_Post $post The post being edited.
		 * @param   array   $metabox The add_meta_box() args.
		 * @return  void
		 */
		public function render_meta_box( $post, $metabox ) {

			// vars
			$id          = $metabox['id'];
			$field_group = $metabox['args']['field_group'];

			// Render fields.
			$fields = acf_get_fields( $field_group );
			acf_render_fields( $fields, $post->ID, 'div', $field_group['instruction_placement'] );
		}

		/**
		 *
		 * Allows WP to insert a new post without title or post_content if ACF data exists.
		 *
		 * @date    16/07/2014
		 * @since   ACF 5.0.1.0.1
		 *
		 * @param   boolean $maybe_empty Whether the post should be considered "empty".
		 * @return  boolean
		 */
		public function wp_insert_post_empty_content( $maybe_empty ) {

			// Return false and allow insert if '_acf_changed' exists.
			if ( $maybe_empty && acf_maybe_get_POST( '_acf_changed' ) ) {
				return false;
			}
			return $maybe_empty;
		}

		/**
		 * Checks if the $post is allowed to be saved.
		 * Used to avoid triggering "acf/save_post" on dynamically created posts during save.
		 *
		 * @type    function
		 * @date    26/06/2016
		 * @since   ACF 5.3.8.3.8
		 *
		 * @param   WP_Post $post The post to check.
		 * @return  boolean
		 */
		public function allow_save_post( $post ) {

			$allow = true;

			// Restricted post types.
			$restrict = array( 'auto-draft', 'revision', 'acf-field', 'acf-field-group' );
			if ( in_array( $post->post_type, $restrict, true ) ) {
				$allow = false;
			}

			// Disallow if the $_POST ID value does not match the $post->ID.
			$form_post_id = (int) acf_maybe_get_POST( 'post_ID' );
			if ( $form_post_id && $form_post_id !== $post->ID ) {
				$allow = false;
			}

			// Revision (preview).
			if ( 'revision' === $post->post_type ) {

				// allow if doing preview and this $post is a child of the $_POST ID
				if ( 'dopreview' === acf_maybe_get_POST( 'wp-preview' ) && $form_post_id === $post->post_parent ) {
					$allow = true;
				}
			}

			return $allow;
		}

		/**
		 * Triggers during the 'save_post' action to save the $_POST data.
		 *
		 * @since   ACF 1.0.0.0.0
		 *
		 * @param integer $post_id The post ID.
		 * @param WP_Post $post    The post object.
		 * @return void
		 */
		public function save_post( $post_id, $post ) {
			// Bail early if not allowed to save this post type.
			if ( ! $this->allow_save_post( $post ) ) {
				return;
			}

			/**
			 * Filters whether ACF_Form_Post::save_post() should bail without
			 * verifying the nonce or running acf_save_post(). Allows extensions
			 * to short-circuit the save when another handler is responsible for
			 * persisting field values for the current request.
			 *
			 * @since ACF 6.8.1
			 *
			 * @param boolean $skip    Whether to skip the save.
			 * @param integer $post_id The post ID being saved.
			 * @param WP_Post $post    The post being saved.
			 */
			if ( apply_filters( 'acf/form-post/skip_save', false, $post_id, $post ) ) {
				return $post_id;
			}

			// Verify nonce.
			if ( ! acf_verify_nonce( 'post' ) ) {
				return $post_id;
			}

			// Validate for published post (allow draft to save without validation).
			if ( 'publish' === $post->post_status ) {
				// Bail early if validation fails.
				if ( ! acf_validate_save_post() ) {
					return;
				}
			}

			acf_save_post( $post_id );

			// We handle revisions differently on WP 6.4+.
			if ( version_compare( get_bloginfo( 'version' ), '6.4', '<' ) && post_type_supports( $post->post_type, 'revisions' ) ) {
				acf_save_post_revision( $post_id );
			}
		}
	}

	acf_new_instance( 'ACF_Form_Post' );
endif;
