<?php
/**
 * Admin Options Page Class
 *
 * Handles the admin interface for options pages.
 *
 * @package wordpress/secure-custom-fields
 */

// phpcs:disable PEAR.NamingConventions.ValidClassName
if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

if ( ! class_exists( 'acf_admin_options_page' ) ) :

	/**
	 * Class for managing options pages in the WordPress admin.
	 */
	class acf_admin_options_page {


		/**
		 * Current options page data.
		 *
		 * @var array Contains the current options page
		 */
		public $page;


		/**
		 * Initialize filters, action, variables and includes
		 *
		 * @since   ACF 5.0.0
		 */
		public function __construct() {
			// add menu items
			add_action( 'admin_menu', array( $this, 'admin_menu' ), 99, 0 );
		}


		/**
		 * Adds menu items for registered options pages.
		 *
		 * @since ACF 5.0.0
		 */
		public function admin_menu() {

			// vars
			$pages = acf_get_options_pages();

			// bail early if no pages
			if ( empty( $pages ) ) {
				return;
			}

			// loop
			foreach ( $pages as $page ) {

				// vars
				$slug = '';
				// parent
				if ( empty( $page['parent_slug'] ) ) {
					$slug = add_menu_page( $page['page_title'], $page['menu_title'], $page['capability'], $page['menu_slug'], array( $this, 'html' ), $page['icon_url'], $page['position'] );
					// child
				} else {
					$slug = add_submenu_page( $page['parent_slug'], $page['page_title'], $page['menu_title'], $page['capability'], $page['menu_slug'], array( $this, 'html' ), $page['position'] );
				}

				// actions
				add_action( "load-{$slug}", array( $this, 'admin_load' ) );
			}
		}


		/**
		 * Handles the load action for the options page.
		 *
		 * Enqueues scripts, validates saves, and sets up the options page.
		 *
		 * @since ACF 3.6.0
		 */
		public function admin_load() {

			// globals
			global $plugin_page;

			// vars
			$this->page = acf_get_options_page( $plugin_page );

			// get post_id (allow lang modification)
			$this->page['post_id'] = acf_get_valid_post_id( $this->page['post_id'] );

			// verify and remove nonce
			if ( acf_verify_nonce( 'options' ) ) {

				// Restrict submitted values to fields assigned to the current options page.
				// phpcs:disable WordPress.Security.NonceVerification.Missing -- Verified above.
				if ( isset( $_POST['acf'] ) && is_array( $_POST['acf'] ) ) {
					$_POST['acf'] = $this->filter_options_page_field_values( $_POST['acf'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Sanitized downstream; save pipeline expects slashed input.
				}
				// phpcs:enable WordPress.Security.NonceVerification.Missing

				// save data
				if ( acf_validate_save_post( true ) ) {

					// set autoload
					acf_update_setting( 'autoload', $this->page['autoload'] );

					// save
					acf_save_post( $this->page['post_id'] );

					/**
					 * Fires after publishing a save on an options page.
					 *
					 * @since ACF 6.1.7
					 *
					 * @param string|int  $post_id   The current id.
					 * @param string      $menu_slug The current options page menu slug.
					 */
					do_action( 'acf/options_page/save', $this->page['post_id'], $this->page['menu_slug'] );

					// redirect
					wp_safe_redirect( add_query_arg( array( 'message' => '1' ) ) );
					exit;
				}
			}

			// load acf scripts
			acf_enqueue_scripts();

			// Localize options page slug for repeater pagination capability checks.
			acf_localize_data( array( 'options_page_slug' => $this->page['menu_slug'] ) );

			// actions
			add_action( 'acf/input/admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
			add_action( 'acf/input/admin_head', array( $this, 'admin_head' ) );

			// add columns support
			add_screen_option(
				'layout_columns',
				array(
					'max'     => 2,
					'default' => 2,
				)
			);
		}


		/**
		 * This function will enqueue the 'post.js' script which adds support for 'Screen Options' column toggle
		 *
		 * @since   ACF 5.3.2
		 */
		public function admin_enqueue_scripts() {

			wp_enqueue_script( 'post' );
		}

		/**
		 * Returns the field groups assigned to the current options page.
		 *
		 * @since SCF 6.9.3
		 *
		 * @return array
		 */
		protected function get_options_page_field_groups() {
			return acf_get_field_groups(
				array(
					'options_page' => $this->page['menu_slug'],
				)
			);
		}

		/**
		 * Returns the top-level submitted field keys accepted by the current options page.
		 *
		 * Seamless clone subfields are submitted below the clone field's key, which is
		 * extracted from their input prefix.
		 *
		 * @since SCF 6.9.3
		 *
		 * @return array
		 */
		protected function get_options_page_allowed_field_keys() {
			$keys = array();

			foreach ( $this->get_options_page_field_groups() as $field_group ) {
				$fields = acf_get_fields( $field_group );

				if ( ! $fields ) {
					continue;
				}

				foreach ( $fields as $field ) {
					$prefix = $field['prefix'] ?? 'acf';

					if ( 'acf' === $prefix ) {
						if ( ! empty( $field['key'] ) ) {
							$keys[] = $field['key'];
						}
					} elseif ( is_string( $prefix ) && preg_match( '/^acf\[([^]]+)]$/', $prefix, $matches ) ) {
						$keys[] = $matches[1];
					}
				}
			}

			return array_values( array_unique( array_filter( $keys ) ) );
		}

		/**
		 * Filters submitted ACF values to the roots accepted by the current options page.
		 *
		 * @since SCF 6.9.3
		 *
		 * @param array $values Submitted ACF values.
		 * @return array
		 */
		protected function filter_options_page_field_values( array $values ): array {
			return array_intersect_key( $values, array_flip( $this->get_options_page_allowed_field_keys() ) );
		}


		/**
		 * This action will find and add field groups to the current edit page
		 *
		 * @type    action (admin_head)
		 * @since   ACF 3.1.8
		 */
		public function admin_head() {

			// get field groups
			$field_groups = $this->get_options_page_field_groups();

			// notices
			if ( ! empty( $_GET['message'] ) && '1' === $_GET['message'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Used to display a notice.
				acf_add_admin_notice( $this->page['updated_message'], 'success' );
			}

			// add submit div
			add_meta_box( 'submitdiv', __( 'Publish', 'secure-custom-fields' ), array( $this, 'postbox_submitdiv' ), 'acf_options_page', 'side', 'high' );

			if ( empty( $field_groups ) ) {
				/* translators: %s: URL to create a new field group */
				acf_add_admin_notice( sprintf( __( 'No Custom Field Groups found for this options page. <a href="%s">Create a Custom Field Group</a>', 'secure-custom-fields' ), admin_url( 'post-new.php?post_type=acf-field-group' ) ), 'warning' );
			} else {
				foreach ( $field_groups as $i => $field_group ) {

					// vars
					$id       = "acf-{$field_group['key']}";
					$context  = $field_group['position'];
					$priority = 'high';
					$args     = array( 'field_group' => $field_group );

					// tweaks to vars
					if ( 'acf_after_title' === $context ) {
						$context = 'normal';
					} elseif ( 'side' === $context ) {
						$priority = 'core';
					}

					// filter for 3rd party customization
					$priority = apply_filters( 'acf/input/meta_box_priority', $priority, $field_group );

					// add meta box
					add_meta_box(
						$id,
						acf_esc_html( acf_get_field_group_title( $field_group ) ),
						array( $this, 'postbox_acf' ),
						'acf_options_page',
						$context,
						$priority,
						$args
					);
				}
				// foreach
			}
			// if
		}


		/**
		 * This function will render the submitdiv metabox
		 *
		 * @since   ACF 5.3.2
		 */
		public function postbox_submitdiv() {

			/**
			 *  Fires before the major-publishing-actions div.
			 *
			 * @date    24/9/18
			 * @since   ACF 5.7.7
			 *
			 * @param array $page The current options page.
			 */
			do_action( 'acf/options_page/submitbox_before_major_actions', $this->page );
			?>
			<div id="major-publishing-actions">

				<div id="publishing-action">
					<span class="spinner"></span>
					<input type="submit" accesskey="p" value="<?php echo esc_attr( $this->page['update_button'] ); ?>" class="button button-primary button-large" id="publish" name="publish">
				</div>

				<?php
				/**
				 *  Fires before the major-publishing-actions div.
				 *
				 * @date    24/9/18
				 * @since   ACF 5.7.7
				 *
				 * @param array $page The current options page.
				 */
				do_action( 'acf/options_page/submitbox_major_actions', $this->page );
				?>
				<div class="clear"></div>

			</div>
			<?php
		}


		/**
		 * Renders a postbox on an ACF options page.
		 *
		 * @since   ACF 5.0.0
		 *
		 * @param object $post The post object.
		 * @param array  $args The metabox arguments.
		 */
		public function postbox_acf( $post, $args ) {
			$id          = $args['id'];
			$field_group = $args['args']['field_group'];

			// vars
			$o = array(
				'id'         => $id,
				'key'        => $field_group['key'],
				'style'      => $field_group['style'],
				'label'      => $field_group['label_placement'],
				'editLink'   => '',
				'editTitle'  => __( 'Edit field group', 'secure-custom-fields' ),
				'visibility' => true,
			);

			// edit_url
			if ( $field_group['ID'] && acf_current_user_can_admin() ) {
				$o['editLink'] = admin_url( 'post.php?post=' . $field_group['ID'] . '&action=edit' );
			}

			// load fields
			$fields = acf_get_fields( $field_group );

			// render
			acf_render_fields( $fields, $this->page['post_id'], 'div', $field_group['instruction_placement'] );

			?>
			<script type="text/javascript">
				if (typeof acf !== 'undefined') {

					acf.newPostbox(<?php echo wp_json_encode( $o ); ?>);

				}
			</script>
			<?php
		}


		/**
		 * Renders the options page HTML content.
		 *
		 * @since ACF 2.0.4
		 */
		public function html() {

			// load view
			acf_get_view( __DIR__ . '/views/html-options-page.php', $this->page );
		}
	}


	// initialize
	new acf_admin_options_page();
endif;

?>
