<?php

// metaboxes directory constant
define( 'CUSTOM_METABOXES_DIR', get_template_directory_uri() . '/includes/plugins/metaboxes' );

function in_array_of_terms($array, $hierarchical, $term_id, $slug) {
	$item = null;
	foreach($array as $term) {
		if ($hierarchical && $term_id == $term->term_id) {
			$item = $term->term_id;
			break;
		} else if (!$hierarchical && $slug == $term->slug) {
			$item = $term->slug;
			break;		
		}
	}
	return $item;
}

/**
 * recives data about a form field and spits out the proper html
 *
 * @param	array					$field			array with various bits of information about the field
 * @param	string|int|bool|array	$meta			the saved data for this field
 * @param	array					$repeatable		if is this for a repeatable field, contains parant id and the current integar
 *
 * @return	string									html for the field
 */
function custom_meta_box_field( $field, $meta = null, $repeatable = null ) {
	if ( ! ( $field || is_array( $field ) ) )
		return;
	
	// get field data
	$std = isset( $field['std'] ) ? $field['std'] : null;
	$type = isset( $field['type'] ) ? $field['type'] : null;
	$label = isset( $field['label'] ) ? $field['label'] : null;
	$desc = isset( $field['desc'] ) ? '<span class="description">' . $field['desc'] . '</span>' : null;
	$place = isset( $field['place'] ) ? $field['place'] : null;
	$size = isset( $field['size'] ) ? $field['size'] : null;
	$min = isset( $field['min'] ) ? $field['min'] : null;
	$max = isset( $field['max'] ) ? $field['max'] : null;
	$step = isset( $field['step'] ) ? $field['step'] : null;
	$post_type = isset( $field['post_type'] ) ? $field['post_type'] : null;
	$options = isset( $field['options'] ) ? $field['options'] : null;
	$settings = isset( $field['settings'] ) ? $field['settings'] : null;
	$repeatable_fields = isset( $field['repeatable_fields'] ) ? $field['repeatable_fields'] : null;
	
	// the id and name for each field
	$id = $name = isset( $field['id'] ) ? $field['id'] : null;
	if ( $repeatable ) {
		$name = $repeatable[0] . '[' . $repeatable[1] . '][' . $id .']';
		$id = $repeatable[0] . '_' . $repeatable[1] . '_' . $id;
	}
	switch( $type ) {
		// basic
		case 'text':
		case 'tel':
		case 'email':
		default:
			echo '<input type="' . $type . '" name="' . esc_attr( $name ) . '" id="' . esc_attr( $id ) . '" value="' . esc_attr( $meta ) . '" class="regular-text" size="30" />
					<br />' . $desc;
		break;
		case 'url':
			echo '<input type="' . $type . '" name="' . esc_attr( $name ) . '" id="' . esc_attr( $id ) . '" value="' . esc_url( $meta ) . '" class="regular-text" size="30" />
					<br />' . $desc;
		break;
		case 'number':
			$meta = isset($meta) && $meta !== "" ? $meta : $std;
			echo '<input type="' . $type . '" name="' . esc_attr( $name ) . '" id="' . esc_attr( $id ) . '" value="' . intval( $meta ) . '" ' .
			(isset($min) ? 'min="' . esc_attr( $min ) . '"' : '') .
			(isset($max) ? 'max="' . esc_attr( $max ) . '"' : '') .
			(isset($step) ? 'step="' . esc_attr( $step ) . '"' : '') .
			' class="regular-text" size="30" /><br />' . $desc;
		break;
		case 'decimal':
			echo '<input type="number" name="' . esc_attr( $name ) . '" id="' . esc_attr( $id ) . '" value="' . floatval( $meta ) . '" class="regular-text" size="15" />
					<br />' . $desc;
		break;
		// textarea
		case 'textarea':
			echo '<textarea name="' . esc_attr( $name ) . '" id="' . esc_attr( $id ) . '" cols="60" rows="4">' . esc_textarea( $meta ) . '</textarea>
					<br />' . $desc;
		break;
		// editor
		case 'editor':
			echo wp_editor( $meta, $id, $settings ) . '<br />' . $desc;
		break;
		// checkbox
		case 'checkbox':
			echo '<input type="checkbox" name="' . esc_attr( $name ) . '" id="' . esc_attr( $id ) . '" ' . checked( $meta, true, false ) . ' value="1" />
					<label for="' . esc_attr( $id ) . '">' . $desc . '</label>';
		break;
		// select, chosen
		case 'select':
		case 'chosen':
			echo '<select name="' . esc_attr( $name ) . '" id="' . esc_attr( $id ) . '"' , $type == 'chosen' ? ' class="chosen"' : '' , isset( $multiple ) && $multiple == true ? ' multiple="multiple"' : '' , '>
					<option value="">Select One</option>'; // Select One
			$sel = null;
			foreach ( $options as $option ) {
				if ($option['value'] == $meta) {
					$sel = $meta;
				}
			}
			if (!isset($sel)) {
				$sel = $std;
			}
					
			foreach ( $options as $option )
				echo '<option' . selected( $sel, $option['value'], false ) . ' value="' . $option['value'] . '">' . $option['label'] . '</option>';
			echo '</select><br />' . $desc;
		break;
		// radio
		case 'radio':
			echo '<ul class="meta_box_items ' . esc_attr( $name ) . '">';
			foreach ( $options as $option )
				echo '<li><input type="radio" name="' . esc_attr( $name ) . '" id="' . esc_attr( $id ) . '-' . $option['value'] . '" value="' . $option['value'] . '" ' . checked( $meta, $option['value'], false ) . ' />
						<label for="' . esc_attr( $id ) . '-' . $option['value'] . '">' . $option['label'] . '</label></li>';
			echo '</ul>' . $desc;
		break;
		// checkbox_group
		case 'checkbox_group':
			echo '<ul class="meta_box_items ' . esc_attr( $name ) . '">';
			foreach ( $options as $option )
				echo '<li><input type="checkbox" value="' . $option['value'] . '" name="' . esc_attr( $name ) . '[]" id="' . esc_attr( $id ) . '-' . $option['value'] . '"' , is_array( $meta ) && in_array( $option['value'], $meta ) ? ' checked="checked"' : '' , ' /> 
						<label for="' . esc_attr( $id ) . '-' . $option['value'] . '">' . $option['label'] . '</label></li>';
			echo '</ul>' . $desc;
		break;
		// color
		case 'color':
			$meta = $meta ? $meta : '#';
			echo '<input type="text" name="' . esc_attr( $name ) . '" id="' . esc_attr( $id ) . '" value="' . $meta . '" size="10" />
				<br />' . $desc;
			echo '<div id="colorpicker-' . esc_attr( $id ) . '"></div>
				<script type="text/javascript">
				jQuery(function(jQuery) {
					jQuery("#colorpicker-' . esc_attr( $id ) . '").hide();
					jQuery("#colorpicker-' . esc_attr( $id ) . '").farbtastic("#' . esc_attr( $id ) . '");
					jQuery("#' . esc_attr( $id ) . '").bind("blur", function() { jQuery("#colorpicker-' . esc_attr( $id ) . '").slideToggle(); } );
					jQuery("#' . esc_attr( $id ) . '").bind("focus", function() { jQuery("#colorpicker-' . esc_attr( $id ) . '").slideToggle(); } );
				});
				</script>';
		break;
		// post_select, post_chosen
		case 'post_select':
		case 'post_list':
		case 'post_chosen':
			if (function_exists('icl_object_id')) {
				 $meta = icl_object_id( (int)$meta, $post_type[0], true );
			}
			echo '<select data-placeholder="Select One" name="' . esc_attr( $name ) . '" id="' . esc_attr( $id ) . '"' , $type == 'post_chosen' ? ' class="chosen"' : '' , isset( $multiple ) && $multiple == true ? ' multiple="multiple"' : '' , '>
					<option value=""></option>'; // Select One
			$posts = get_posts( array( 'post_type' => $post_type, 'posts_per_page' => -1, 'orderby' => 'title', 'order' => 'ASC', 'suppress_filters' => 0 ) );
			
			foreach ( $posts as $item )
				echo '<option value="' . $item->ID . '"' . selected( $item->ID, $meta, false ) . '>' . $item->post_title . '</option>';
			
			$post_type_object = get_post_type_object( $post_type[0] );
			
			
			echo '</select> &nbsp;<span class="description"><a href="' . admin_url( 'edit.php?post_type=' . $post_type[0]) . '">Manage ' . $post_type_object->labels->name . '</a></span><br />' . $desc;
		break;
		// post_checkboxes
		case 'post_checkboxes':
			if (function_exists('icl_object_id')) {
				if (is_array( $meta )) {
					for ($i=0;$i < sizeof($meta); $i++) {
						$meta[$i] = icl_object_id( (int)$meta[$i], $post_type[0], true );
					}
				}
			}
		
			$posts = get_posts( array( 'post_type' => $post_type, 'posts_per_page' => -1, 'suppress_filters' => 0, 'orderby' => 'title', 'order' => 'ASC' ) );
			echo '<ul class="meta_box_items ' . esc_attr( $name ) . '">';
			foreach ( $posts as $item ) 
				echo '<li><input type="checkbox" value="' . $item->ID . '" name="' . esc_attr( $name ) . '[]" id="' . esc_attr( $id ) . '-' . $item->ID . '"' , is_array( $meta ) && in_array( $item->ID, $meta ) ? ' checked="checked"' : '' , ' />
						<label for="' . esc_attr( $id ) . '-' . $item->ID . '">' . $item->post_title . '</label></li>';
			
			$post_type_object = get_post_type_object( $post_type[0] );
			echo '</ul> ' . $desc , ' &nbsp;<span class="description"><a href="' . admin_url( 'edit.php?post_type=' . $post_type[0]) . '">Manage ' . $post_type_object->labels->name . '</a></span>';
		break;
		// post_drop_sort
		case 'post_drop_sort':
			//areas
			$post_type_object = get_post_type_object( $post_type[0] );
			echo '<p>' . $desc . ' &nbsp;<span class="description"><a href="' . admin_url( 'edit.php?post_type=' . $post_type[0]) . '">Manage ' . $post_type_object->labels->name . '</a></span></p><div class="post_drop_sort_areas">';
			foreach ( $areas as $area ) {
				echo '<ul id="area-' . $area['id']  . '" class="sort_list">
						<li class="post_drop_sort_area_name">' . $area['label'] . '</li>';
						if ( is_array( $meta ) ) {
							$items = explode( ',', $meta[$area['id']] );
							foreach ( $items as $item ) {
								$output = $display == 'thumbnail' ? get_the_post_thumbnail( $item, array( 204, 30 ) ) : get_the_title( $item ); 
								echo '<li id="' . $item . '">' . $output . '</li>';
							}
						}
				echo '</ul>
					<input type="hidden" name="' . esc_attr( $name ) . '[' . $area['id'] . ']" 
					class="store-area-' . $area['id'] . '" 
					value="' , $meta ? $meta[$area['id']] : '' , '" />';
			}
			echo '</div>';
			// source
			$exclude = null;
			if ( !empty( $meta ) ) {
				$exclude = implode( ',', $meta ); // because each ID is in a unique key
				$exclude = explode( ',', $exclude ); // put all the ID's back into a single array
			}
			$posts = get_posts( array( 'post_type' => $post_type, 'posts_per_page' => -1, 'post__not_in' => $exclude, 'suppress_filters' => 0, 'orderby' => 'title', 'order' => 'ASC' ) );
			echo '<ul class="post_drop_sort_source sort_list">
					<li class="post_drop_sort_area_name">Available ' . $label . '</li>';
			foreach ( $posts as $item ) {
				$output = $display == 'thumbnail' ? get_the_post_thumbnail( $item->ID, array( 204, 30 ) ) : get_the_title( $item->ID ); 
				echo '<li id="' . $item->ID . '">' . $output . '</li>';
			}
			echo '</ul>';
		break;
		// tax_select
		case 'tax_select':
			echo '<select name="' . esc_attr( $name ) . '" id="' . esc_attr( $id ) . '"><option value="">Select One</option>'; // Select One
			$tax_id = $id;
			if ($repeatable)
			{
				// we are inside repeatable so get rid of tagged on text
				$tax_id = str_replace($repeatable[0] . '_' . $repeatable[1] . '_', '', $id);
			}
			$terms = get_terms( $tax_id, 'get=all&suppress_filters=0&orderby=name&order=ASC' );
			$post_terms = wp_get_object_terms( get_the_ID(), $tax_id );
			$taxonomy = get_taxonomy( $tax_id );
			// $selected = $post_terms ? $taxonomy->hierarchical ? $post_terms[0]->term_id : $post_terms[0]->slug : null;
			foreach ( $terms as $term ) {
				$selected = in_array_of_terms($post_terms, $taxonomy->hierarchical, $term->term_id, $term->slug); 
				$term_value = $taxonomy->hierarchical ? $term->term_id : $term->slug;
				echo '<option value="' . $term_value . '"' . selected( $selected, $term_value, false ) . '>' . $term->name . '</option>'; 
			}
			echo '</select> &nbsp;<span class="description"><a href="' . admin_url('edit-tags.php?taxonomy=' . $tax_id) . '">Manage ' . $taxonomy->label . '</a></span>
				<br />' . $desc;
		break;
		// tax_checkboxes
		case 'tax_checkboxes':
			$tax_id = $id;
			if ($repeatable)
			{
				// we are inside repeatable so get rid of tagged on text
				$tax_id = str_replace($repeatable[0] . '_' . $repeatable[1] . '_', '', $id);
			}
			$terms = get_terms( $tax_id, 'get=all&suppress_filters=0&orderby=name&order=ASC' );
			$post_terms = wp_get_object_terms( get_the_ID(), $tax_id );
			$taxonomy = get_taxonomy( $tax_id );

			// $checked = $post_terms ? $taxonomy->hierarchical ? $post_terms[0]->term_id : $post_terms[0]->slug : null;
			
			echo '<ul class="meta_box_items ' . esc_attr( $name ) . '">';
			foreach ( $terms as $term ) {
				$checked = in_array_of_terms($post_terms, $taxonomy->hierarchical, $term->term_id, $term->slug); 
				$checked = ($post_terms && $checked) ? $taxonomy->hierarchical ? $term->term_id : $term->slug : null;
				$term_value = $taxonomy->hierarchical ? $term->term_id : $term->slug;

				echo '<li>';
				echo '<input type="checkbox" value="' . $term_value . '" name="' . $id . '[]" id="term-' . $term_value . '"' . checked( $checked, $term_value, false ) . ' />';
				echo '<label for="term-' . $term_value . '">' . $term->name . '</label>';
				echo '</li>';
			}
			
			echo '</ul><span class="description">' . (isset($field['desc']) ? $field['desc'] : '') . ' <a href="' . admin_url('edit-tags.php?taxonomy=' . $tax_id) . '">Manage ' . $taxonomy->label . '</a></span>';
			
		break;
		// date
		case 'date':
			echo '<input type="text" class="datepicker" name="' . esc_attr( $name ) . '" id="' . esc_attr( $id ) . '" value="' . $meta . '" size="30" />
					<br />' . $desc;
		break;
		// slider
		case 'slider':
			
			$min_value = isset($field['min']) ? $field['min'] : '0';
			if ((!isset($meta) || $meta == '') && isset($std)) {
				$value = $std;
			}

			if (!isset($value)) {
				$value = $meta != '' ? intval( $meta ) : $min_value;
			}
			echo '<div id="' . esc_attr( $id ) . '-slider"></div>
					<input type="text" name="' . esc_attr( $name ) . '" id="' . esc_attr( $id ) . '" value="' . $value . '" size="5" />
					<br />' . $desc;
		break;
		// image
		case 'image':
			$image = CUSTOM_METABOXES_DIR . '/images/image.png';	
			echo '<div class="meta_box_image"><span class="meta_box_default_image" style="display:none">' . $image . '</span>';
			if ( $meta ) {
				$image = wp_get_attachment_image_src( intval( $meta ), 'medium' );
				$image = is_array($image) && count($image) > 0 ? $image[0] : '';
			}				
			echo	'<input name="' . esc_attr( $name ) . '" type="hidden" class="meta_box_upload_image" value="' . intval( $meta ) . '" />
						<img src="' . esc_attr( $image ) . '" class="meta_box_preview_image" alt="' . esc_attr( $name ) . '" />
							<a href="#" class="meta_box_upload_image_button button" rel="' . get_the_ID() . '">Choose Image</a>
							<small>&nbsp;<a href="#" class="meta_box_clear_image_button">Remove Image</a></small></div>
							<br clear="all" />' . $desc;
		break;
		// file
		case 'file':		
			$iconClass = 'meta_box_file';
			if ( $meta ) $iconClass .= ' checked';
			echo	'<div class="meta_box_file_stuff"><input name="' . esc_attr( $name ) . '" type="hidden" class="meta_box_upload_file" value="' . esc_url( $meta ) . '" />
						<span class="' . $iconClass . '"></span>
						<span class="meta_box_filename">' . esc_url( $meta ) . '</span>
							<a href="#" class="meta_box_upload_image_button button" rel="' . get_the_ID() . '">Choose File</a>
							<small>&nbsp;<a href="#" class="meta_box_clear_file_button">Remove File</a></small></div>
							<br clear="all" />' . $desc;
		break;
		// repeatable
		case 'repeatable':
			echo '<table id="' . esc_attr( $id ) . '-repeatable" class="meta_box_repeatable" cellspacing="0">
				<thead>
					<tr>
						<th><span class="sort_label"></span></th>
						<th>Fields</th>
						<th><a class="meta_box_repeatable_add" href="#"></a></th>
					</tr>
				</thead>
				<tbody>';
			$i = 0;
			// create an empty array
			if ( $meta == '' || $meta == array() ) {
				$keys = wp_list_pluck( $repeatable_fields, 'id' );
				$meta = array ( array_fill_keys( $keys, null ) );
			}
			$meta = array_values( $meta );
			foreach( $meta as $row ) {
				echo '<tr>
						<td><span class="sort hndle"></span></td><td>';
				foreach ( $repeatable_fields as $repeatable_field ) {
					if ( ! array_key_exists( $repeatable_field['id'], $meta[$i] ) )
						$meta[$i][$repeatable_field['id']] = null;
					echo '<label>' . $repeatable_field['label']  . '</label><p>';
					echo custom_meta_box_field( $repeatable_field, $meta[$i][$repeatable_field['id']], array( $id, $i ) );
					echo '</p>';
				} // end each field
				echo '</td><td><a class="meta_box_repeatable_remove" href="#"></a></td></tr>';
				$i++;
			} // end each row
			echo '</tbody>';
			echo '
				<tfoot>
					<tr>
						<th><span class="sort_label"></span></th>
						<th>Fields</th>
						<th><a class="meta_box_repeatable_add" href="#"></a></th>
					</tr>
				</tfoot>';
			echo '</table>
				' . $desc;
		break;
	} //end switch
		
}

/**
 * Finds any item in any level of an array
 *
 * @param	string	$needle 	field type to look for
 * @param	array	$haystack	an array to search the type in
 *
 * @return	bool				whether or not the type is in the provided array
 */
function meta_box_find_field_type( $needle, $haystack ) {

	foreach ( $haystack as $h ) {
		if ( isset( $h['type'] ) && $h['type'] == $needle ) {
			return true;
		} else if ( isset( $h['repeatable_type'] ) && $h['repeatable_type'] == $needle ) {
			return true;
		}
	}
	return false;
}

/**
 * Find repeatable
 *
 * This function does almost the same exact thing that the above function 
 * does, except we're exclusively looking for the repeatable field. The 
 * reason is that we need a way to look for other fields nested within a 
 * repeatable, but also need a way to stop at repeatable being true. 
 * Hopefully I'll find a better way to do this later.
 *
 * @param	string	$needle 	field type to look for
 * @param	array	$haystack	an array to search the type in
 *
 * @return	bool				whether or not the type is in the provided array
 */
function meta_box_find_repeatable( $needle, $haystack ) {
	foreach ( $haystack as $h )
		if ( isset( $h['type'] ) && $h['type'] == $needle )
			return true;
	return false;
}

/**
 * sanitize boolean inputs
 */
function meta_box_santitize_boolean( $string ) {
	if ( ! isset( $string ) || $string != 1 || $string != true )
		return false;
	else
		return true;
}

/**
 * outputs properly sanitized data
 *
 * @param	string	$string		the string to run through a validation function
 * @param	string	$function	the validation function
 *
 * @return						a validated string
 */
function meta_box_sanitize( $string, $function = 'sanitize_text_field' ) {
	$allowed_html = array(
		'h1' => array(
			'class' => true,
			'style' => true,
		),
		'h2' => array(
			'class' => true,
			'style' => true,
		),
		'h3' => array(
			'class' => true,
			'style' => true,
		),
		'h4' => array(
			'class' => true,
			'style' => true,
		),
		'h5' => array(
			'class' => true,
			'style' => true,
		),
		'h6' => array(
			'class' => true,
			'style' => true,
		),						
		'a' => array(
			'href' => true,
			'title' => true,
			'target' => true,	
			'class' => true,
			'id' => true,
			'rel' => true,
			'style' => true,
		),
		'abbr' => array(
			'title' => true,
			'style' => true,
		),
		'acronym' => array(
			'title' => true,
			'style' => true,
		),
		'b' => array(),
		'blockquote' => array(
			'cite' => true,
		),
		'cite' => array(),
		'code' => array(),
		'del' => array(
			'datetime' => true,
		),
		'em' => array(),
		'i' => array(
			'class' => true,
			'id' => true,
			'style' => true,
		),
		'q' => array(
			'cite' => true,
		),
		'strike' => array(),
		'strong' => array(),
		'iframe' => array(
			'width' => true,
			'height' => true,
			'frameborder' => true,
			'scrolling' => true,
			'marginheight' => true,
			'marginwidth' => true,
			'src' => true,
			'class' => true,
			'id' => true,
			'style' => true,		
		),
		'script' => array(
			'src' => true,
			'type' => true,
			'id' => true,
			'class' => true,
			'async' => true
		),
		'style' => array(
			'src' => true,
			'type' => true,
			'id' => true,
			'class' => true
		),
		'button' => array(
			'class' => true,
			'disabled' => true,
			'id' => true,
			'data-src' => true,
			'data-testid' => true
		),				
		'noscript' => array(),		
		'p' => array(
			'class' => true,
			'style' => true,
		),
		'br' => array(),
		'hr' => array(),
		'div' => array(
			'class' => true,
			'id' => true,
			'style' => true,
			'data-src' => true,
			'data-testid' => true			
		),
		'span' => array(
			'class' => true,
			'id' => true,
			'style' => true,
		),
		'img' => array(
			'src' => true, 
			'alt' => true, 
			'width' => true, 
			'height' => true,
			'class' => true,
			'id' => true,			
			'title' => true,
			'style' => true,
		),
		'table' =>  array(
			'id' => true,
			'class' => true,
			'style' => true,	
		),
		'tr' => array(
			'class' => true,
			'id' => true,
			'style' => true,	
		),
		'td' => array(
			'class' => true,
			'id' => true,
			'colspan' => true,
			'rowspan' => true,
			'style' => true,
		),
		'tbody' => array(
			'class' => true,
			'id' => true,
			'style' => true,	
		),		
		'tfoot' => array(
			'class' => true,
			'id' => true,
			'style' => true,		
		),
		'thead' => array(
			'class' => true,
			'id' => true,
			'style' => true,		
		),
		'ul' =>  array(
			'class' => true,
			'id' => true,
			'style' => true,
		),
		'ol' =>  array(
			'class' => true,
			'id' => true,
			'style' => true,
		),
		'li' => array(
			'class' => true,
			'id' => true,
			'style' => true,	
		)
	);
	
	$allowed_html = apply_filters('bookyourtravel_metabox_allowed_html', $allowed_html);

	switch ( $function ) {
		case 'intval':
			return intval( $string );
		case 'absint':
			return absint( $string );
		case 'wp_kses_post':
			return wp_kses( $string, $allowed_html );
		case 'wp_kses_data':
			return wp_kses_data( $string );
		case 'esc_url_raw':
			return esc_url_raw( $string );
		case 'is_email':
			return is_email( $string );
		case 'sanitize_title':
			return sanitize_title( $string );
		case 'santitize_boolean':
			return santitize_boolean( $string );
		case 'sanitize_text_field':
		default:
			return wp_kses( $string, $allowed_html );
	}
}

/**
 * Map a multideminsional array
 *
 * @param	string	$func		the function to map
 * @param	array	$meta		a multidimensional array
 * @param	array	$sanitizer	a matching multidimensional array of sanitizers
 *
 * @return	array				new array, fully mapped with the provided arrays
 */
function meta_box_array_map_r( $func, $meta, $sanitizer ) {
		
	$newMeta = array();
	$meta = array_values( $meta );
	
	foreach( $meta as $key => $array ) {
		if ( $array == '' )
			continue;
		/**
		 * some values are stored as array, we only want multidimensional ones
		 */
		if ( ! is_array( $array ) ) {
			return array_map( $func, $meta, (array)$sanitizer );
			break;
		}
		/**
		 * the sanitizer will have all of the fields, but the item may only 
		 * have valeus for a few, remove the ones we don't have from the santizer
		 */
		$keys = array_keys( $array );
		$newSanitizer = $sanitizer;
		if ( is_array( $sanitizer ) ) {
			foreach( $newSanitizer as $sanitizerKey => $value )
				if ( ! in_array( $sanitizerKey, $keys ) )
					unset( $newSanitizer[$sanitizerKey] );
		}
		/**
		 * run the function as deep as the array goes
		 */
		foreach( $array as $arrayKey => $arrayValue )
			if ( is_array( $arrayValue ) )
				$array[$arrayKey] = meta_box_array_map_r( $func, $arrayValue, $newSanitizer[$arrayKey] );
		
		$array = array_map( $func, $array, $newSanitizer );
		$newMeta[$key] = array_combine( $keys, array_values( $array ) );
	}
	return $newMeta;
}

/**
 * takes in a few peices of data and creates a custom meta box
 *
 * @param	string			$id			meta box id
 * @param	string			$title		title
 * @param	array			$fields		array of each field the box should include
 * @param	string|array	$page		post type to add meta box to
 */
class Custom_Add_Meta_Box {
	
	var $id;
	var $title;
	var $fields;
	var $tabs;
	var $page;
	
    public function __construct( $id, $title, $fields, $tabs, $page ) {
		$this->id = $id;
		$this->title = $title;
		$this->fields = $fields;
		$this->tabs = $tabs;
		$this->page = $page;
		
		if( ! is_array( $this->page ) )
			$this->page = array( $this->page );
		
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
		add_action( 'admin_head',  array( $this, 'admin_head' ) );
		add_action( 'add_meta_boxes', array( $this, 'add_box' ) );
		add_action( 'save_post',  array( $this, 'save_box' ));
    }
	
	/**
	 * enqueue necessary scripts and styles
	 */
	function admin_enqueue_scripts() {
		global $pagenow;
		
		if ( in_array( $pagenow, array( 'post-new.php', 'post.php' ) ) && in_array( get_post_type(), $this->page ) ) {
			// js
			$deps = array( 'jquery' );
			if ( meta_box_find_field_type( 'date', $this->fields ) )
				$deps[] = 'jquery-ui-datepicker';
			if ( meta_box_find_field_type( 'slider', $this->fields ) )
				$deps[] = 'jquery-ui-slider';
			if ( meta_box_find_field_type( 'color', $this->fields ) )
				$deps[] = 'farbtastic';
			if ( in_array( true, array(
				meta_box_find_field_type( 'chosen', $this->fields ),
				meta_box_find_field_type( 'post_chosen', $this->fields )
			) ) ) {
				wp_register_script( 'chosen', CUSTOM_METABOXES_DIR . '/js/chosen.js', array( 'jquery' ) );
				$deps[] = 'chosen';
				wp_enqueue_style( 'chosen', CUSTOM_METABOXES_DIR . '/css/chosen.css' );
			}
			if ( in_array( true, array( 
				meta_box_find_field_type( 'date', $this->fields ), 
				meta_box_find_field_type( 'slider', $this->fields ),
				meta_box_find_field_type( 'color', $this->fields ),
				meta_box_find_field_type( 'chosen', $this->fields ),
				meta_box_find_field_type( 'post_chosen', $this->fields ),
				meta_box_find_repeatable( 'repeatable', $this->fields ),
				meta_box_find_field_type( 'image', $this->fields ),
				meta_box_find_field_type( 'file', $this->fields )
			) ) ) {
				wp_enqueue_script( 'meta_box', CUSTOM_METABOXES_DIR . '/js/scripts.js', $deps );
			}
			
			// css
			$deps = array();
			wp_register_style( 'jqueryui', CUSTOM_METABOXES_DIR . '/css/jqueryui.css' );
			if ( meta_box_find_field_type( 'date', $this->fields ) || meta_box_find_field_type( 'slider', $this->fields ) ) {
				$deps[] = 'jqueryui';
				wp_enqueue_style('jqueryui');
			}
			if ( meta_box_find_field_type( 'color', $this->fields ) )
				$deps[] = 'farbtastic';
			
			wp_enqueue_style( 'meta_box', CUSTOM_METABOXES_DIR . '/css/meta_box.css', $deps );
		}
	}
	
	/**
	 * adds scripts to the head for special fields with extra js requirements
	 */
	function admin_head() {

		if (get_post_type() == "car_rental" && in_array( get_post_type(), $this->page )) {
			$in = meta_box_find_field_type( 'slider', $this->fields );
		}
		
		if ( in_array( get_post_type(), $this->page ) && ( meta_box_find_field_type( 'date', $this->fields ) || meta_box_find_field_type( 'slider', $this->fields ) ) ) {
			echo '<script type="text/javascript">
						jQuery(function( $) {
						';
			
			foreach ( $this->fields as $field ) {
				switch( $field['type'] ) {
					// date
					case 'date' :
						echo '$("#' . $field['id'] . '").datepicker({
								dateFormat: \'yy-mm-dd\'
							});';
					break;
					// slider
					case 'slider' :
					$value = get_post_meta( get_the_ID(), $field['id'], true );
					if ( $value == '' ) {
						if (isset($field['std'])) {
							$value = $field['std'];
						} else {
							$value = $field['min'];
						}
					}
					
					echo '
							if ($.fn.slider) {	
								$( "#' . $field['id'] . '-slider" ).slider({
									value: ' . $value . ',
									min: ' . $field['min'] . ',
									max: ' . $field['max'] . ',
									step: ' . $field['step'] . ',
									slide: function( event, ui ) {
										$( "#' . $field['id'] . '" ).val( ui.value );
									}
								});
							}
							';					

					break;
				}
			}
			
			echo '
				});
				</script>';
		
		}
	}
	
	/**
	 * adds the meta box for every post type in $page
	 */
	function add_box() {
		foreach ( $this->page as $page ) {
			add_meta_box( $this->id, $this->title, array( $this, 'meta_box_callback' ), $page, 'normal', 'high' );
		}
	}
	
	/**
	 * outputs the meta box
	 */
	function meta_box_callback() {
		// Use nonce for verification
		wp_nonce_field( 'custom_meta_box_nonce_action', 'custom_meta_box_nonce_field' );
		
		$initial_style = '';
		
		if (isset($this->tabs) && count($this->tabs) > 0) {
			$initial_style = 'display:none';
			echo '<div class="meta_fields_tabs">';
			echo '<ul class="meta_tabs">';
			
			foreach ( $this->tabs as $tab) {
				echo sprintf('<li class="%s" id="%s"><a href="#%s">%s</a></li>', $tab['class'], $tab['id'], $tab['class'], $tab['label']);
			}			
			
			echo '</ul>';
		}
		
		// Begin the field table and loop
		echo '<div class="form-table meta_box">';
		foreach ( $this->fields as $field) {
			$tab_id = isset($field['admin_tab_id']) ? $field["admin_tab_id"] : "";
			$field_container_class = isset($field['field_container_class']) ? $field["field_container_class"] : "";

			if ( $field['type'] == 'section' ) {
				echo sprintf('<div style="%s" class="tab-content %s"><div class="heading"><h2>%s</h2></div></div>', $initial_style, $tab_id, $field['label']);
			}
			else {
				echo sprintf('<div style="%s" class="tab-content %s">', $initial_style, $tab_id);
				echo sprintf('<div class="meta-holder %s">', $field_container_class);
				echo sprintf('<div class="label">%s</div>', $field['label']);
						
				echo '<div class="value">';
				$meta = get_post_meta( get_the_ID(), $field['id'], true);
				echo custom_meta_box_field( $field, $meta );
				echo '</div>';
				echo '</div>';
				echo '</div>';
			}
		} // end foreach
		echo '</div>'; // end table
		if (isset($this->tabs) && count($this->tabs) > 0) {
			echo '</div>';
		}
	}
	
	/**
	 * saves the captured data
	 */
	function save_box( $post_id ) {
		$post_type = get_post_type();
		
		// verify nonce
		if ( ! isset( $_POST['custom_meta_box_nonce_field'] ) )
			return $post_id;
		if ( ! ( in_array( $post_type, $this->page ) || wp_verify_nonce( $_POST['custom_meta_box_nonce_field'],  'custom_meta_box_nonce_action' ) ) ) 
			return $post_id;
		// check autosave
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
			return $post_id;
		// check permissions
		if ( ! current_user_can( 'edit_page', $post_id ) )
			return $post_id;
		
		// loop through fields and save the data
		foreach ( $this->fields as $field ) {
			if( $field['type'] == 'section' ) {
				$sanitizer = null;
				continue;
			}
			if( in_array( $field['type'], array( 'tax_select', 'tax_checkboxes' ) ) ) {
				// save taxonomies
				if ($field['type'] == 'tax_select') {
					if ( isset( $_POST[$field['id']] ) ) {
						$term = $_POST[$field['id']];
						if (is_numeric($term))
							wp_set_object_terms( $post_id, (int)$term, $field['id'] );
						else
							wp_set_object_terms( $post_id, $term, $field['id'] );
					}
				} else if ($field['type'] == 'tax_checkboxes') {
					if ( isset( $_POST[$field['id']] ) ) {
						$term = $_POST[$field['id']];
					} else {
						$term = array();
					}					
					if (is_numeric($term))
						wp_set_object_terms( $post_id, (int)$term, $field['id'] );
					else
						wp_set_object_terms( $post_id, $term, $field['id'] );
				}
			}
			else {
			
				// save the rest
				$new = false;
				$old = get_post_meta( $post_id, $field['id'], true );
				if ( isset( $_POST[$field['id']] ) )
					$new = $_POST[$field['id']];
				if ( isset( $new ) && '' == $new && $old ) {
					delete_post_meta( $post_id, $field['id'], $old );
				} elseif ( isset( $new ) ) { //  && $new != $old
					$sanitizer = isset( $field['sanitizer'] ) ? $field['sanitizer'] : 'sanitize_text_field';

					if ( is_array( $new ) )
						$new = meta_box_array_map_r( 'meta_box_sanitize', $new, $sanitizer );
					else
						$new = meta_box_sanitize( $new, $sanitizer );
						
					update_post_meta( $post_id, $field['id'], $new );
				}
			}
		} // end foreach
	}
	
}