Source: my-calendar-custom-fields.php

<?php
/**
 * Custom Field Creator
 *
 * @category Features
 * @package  My Calendar Pro
 * @author   Joe Dolson
 * @license  GPLv2 or later
 * @link     https://www.joedolson.com/my-calendar-pro/
 */

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

/**
 * Generate a unique array key for a form.
 *
 * @param string $key Possible unique ID.
 * @param array  $keys Currently defined keys.
 *
 * @return string
 */
function mcs_generate_array_key( $key, $keys ) {
	if ( ! in_array( $key, $keys, true ) ) {
		return $key;
	} else {
		return mcs_generate_array_key( uniqid(), $keys );
	}
}

add_filter( 'mcs_custom_settings_update', 'mcs_custom_field_update', 10, 2 );
/**
 * Update settings for responsive options.
 *
 * @param mixed $value Value.
 * @param array $post POST data.
 *
 * @return string
 */
function mcs_custom_field_update( $value, $post ) {
	// save custom field settings.
	if ( isset( $post['custom-fields_settings'] ) ) {
		$existing = get_option( 'mcs_custom_fields', array() );
		$keys     = array_keys( $existing );
		// New fields.
		if ( isset( $post['new_field'] ) ) {
			$fields = ( isset( $post['new_field'] ) ) ? map_deep( $post['new_field'], 'sanitize_textarea_field' ) : array();
			if ( function_exists( 'transliterator_transliterate' ) ) {
				$id = transliterator_transliterate( 'Any-Latin; Latin-ASCII; Lower()', $fields['field_label'] );
				$id = sanitize_title( $id );
			} else {
				$id = sanitize_title( $fields['field_label'] );
			}
			$fields['id'] = $id;
			$key          = ( isset( $existing[ $id ] ) ) ? $id . '_' . mcs_generate_array_key( uniqid(), $keys ) : $id;
			$fields       = array( $key => $fields );
			$update       = array_merge( $existing, $fields );

			update_option( 'mcs_custom_fields', $update );
		}
		// Updating fields.
		if ( isset( $post['fields'] ) ) {
			$key = sanitize_title( $post['field_key'] );
			if ( ! isset( $post['fields'][ $key ]['id'] ) || '' === $post['fields'][ $key ]['id'] ) {
				$post['fields'][ $key ]['id'] = sanitize_title( $post['fields'][ $key ]['field_label'] );
			}
			$existing[ $key ] = map_deep( $post['fields'][ $key ], 'sanitize_textarea_field' );
			update_option( 'mcs_custom_fields', $existing );
		}

		return __( 'Custom Fields Updated', 'my-calendar-pro' );
	}

	return $value;
}

add_filter( 'mcs_settings_tabs', 'mcs_custom_field_tabs' );
/**
 * Add responsive tab to tab list.
 *
 * @param array $tabs existing tabs.
 *
 * @return array $tabs
 */
function mcs_custom_field_tabs( $tabs ) {
	$tabs['custom-fields'] = __( 'Custom Fields', 'my-calendar-pro' );

	return $tabs;
}

add_filter( 'mcs_settings_panels', 'mcs_custom_field_settings' );
/**
 * Add custom fields panel to panel list.
 *
 * @param array $panels existing panels.
 *
 * @return array $panels
 */
function mcs_custom_field_settings( $panels ) {
	$fields   = mcs_get_fields( 'editor' );
	$wp_nonce = wp_nonce_field( 'custom-fields', '_wpnonce', true, false );
	$editor   = '';
	foreach ( $fields as $key => $field ) {
		$field_output = mcs_create_field_editor( $key, $field );
		$editor      .= $field_output;
	}
	$new_field   = mcs_create_editor();
	$nav         = mcs_fields_nav();
	$editable    = mcs_get_fields( 'editor' );
	$add_button  = get_submit_button( __( 'Add Custom Field', 'my-calendar-pro' ), 'primary', 'custom-fields_settings' );
	$edit_button = ( count( $editable ) > 0 ) ? get_submit_button( __( 'Update Custom Fields', 'my-calendar-pro' ), 'primary', 'custom-fields_settings' ) : '';
	// Translators: URL to form builder.
	$fields_note = sprintf( __( 'New custom fields are added to Submissions forms using their field tags in shortcodes or in the <a href="%s">form building tool</a>. All fields are added to the admin form automatically. Required fields are only required on the front-end.', 'my-calendar-pro' ), esc_url( admin_url( 'admin.php?page-my-calendar-shortcodes#mc_builder' ) ) );
	$controls    = "<p id='custom_fields_note'>
						" . $fields_note . "
					</p>
					$new_field
					$add_button
				</div>
			</div>
		</div>
	</form>
	<div class='ui-sortable meta-box-sortables'>
		<div class='postbox'>
			<h2>" . __( 'Custom Fields', 'my-calendar-pro' ) . "</h2>
			<div class='inside'>" . $nav;
	$controls   .= ( count( $editable ) > 0 ) ? "</div>
		</div>
	</div>
	<form method='post' action='" . admin_url( 'admin.php?page=my-calendar-submissions#mcs_custom-fields_tab' ) . "' enctype='multipart/form-data'>" . $wp_nonce . '
		<div class="ui-sortable meta-box-sortables">
			<div class="postbox">
				<h2>' . __( 'Edit Custom Fields', 'my-calendar-pro' ) . '</h2>
				<div class="inside">' . $editor : '';

	$panels['custom-fields'] = '<h2>' . __( 'Custom Fields', 'my-calendar-pro' ) . '</h2>
		<div class="inside">' . $controls . $edit_button . '</div>';

	return $panels;
}

/**
 * List fields for in-page navigation.
 *
 * @return string
 */
function mcs_fields_nav() {
	$fields    = mcs_get_fields();
	$editable  = mcs_get_fields( 'editor' );
	$diff      = array_diff_key( $fields, $editable );
	$edit_list = '';
	$add_list  = '';
	if ( count( $editable ) > 0 ) {
		foreach ( $editable as $key => $field ) {
			$label      = ( isset( $field['field_label'] ) && '' !== $field['field_label'] ) ? $field['field_label'] : 'Untitled';
			$edit_list .= '<li><a href="#mcs_editor_' . esc_attr( $key ) . '">' . esc_html( stripslashes( $label ) ) . '</a></li>';
		}
	} else {
		$edit_list .= '<li>' . __( 'None', 'my-calendar-pro' ) . '</li>';
	}
	if ( count( $diff ) > 0 ) {
		foreach ( $diff as $key => $field ) {
			$label     = ( '' !== $field['field_label'] ) ? $field['field_label'] : 'Untitled';
			$add_list .= '<li>' . esc_html( stripslashes( $label ) ) . '</li>';
		}
	} else {
		$add_list .= '<li>' . __( 'None', 'my-calendar-pro' ) . '</li>';
	}

	return '<div class="mc-editor-nav"><div class="mc-editor-editable"><h3>' . __( 'Editable Fields', 'my-calendar-pro' ) . '</h3><ul class="checkboxes">' . $edit_list . '</ul></div><div class="mc-editor-noneditable"><h3>' . __( 'Additional Custom Fields', 'my-calendar-pro' ) . '</h3><p>' . __( 'Fields configured from JSON or PHP sources.', 'my-calendar-pro' ) . '</p><ul class="checkboxes">' . $add_list . '</ul></div></div>';
}

/**
 * Create field editor.
 *
 * @param string $key Editing array key.
 * @param array  $field Field data.
 *
 * @return string
 */
function mcs_create_field_editor( $key, $field ) {
	$toggle = '<div class="mcs-editor-controls"><button type="submit" class="button hidden" name="custom-fields_settings">' . __( 'Update', 'my-calendar-pro' ) . '</button><button type="button" class="toggle-view toggle button-secondary"><span class="dashicons dashicons-edit" aria-hidden="true" data-key="' . esc_attr( $key ) . '"></span>' . __( 'Edit', 'my-calendar-pro' ) . '</button><button type="button" class="toggle-delete toggle button-secondary"><span class="dashicons dashicons-no" aria-hidden="true" data-key="' . esc_attr( $key ) . '"></span>' . __( 'Delete', 'my-calendar-pro' ) . '</button></div>';
	$input  = mcs_create_field( $key, $field );
	$editor = mcs_create_editor( $key, $field );
	$label  = isset( $field['field_label'] ) ? $field['field_label'] : '';
	// Translators: label for this field.
	$delete_text = sprintf( __( 'Delete &ldquo;%s&rdquo;', 'my-calendar-pro' ), $label );
	return '<div class="field-editor mcs-field-editor" id="mcs_editor_' . esc_attr( $key ) . '">
		' . $toggle . '
		<div class="field-input-view">' . $input . '</div>
		<div class="field-input-editor">' . $editor . '</div>
		<div class="field-input-notice"><button type="button" class="confirm-delete button-primary" data-key="' . esc_attr( $key ) . '">' . $delete_text . '</button> <button type="button" class="cancel-delete button-secondary" data-key="' . esc_attr( $key ) . '">Cancel</button></div>
	</div>';
}

/**
 * Create editor field.
 *
 * @param string $key Editing array key.
 * @param array  $source Field data.
 *
 * @return string
 */
function mcs_create_editor( $key = '', $source = array() ) {
	$schema = array(
		'field_label'    => '',
		'field_type'     => 'text',
		'field_notes'    => '',
		'field_options'  => '',
		'id'             => '',
		'field_required' => '',
		'field_default'  => '',
	);
	$field  = array_merge( $schema, $source );
	// Translators: Name of field currently displayed for editing.
	$legend   = ( empty( $source ) ) ? __( 'Create New Field', 'my-calendar-pro' ) : sprintf( __( 'Edit %s', 'my-calendar-pro' ), $field['field_label'] );
	$label    = $field['field_label'];
	$type     = $field['field_type'];
	$notes    = $field['field_notes'];
	$options  = $field['field_options'];
	$default  = $field['field_default'];
	$required = $field['field_required'];
	$prefix   = ( empty( $key ) ) ? 'new_' : $key . '_';
	$name     = ( empty( $key ) ) ? 'new_field[{field_name}]' : 'fields[' . $key . '][{field_name}]';
	$hidden   = ( empty( $key ) ) ? '' : '<input type="hidden" class="field_key" name="field_key" value="' . esc_attr( $key ) . '" />';
	$hidden  .= ( ! empty( $field['id'] ) ) ? '<input type="hidden" class="field_name" name="' . str_replace( '{field_name}', 'id', $name ) . '" value="' . esc_attr( $field['id'] ) . '" />' : '';
	$class    = ( empty( $key ) ) ? 'new-field-row' : 'editor-field-row';
	$output   = '
		<div class="field-row ' . $class . '">
			' . $hidden . '
			<fieldset>
				<legend>' . esc_html( stripslashes( $legend ) ) . '</legend>
				<div class="field-group">
					<div class="field-fields field-label">
						<label for="' . $prefix . 'field_label">' . __( 'Field Label', 'my-calendar-pro' ) . ' ' . mcs_required_text() . '</label><br />
						<input class="widefat mcs-field field-input-label" type="text" value="' . stripslashes( esc_attr( $label ) ) . '" id="' . $prefix . 'field_label" data-field="field_label" name="' . str_replace( '{field_name}', 'field_label', $name ) . '" required="required" />
					</div>
					<div class="field-fields field-type">
						<label for="' . $prefix . 'field_type">' . __( 'Field Type', 'my-calendar-pro' ) . '</label><br />
						<select data-field="field_type" class="widefat mcs-field field-input-type" id="' . $prefix . 'field_type" name="' . str_replace( '{field_name}', 'field_type', $name ) . '">
							<option value="text" ' . selected( 'text', $type, false ) . '>text</option>
							<option value="email" ' . selected( 'email', $type, false ) . '>email</option>
							<option value="number" ' . selected( 'number', $type, false ) . '>number</option>
							<option value="date" ' . selected( 'date', $type, false ) . '>date</option>
							<option value="time" ' . selected( 'time', $type, false ) . '>time</option>
							<option value="url" ' . selected( 'url', $type, false ) . '>url</option>
							<option value="tel" ' . selected( 'tel', $type, false ) . '>tel</option>
							<option value="select" ' . selected( 'select', $type, false ) . '>select</option>
							<option value="checkbox" ' . selected( 'checkbox', $type, false ) . '>checkbox (single)</option>
							<option value="checkbox-group" ' . selected( 'checkbox-group', $type, false ) . '>checkbox (group)</option>
							<option value="radio" ' . selected( 'radio', $type, false ) . '>radio</option>
							<option value="textarea" ' . selected( 'textarea', $type, false ) . '>textarea</option>
							<option value="hidden" ' . selected( 'hidden', $type, false ) . '>hidden</option>
						</select>
					</div>
					<div class="field-fields field-notes">
						<label for="' . $prefix . 'field_notes">' . __( 'Field Description', 'my-calendar-pro' ) . '</label><br />
						<input data-field="field_notes" class="widefat mcs-field field-input-notes" type="text" value="' . stripslashes( esc_attr( $notes ) ) . '" id="' . $prefix . 'field_notes" name="' . str_replace( '{field_name}', 'field_notes', $name ) . '" />
					</div>
					<div class="field-fields field-options">
						<label for="' . $prefix . 'field_options">' . __( 'Field Options (comma separated)', 'my-calendar-pro' ) . '</label><br />
						<input data-field="field_options" class="widefat mcs-field field-input-options" type="text" value="' . stripslashes( esc_attr( $options ) ) . '" id="' . $prefix . 'field_options" name="' . str_replace( '{field_name}', 'field_options', $name ) . '" />
					</div>
					<div class="field-fields field-default">
						<label for="' . $prefix . 'field_default">' . __( 'Field Default value', 'my-calendar-pro' ) . '</label><br />
						<input data-field="field_default" class="widefat mcs-field field-input-default" type="text" value="' . stripslashes( esc_attr( $default ) ) . '" id="' . $prefix . 'field_default" name="' . str_replace( '{field_name}', 'field_default', $name ) . '" />
					</div>
					<div class="field-fields field-required checkbox">
						<input data-field="field_required" class="mcs-field field-input-required" type="checkbox" value="true"' . checked( $required, 'true', false ) . ' name="' . str_replace( '{field_name}', 'field_required', $name ) . '" id="' . $prefix . 'field_required" /> <label for="' . $prefix . 'field_required">' . __( 'Required', 'my-calendar-pro' ) . '</label>
					</div>
				</div>
			</fieldset>
		</div>';

	return $output;
}

/**
 * Get My Calendar custom fields array.
 *
 * @param string $form_id Form ID.
 *
 * @return array
 */
function mcs_get_fields( $form_id = '' ) {
	$fields = get_option( 'mcs_custom_fields', array() );
	if ( 'editor' !== $form_id ) {
		// Don't pick up fields created from filters or JSON in editor.
		$json_fields = array();
		$file        = mc_file_exists( 'mc-custom-fields.json' );
		if ( $file ) {
			global $wp_filesystem;
			require_once ABSPATH . '/wp-admin/includes/file.php';
			WP_Filesystem();
			$json = $wp_filesystem->get_contents( mc_get_file( 'custom-fields.json' ) );
			$json = json_decode( $json, true );
			$keys = array_keys( $json );
			// Make sure the decoded JSON looks like the right format.
			if ( is_array( $json ) && ( isset( $json[ $keys[0] ]['field_type'] ) && isset( $json[ $keys[0] ]['field_label'] ) ) ) {
				$json_fields = $json;
			}
		}
		$fields = array_merge( $fields, $json_fields );
		/**
		 * Filter custom field array.
		 *
		 * @hook mcs_custom_fields
		 *
		* @param {array} $fields Array of field details.
		* @param {string} $form_id Form ID for form being rendered.
		*
		* @return {array}
		*/
		$fields = apply_filters( 'mcs_custom_fields', $fields, $form_id );
	}

	return $fields;
}

/**
 * Save custom fields into post meta.
 *
 * @param string   $action Current action (add, edit, copy).
 * @param object   $data Event data.
 * @param int      $event_id Event ID.
 * @param int|bool $result Insertion status.
 */
function mcs_event_save( $action, $data, $event_id, $result ) {
	$post = array_merge( $_GET, $_POST );
	$post = isset( $post['post'] ) ? $post['post'] : $post;
	// Parse serialized data.
	if ( is_string( $post ) ) {
		parse_str( $post, $data );
		$post = map_deep( $data, 'mcs_ajax_sanitize' );
	}
	$post_id = mc_get_event_post( $event_id );
	$fields  = mcs_get_fields();
	foreach ( $fields as $field ) {
		$key = 'mcs_' . $field['id'];
		// Collect and sanitize value.
		$sanitize = ( isset( $field['sanitize_callback'] ) && function_exists( $field['sanitize_callback'] ) ) ? $field['sanitize_callback'] : 'sanitize_textarea_field';
		$value    = isset( $post[ $key ] ) ? call_user_func( $sanitize, $post[ $key ] ) : '';
		update_post_meta( $post_id, $key, $value );
	}
}
add_action( 'mc_save_event', 'mcs_event_save', 20, 4 );

/**
 * Add custom field into template tags array.
 *
 * @param array  $details Array of template tags as $tag => $value.
 * @param object $event Event object as fetched from database.
 *
 * @return array $details
 */
function mcs_event_tag( $details, $event ) {
	$post_id = $event->event_post;
	$fields  = mcs_get_fields();
	foreach ( $fields as $field ) {
		$key   = 'mcs_' . $field['id'];
		$value = get_post_meta( $post_id, $key, true );
		/**
		 * Filter the value of a tag as rendered in the My Calendar template tagging.
		 *
		 * @hook mcs_format_tag
		 *
		 * @param {string|array} $value Value saved in post meta.
		 * @param {array}        $field Array of field information.
		 * @param {int}          $post_id Post ID.
		 *
		 * @return {string}
		 */
		$value   = apply_filters( 'mcs_format_tag', $value, $field, $post_id );
		$display = ( isset( $field['display_callback'] ) ) ? $field['display_callback'] : 'wp_kses_post';
		if ( is_array( $value ) ) {
			$details[ $key ] = map_deep( $value, $display );
		} else {
			$details[ $key ] = ( $value ) ? call_user_func( $display, $value ) : '';
		}
	}

	return $details;
}
add_filter( 'mc_filter_shortcodes', 'mcs_event_tag', 10, 2 );

/**
 * Add fields that are sortable in My Calendar Pro.
 *
 * @param array   $fields Array of fields available in Pro.
 * @param boolean $has_data If true, this is an event being edited or corrected.
 * @param object  $event The event object saved.
 * @param string  $context 'public' or 'admin', depending on whether this is being rendered in the Pro submissions form or WP Admin.
 * @param string  $form_id Unique ID string for this form.
 *
 * @since My Calendar Pro 2.0.0
 *
 * @return array of fields
 */
function mcs_customize_event_form( $fields, $has_data, $event, $context, $form_id ) {
	$custom_fields = mcs_get_fields();
	$all_fields    = array_merge( $fields, $custom_fields );
	$return        = array();
	foreach ( $all_fields as $field => $data ) {
		if ( in_array( $field, array_keys( $fields ), true ) ) {
			$return[ $field ] = $data;
		} else {
			$return[ $field ] = isset( $custom_fields[ $field ] ) ? mcs_create_field( $field, $custom_fields[ $field ], $event ) : '';
		}
	}

	return $return;
}
add_filter( 'mc_custom_fields', 'mcs_customize_event_form', 10, 5 );

/**
 * Add the input field for your custom field into the main section of My Calendar event details.
 * These fields will be lumped together in the 'custom_fields' parameter in My Calendar Pro. To make sortable in Pro without being duplicated, make conditional based on the `$context` param. This example will only show in the admin.
 *
 * @param string  $form HTML of any other added custom fields.
 * @param boolean $has_data If true, this is an event being edited or corrected.
 * @param object  $event The event object saved.
 * @param string  $context 'public' or 'admin', depending on whether this is being rendered in the Pro submissions form or WP Admin.
 *
 * @return string
 */
function mcs_custom_fields( $form, $has_data, $event, $context ) {
	if ( 'admin' === $context ) {
		$fields = mcs_get_fields();
		foreach ( $fields as $key => $field ) {
			$form .= mcs_create_field( $key, $field, $event );
		}
	}

	return $form;
}
add_filter( 'mc_event_details', 'mcs_custom_fields', 10, 4 );

/**
 * Create a field from array data.
 *
 * @param string $key Field key.
 * @param array  $field Field data array.
 * @param object $event Event object if exists.
 * @param string $form_id ID for current form if passed.
 *
 * @return string
 */
function mcs_create_field( $key, $field, $event = null, $form_id = '' ) {
	$output = '';
	/**
	 * Test whether a field is called for the current form.
	 *
	 * @hook mcs_apply_custom_field
	 *
	 * @param {bool}   $continue False to ignore this field.
	 * @param {array}  $field Array of field data.
	 * @param {object} $event Event data.
	 * @param {string} $form_id ID for this form.
	 *
	 * @return bool
	 */
	$continue = apply_filters( 'mcs_apply_custom_field', true, $field, $event, $form_id );
	if ( $continue ) {
		$field_id = ( isset( $field['id'] ) ) ? $field['id'] : '';
		$default  = ( isset( $field['field_default'] ) ) ? $field['field_default'] : '';
		// If there's no key, it's not possible to fetch data.
		$value       = ( $event && $field_id ) ? get_post_meta( $event->event_post, 'mcs_' . $field_id, true ) : $default;
		$name        = 'mcs_' . esc_attr( $field_id );
		$id          = $key;
		$is_required = ( isset( $field['field_required'] ) && ! empty( $field['field_required'] ) ) ? true : false;
		$required    = $is_required && ! is_admin() ? ' required' : '';
		$req_text    = is_admin() ? __( 'required for public submitters', 'my-calendar-pro' ) : __( 'required', 'my-calendar-pro' );
		$req_label   = $is_required ? ' <span class="required">(' . $req_text . ')</span>' : '';
		$type        = isset( $field['field_type'] ) ? $field['field_type'] : 'text';
		$escape      = ( isset( $field['escape_callback'] ) && function_exists( $field['escape_callback'] ) ) ? $field['escape_callback'] : '';
		$notes       = isset( $field['field_notes'] ) ? '<span class="mcs-field-notes" id="' . $id . '_notes">' . esc_html( stripslashes( $field['field_notes'] ) ) . '</span>' : '';
		$labelled    = isset( $field['field_notes'] ) ? ' aria-describedby="' . $id . '_notes"' : '';
		$label       = isset( $field['field_label'] ) ? $field['field_label'] : '';
		switch ( $type ) {
			case 'text':
			case 'number':
			case 'email':
			case 'url':
			case 'date':
			case 'tel':
				$value  = ( $escape ) ? call_user_func( $escape, $value ) : esc_attr( $value );
				$output = "<input class='widefat mcs-$type-input' type='" . esc_attr( $type ) . "' name='$name' id='$id' value='$value'$required $labelled />$notes";
				break;
			case 'textarea':
				$value  = ( $escape ) ? call_user_func( $escape, $value ) : esc_textarea( $value );
				$output = "<textarea class='widefat mcs-textarea-input' rows='6' cols='60' name='$name' id='$id'$required $labelled>" . stripslashes( $value ) . '</textarea>' . $notes;
				break;
			case 'select':
				if ( isset( $field['field_options'] ) ) {
					$output = "<select class='widefat mcs-select-input'  name='$name' id='$id'$required $labelled>";
					if ( ! is_array( $field['field_options'] ) ) {
						$field_options = explode( ',', $field['field_options'] );
					} else {
						$field_options = $field['field_options'];
					}
					$field_options = map_deep( $field_options, 'trim' );
					foreach ( $field_options as $val ) {
						$value = stripslashes( $value );
						$val   = stripslashes( $val );
						if ( $value === $val ) {
							$selected = " selected='selected'";
						} else {
							$selected = '';
						}
						$output .= "<option value='" . esc_attr( trim( $val ) ) . "'$selected>" . esc_html( trim( $val ) ) . "</option>\n";
					}
					$output .= '</select>' . $notes;
				}
				break;
			case 'checkbox':
				if ( isset( $field['field_options'] ) ) {
					$values = $field['field_options'];
					if ( '' !== $value ) {
						$checked = ' checked="checked"';
					} else {
						$checked = '';
					}
					$output = "<input class='mcs-checkbox-input'  type='" . esc_attr( $field['field_type'] ) . "' name='$name' id='$id' value='" . esc_attr( stripslashes( $values ) ) . "'$checked $required $labelled />";
				}
				break;
			case 'checkbox-group':
			case 'radio':
				$output = '';
				if ( isset( $field['field_options'] ) ) {
					if ( ! is_array( $field['field_options'] ) ) {
						$field_options = explode( ',', $field['field_options'] );
					} else {
						$field_options = $field['field_options'];
					}
					$field_options = map_deep( $field_options, 'trim' );
					$output       .= '<ul class="mcs-field-list">';
					foreach ( $field_options as $val ) {
						$els = ( false === stripos( $val, '|' ) ) ? false : explode( '|', $val );
						if ( ! $els ) {
							$els = array(
								'label' => ucwords( $val ),
								'value' => $val,
							);
						} else {
							$els = array(
								'label' => $els[1],
								'value' => $els[0],
							);
						}
						$value = stripslashes( $value );
						$val   = stripslashes( $els['value'] );
						if ( $value === $val ) {
							$checked = " checked='checked'";
						} else {
							$checked = '';
						}
						$group_label = $els['label'];
						$group_id    = $id . '-' . md5( $val );
						$field_type  = ( 'checkbox-group' === $field['field_type'] ) ? 'checkbox' : 'radio';
						$output     .= "<li><input class='mcs-$field_type-input' type='" . esc_attr( $field_type ) . "' name='$name' id='$group_id' value='" . esc_attr( stripslashes( $val ) ) . "'$checked $required />";
						$output     .= " <label for='$group_id'><span class='label'>" . esc_html( stripslashes( $group_label ) ) . '</span></label></li>';
					}
					$output .= '</ul>';
				}
				break;
			case 'hidden':
				$output = "<input type='hidden' name='$name' id='$id' value='" . esc_attr( $value ) . "' />";
				break;
			default:
				$output = "<input class='widefat mcs-text' type='text' name='$name' id='$id' value='" . esc_attr( $value ) . "' $required $labelled />$notes";
		}
		$class    = 'mcs-' . sanitize_html_class( sanitize_title( strtolower( $label ) ) );
		$tag      = ' <code>{mcs_' . $id . '}</code>';
		$function = ' <code>mc_template_tag( ' . '$' . "data, 'mcs_" . $id . "' )</code>";
		if ( 'checkbox' === $type ) {
			$output = "<div class='mcs-custom-field $class mcs-$type'>$output <label for='$id'><span class='label'>" . esc_html( stripslashes( $label ) ) . '</span>' . $req_label . '</label>' . $notes . '</div>';
		} elseif ( 'radio' === $type || 'checkbox-group' === $type ) {
			$output = "<div class='mcs-custom-field $class mcs-$type'><fieldset aria-labelledby='$id-legend" . ' ' . $id . '_notes' . "'><legend id='$id-legend'>" . esc_html( stripslashes( $label ) ) . "$req_label</legend>$output$notes</fieldset></div>";
		} else {
			$output = "<div class='mcs-custom-field $class mcs-$type'><p><label for='$id'><span class='label'>" . esc_html( stripslashes( $label ) ) . '</span>' . $req_label . '</label> ' . $output . '</p></div>';
		}
		if ( isset( $_GET['page'] ) && 'my-calendar-submissions' === $_GET['page'] && ! mcs_core_custom_field( $id ) ) {
			$output .= '<ul>
				<li>' . __( 'Template tag:', 'my-calendar-pro' ) . $tag . '</li>
				<li>' . __( 'Template function:', 'my-calendar-pro' ) . $function . '</li>
			</ul>';
		}
	}

	return $output;
}

/**
 * Check whether a field is in the core settings fields, because the same function produces custom fields and misc. settings.
 *
 * @param string $id Field ID.
 *
 * @return bool
 */
function mcs_core_custom_field( $id ) {
	$fields = array( 'multilingual_events', 'private_content', 'custom_hosts', 'migrate_hosts' );
	if ( in_array( $id, $fields, true ) ) {
		return true;
	}

	return false;
}

/**
 * Delete a custom field.
 *
 * @param string $field_key An array key referencing a field.
 */
function mcs_delete_field( $field_key = '' ) {
	$fields   = get_option( 'mcs_custom_fields', array() );
	$original = $fields;
	unset( $fields[ $field_key ] );
	update_option( 'mcs_custom_fields', $fields );
	$response = array(
		'success'  => 1,
		'response' => __( 'Field has been deleted', 'my-calendar-pro' ),
		'data'     => $fields,
		'original' => $original,
		'key'      => $field_key,
	);

	return $response;
}

/**
 * Handle AJAX field update.
 *
 * @param array $request AJAX Request data.
 *
 * @return array
 */
function mcs_update_field( $request ) {
	$fields    = get_option( 'mcs_custom_fields', array() );
	$field_key = $request['field_key'];
	$editing   = isset( $fields[ $field_key ] ) ? $fields[ $field_key ] : false;
	$new       = $editing;
	if ( ! $editing ) {
		$response = array(
			'success'  => 0,
			'response' => __( 'Field not found. Save new fields first.', 'my-calendar-pro' ),
		);
	} else {
		$keys  = $request['keys'];
		$vals  = $request['values'];
		$index = 0;
		$new   = array();
		foreach ( $keys as $key ) {
			$new[ $key ] = $vals[ $index ];
			++$index;
		}
		$updated              = array_merge( $editing, $new );
		$fields[ $field_key ] = $updated;
		update_option( 'mcs_custom_fields', $fields );
		$response = array(
			'success'  => 1,
			'response' => __( 'Custom field updated', 'my-calendar-pro' ),
			'data'     => $updated,
			'original' => $editing,
			'new'      => $new,
		);
	}

	return $response;
}

/**
 * Handle AJAX field actions.
 *
 * @return void
 */
function mcs_fields_action() {
	if ( isset( $_REQUEST['action'] ) && 'mcs_fields_action' === $_REQUEST['action'] ) {
		$security  = $_REQUEST['security'];
		$field_key = ( isset( $_REQUEST['field_key'] ) ) ? sanitize_text_field( $_REQUEST['field_key'] ) : false;

		if ( ! wp_verify_nonce( $security, 'mcs-fields-action' ) ) {
			wp_send_json(
				array(
					'success'  => 0,
					'response' => '',
				)
			);
		}
		if ( isset( $_REQUEST['mcs_delete_field'] ) ) {
			$response = mcs_delete_field( $field_key );
		}
		if ( isset( $_REQUEST['mcs_update_field'] ) ) {
			$response = mcs_update_field( $_REQUEST );
		}

		wp_send_json( $response );
	}
}
add_action( 'wp_ajax_mcs_fields_action', 'mcs_fields_action' );
add_action( 'wp_ajax_nopriv_mcs_fields_action', 'mcs_fields_action' );