Source: my-calendar-submit.php

<?php
/**
 * Main submissions form
 *
 * @category Submissions
 * @package  My Calendar Pro
 * @author   Joe Dolson
 * @license  GPLv2 or later
 * @link     https://www.joedolson.com/my-calendar-pro/
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

add_shortcode( 'submit_event', 'mcs_submit_form' );
/**
 * Create the submissions form via shortcode
 *
 * @param array  $atts Shortcode attributes.
 * @param string $content Wrapped shortcode content.
 *
 * @return submit form
 */
function mcs_submit_form( $atts, $content = '' ) {
	$args = shortcode_atts(
		array(
			'fields'          => 'submitter,event_title,event_date',
			'categories'      => '1',
			'locations'       => 'either',
			'category'        => '1', // default category value.
			'location'        => '0', // default location value.
			'location_fields' => 'event_label,street,street2,phone,city,state,postcode,country,url',
			'form_id'         => '',
			'is_widget'       => '0',
		),
		$atts,
		'submit_event'
	);
	// Convert this shortcode into a form ID.
	$args      = mcs_convert_args( $args );
	$form_id   = $args['form_id'];
	$forms     = get_option( 'mcs_forms', array() );
	$loc       = false;
	$cat_field = array();
	if ( '' !== $form_id && isset( $forms[ $form_id ] ) ) {
		$settings   = $forms[ $form_id ];
		$fld        = $settings['fields'];
		$categories = isset( $settings['categories'] ) ? $settings['categories'] : 1;
		$locations  = isset( $settings['locations'] ) ? $settings['locations'] : 'either';
		$category   = isset( $settings['category'] ) ? $settings['category'] : '1';
		$location   = isset( $settings['location'] ) ? $settings['location'] : '0';
		$loc        = isset( $settings['location_fields'] ) ? $settings['location_fields'] : false;
		$cat_field  = isset( $settings['category_field'] ) ? $settings['category_field'] : $cat_field;
	} else {
		$categories = $args['categories'];
		$locations  = $args['locations'];
		$category   = $args['category'];
		$location   = $args['location'];
		$fld        = mcs_create_field_array( $args['fields'] );
	}
	$is_widget = isset( $args['is_widget'] ) ? 1 : 0;

	if ( ! $loc && isset( $args['location_fields'] ) ) {
		$loc = mcs_create_field_array( $args['location_fields'] );
	}

	if ( mcs_user_can_submit_events() ) {
		$args = array(
			'fields'          => $fld,
			'categories'      => $categories,
			'locations'       => $locations,
			'category'        => $category,
			'location'        => $location,
			'location_fields' => $loc,
			'form_id'         => $form_id,
			'category_field'  => $cat_field,
			'is_widget'       => $is_widget,
		);
		return mcs_build_submit_form( $args );
	} else {
		return ( '' === $content ) ? mcs_submission_not_available() : $content;
	}
}

/**
 * Convert old shortcode arguments into a form ID.
 *
 * @param array $args Array of arguments used to build forms.
 *
 * @return array
 */
function mcs_convert_args( $args ) {
	if ( isset( $args['form_id'] ) && '' !== $args['form_id'] ) {
		$form_id = $args['form_id'];
	} else {
		$form_id = md5( implode( ',', $args ) );
	}
	$is_widget = isset( $args['is_widget'] ) && '0' !== $args['is_widget'] ? true : false;
	$forms     = get_option( 'mcs_forms' );
	if ( isset( $forms[ $form_id ] ) ) {
		$return = array(
			'form_id' => $form_id,
		);
		if ( $is_widget ) {
			$return['is_widget'] = 1;
		}
	} else {
		$form_fields     = isset( $args['fields'] ) ? $args['fields'] : array();
		$location_fields = isset( $args['location_fields'] ) ? $args['location_fields'] : array();
		$category_field  = array( 'entry_type' => 'single' );

		$return = array(
			'fields'          => mcs_create_field_array( $form_fields ),
			'category'        => isset( $args['category'] ) ? $args['category'] : '',
			'categories'      => isset( $args['categories'] ) ? $args['categories'] : '',
			'category_field'  => $category_field,
			'locations'       => isset( $args['locations'] ) ? $args['locations'] : '',
			'location'        => isset( $args['location'] ) ? $args['location'] : '',
			'location_fields' => mcs_create_field_array( $location_fields ),
			'form_id'         => $form_id,
		);
		if ( $is_widget ) {
			$return['is_widget'] = 1;
		}
		$forms[ $form_id ] = $return;
		update_option( 'mcs_forms', $forms );
	}

	return $return;
}

/**
 * Verify data from hidden input fields, that shouldn't be changed. Checks that the hidden fields present at form render match the values submitted.
 *
 * @param array  $post Array of data sent from form.
 * @param string $form_id ID of current form.
 *
 * @return bool
 */
function mcs_verify_hidden_inputs( $post, $form_id ) {
	$fields = mcs_hidden_input_fields( $form_id );
	$test   = array();
	foreach ( $fields as $key => $value ) {
		$test[ $key ] = ( isset( $post[ $key ] ) ) ? (string) $post[ $key ] : '';
		if ( ! isset( $post[ $key ] ) || '' === $post[ $key ] ) {
			unset( $test[ $key ] );
		}
	}
	// The timestamp will always change between initial state and submission, so it's not a useful test here.
	unset( $test['mcs_form_timestamp'] );
	$cookie = ( isset( $_COOKIE['mcs_unique_id'] ) ) ? sanitize_text_field( wp_unslash( $_COOKIE['mcs_unique_id'] ) ) : false;
	$fields = ( $cookie ) ? get_transient( 'mcs_hidden_fields_' . $cookie ) : false;
	// If the transient doesn't exist, that's a DB problem, not a user problem. Don't block submissions if the transient isn't available.
	if ( $test === $fields || ! $fields ) {
		return true;
	}

	return false;
}

add_action( 'init', 'mcs_run_processor' );
/**
 * Process submission before page executes.
 */
function mcs_run_processor() {
	if ( ! is_admin() && function_exists( 'mc_kses_post' ) ) {
		// Nonce verification happens in `mcs_processor()`.
		$post      = map_deep( wp_unslash( $_POST ), 'mc_kses_post' );
		$response  = mcs_processor( $post );
		$unique_id = isset( $_COOKIE['mcs_unique_id'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['mcs_unique_id'] ) ) : false;
		if ( false !== $response ) {
			set_transient( 'mcs_' . $unique_id, $response, 10 );
			/**
			 * The form submission has been processed and transient storage created.
			 *
			 * @hook mcs_run_processor_completed
			 *
			 * @param {string} $unique_id Unique string tracking this submission.
			 * @param {array}  $response Server response after submission.
			 */
			do_action( 'mcs_run_processor_completed', $unique_id, $response );
		} else {
			delete_transient( 'mcs_' . $unique_id );
			delete_transient( 'mcs_hidden_fields_' . $unique_id );
		}
	}
}

/**
 * Get stored response from last request
 */
function mcs_processor_response() {
	$unique_id = isset( $_COOKIE['mcs_unique_id'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['mcs_unique_id'] ) ) : false;
	$response  = get_transient( 'mcs_' . $unique_id );

	if ( '' !== $response ) {
		return $response;
	}
}

/**
 * Set up arguments and JS to configure Duet date picker.
 */
function mcs_setup_duetjs() {
	/**
	 * Disable the datepicker. Falls back to a standard browser date input.
	 *
	 * @hook mcs_datepicker_enabled
	 *
	 * @param {bool} $enabled True if enabled, false to disable.
	 *
	 * @return {bool}
	 */
	if ( false === apply_filters( 'mcs_datepicker_enabled', true ) ) {
		return;
	}
	global $mcs_version;
	if ( SCRIPT_DEBUG && true === SCRIPT_DEBUG ) {
		$mcs_version = $mcs_version . uniqid();
	}
	wp_enqueue_script( 'duet.js' );
	wp_enqueue_style( 'duet.css' );
	wp_enqueue_script( 'mcs.duet' );
	wp_localize_script(
		'mcs.duet',
		'duetFormats',
		array(
			'date' => get_option( 'mcs_date_format' ),
		)
	);
	wp_localize_script(
		'mcs.duet',
		'duetLocalization',
		array(
			'buttonLabel'         => __( 'Choose date', 'my-calendar-pro' ),
			'placeholder'         => mcs_parse_date_format(),
			'selectedDateMessage' => __( 'Selected date is', 'my-calendar-pro' ),
			'prevMonthLabel'      => __( 'Previous month', 'my-calendar-pro' ),
			'nextMonthLabel'      => __( 'Next month', 'my-calendar-pro' ),
			'monthSelectLabel'    => __( 'Month', 'my-calendar-pro' ),
			'yearSelectLabel'     => __( 'Year', 'my-calendar-pro' ),
			'closeLabel'          => __( 'Close window', 'my-calendar-pro' ),
			'keyboardInstruction' => __( 'You can use arrow keys to navigate dates', 'my-calendar-pro' ),
			'calendarHeading'     => __( 'Choose a date', 'my-calendar-pro' ),
			'dayNames'            => array(
				date_i18n( 'D', strtotime( 'Sunday' ) ),
				date_i18n( 'D', strtotime( 'Monday' ) ),
				date_i18n( 'D', strtotime( 'Tuesday' ) ),
				date_i18n( 'D', strtotime( 'Wednesday' ) ),
				date_i18n( 'D', strtotime( 'Thursday' ) ),
				date_i18n( 'D', strtotime( 'Friday' ) ),
				date_i18n( 'D', strtotime( 'Saturday' ) ),
			),
			'monthNames'          => array(
				date_i18n( 'F', strtotime( 'January 1' ) ),
				date_i18n( 'F', strtotime( 'February 1' ) ),
				date_i18n( 'F', strtotime( 'March 1' ) ),
				date_i18n( 'F', strtotime( 'April 1' ) ),
				date_i18n( 'F', strtotime( 'May 1' ) ),
				date_i18n( 'F', strtotime( 'June 1' ) ),
				date_i18n( 'F', strtotime( 'July 1' ) ),
				date_i18n( 'F', strtotime( 'August 1' ) ),
				date_i18n( 'F', strtotime( 'September 1' ) ),
				date_i18n( 'F', strtotime( 'October 1' ) ),
				date_i18n( 'F', strtotime( 'November 1' ) ),
				date_i18n( 'F', strtotime( 'December 1' ) ),
			),
			'monthNamesShort'     => array(
				date_i18n( 'M', strtotime( 'January 1' ) ),
				date_i18n( 'M', strtotime( 'February 1' ) ),
				date_i18n( 'M', strtotime( 'March 1' ) ),
				date_i18n( 'M', strtotime( 'April 1' ) ),
				date_i18n( 'M', strtotime( 'May 1' ) ),
				date_i18n( 'M', strtotime( 'June 1' ) ),
				date_i18n( 'M', strtotime( 'July 1' ) ),
				date_i18n( 'M', strtotime( 'August 1' ) ),
				date_i18n( 'M', strtotime( 'September 1' ) ),
				date_i18n( 'M', strtotime( 'October 1' ) ),
				date_i18n( 'M', strtotime( 'November 1' ) ),
				date_i18n( 'M', strtotime( 'December 1' ) ),
			),
			'locale'              => str_replace( '_', '-', get_locale() ),
		)
	);
}

/**
 * Search location titles.
 *
 * @param string $query Location query.
 *
 * @return array locations
 */
function mcs_search_locations( $query = '' ) {
	global $wpdb;
	$search  = '';
	$db_type = mc_get_db_type();
	$query   = esc_sql( $query );

	if ( '' !== $query ) {
		// Fulltext is supported in InnoDB since MySQL 5.6; minimum required by WP is 5.0 as of WP 5.5.
		// 37% of installs still below 5.6 as of 11/30/2020.
		// InnoDB requires a fulltext index to be added for support.
		/**
		 * Filter fields used to search locations when using MATCH queries on MyISAM dbs.
		 *
		 * @hook mcs_location_search_fields
		 *
		 * @param {string} $fields Comma separated list of database columns. Default `location_label`.
		 * @param {string} $query Search query.
		 *
		 * @return {string}
		 */
		$search_fields = apply_filters( 'mcs_location_search_fields', 'location_label', $query );
		if ( 'MyISAM' === $db_type ) {
			$search = ' WHERE MATCH(' . $search_fields . ") AGAINST ( '$query' IN BOOLEAN MODE ) ";
		} else {
			$search = " WHERE location_label LIKE '%$query%' ";
		}
	} else {
		$search = '';
	}

	$locations = $wpdb->get_results( 'SELECT location_id, location_label FROM ' . my_calendar_locations_table() . " $search ORDER BY location_label ASC" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared

	return $locations;
}

/**
 * Get information about location.
 */
function mcs_autocomplete_search_locations() {
	if ( isset( $_REQUEST['action'] ) && 'mcs_autocomplete_search_locations' === $_REQUEST['action'] ) {
		$security = $_REQUEST['security'];
		if ( ! wp_verify_nonce( $security, 'mc-search-locations' ) ) {
			wp_send_json(
				array(
					'success'  => 0,
					'response' => array( 'error' => 'Invalid security value.' ),
				)
			);
		}
		$query = $_REQUEST['data'];

		$locations = mcs_search_locations( $query );
		$response  = array();
		foreach ( $locations as $location ) {
			$response[] = array(
				'location_id'    => $location->location_id,
				'location_label' => html_entity_decode( wp_strip_all_tags( $location->location_label ) ),
			);
		}
		wp_send_json(
			array(
				'success'  => 1,
				'response' => $response,
			)
		);
	}
}
add_action( 'wp_ajax_mcs_autocomplete_search_locations', 'mcs_autocomplete_search_locations' );
add_action( 'wp_ajax_nopriv_mcs_autocomplete_search_locations', 'mcs_autocomplete_search_locations' );

/**
 * Register scripts and styles.
 */
function mcs_register_scripts() {
	global $mcs_version;
	$submit_url    = plugins_url( 'js/jquery.mcs-submit.min.js', __FILE__ );
	$datepicker_js = plugins_url( 'js/mcs-datepicker.min.js', __FILE__ );
	if ( SCRIPT_DEBUG && true === SCRIPT_DEBUG ) {
		$mcs_version   = $mcs_version . uniqid();
		$submit_url    = plugins_url( 'js/jquery.mcs-submit.js', __FILE__ );
		$datepicker_js = plugins_url( 'js/mcs-datepicker.js', __FILE__ );
	}
	wp_register_script( 'mcs.submit', $submit_url, array( 'jquery' ), $mcs_version, true );
	wp_register_script( 'mcs.charcount', plugins_url( '/js/jquery.charcount.js', __FILE__ ), array( 'jquery' ), $mcs_version, true );
	if ( ! is_admin() ) {
		// My Calendar Pro needs this on the front-end; My Calendar already has it in the admin.
		if ( SCRIPT_DEBUG ) {
			wp_register_script( 'accessible-autocomplete', plugins_url( '/js/accessible-autocomplete.js', __FILE__ ), array(), $mcs_version );
		} else {
			wp_register_script( 'accessible-autocomplete', plugins_url( '/js/accessible-autocomplete.min.js', __FILE__ ), array(), $mcs_version );
		}
	}
	wp_register_script( 'mcs-autocomplete', plugins_url( '/js/locations-autocomplete.js', __FILE__ ), array( 'accessible-autocomplete' ), $mcs_version, true );
	wp_register_script( 'duet.js', plugins_url( 'js/duet/duet.js', __FILE__ ), array(), $mcs_version, true );
	wp_register_style( 'duet.css', plugins_url( 'js/duet/themes/default.css', __FILE__ ), array(), $mcs_version );
	wp_register_script( 'mcs.duet', $datepicker_js, array( 'duet.js' ), $mcs_version );
}
add_action( 'wp_enqueue_scripts', 'mcs_register_scripts' );
add_action( 'admin_enqueue_scripts', 'mcs_register_scripts' );

/**
 * Build the submission form from an array of data specifying what should be in it.
 *
 * @param array $args {
 *     Array of data used to build submit form.
 *
 *     @type array  $fields Fields to include in the form.
 *     @type int    $categories Included or not included.
 *     @type int    $locations Included or not included.
 *     @type int    $category Default category.
 *     @type int    $location Default location.
 *     @type array  $location_fields Array of location fields to include.
 *     @type string $form_id Unique ID for this form.
 *     @type bool   $is_widget Whether this form is generated from a widget.
 * }
 *
 * @return string
 */
function mcs_build_submit_form( $args ) {
	$form_id   = ( isset( $args['form_id'] ) && '' !== $args['form_id'] ) ? $args['form_id'] : md5( get_the_permalink() . implode( '-', $args['fields'] ) );
	$is_widget = ( isset( $args['is_widget'] ) ) ? $args['is_widget'] : false;

	global $mcs_version;
	if ( SCRIPT_DEBUG && true === SCRIPT_DEBUG ) {
		$mcs_version = $mcs_version . '-' . uniqid();
	}
	$args['form_id']   = $form_id;
	$args['is_widget'] = $is_widget;
	/**
	 * Filter the event fields enabled for a form.
	 *
	 * @hook mcs_submit_fields
	 *
	 * @param {array}  $fields Array of fields as defined by shortcode or ID.
	 * @param {string} $form_id Form ID.
	 *
	 * @return {array}
	 */
	$args['fields'] = apply_filters( 'mcs_submit_fields', $args['fields'], $form_id );
	/**
	 * Filter the location fields enabled for a form.
	 *
	 * @hook mcs_submit_location_fields
	 *
	 * @param {array}  $location_fields Array of fields as defined by shortcode or ID.
	 * @param {string} $form_id Form ID.
	 *
	 * @return {array}
	 */
	$args['location_fields'] = apply_filters( 'mcs_submit_location_fields', $args['location_fields'], $form_id );

	wp_enqueue_script( 'mcs.submit' );
	wp_localize_script(
		'mcs.submit',
		'mcssubmit',
		array(
			'ajaxurl'  => admin_url( 'admin-ajax.php' ),
			'security' => wp_create_nonce( 'mc-query-purchase' ),
			'action'   => 'mcs_purchase_request',
		)
	);
	wp_enqueue_script( 'mcs.charcount' );
	wp_localize_script(
		'mcs.charcount',
		'mcsCounter',
		array(
			'text' => __( 'Words left: ', 'my-calendar-pro' ),
		)
	);
	if ( function_exists( 'mc_count_locations' ) ) {
		$count = mc_count_locations();
		/**
		 * Set number of locations where front-end submissions switches from a select to an autocomplete.
		 *
		 * @hook mcs_convert_locations_select_to_autocomplete
		 *
		 * @param {int} $count Number of locations. Default 90.
		 * @param {string} $form_id ID for the current form.
		 *
		 * @return {int}
		 */
		if ( $count > apply_filters( 'mcs_convert_locations_select_to_autocomplete', 90, $form_id ) ) {
			wp_enqueue_script( 'mcs-autocomplete' );
			wp_localize_script(
				'mcs-autocomplete',
				'mcslocations',
				array(
					'ajaxurl'  => admin_url( 'admin-ajax.php' ),
					'security' => wp_create_nonce( 'mc-search-locations' ),
					'action'   => 'mcs_autocomplete_search_locations',
				)
			);
		}
	}

	mcs_setup_duetjs();
	$message = '';

	$response = mcs_processor_response();
	$event    = false;
	$mc_id    = false;
	if ( empty( $response[1] ) && isset( $_GET['mcs_id'] ) && is_user_logged_in() ) {
		$mc_id = intval( $_GET['mcs_id'] );
		$event = mc_form_data( $mc_id );
		if ( ! mc_can_edit_event( absint( $event->event_id ) ) ) {
			$event = false;
		}
	}

	if ( isset( $_GET['mcs_id'] ) && ! is_user_logged_in() ) {
		$message = "<div class='updated'><p>" . __( "You'll need to log-in to edit this event.", 'my-calendar-pro' ) . '</p></div>';
	}
	// To Do: function that parses $data to populate an object in this manner.
	$data = ( ! empty( $response[1] ) ) ? $response[1] : $event;

	$payment_form = ( mcs_payment_required() ) ? mcs_payment_form() : '';
	$enctype      = ( 'true' === get_option( 'mcs_upload_images' ) ) ? ' enctype="multipart/form-data"' : '';

	if ( current_user_can( 'mc_manage_events' ) || current_user_can( 'mc_add_events' ) ) {

		$action   = ! empty( $_GET['action'] ) ? sanitize_text_field( $_GET['action'] ) : '';
		$confirm  = ! empty( $_GET['confirm'] ) ? sanitize_text_field( $_GET['confirm'] ) : 'false';
		$event_id = ! empty( $_GET['mcs_id'] ) ? intval( $_GET['mcs_id'] ) : '';
		if ( 'delete' === $action ) {
			if ( 'false' === $confirm ) {
				$title = ( $event ) ? stripslashes( esc_html( $event->event_title ) ) : 'Untitled Event';
				// Translators: Title of event to delete.
				$delete_text = sprintf( __( 'You are deleting &ldquo;%s&rdquo;.', 'my-calendar-pro' ), $title );
				$message    .= "<div class='updated'>
								<p>" . $delete_text . " <a href='" . mcs_delete_url( $event_id, true ) . "'>" . __( 'Please confirm deletion.', 'my-calendar-pro' ) . '</a></p>
							</div>';
			} elseif ( 'true' === $confirm ) {
				$verify = wp_verify_nonce( $_GET['_mc_delete_event'], 'mc-delete-event' );
				if ( $verify ) {
					$message .= mc_delete_event( $event_id );
				} else {
					$message .= __( 'Could not verify your request.', 'my-calendar-pro' );
				}
			}
		}
	}

	$message = str_replace( array( 'class="', "class='" ), array( 'class="notice ', "class='notice " ), $message );
	$respond = '';
	if ( $response ) {
		$respond = str_replace( array( 'class="', "class='" ), array( 'class="notice ', "class='notice " ), $response[0] );
	}
	$submit_id = get_option( 'mcs_submit_id' );
	$message   = ( 'true' === get_option( 'mcs_ajax', 'true' ) ) ? "<div class='mcs-status' id='mcs-status-$form_id' tabindex='-1' aria-live='assertive'>" . $message . '</div>' : $message;
	// if there are multiple forms, submissions should go to the current form.
	if ( ( is_single() || $is_widget ) && get_the_ID() !== $submit_id ) {
		$submit_id = get_the_ID();
	}
	$action  = ( $submit_id ) ? get_permalink( $submit_id ) : '';
	$action  = ( isset( $_GET['confirm'] ) ) ? $action : add_query_arg( 'mcs_id', $mc_id, $action );
	$action  = ( $form_id ) ? add_query_arg( 'form-id', sanitize_title( $form_id ), $action ) : $action;
	$id_form = ( $form_id ) ? " id='mcs_" . sanitize_title( $form_id ) . "'" : '';
	$return  = "
	<div class='mc-submissions'$id_form>
		$message
		$respond
		$payment_form
		<form action='" . esc_url( $action ) . "' method='post' id='" . esc_attr( $form_id ) . "' class='mcs-submission' $enctype>
		" . mcs_input_fields( $data, $event, $args );
	if ( 'true' === get_option( 'mcs_automatic_approval' ) || current_user_can( 'mc_approve_events' ) ) {
		$name = 'save';
	} else {
		$name = 'draft';
	}

	$button_text = ( isset( $_GET['mcs_id'] ) && ! isset( $_GET['confirm'] ) ) ? __( 'Edit your event', 'my-calendar-pro' ) : __( 'Submit your event', 'my-calendar-pro' );
	$return     .= "<p class='mcs-submit-button'><input type='submit' id='mcs-submit-$form_id' class='button-primary mc-submit' name='$name' value='" . $button_text . "' /></p>";
	$edit        = '';
	if ( current_user_can( 'manage_options' ) ) {
		$edit_link = add_query_arg( 'form_id', $form_id, admin_url( '/admin.php?page=my-calendar-shortcodes' ) ) . '#mc_builder';
		$edit      = wpautop( '<a class="mcs-edit-form" href="' . esc_url( $edit_link ) . '">' . __( 'Edit this form', 'my-calendar-pro' ) ) . '</a>';
	}

	$return .= '
		</form>
	</div>' . $edit;
	/**
	 * Filter the complete output of the My Calendar submissions form.
	 *
	 * @hook mcs_after_submission
	 *
	 * @param {string} $return HTML for form.
	 * @param {string} $response Feedback after submitting form.
	 * @param {string} $form_id Form ID.
	 *
	 * @return {string}
	 */
	$return = apply_filters( 'mcs_after_submissions', $return, $response, $form_id );

	return $return;
}

/**
 * Determine what type of image upload we're allowing.
 *
 * @param string|int $image Image URL or post thumbnail ID.
 *
 * @return string
 */
function mcs_image_input_type( $image = '' ) {
	if ( 'true' === get_option( 'mcs_upload_images' ) && ( '' === $image || is_int( $image ) ) ) {
		$input_type = 'file';
	} else {
		$input_type = 'url';
	}

	return $input_type;
}

/**
 * Array of empty input field containers.
 *
 * @return array
 */
function mcs_input_field_array() {
	$input_fields = array(
		'event_title'       => '',
		'event_date'        => '',
		'event_time'        => '',
		'event_end'         => '',
		'mcs_name'          => '',
		'mcs_email'         => '',
		'event_host'        => '',
		'event_recurring'   => '',
		'description'       => '',
		'short_description' => '',
		'access'            => '',
		'event_link'        => '',
		'event_image'       => '',
		'event_category'    => '',
		'registration'      => '',
		'locations'         => '',
	);

	return $input_fields;
}

/**
 * Set up hidden inputs required when editing an event.
 *
 * @param object $event Event object.
 * @param object $data Saved data object.
 *
 * @return string
 */
function mcs_setup_editing_event( $event, $data ) {
	if ( $event && ! isset( $_GET['confirm'] ) ) {
		$begin   = ( ! empty( $data ) ) ? esc_attr( mcs_date( 'Y-m-d', strtotime( $data->event_begin ), false ) ) : '';
		$recur   = ( ! empty( $data ) ) ? esc_attr( $data->event_recur ) : 'S';
		$recurs  = str_split( $recur, 1 );
		$recur   = $recurs[0];
		$every   = ( isset( $recurs[1] ) && ! empty( $data ) ) ? str_replace( $recurs[0], '', $data->event_recur ) : 1;
		$every   = ( 1 === $every && 'B' === $recur ) ? 2 : $every;
		$repeats = ( ! empty( $data ) ) ? esc_attr( $data->event_repeats ) : '';

		// For older events with a numeric repetition, update the repetition to the new format.
		if ( $event && is_numeric( $repeats ) ) {
			$occurrences = mc_get_occurrences( $event->event_id );
			$last        = array_pop( $occurrences );
			$instance    = mc_get_instance_data( $last->occur_id );
			$repeats     = gmdate( 'Y-m-d', strtotime( $instance->occur_begin ) );
		}

		return "<input type='hidden' name='event_edit' value='$event->event_id' />
				<input type='hidden' name='prev_event_begin' value='$begin' />
				<input type='hidden' name='prev_event_repeats' value='$repeats' />
				<input type='hidden' name='prev_event_recur' value='$recur' />
				<input type='hidden' name='prev_event_status' value='$event->event_approved' />
				<input type='hidden' name='event_post' value='$event->event_post' />";
	} else {
		return '';
	}
}

/**
 * Hidden input fields.
 *
 * @param string $form_id ID for currently processed form.
 *
 * @return array array of field keys for hidden inputs in calendar.
 */
function mcs_hidden_input_fields( $form_id ) {
	$fields  = array(
		'event_link_expires'  => '',
		'event_holiday'       => '',
		'event_fifth_week'    => '',
		'event_nonce_name'    => '',
		'mcs_submission'      => '',
		'event_author'        => '',
		'event_group_id'      => '',
		'mcs_form_id'         => '',
		'mcs_form_timestamp'  => '',
		'event_all_day'       => '',
		'event_hide_end'      => '',
		'mcs_check_conflicts' => '',
		'location_preset'     => '',
	);
	$forms   = get_option( 'mcs_forms', array() );
	$form    = isset( $forms[ $form_id ] ) ? $forms[ $form_id ] : array();
	$allcats = mc_no_category_default();
	// If there's only one category, the form is removed and inserted as a hidden field regardless of settings.
	if ( '0' === $form['categories'] || ( '1' === $form['categories'] && 1 === count( $allcats ) ) || 'hidden' === $form['category_field']['entry_type'] ) {
		$fields['event_category'] = isset( $form['category'] ) ? $form['category'] : '1';
	}
	$omit_location_preset = array( 'either', 'choose' );
	// If users can select locations, then location_preset is not a hidden field.
	if ( in_array( $form['locations'], $omit_location_preset, true ) ) {
		unset( $fields['location_preset'] );
	}

	/**
	 * Filter the hidden field inputs used in My Calendar Pro forms.
	 *
	 * @hook mcs_hidden_fields
	 *
	 * @param {array} $fields An array of fields in format [key => $value].
	 * @param {string} $form_id The ID of the current form.
	 *
	 * @return {array}
	 */
	$return = (array) apply_filters( 'mcs_hidden_fields', $fields, $form_id );

	return $return;
}

/**
 * Hidden inputs used in My Calendar submissions.
 *
 * @param object $event Event object.
 * @param object $data Saved data object.
 * @param array  $fields Fields passed from shortcode.
 * @param array  $hidden Hidden location & category inputs.
 * @param string $form_id Form ID passed from shortcode.
 *
 * @return string
 */
function mcs_submissions_hidden_inputs( $event, $data, $fields, $hidden, $form_id ) {
	$user          = wp_get_current_user();
	$auth          = ( is_user_logged_in() ) ? $user->ID : 0;
	$values        = mcs_hidden_input_fields( $form_id );
	$values        = array_merge( $values, $hidden );
	$hidden_fields = '';
	if ( $event && ! isset( $_GET['confirm'] ) ) {
		$values['event_link_expires'] = $event->event_link_expires;
		$values['event_holiday']      = $event->event_holiday;
		$values['event_fifth_week']   = $event->event_fifth_week;
	} else {
		$values['event_link_expires'] = ( function_exists( 'mc_event_link_expires' ) && ! mc_event_link_expires() ) ? 1 : 0;
		$values['event_holiday']      = ( 'true' === get_option( 'mc_skip_holidays' ) ) ? 'on' : 'false';
		$values['event_fifth_week']   = ( function_exists( 'mc_no_fifth_week' ) && mc_no_fifth_week() ) ? 1 : '';
	}
	$values['event_nonce_name']   = wp_create_nonce( 'event_nonce' );
	$values['mcs_submission']     = 'on';
	$values['event_author']       = (int) $auth;
	$values['event_group_id']     = mc_group_id();
	$values['mcs_form_id']        = $form_id;
	$values['mcs_form_timestamp'] = time();
	if ( ! isset( $fields['event_allday'] ) ) {
		/**
		 * Set the all day event value to 1 (true). Default 0 (false). Obsolete in My Calendar Pro 3.0.
		 *
		 * @hook mcs_event_allday
		 *
		 * @param {int} $hidden Default '0', false.
		 *
		 * @return {int}
		 */
		if ( true === (bool) apply_filters( 'mcs_event_allday', 0 ) ) {
			$values['event_allday'] = '1';
		}
	}
	if ( ! isset( $fields['event_hide_end'] ) ) {
		/**
		 * Set the event hide end value to 1 (true). Default 0 (false). Obsolete in My Calendar Pro 3.0.
		 *
		 * @hook mcs_event_hide_end
		 *
		 * @param {int} $hidden Default '0', false.
		 *
		 * @return {int}
		 */
		if ( true === (bool) apply_filters( 'mcs_event_hide_end', 0 ) ) {
			$values['event_hide_end'] = '1';
		}
	}
	if ( 'true' === get_option( 'mcs_check_conflicts' ) ) {
		$values['mcs_check_conflicts'] = 'true';
	}
	foreach ( $values as $key => $value ) {
		// Hidden fields don't support array values, but categories can be arrays.
		if ( is_array( $value ) ) {
			$value = $value[0];
		}
		$values[ $key ] = (string) $value;
		if ( '' !== $value ) {
			$hidden_fields .= '<input type="hidden" name="' . $key . '" value="' . esc_attr( $value ) . '">' . PHP_EOL;
		} else {
			unset( $values[ $key ] );
		}
	}
	$unique_id = ( isset( $_COOKIE['mcs_unique_id'] ) ) ? sanitize_text_field( wp_unslash( $_COOKIE['mcs_unique_id'] ) ) : false;
	// Form timestamp always changes between render and submission, so can't be tested this way.
	if ( $unique_id ) {
		unset( $values['mcs_form_timestamp'] );
		delete_transient( 'mcs_hidden_fields_' . $unique_id );
		set_transient( 'mcs_hidden_fields_' . $unique_id, $values, HOUR_IN_SECONDS );
	}
	$edit           = mcs_setup_editing_event( $event, $data );
	$hidden_inputs  = "
		<div class='hidden-inputs'>
			$hidden_fields
			<div style='display: none;' class='mcs-hidden'>
				<label for='your_name'>" . __( 'Do not fill-in this field.', 'my-calendar-pro' ) . "</label>
				<input type='text' name='your_name' id='your_name' value='' autocomplete='off' />
			</div>
			$edit";
	$hidden_inputs .= '</div>';

	return $hidden_inputs;
}

/**
 * Generate user-fillable input fields for a submit form (all arguments passed from mc_submit_form function).
 *
 * @param object      $data Object with event data.
 * @param object|bool $event Event object; false if new event.
 * @param array       $args Array of objects passed to form. Documented on mcs_build_submit_form.
 *
 * @return string
 */
function mcs_input_fields( $data, $event, $args ) {
	$fields   = $args['fields'];
	$form_id  = $args['form_id'];
	$has_data = ( is_object( $data ) && property_exists( $data, 'event_id' ) ) ? true : false;
	$event_id = ( $has_data ) ? $data->event_id : false;

	$location_fields = mcs_location_fields( $args, $data );
	$category        = $args['category'];
	$categories      = $args['categories'];
	$category_field  = $args['category_field']; // meta data about category field.
	$selected_cat    = ( $has_data ) ? mc_get_categories( $event_id ) : $category;
	$category_fields = mcs_category_fields( $selected_cat, $categories, $category_field );

	$hidden       = array_merge( $category_fields['hidden'], $location_fields['hidden'] );
	$return       = '';
	$defaults     = mcs_default_fields();
	$input_fields = mcs_input_field_array();

	/**
	 * Filter the required fields in front-end event editing and creation.
	 *
	 * @hook mcs_required_fields
	 *
	 * @param {array}  $required Array of required fields.
	 * @param {string} $form_id Form ID.
	 *
	 * @return {array}
	 */
	$required_fields = apply_filters( 'mcs_required_fields', get_option( 'mcs_required_fields', array() ), $form_id );
	$required_text   = mcs_required_text();

	if ( mcs_payment_required() ) {
		$key     = ( isset( $_POST['mcs_key'] ) ) ? esc_attr( sanitize_text_field( wp_unslash( $_POST['mcs_key'] ) ) ) : '';
		$key     = ( isset( $_GET['mcs_key'] ) ) ? esc_attr( sanitize_text_field( wp_unslash( $_GET['mcs_key'] ) ) ) : $key;
		$return .= "<p><label for='mcs_key'>" . __( 'Payment Key', 'my-calendar-pro' ) . ' ' . $required_text . "</label> <input type='text' name='mcs_key' id='mcs_key' value='$key' required='required' /></p>";
	}

	$hidden_inputs = mcs_submissions_hidden_inputs( $event, $data, $fields, $hidden, $form_id );

	$input_fields['event_title'] = mcs_title_input( $data, $fields );
	$input_fields['event_date']  = mcs_event_date_input( $data, $fields );
	$input_fields['event_end']   = mcs_end_date_input( $data, $fields );
	if ( isset( $fields['submitter'] ) ) {
		$input_fields['submitter'] = mcs_name_and_email_input( $data, $fields );
	} else {
		$input_fields['mcs_name'] = mcs_name_and_email_input( $data, $fields );
	}
	$input_fields['event_host']        = mcs_event_host_input( $data, $fields );
	$input_fields['event_recurring']   = mcs_event_recurring_input( $data, $fields );
	$input_fields['registration']      = mcs_event_registration_input( $data, $fields );
	$input_fields['description']       = mcs_description_input( $data, $fields );
	$input_fields['short_description'] = mcs_short_description_input( $data, $fields );

	// This stays as it is, because it's using a function from My Calendar.
	if ( isset( $fields['access'] ) && function_exists( 'mc_event_accessibility' ) ) {
		$flabel                 = ( 'true' !== $fields['access'] ) ? $fields['access'] : $defaults['access']['field_label'];
		$input_fields['access'] = mc_event_accessibility( '', $data, $flabel );
	}

	$input_fields['event_link']  = mcs_event_link_input( $data, $fields );
	$input_fields['event_image'] = mcs_event_image_input( $data, $fields );
	$input_fields['categories']  = $category_fields['html'];
	$input_fields['locations']   = $location_fields['html'];
	/**
	 * Filter the contents of the `custom_fields` parameter in My Calendar Pro. Legacy support for older custom field setup.
	 *
	 * @hook mc_event_details
	 *
	 * @param {string} $html HTML output for custom fields.
	 * @param {bool}   $has_data Whether current event object is populated.
	 * @param {object} $data Event object.
	 * @param {string} $public Public context.
	 *
	 * @return {string}
	 */
	$input_fields['custom_fields'] = apply_filters( 'mc_event_details', '', $has_data, $data, 'public' );

	/**
	 * Set up custom fields to a form that are sortable in Pro.
	 *
	 * @hook mc_custom_fields
	 *
	 * @param {array}  $input_fields Array of fields to add.
	 * @param {bool}   $has_data Boolean indicating whether the current event is populated.
	 * @param {object} $data Event object.
	 * @param {string} $public 'public' indicating that this is the public display of these fields.
	 * @param {string} $form_id Form ID.
	 *
	 * @return {array}
	 */
	$input_fields = apply_filters( 'mc_custom_fields', $input_fields, $has_data, $data, 'public', $form_id );

	// These fields are always required.
	$mandatory_fields = array(
		'event_title'   => true,
		'event_date'    => true,
		'event_time'    => true,
		'submitter'     => true,
		'custom_fields' => true,
	);

	// If a required field is already called in the shortcode settings, don't append to top.
	foreach ( $mandatory_fields as $field => $enabled ) {
		$field_keys = array_keys( $fields );
		if ( in_array( $field, $field_keys, true ) ) {
			unset( $mandatory_fields[ $field ] );
		}
	}
	$fields = array_merge( $mandatory_fields, $fields );
	foreach ( $required_fields as $req ) {
		if ( ! in_array( $req, $fields, true ) ) {
			$fields[ $req ] = true;
		}
	}

	// Locations are always added at the end unless set elsewhere. Locations field setter controls output.
	if ( ! isset( $fields['locations'] ) ) {
		$fields['locations'] = 'true';
	}
	if ( ! isset( $fields['categories'] ) ) {
		$fields['categories'] = 'true';
	}
	/**
	 * Filter the input fields for a given form.
	 *
	 * @hook mcs_input_fields
	 *
	 * @param {array} $fields Array of field data & form parameters.
	 * @param {array} $mandatory_fields The array of fields that are always required by forms.
	 * @param {string} $form_id ID for the current form.
	 *
	 * @return {array}
	 */
	$fields = apply_filters( 'mcs_input_fields', $fields, $mandatory_fields, $form_id );

	$event_end = true;
	foreach ( $fields as $field => $label ) {
		if ( ( 'end_time' === $field || 'end_date' === $field || 'event_end' === $field || 'event_endtime' === $field ) && true === $event_end ) {
			// Only add end values once.
			$event_end = false;
			$end_field = ( isset( $input_fields['event_end'] ) ) ? $input_fields['event_end'] : '';
			/**
			 * Filter the HTML output of a submissions input field. Special handling filter for end date/time fields.
			 *
			 * @hook mcs_{field}_input_field
			 *
			 * @param {string} $value HTML output for field.
			 * @param {string} $field Field keyword.
			 * @param {string} $form_id Form ID.
			 *
			 * @return {string}
			 */
			$return .= apply_filters( 'mcs_' . $field . '_input_field', $end_field, $field, $form_id );
		} else {
			if ( 'end_time' === $field || 'end_date' === $field || 'event_end' === $field || 'event_endtime' === $field ) {
				continue;
			}
			$value = isset( $input_fields[ $field ] ) ? $input_fields[ $field ] : '';
			/**
			 * Filter the HTML output of a submissions input field.
			 *
			 * @hook mcs_{field}_input_field
			 *
			 * @param {string} $value HTML output for field.
			 * @param {string} $field Field keyword.
			 * @param {string} $form_id Form ID.
			 *
			 * @return {string}
			 */
			$return .= apply_filters( 'mcs_' . $field . '_input_field', $value, $field, $form_id );
		}
	}

	$submitting_as = '';
	if ( is_user_logged_in() ) {
		$name  = wp_get_current_user()->display_name;
		$email = wp_get_current_user()->user_email;
		// Translators: user's name, user's email.
		$submitting_as = '<div class="mcs_submitting"><p>' . sprintf( __( 'Submitting as %1$s at %2$s', 'my-calendar-pro' ), '<strong>' . esc_html( $name ) . '</strong>', '<code>' . esc_html( $email ) . '</code>' ) . '</p></div>';
	}

	return $submitting_as . $hidden_inputs . $return;
}

/**
 * Return input fields for category selection
 *
 * @param int|array $category Category to be selected in form. Can be an array.
 * @param int       $categories integer (used as boolean) whether categories are enabled.
 * @param array     $field Array of field settings for categories.
 *
 * @return string; selection form
 */
function mcs_category_fields( $category, $categories, $field = array() ) {
	$return = '';
	$hidden = array();
	if ( isset( $field['entry_type'] ) && 'hidden' === $field['entry_type'] ) {
		$categories = false;
	}
	if ( ! $categories ) {
		if ( ! $category || empty( $category ) ) {
			$hidden['event_category'] = '1';
		} else {
			if ( is_array( $category ) ) {
				foreach ( $category as $cat ) {
					$hidden['event_category'][] = $cat;
				}
			} else {
				$hidden['event_category'] = $category;
			}
		}
	} else {
		$allcats = mc_no_category_default();
		if ( 1 === count( $allcats ) ) {
			$hidden['event_category'] = '1';
		} else {
			if ( isset( $field['entry_type'] ) && 'single' === $field['entry_type'] ) {
				$category = ( is_array( $category ) ) ? $category[0] : $category; // If categories are an array, can only use first selected.
				$label    = ( isset( $field['field_label'] ) && '' !== $field['field_label'] ) ? $field['field_label'] : __( 'Category', 'my-calendar-pro' );
				$return   = '<div class="mc-event-category mcs-select mc-category-single"><p><label for="event_category">' . $label . '</label><select id="event_category" name="event_category[]">' . mc_category_select( $category, true, false ) . '</select></p></div>';
			} else {
				$label  = ( isset( $field['field_label'] ) && '' !== $field['field_label'] ) ? $field['field_label'] : __( 'Categories', 'my-calendar-pro' );
				$return = '
				<fieldset class="mc-event-category mc-fieldset mc-category-multiple">
					<legend class="mc-categories-group">' . $label . '</legend>
					<ul class="mc-categories checkboxes">
						' . mc_category_select( $category, true, true ) . '
					</ul>
				</fieldset>';
			}
		}
	}
	$return = array(
		'html'   => $return,
		'hidden' => $hidden,
	);

	return $return;
}

/**
 * Return form fields for selecting or inputting a location
 *
 * @param array  $args Array of location form arguments.
 * @param object $event Event with location data.
 *
 * @return array string HTML for input fields and array of hidden inputs.
 */
function mcs_location_fields( $args, $event ) {
	$location_fields = $args['location_fields'];
	$locations       = $args['locations'];
	$has_data        = ( is_object( $event ) && property_exists( $event, 'event_id' ) ) ? true : false;
	$location        = ( $has_data && property_exists( $event, 'event_location' ) ) ? $event->event_location : $args['location'];
	$loc             = mc_get_location( $location );
	// Selected location data.
	$return            = '';
	$hidden            = array();
	$selected_location = array(
		'label'     => ( ! empty( $loc ) ) ? $loc->location_label : '',
		'street'    => ( ! empty( $loc ) ) ? $loc->location_street : '',
		'street2'   => ( ! empty( $loc ) ) ? $loc->location_street2 : '',
		'city'      => ( ! empty( $loc ) ) ? $loc->location_city : '',
		'state'     => ( ! empty( $loc ) ) ? $loc->location_state : '',
		'postcode'  => ( ! empty( $loc ) ) ? $loc->location_postcode : '',
		'country'   => ( ! empty( $loc ) ) ? $loc->location_country : '',
		'region'    => ( ! empty( $loc ) ) ? $loc->location_region : '',
		'url'       => ( ! empty( $loc ) ) ? $loc->location_url : '',
		'longitude' => ( ! empty( $loc ) ) ? $loc->location_longitude : '',
		'latitude'  => ( ! empty( $loc ) ) ? $loc->location_latitude : '',
		'phone'     => ( ! empty( $loc ) ) ? $loc->location_phone : '',
	);

	$autocomplete = false;
	if ( function_exists( 'mc_count_locations' ) ) {
		$count = mc_count_locations();
		/**
		 * Set number of locations where front-end submissions switches from a select to an autocomplete.
		 *
		 * @hook mcs_convert_locations_select_to_autocomplete
		 *
		 * @param {int}    $count Number of locations. Default 90.
		 * @param {string} $form_id ID for the current form.
		 *
		 * @return {int}
		 */
		if ( $count > apply_filters( 'mcs_convert_locations_select_to_autocomplete', 90, $args['form_id'] ) ) {
			$autocomplete = true;
		}
	}
	if ( 'none' === $locations && ! $location ) {
		$hidden['location_preset'] = 'none';
	} else {
		if ( isset( $_GET['mcs_id'] ) ) {
			$locations = 'choose';
		}
		switch ( $locations ) {
			case 'choose':
				$return .= "<p class='mc-select-location'>
				<label for='mcs_event_location'>" . __( 'Location', 'my-calendar-pro' ) . '</label>';
				if ( $autocomplete ) {
					$location_label = ( $location && is_numeric( $location ) ) ? mc_get_location( $location )->location_label : '';
					$return        .= '<div id="mc-locations-autocomplete" class="mc-autocomplete autocomplete">
									<input class="autocomplete-input" id="mcs_event_location" placeholder="' . __( 'Search locations...', 'my-calendar-pro' ) . '" value="' . esc_attr( $location_label ) . '" />
									<ul class="autocomplete-result-list"></ul>
								</div>
								<input type="hidden" name="location_preset" id="mcs_event_location_value" value="' . esc_attr( $location ) . '" />';
				} else {
					$return .= "<select name='location_preset' id='mcs_event_location'>
						" . mc_location_select( $location ) . '
					</select>';
				}
				$return .= '</p>';
				break;
			case 'either':
				$return .= "<div class='mc-select-location-group'><div class='mc-select-location'>
				<label for='mcs_event_location'>" . __( 'Location', 'my-calendar-pro' ) . '</label>';
				if ( $autocomplete ) {
					$location_label = ( $location && is_numeric( $location ) ) ? mc_get_location( $location )->location_label : '';
					$return        .= '<div id="mc-locations-autocomplete" class="mc-autocomplete autocomplete">
									<input class="autocomplete-input" id="mcs_event_location" placeholder="' . __( 'Search locations...', 'my-calendar-pro' ) . '" value="' . esc_attr( $location_label ) . '" />
									<ul class="autocomplete-result-list"></ul>
								</div>
								<input type="hidden" name="location_preset" id="mcs_event_location_value" value="' . esc_attr( $location ) . '" />';
				} else {
					$return .= "<select name='location_preset' id='mcs_event_location'>
						" . mc_location_select( $location ) . '
					</select>';
				}
				$return .= '</div>';
				/**
				 * Set whether location fields are hidden in the submission form with a toggle to display them or visible at all times.
				 *
				 * @hook mcs_expand_location_fields
				 *
				 * @param {bool} $visible true to show fields. Default false.
				 *
				 * @return {bool}
				 */
				if ( ! apply_filters( 'mcs_expand_location_fields', false ) ) {
					/**
					 * Filter the HTML for the button used to toggle location fields.
					 *
					 * @hook mcs_expand_button
					 *
					 * @param {string} $html HTML string for button.
					 *
					 * @return {string}
					 */
					$button  = apply_filters( 'mcs_expand_button', "<button type='button' class='toggle_location_fields' aria-expanded='false'><span class='mc-button-text'>" . __( 'Add New Location', 'my-calendar-pro' ) . "</span><span class='dashicons dashicons-plus' aria-hidden='true'></span></button>" );
					$return .= "
						$button
						<div class='mcs_location_fields'>
							" . mcs_location_form( $location_fields, $selected_location, 'hidden' ) . '
						</div>
					</div>';
				} else {
					$return .= mcs_location_form( $location_fields, $selected_location, 'visible' );
				}
				break;
			case 'enter':
				$return                   .= mcs_location_form( $location_fields, $selected_location, 'visible' );
				$hidden['location_preset'] = 'none';
				break;
			default:
				if ( $location ) {
					$hidden['location_preset'] = $location;
				} else {
					$hidden['location_preset'] = 'none';
				}
		}
	}
	$return = array(
		'html'   => $return,
		'hidden' => $hidden,
	);

	return $return;
}

/**
 * Generate location field for frontend.
 *
 * @param string $field Field ID.
 * @param array  $data  Field data.
 * @param array  $loc Location data.
 * @param string $visibility Visibility of containing form.
 *
 * @return string
 */
function mcs_location_build_field( $field, $data, $loc, $visibility ) {
	$defaults       = mcs_default_location_fields();
	$default_label  = ( isset( $defaults[ $field ]['field_label'] ) ) ? $defaults[ $field ]['field_label'] : __( 'No label defined', 'my-calendar-pro' );
	$control        = str_replace( 'event_', '', $field );
	$required       = 'false';
	$required_field = '';
	$required_text  = '';
	if ( is_array( $data ) ) {
		$label          = ( isset( $data['field_label'] ) && 'true' !== $data['field_label'] ) ? $data['field_label'] : $default_label;
		$required       = ( isset( $data['required'] ) && 'on' === $data['required'] ) ? 'true' : 'false';
		$required_field = ( 'true' === $required && 'visible' === $visibility ) ? 'required ' : '';
		$required_text  = ( 'true' === $required ) ? ' ' . mcs_required_text() : '';
	} else {
		$label = ( 'true' !== $data ) ? $data : $default_label;
	}
	$name = ( 0 === stripos( $field, 'event_' ) ) ? $field : 'event_' . $field;
	if ( 'gps' === $field ) {
		$return = '
		<fieldset class="mcs-field mcs-location-field mcs-location-' . esc_attr( $field ) . '">
			<legend>' . __( 'GPS Coordinates', 'my-calendar-pro' ) . '</legend>
			<p>
				<label for="event_latitude">' . __( 'Latitude', 'my-calendar-pro' ) . $required_text . '</label>
				<input type="text" ' . $required_field . ' data-required="' . $required . '" id="event_latitude" name="event_latitude" class="input mc-location-field mc-location-' . esc_attr( $field ) . '" size="10" value="' . stripslashes( esc_attr( $loc['latitude'] ) ) . '" />
			</p>
			<p>
				<label for="event_longitude">' . __( 'Longitude', 'my-calendar-pro' ) . $required_text . '</label>
				<input type="text" ' . $required_field . ' data-required="' . $required . '" id="event_longitude" name="event_longitude" class="input mc-location-field mc-location-' . esc_attr( $field ) . '" size="10" value="' . stripslashes( esc_attr( $loc['longitude'] ) ) . '" />
			</p>
			<input type="hidden" name="event_zoom" value="16" />
		</fieldset>';
	} else {
		// Correct legacy field name.
		$field  = ( 'event_label' === $field ) ? 'label' : $field;
		$value  = ( isset( $loc[ $field ] ) ) ? $loc[ $field ] : '';
		$return = '
		<p class="mcs-field mcs-location-field mcs-location-' . esc_attr( $field ) . '">
			<label for="' . esc_attr( $name ) . '">' . $label . $required_text . '</label>';
		if ( mc_controlled_field( $control ) && 'label' !== $control ) {
			$return .= mc_location_controller( $control, $value, 'event' );
		} else {
			$return .= '<input type="text" ' . $required_field . ' data-required="' . $required . '" id="' . esc_attr( $name ) . '" name="' . esc_attr( $name ) . '" class="input" value="' . stripslashes( esc_attr( $value ) ) . '" />';
		}
	}

	return $return;
}

/**
 * Produce location form.
 *
 * @param array   $fields Array of fields and labels to display.
 * @param integer $loc Integer for pre-defined location in location tables to default to.
 * @param string  $visibility 'visible' or 'hidden' to indicate whether fields are visible by default.
 *
 * @return String Form HTML
 */
function mcs_location_form( $fields, $loc, $visibility ) {
	$return = '';
	foreach ( $fields as $field => $data ) {
		$output[] = mcs_location_build_field( $field, $data, $loc, $visibility );
	}
	$return .= implode( PHP_EOL, $output );

	return $return;
}

/**
 * Process submission from form.
 *
 * @param array $post POSTed data.
 *
 * @return array|false
 */
function mcs_processor( $post ) {
	$error = false;
	if ( isset( $post['mcs_submission'] ) ) {
		$attach_id = false;
		$nonce     = $post['event_nonce_name'];
		$posted    = mc_check_data( 'check', $post, 0 );
		$form_id   = ( isset( $post['mcs_form_id'] ) && '' !== $post['mcs_form_id'] ) ? $post['mcs_form_id'] : '';
		if ( ! wp_verify_nonce( $nonce, 'event_nonce' ) ) {
			return array( '<div class="notice error"><p>' . __( 'Invalid nonce provided. Please refresh your page.', 'my-calendar-pro' ) . '</p></div>', $posted[1], false );
		}
		if ( ! mcs_verify_hidden_inputs( $post, $form_id ) ) {
			return array( '<div class="notice error"><p>' . __( 'Could not verify input.', 'my-calendar-pro' ) . '</p></div>', $posted[1], false );
		}
		// reject invalid email addresses.
		if ( isset( $post['mcs_email'] ) && ! is_email( $post['mcs_email'] ) ) {
			return array( '<div class="notice error"><p>' . __( 'Invalid email provided', 'my-calendar-pro' ) . '</p></div>', $posted[1], false );
		}
		// honeypot - only bots should complete this field.
		$honeypot = ( isset( $post['your_name'] ) && '' !== $post['your_name'] ) ? true : false;
		if ( $honeypot ) {
			if ( SCRIPT_DEBUG ) {
				return array( '<div class="notice error"><p>' . __( 'Honeypot', 'my-calendar-pro' ) . '</p></div>', $posted[1], false );
			}
			return false;
		}
		if ( isset( $post['mcs_form_timestamp'] ) ) {
			$timestamp = absint( $post['mcs_form_timestamp'] );
			$now       = time();
			// If current time is before the timestamp, it's been spoofed.
			if ( $now < $timestamp ) {
				if ( SCRIPT_DEBUG ) {
					return array( '<div class="notice error"><p>' . __( 'Invalid time frame', 'my-calendar-pro' ) . '</p></div>', $posted[1], false );
				}
				return false;
			}
			$diff = $now - $timestamp;
			/**
			 * Set minimum submission time limit for the My Calendar form honeypot. If it took less than x seconds to submit this form, reject it.
			 *
			 * @hook mcs_honeypot_limit
			 *
			 * @param {int} $limit Minimum number of seconds submissions are reasonably expected to take. Default 6.
			 *
			 * @return {int}
			 */
			$margin = apply_filters( 'mcs_honeypot_limit', 6 );
			if ( $diff < $margin ) {
				if ( SCRIPT_DEBUG ) {
					return array( '<div class="notice error"><p>' . __( 'Too fast', 'my-calendar-pro' ) . '</p></div>', $posted[1], false );
				}
				return false;
			}
		}
		/**
		 * Run action on front-end processing of event submission. After honeypot & other checks are run, before any event processing is done.
		 *
		 * @hook mcs_before_processing
		 *
		 * @param {array} $post POSTed event data.
		 */
		do_action( 'mcs_before_processing', $post );
		$post_alt = false;
		// if files being uploaded, upload file and convert to a string for $post.
		if ( 'true' === get_option( 'mcs_ajax', 'true' ) ) {
			if ( 'true' === get_option( 'mcs_upload_images' ) ) {
				$post['event_image'] = ( isset( $post['event_image_id'] ) && is_numeric( $post['event_image_id'] ) ) ? wp_get_attachment_image_url( $post['event_image_id'], 'large' ) : '';
			} else {
				if ( isset( $post['event_image'] ) ) {
					// Verify supplied URL is a valid image.
					if ( mcs_check_remote_image( $post['event_image'] ) ) {
						$post['event_image'] = esc_url_raw( $post['event_image'] );
						$post_alt            = sanitize_text_field( $post['event_image_alt'] );
					} else {
						$post['event_image'] = '';
					}
				}
			}
		} else {
			if ( 'true' === get_option( 'mcs_upload_images' ) ) {
				if ( ! empty( $_FILES['event_image'] ) ) {
					require_once ABSPATH . '/wp-admin/includes/file.php';
					require_once ABSPATH . '/wp-admin/includes/image.php';
					$file   = $_FILES['event_image'];
					$upload = wp_handle_upload( $file, array( 'test_form' => false ) );
					if ( ! isset( $upload['error'] ) && isset( $upload['file'] ) ) {
						$filetype    = wp_check_filetype( basename( $upload['file'] ), null );
						$title       = $file['name'];
						$ext         = strrchr( $title, '.' );
						$title       = ( false !== $ext ) ? substr( $title, 0, - strlen( $ext ) ) : $title;
						$attachment  = array(
							'post_mime_type' => $filetype['type'],
							'post_title'     => addslashes( $title ),
							'post_content'   => '',
							'post_status'    => 'inherit',
						);
						$alt         = ( isset( $post['event_image_alt'] ) ) ? sanitize_text_field( $post['event_image_alt'] ) : '';
						$attach_id   = wp_insert_attachment( $attachment, $upload['file'] );
						$attach_data = wp_generate_attachment_metadata( $attach_id, $upload['file'] );

						$post['event_image']    = esc_url_raw( $upload['url'] );
						$post['event_image_id'] = (int) $attach_id;

						update_post_meta( $attach_id, '_wp_attachment_image_alt', $alt );
						wp_update_attachment_metadata( $attach_id, $attach_data );
					}
				}
			} else {
				// Verify supplied URL is a valid image.
				if ( isset( $post['event_image'] ) && mcs_check_remote_image( $post['event_image'] ) ) {
					$post['event_image'] = esc_url_raw( $post['event_image'] );
					$post_alt            = sanitize_text_field( $post['event_image_alt'] );
				} else {
					$post['event_image'] = '';
				}
			}
		}

		if ( 'true' === get_option( 'mcs_automatic_approval' ) || current_user_can( 'mc_approve_events' ) ) {
			$post['event_approved'] = 1;
		} else {
			$post['event_approved'] = 0;
		}

		// end file upload.
		$check   = mc_check_data( 'add', $post, 0 );
		$message = '';
		if ( mcs_payment_required() ) {
			$key      = ( isset( $post['mcs_key'] ) ) ? $post['mcs_key'] : false;
			$quantity = mcs_check_key( $key );
			if ( ! $quantity ) {
				$reason = mcs_key_status( $key );
				$error  = true;
				// Translators: invalid payment key.
				$return = array( "<div class='notice error'><p>" . sprintf( __( 'That was not a valid payment key: %s', 'my-calendar-pro' ), $reason ) . '</p></div>', $check[1], false );
			} else {
				// Translators: Number of submissions remaining.
				$message = sprintf( "<div class='notice error'><p>" . _n( '%d submissions remaining with this payment key.', '%d submissions remaining with this payment key.', ( $quantity - 1 ), 'my-calendar-pro' ) . '</p></div>', ( $quantity - 1 ) );
			}
		}
		if ( ! $error ) {
			if ( $check[0] ) {
				if ( ! isset( $post['event_edit'] ) ) {
					$response = my_calendar_save( 'add', $check );
					$action   = 'add';
				} else {
					$response = my_calendar_save( 'edit', $check, (int) $post['event_edit'] );
					$action   = 'edit';
				}
				$event_id = $response['event_id'];
				$notice   = $response['message'];
				$event    = mc_get_first_event( $event_id );
				$post_id  = $event->event_post;
				if ( $post_alt ) {
					update_post_meta( $post_id, '_mcs_submitted_alt', $post_alt );
				}

				if ( isset( $post['mcs_form_id'] ) && '' !== $post['mcs_form_id'] ) {
					update_post_meta( $post_id, '_mcs_form_id', sanitize_title( $post['mcs_form_id'] ) );
				}

				set_post_thumbnail( $post_id, $attach_id );
				if ( '' !== $message ) {
					$notice .= " $message";
				}

				$return = array( $notice, array(), true );
			} else {
				$return = array( $check[3], $check[1], false );
				$error  = true;
			}
		}
		if ( $event_id && ! $error ) {
			$name  = $post['mcs_name'];
			$email = $post['mcs_email'];
			if ( mcs_payment_required() ) {
				// Note: payments will be processed on both submissions & on edits.
				mcs_update_key_quantity( $key, $quantity );
			}
			// if no errors and NOT SPAM send notifications.
			if ( mcs_event_is_spam( $event_id ) ) {
				/**
				 * The submitted event has been determined as spam.
				 *
				 * @hook mcs_spam_submission
				 *
				 * @param {string} $name Submitter's name.
				 * @param {string} $email Submitter's email.
				 * @param {int}    $event_id Submitted event ID.
				 */
				do_action( 'mcs_spam_submission', $name, $email, $event_id );
			} else {
				/**
				 * An event has been submitted and accepted.
				 *
				 * @hook mcs_complete_submission
				 *
				 * @param {string} $name Submitter's name.
				 * @param {string} $email Submitter's email.
				 * @param {int}    $event_id Submitted event ID.
				 * @param {string} $action Action taken. [edit,add,etc.].
				 */
				do_action( 'mcs_complete_submission', $name, $email, $event_id, $action );
			}
		}
		/**
		 * Run action on front-end processing after event submission. After event processing is completed.
		 *
		 * @hook mcs_before_processing
		 *
		 * @param {array} $post POSTed event data.
		 */
		do_action( 'mcs_after_processing', $post, $return );

		return $return;
	} else {

		return false;
	}
}

/**
 * Check if this event is spam.
 *
 * @param int $event_id Event ID.
 *
 * @return boolean.
 */
function mcs_event_is_spam( $event_id ) {
	$event = mc_get_first_event( $event_id );
	$flag  = $event->event_flagged;
	if ( 1 === (int) $flag ) {
		return true;
	}

	return false;
}

/**
 * Get the current submissions URL.
 *
 * @param int    $event_id Event ID. Optional.
 * @param object $event Event object. Optional.
 *
 * @return string URL.
 */
function mcs_submit_url( $event_id = false, $event = false ) {
	$submit_id = get_option( 'mcs_submit_id' );
	$referer   = urlencode( mc_get_current_url() );
	$url       = admin_url( "admin.php?page=my-calendar&amp;mode=edit&amp;ref=$referer" );
	if ( $event_id ) {
		$url = add_query_arg( 'event_id', $event_id, $url );
	}

	if ( $submit_id ) {
		$url = get_permalink( $submit_id );
		if ( $url && $event_id ) {
			if ( $event && is_object( $event ) ) {
				$post_id = $event->event_post;
				$form_id = get_post_meta( $post_id, '_mcs_form_id', true );
				if ( $form_id ) {
					$url = add_query_arg( 'form-id', $form_id, $url );
				}
			}
			$url = add_query_arg( 'mcs_id', $event_id, $url );
		}
	}
	/**
	 * Filter the target submission URL for a given event. Sends events to different submission forms if multiple forms exist.
	 *
	 * @hook mcs_submit_url
	 *
	 * @param {string} $url Submission URL.
	 * @param {int}    $event_id ID of the event being parsed.
	 *
	 * @return {string}
	 */
	$url = apply_filters( 'mcs_submit_url', $url, $event_id );

	return ( $url ) ? $url : '';
}

/**
 * Get the current deletion URL.
 *
 * @param int     $event_id Event ID.
 * @param boolean $confirm Confirmation URL if true.
 *
 * @return string URL.
 */
function mcs_delete_url( $event_id = false, $confirm = false ) {
	$submit_id = get_option( 'mcs_submit_id' );
	$nonce     = wp_create_nonce( 'mc-delete-event' );
	$referer   = urlencode( mc_get_current_url() );
	$url       = admin_url( "admin.php?page=my-calendar-manage&amp;mode=delete&amp;event_id=$event_id&amp;ref=$referer" );

	if ( $submit_id ) {
		$url = get_permalink( $submit_id );
		if ( $url && $event_id ) {
			$url = add_query_arg( 'mcs_id', $event_id, $url );
			$url = add_query_arg( 'action', 'delete', $url );
			$url = add_query_arg( '_mc_delete_event', $nonce, $url );
			$url = ( $confirm ) ? add_query_arg( 'confirm', 'true', $url ) : $url;
		}
	}
	/**
	 * Filter the event deletion URL.
	 *
	 * @hook mcs_delete_url
	 *
	 * @param {string} $url URL to delete an event.
	 * @param {int}    $event_id Event ID.
	 *
	 * @return {string}
	 */
	return ( $url ) ? apply_filters( 'mcs_delete_url', $url, $event_id ) : '';
}

add_filter( 'mc_before_event_form', 'mcs_show_author', 10, 2 );
/**
 * Show who submitted the current event in the admin manager.
 *
 * @param string $content Content before event form.
 * @param int    $event_id Event ID.
 *
 * @return string Info.
 */
function mcs_show_author( $content, $event_id ) {
	if ( $event_id ) {
		$event = mc_get_first_event( $event_id );
		if ( ! is_object( $event ) ) {
			return '';
		}
		$post_id = $event->event_post;
		$author  = get_post_meta( $post_id, '_submitter_details', true );
		if ( is_array( $author ) ) {
			$fname = ( isset( $author['first_name'] ) ) ? $author['first_name'] : '';
			$lname = ( isset( $author['last_name'] ) ) ? $author['last_name'] : '';
			$email = ( isset( $author['email'] ) && is_email( $author['email'] ) ) ? $author['email'] : '';
			$date  = get_the_time( mc_date_format(), $post_id );

			$return = ( '' !== $email ) ? "<a href='mailto:$email'>" . trim( $fname . ' ' . $lname ) . "</a> ($email)" : "$fname $lname";
			// Translators: 1. Who submitted the event, 2. date it was submitted.
			$return   = sprintf( __( 'Event submitted by %1$s on %2$s', 'my-calendar-pro' ), $return, $date );
			$content .= "
				<div class='ui-sortable meta-box-sortables'>
					<div class='postbox'>
						<div class='inside'>
							<p class='submitter'>$return</p>
						</div>
					</div>
				</div>";
		}
	}

	return $content;
}

/**
 * Save public submitter data in event post
 *
 * @param string $fname Author first name.
 * @param string $lname Author last name.
 * @param string $email Author email.
 * @param object $event Event object.
 */
function mcs_save_author( $fname, $lname, $email, $event ) {
	$post_ID = $event->event_post;
	if ( ! $post_ID ) {
		$post    = map_deep( $_POST, 'sanitize_textarea_field' );
		$post_ID = mc_event_post( 'add', $post, $event->event_id );
	}
	$add = array(
		'first_name' => $fname,
		'last_name'  => $lname,
		'email'      => $email,
	);

	add_post_meta( $post_ID, '_submitter_details', $add );
}

add_filter( 'mc_filter_shortcodes', 'mcs_tag_author', 10, 4 );
/**
 * Make submitter data available from templates
 *
 * @param array  $e Array of event tags.
 * @param object $event Event object.
 *
 * @return updated array.
 */
function mcs_tag_author( $e, $event ) {
	$submitter = get_post_meta( $event->event_post, '_submitter_details', true );
	if ( is_array( $submitter ) ) {
		$e['public_first'] = ( isset( $submitter['first_name'] ) ) ? $submitter['first_name'] : '';
		$e['public_last']  = ( isset( $submitter['last_name'] ) ) ? $submitter['last_name'] : '';
		$e['public_email'] = ( isset( $submitter['email'] ) ) ? $submitter['email'] : '';
	} else {
		$e['public_first'] = '';
		$e['public_last']  = '';
		$e['public_email'] = '';
	}

	return $e;
}

/**
 * Process required fields to check status.
 *
 * @param string $errors error message.
 * @param array  $submit Submitted data after processing.
 *
 * @return string
 */
function mcs_handle_required_fields( $errors, $submit ) {
	// This requirement applies only to front-end submissions.
	if ( ! is_admin() ) {
		/**
		 * Filter required fields.
		 *
		 * @hook mcs_required_fields
		 *
		 * @param {array} $mcs_required_fields Array of fields keys required by forms.
		 *
		 * @return {array}
		 */
		$required_fields = apply_filters( 'mcs_required_fields', get_option( 'mcs_required_fields', array() ) );
		$map             = array(
			'event_title'       => 'event_title',
			'end_date'          => 'event_end',
			'end_time'          => 'event_endtime',
			'event_host'        => 'event_host',
			'description'       => 'event_desc',
			'short_description' => 'event_short',
			'event_link'        => 'event_link',
			'event_image'       => 'event_image',
		);
		$labels          = array(
			'event_title'       => 'Event Title',
			'end_date'          => 'End Date',
			'end_time'          => 'End Time',
			'event_host'        => 'Event Host',
			'description'       => 'Event Description',
			'short_description' => 'Excerpt',
			'event_link'        => 'Event Link',
			'event_image'       => 'Event Image',
		);
		$ids             = array(
			'event_title'       => 'mc_event_title',
			'end_date'          => 'mc_event_enddate',
			'end_time'          => 'mc_event_endtime',
			'event_host'        => 'e_host',
			'description'       => 'mc_event_description',
			'short_description' => 'mc_event_short_description',
			'event_link'        => 'mc_event_link',
			'event_image'       => 'mc_event_image',
		);
		foreach ( $required_fields as $field ) {
			if ( ! isset( $map[ $field ] ) ) {
				continue;
			}
			$key   = $map[ $field ];
			$value = $submit[ $key ];
			$label = $labels[ $field ];
			$id    = $ids[ $field ];
			$link  = "#$id";
			if ( ! $value || 'Untitled Event' === $value ) {
				// Translators: Label of required field.
				$errors .= "<a href='$link'>" . sprintf( __( '%s is a required field.', 'my-calendar-pro' ), $label ) . '</a><br />';
			}
		}
		$errors = mc_show_error( $errors, false );
	}

	return $errors;
}
add_filter( 'mc_fields_required', 'mcs_handle_required_fields', 10, 2 );

/**
 * Filter selected submission notices to be more suitable for front-end usage.
 *
 * @param string       $message Original message.
 * @param string|false $code Notification code.
 *
 * @return string
 */
function mcs_show_notice( $message, $code = false ) {
	if ( ! $code ) {
		return $message;
	}
	if ( ! is_admin() ) {
		switch ( $code ) {
			case 'event-deleted':
				$message = __( 'You\'ve deleted your event!', 'my-calendar-pro' );
				break;
			case 'event-updated':
				$message = __( 'Your event has been updated.', 'my-calendar-pro' );
				break;
			case 'date-updated':
				$message = __( 'The date of your event has been changed.', 'my-calendar-pro' );
				break;
			case 'new-event':
				$message = __( 'Your event has been published!', 'my-calendar-pro' );
				break;
			case 'draft-saved':
				$message = __( 'Thanks for submitting an event! Your event will be reviewed by an administrator.', 'my-calendar-pro' );
				break;
			default:
				$message = $message;
		}
	}

	return $message;
}
add_filter( 'mc_filter_notice', 'mcs_show_notice', 10, 2 );

/**
 * Render My Calendar event submissions on configured page.
 *
 * @param string $content Post content.
 *
 * @return string
 */
function mcs_render_submit( $content ) {
	$mcs_submit_id = get_option( 'mcs_submit_id' );
	if ( is_singular() && get_queried_object_id() === (int) $mcs_submit_id ) {
		if ( ! has_shortcode( $content, 'submit_event' ) ) {
			$content .= mcs_submit_form( array(), '' );
		}
	}

	return $content;
}
add_filter( 'the_content', 'mcs_render_submit' );