<?php



/**

 * @author		Shahbaz Ahmed

 * @copyright	Copyright (c) 2014

 * @license		http://en.wikipedia.org/wiki/MIT_License The MIT License

 * @package		Taxonomy

 * @version		1.0

 */





add_action('admin_head', array('WPAlchemy_MetaBox', '_global_head'));



add_action('admin_footer', array('WPAlchemy_MetaBox', '_global_foot'));





class WPAlchemy_Taxonomy

{

	/**

	 * User defined identifier for the meta box, prefix with an underscore to

	 * prevent option(s) form showing up in the custom fields meta box, this

	 * option should be used when instantiating the class.

	 *

	 * @since	1.0

	 * @access	public

	 * @var		string required

	 */

	var $id;



	/**

	 * Used to set the title of the meta box, this option should be used when

	 * instantiating the class.

	 *

	 * @since	1.0

	 * @access	public

	 * @var		string required

	 * @see		$hide_title

	 */

	var $title = 'Custom Meta';



	/**

	 * Used to set the meta box content, the contents of your meta box should be

	 * defined within this file, this option should be used when instantiating

	 * the class.

	 *

	 * @since	1.0

	 * @access	public

	 * @var		string required

	 */

	var $template;



	/**

	 * Used to set the post types that the meta box can appear in, this option 

	 * should be used when instantiating the class.

	 *

	 * @since	1.0

	 * @access	public

	 * @var		array 

	 */

	var $types;



	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $context = 'normal';



	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $priority = 'high';

	

	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $autosave = TRUE;



	/**

	 * Used to set how the class does its data storage, data will be stored as

	 * an associative array in a single meta entry in the wp_postmeta table or

	 * data can be set and individual entries in the wp_postmeta table, the 

	 * following constants should be used when setting this option, 

	 * WPALCHEMY_MODE_ARRAY (default) and WPALCHEMY_MODE_EXTRACT, this option

	 * should be used when instantiating the class.

	 *

	 * @since	1.2

	 * @access	public

	 * @var		string

	 */

	var $mode = WPALCHEMY_MODE_ARRAY;



	/**

	 * When the mode option is set to WPALCHEMY_MODE_EXTRACT, you have to take

	 * care to avoid name collisions with other meta entries. Use this option to

	 * automatically add a prefix to your variables, this option should be used

	 * when instantiating the class.

	 *

	 * @since	1.2

	 * @access	public

	 * @var		array

	 */

	var $prefix;



	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $exclude_template;



	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $exclude_category_id;



	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $exclude_category;



	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $exclude_tag_id;



	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $exclude_tag;



	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $exclude_post_id;



	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $include_template;



	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $include_category_id;



	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $include_category;



	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $include_tag_id;



	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $include_tag;



	/**

	 * @since	1.0

	 * @access	public

	 * @var		bool

	 */

	var $include_post_id;



	/**

	 * Callback used on the WordPress "admin_init" action, the main benefit is 

	 * that this callback is executed only when the meta box is present, this

	 * option should be used when instantiating the class.

	 *

	 * @since	1.3.4

	 * @access	public

	 * @var		string|array optional

	 */

	var $init_action;



	/**

	 * Callback used to override when the meta box gets displayed, must return

	 * true or false to determine if the meta box should or should not be

	 * displayed, this option should be used when instantiating the class.

	 *

	 * @since	1.3

	 * @access	public

	 * @var		string|array optional

	 * @param	array $post_id first variable passed to the callback function

	 * @see		can_output()

	 */

	var $output_filter;



	/**

	 * Callback used to override or insert meta data before saving, you can halt

	 * saving by passing back FALSE (return FALSE), this option should be used

	 * when instantiating the class.

	 *

	 * @since	1.3

	 * @access	public

	 * @var		string|array optional

	 * @param	array $meta meta box data, first variable passed to the callback function

	 * @param	string $post_id second variable passed to the callback function

	 * @see		$save_action, add_filter()

	 */

	var $save_filter;



	/**

	 * Callback used to execute custom code after saving, this option should be

	 * used when instantiating the class.

	 *

	 * @since	1.3

	 * @access	public

	 * @var		string|array optional

	 * @param	array $meta meta box data, first variable passed to the callback function

	 * @param	string $post_id second variable passed to the callback function

	 * @see		$save_filter, add_filter()

	 */

	var $save_action;



	/**

	 * Callback used to override or insert STYLE or SCRIPT tags into the head,

	 * this option should be used when instantiating the class.

	 *

	 * @since	1.3

	 * @access	public

	 * @var		string|array optional

	 * @param	array $content current head content, first variable passed to the callback function

	 * @see		$head_action, add_filter()

	 */

	var $head_filter;



	/**

	 * Callback used to insert STYLE or SCRIPT tags into the head,

	 * this option should be used when instantiating the class.

	 *

	 * @since	1.3

	 * @access	public

	 * @var		string|array optional

	 * @see		$head_filter, add_action()

	 */

	var $head_action;



	/**

	 * Callback used to override or insert SCRIPT tags into the footer, this

	 * option should be used when instantiating the class.

	 *

	 * @since	1.3

	 * @access	public

	 * @var		string|array optional

	 * @param	array $content current foot content, first variable passed to the callback function

	 * @see		$foot_action, add_filter()

	 */

	var $foot_filter;



	/**

	 * Callback used to insert SCRIPT tags into the footer, this option should

	 * be used when instantiating the class.

	 *

	 * @since	1.3

	 * @access	public

	 * @var		string|array optional

	 * @see		$foot_filter, add_action()

	 */

	var $foot_action;



	/**

	 * Used to hide the default content editor in a page or post, this option

	 * should be used when instantiating the class.

	 *

	 * @since	1.3

	 * @access	public

	 * @var		bool optional

	 */

	var $hide_editor = FALSE;



	/**

	 * Used in conjunction with the "hide_editor" option, prevents the media

	 * buttons from also being hidden.

	 *

	 * @since	1.5

	 * @access	public

	 * @var		bool optional

	 */

	var $use_media_buttons = FALSE;

	

	/**

	 * Used to hide the meta box title, this option should be used when

	 * instantiating the class.

	 *

	 * @since	1.3

	 * @access	public

	 * @var		bool optional

	 * @see		$title

	 */

	var $hide_title = FALSE;



	/**

	 * Used to lock a meta box in place, possible values are: top, bottom, 

	 * before_post_title, after_post_title, this option should be used when

	 * instantiating the class.

	 *

	 * @since		1.3.3

	 * @access		public

	 * @var			string optional possible values are: top, bottom, before_post_title, after_post_title

	 */

	var $lock;



	/**

	 * Used to lock a meta box at top (below the default content editor), this

	 * option should be used when instantiating the class.

	 *

	 * @deprecated	deprecated since version 1.3.3

	 * @since		1.3

	 * @access		public

	 * @var			bool optional

	 * @see			$lock

	 */

	var $lock_on_top = FALSE;



	/**

	 * Used to lock a meta box at bottom, this option should be used when

	 * instantiating the class.

	 *

	 * @deprecated	deprecated since version 1.3.3

	 * @since		1.3

	 * @access		public

	 * @var			bool optional

	 * @see			$lock

	 */

	var $lock_on_bottom = FALSE;



	/**

	 * Used to set the initial view state of the meta box, possible values are:

	 * opened, closed, always_opened, this option should be used when

	 * instantiating the class.

	 *

	 * @since	1.3.3

	 * @access	public

	 * @var		string optional possible values are: opened, closed, always_opened

	 */

	var $view;



	/**

	 * Used to hide the show/hide checkbox option from the screen options area,

	 * this option should be used when instantiating the class.

	 *

	 * @since		1.3.4

	 * @access		public

	 * @var			bool optional

	 */

	var $hide_screen_option = FALSE;



	// private



	var $meta;

	var $name;



	/**

	 * Used to provide field type hinting

	 *

	 * @since	1.3

	 * @access	private

	 * @var		string

	 * @see		the_field()

	 */

	var $hint;



	var $length = 0;

	var $current = -1;

	var $in_loop = FALSE;

	var $in_template = FALSE;

	var $group_tag;

	var $current_post_id;

	var $taxonomy;

	/**

	 * Used to store current loop details, cleared after loop ends

	 *

	 * @since	1.4

	 * @access	private

	 * @var		stdClass

	 * @see		have_fields_and_multi(), have_fields()

	 */

	var $_loop_data;

	

	function WPAlchemy_Taxonomy($arr)

	{

		

		$this->taxonomy = isset( $_GET['taxonomy'] ) ? $_GET['taxonomy'] : '';

		$this->_loop_data = new stdClass;

		

		$this->meta = array();



		$this->types = array('post', 'page');



		if (isset($arr) and is_array($arr))

		{

			foreach ($arr as $n => $v)

			{

				$this->$n = $v;

			}



			if (empty($this->id)) die('Meta box ID required');



			if (is_numeric($this->id)) die('Meta box ID must be a string');



			if (empty($this->template)) die('Meta box template file required');



			// check for nonarray values

			

			$exc_inc = array

			(

				'exclude_template',

				'exclude_category_id',

				'exclude_category',

				'exclude_tag_id',

				'exclude_tag',

				'exclude_post_id',



				'include_template',

				'include_category_id',

				'include_category',

				'include_tag_id',

				'include_tag',

				'include_post_id'

			);



			foreach ($exc_inc as $v)

			{

				// ideally the exclude and include values should be in array form, convert to array otherwise

				if (!empty($this->$v) AND !is_array($this->$v))

				{

					$this->$v = array_map('trim',explode(',',$this->$v));

				}

			}



			// convert depreciated variables

			if ($this->lock_on_top) $this->lock = WPALCHEMY_LOCK_TOP;

			elseif ($this->lock_on_bottom) $this->lock = WPALCHEMY_LOCK_BOTTOM;

			

			add_action('admin_init', array($this,'_init'));



			// uses the default wordpress-importer plugin hook

			//add_action('import_post_meta', array($this, '_import'), 10, 3);

		}

		else 

		{

			die('Associative array parameters required');

		}

	}



	/**

	 * Used to correct double serialized data during post/page export/import,

	 * additionally will try to fix corrupted serialized data by recalculating

	 * string length values

	 *

	 * @since	1.3.16

	 * @access	private

	 */

	function _import($post_id, $key, $value)

	{

		if (WPALCHEMY_MODE_ARRAY == $this->mode AND $key == $this->id)

		{

			// using $wp_import to get access to the raw postmeta data prior to it getting passed

			// through "maybe_unserialize()" in "plugins/wordpress-importer/wordpress-importer.php"

			// the "import_post_meta" action is called after "maybe_unserialize()"

			

			global $wp_import;



			foreach ( $wp_import->posts as $post )

			{

				if ( $post_id == $post['post_id'] )

				{

					foreach( $post['postmeta'] as $meta )

					{

						if ( $key == $meta['key'] )

						{

							// try to fix corrupted serialized data, specifically "\r\n" being converted to "\n" during wordpress XML export (WXR)

							// "maybe_unserialize()" fixes a wordpress bug which double serializes already serialized data during export/import

							$value = maybe_unserialize( preg_replace( '!s:(\d+):"(.*?)";!es', "'s:'.strlen('$2').':\"$2\";'", stripslashes( $meta['value'] ) ) );

							

							update_post_meta( $post_id, $key,  $value );

						}

					}

				}

			}

		}

	}



	/**

	 * Used to initialize the meta box, runs on WordPress admin_init action,

	 * properly calls internal WordPress methods

	 *

	 * @since	1.0

	 * @access	private

	 */

	function _init()

	{

		// must be creating or editing a post or page

		//if ( ! WPAlchemy_MetaBox::_is_post() AND ! WPAlchemy_MetaBox::_is_page()) return;

		if( !isset( $_GET['taxonomy'] ) && !is_admin() ) return;

		//$this->types = array('category');

		//exit('tsdfsfs');



		add_action( 'admin_enqueue_scripts',  array( $this, 'vp_enqueue' ) );

		$this->vp_metabox_enqueue();

		

		if ( ! empty($this->output_filter))

		{

			$this->add_filter('output', $this->output_filter);

		}



		if ( true )//$this->can_output())

		{

			foreach ($this->types as $type) 

			{

				add_action( $type.'_add_form_fields', array( $this, '_setup' ), 10, 2 );

				add_action( $type.'_edit_form_fields', array( $this, '_setup' ), 10, 2 );

				//add_meta_box($this->id . '_metabox', $this->title, array($this, '_setup'), $type, $this->context, $this->priority);

			

				//add_action('save_post', array($this,'_save'));

				add_action( 'edited_'.$type, array( $this, '_save' ), 10, 2 );  

				add_action( 'create_'.$type, array( $this, '_save'), 10, 2 );

			}

			

			

	

			add_action('admin_head', array($this,'_head'), 11);



			add_action('admin_footer', array($this,'_foot'), 11);



	

		}

	}

	

	

	function vp_metabox_enqueue()

	{

		

		global $taxonomy;

		

		$loader = VP_WP_Loader::instance();

		$loader->add_main_js( 'vp-metabox' );

		$loader->add_main_css( 'vp-metabox' );

	

	}

	

	function vp_enqueue()

	{

		$loader = VP_WP_Loader::instance();

		wp_enqueue_script( array('jquery-ui-sortable') );

		$loader->build();

	}

	/**

	 * Used to insert STYLE or SCRIPT tags into the head, called on WordPress

	 * admin_head action.

	 * 

	 * @since	1.3

	 * @access	private

	 * @see		_foot()

	 */

	function _head()

	{

		$content = NULL;



		ob_start();



		?>

		<style type="text/css">

			<?php if ($this->hide_editor) { ?> #wp-content-editor-container, #post-status-info, <?php if ($this->use_media_buttons) { ?> #content-html, #content-tmce<?php } else { ?> #wp-content-wrap<?php } ?> { display:none; } <?php } ?>

		</style>

		<?php



		$content = ob_get_contents();



		ob_end_clean();



		// filter: head

		if ($this->has_filter('head'))

		{

			$content = $this->apply_filters('head', $content);

		}



		echo $content;



		// action: head

		if ($this->has_action('head'))

		{

			$this->do_action('head');

		}

	}



	/**

	 * Used to insert SCRIPT tags into the footer, called on WordPress

	 * admin_footer action.

	 *

	 * @since	1.3

	 * @access	private

	 * @see		_head()

	 */

	function _foot()

	{

		$content = NULL;



		if

		(

			$this->lock OR

			$this->hide_title OR

			$this->view OR

			$this->hide_screen_option

		)

		{

			ob_start();



			?>

			<script type="text/javascript">

			/* <![CDATA[ */

			(function($){ /* not using jQuery ondomready, code runs right away in footer */



				var mb_id = '<?php echo $this->id; ?>';

				var mb = $('#' + mb_id + '_metabox');



				<?php if (WPALCHEMY_LOCK_TOP == $this->lock): ?>

				<?php if ('side' == $this->context): ?>

				var id = 'wpalchemy-side-top';

				if ( ! $('#'+id).length)

				{

					$('<div></div>').attr('id',id).prependTo('#side-info-column');

				}

				<?php else: ?>

				var id = 'wpalchemy-content-top';

				if ( ! $('#'+id).length)

				{

					$('<div></div>').attr('id',id).insertAfter('#postdiv, #postdivrich');

				}

				<?php endif; ?>

				$('#'+id).append(mb);

				<?php elseif (WPALCHEMY_LOCK_BOTTOM == $this->lock): ?>

				<?php if ('side' == $this->context): ?>

				var id = 'wpalchemy-side-bottom';

				if ( ! $('#'+id).length)

				{

					$('<div></div>').attr('id',id).appendTo('#side-info-column');

				}

				<?php else: ?>

				if ( ! $('#advanced-sortables').children().length)

				{

					$('#advanced-sortables').css('display','none');

				}



				var id = 'wpalchemy-content-bottom';

				if ( ! $('#'+id).length)

				{

					$('<div></div>').attr('id',id).insertAfter('#advanced-sortables');

				}

				<?php endif; ?>

				$('#'+id).append(mb);

				<?php elseif (WPALCHEMY_LOCK_BEFORE_POST_TITLE == $this->lock): ?>

				<?php if ('side' != $this->context): ?>

				var id = 'wpalchemy-content-bpt';

				if ( ! $('#'+id).length)

				{

					$('<div></div>').attr('id',id).prependTo('#post-body-content');

				}

				$('#'+id).append(mb);

				<?php endif; ?>

				<?php elseif (WPALCHEMY_LOCK_AFTER_POST_TITLE == $this->lock): ?>

				<?php if ('side' != $this->context): ?>

				var id = 'wpalchemy-content-apt';

				if ( ! $('#'+id).length)

				{

					$('<div></div>').attr('id',id).insertAfter('#titlediv');

				}

				$('#'+id).append(mb);

				<?php endif; ?>

				<?php endif; ?>



				<?php if ( ! empty($this->lock)): ?>

				$('.hndle', mb).css('cursor','pointer');

				$('.handlediv', mb).remove();

				<?php endif; ?>



				<?php if ($this->hide_title): ?>

				$('.hndle', mb).remove();

				$('.handlediv', mb).remove();

				mb.removeClass('closed'); /* start opened */

				<?php endif; ?>



				<?php if (WPALCHEMY_VIEW_START_OPENED == $this->view): ?>

				mb.removeClass('closed');

				<?php elseif (WPALCHEMY_VIEW_START_CLOSED == $this->view): ?>

				mb.addClass('closed');

				<?php elseif (WPALCHEMY_VIEW_ALWAYS_OPENED == $this->view): ?>

				/* todo: need to find a way to add this script block below, load-scripts.php?... */

				var h3 = mb.children('h3');

				setTimeout(function(){ h3.unbind('click'); }, 1000);

				$('.handlediv', mb).remove();

				mb.removeClass('closed'); /* start opened */

				$('.hndle', mb).css('cursor','auto');

				<?php endif; ?>



				<?php if ($this->hide_screen_option): ?>

					$('.metabox-prefs label[for='+ mb_id +'_metabox-hide]').remove();

				<?php endif; ?>



				mb = null;



			})(jQuery);

			/* ]]> */

			</script>

			<?php



			$content = ob_get_contents();



			ob_end_clean();

		}

		

		// filter: foot

		if ($this->has_filter('foot'))

		{

			$content = $this->apply_filters('foot', $content);

		}



		echo $content;



		// action: foot

		if ($this->has_action('foot'))

		{

			$this->do_action('foot');

		}

	}



	/**

	 * Used to setup the meta box content template

	 *

	 * @since	1.0

	 * @access	private

	 * @see		_init()

	 */

	function _setup()

	{exit('sss');

		$this->in_template = TRUE;

		

		// also make current post data available

		global $post;



		// shortcuts

		$mb =& $this;

		$metabox =& $this;

		$id = $this->id;

		$meta = $this->_meta(NULL, TRUE);

printr($this->template);

		// use include because users may want to use one templete for multiple meta boxes

		include $this->template;

	 

		// create a nonce for verification

		echo '<input type="hidden" name="'. $this->id .'_nonce" value="' . wp_create_nonce($this->id) . '" />';



		$this->in_template = FALSE;

	}



	/**

	 * Used to properly prefix the filter tag, the tag is unique to the meta

	 * box instance

	 * 

	 * @since	1.3

	 * @access	private

	 * @param	string $tag name of the filter

	 * @return	string uniquely prefixed tag name

	 */

	function _get_filter_tag($tag)

	{

		$prefix = 'wpalchemy_filter_' . $this->id . '_';

		$prefix = preg_replace('/_+/', '_', $prefix);



		$tag = preg_replace('/^'. $prefix .'/i', '', $tag);

		return $prefix . $tag;

	}



	/**

	 * Uses WordPress add_filter() function, see WordPress add_filter()

	 *

	 * @since	1.3

	 * @access	public

	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L65

	 */

	function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1)

	{

		$tag = $this->_get_filter_tag($tag);;

		add_filter($tag, $function_to_add, $priority, $accepted_args);

	}



	/**

	 * Uses WordPress has_filter() function, see WordPress has_filter()

	 *

	 * @since	1.3

	 * @access	public

	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L86

	 */

	function has_filter($tag, $function_to_check = FALSE)

	{

		$tag = $this->_get_filter_tag($tag);

		return has_filter($tag, $function_to_check);

	}



	/**

	 * Uses WordPress apply_filters() function, see WordPress apply_filters()

	 *

	 * @since	1.3

	 * @access	public

	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L134

	 */

	function apply_filters($tag, $value)

	{

		$args = func_get_args();

		$args[0] = $this->_get_filter_tag($tag);

		return call_user_func_array('apply_filters', $args);

	}



	/**

	 * Uses WordPress remove_filter() function, see WordPress remove_filter()

	 *

	 * @since	1.3

	 * @access	public

	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L250

	 */

	function remove_filter($tag, $function_to_remove, $priority = 10, $accepted_args = 1)

	{

		$tag = $this->_get_filter_tag($tag);

		return remove_filter($tag, $function_to_remove, $priority, $accepted_args);

	}



	/**

	 * Used to properly prefix the action tag, the tag is unique to the meta

	 * box instance

	 *

	 * @since	1.3

	 * @access	private

	 * @param	string $tag name of the action

	 * @return	string uniquely prefixed tag name

	 */

	function _get_action_tag($tag)

	{

		$prefix = 'wpalchemy_action_' . $this->id . '_';

		$prefix = preg_replace('/_+/', '_', $prefix);



		$tag = preg_replace('/^'. $prefix .'/i', '', $tag);

		return $prefix . $tag;

	}



	/**

	 * Uses WordPress add_action() function, see WordPress add_action()

	 *

	 * @since	1.3

	 * @access	public

	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L324

	 */

	function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1)

	{

		$tag = $this->_get_action_tag($tag);

		add_action($tag, $function_to_add, $priority, $accepted_args);

	}



	/**

	 * Uses WordPress has_action() function, see WordPress has_action()

	 *

	 * @since	1.3

	 * @access	public

	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L492

	 */

	function has_action($tag, $function_to_check = FALSE)

	{

		$tag = $this->_get_action_tag($tag);

		return has_action($tag, $function_to_check);

	}



	/**

	 * Uses WordPress remove_action() function, see WordPress remove_action()

	 * 

	 * @since	1.3

	 * @access	public

	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L513

	 */

	function remove_action($tag, $function_to_remove, $priority = 10, $accepted_args = 1)

	{

		$tag = $this->_get_action_tag($tag);

		return remove_action($tag, $function_to_remove, $priority, $accepted_args);

	}



	/**

	 * Uses WordPress do_action() function, see WordPress do_action()

	 * @since	1.3

	 * @access	public

	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L352

	 */

	function do_action($tag, $arg = '')

	{

		$args = func_get_args();

		$args[0] = $this->_get_action_tag($tag);

		return call_user_func_array('do_action', $args);

	}



	/**

	 * Used to check if creating a new post or editing one

	 *

	 * @static

	 * @since	1.3.7

	 * @access	private

	 * @return	bool

	 * @see		_is_page()

	 */

	static function _is_post()

	{

		if ('post' == WPAlchemy_MetaBox::_is_post_or_page())

		{

			return TRUE;

		}



		return FALSE;

	}



	/**

	 * Used to check if creating a new page or editing one

	 *

	 * @static

	 * @since	1.3.7

	 * @access	private

	 * @return	bool

	 * @see		_is_post()

	 */

	static function _is_page()

	{

		if ('page' == WPAlchemy_MetaBox::_is_post_or_page())

		{

			return TRUE;

		}



		return FALSE;

	}



	/**

	 * Used to check if creating or editing a post or page

	 *

	 * @static

	 * @since	1.3.8

	 * @access	private

	 * @return	string "post" or "page"

	 * @see		_is_post(), _is_page()

	 */

	static function _is_post_or_page()

	{

		$post_type = WPAlchemy_MetaBox::_get_current_post_type();



		if (isset($post_type))

		{

			if ('page' == $post_type)

			{

				return 'page';

			}

			else

			{

				return 'post';

			}

		}



		return NULL;

	}



	/**

	 * Used to check for the current post type, works when creating or editing a

	 * new post, page or custom post type.

	 *

	 * @static

	 * @since	1.4.6

	 * @return	string [custom_post_type], page or post

	 */

	static function _get_current_post_type()

	{

		$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : NULL ;



		if ( isset( $uri ) )

		{

			$uri_parts = parse_url($uri);



			$file = basename($uri_parts['path']);



			if ($uri AND in_array($file, array('post.php', 'post-new.php')))

			{

				$post_id = WPAlchemy_MetaBox::_get_post_id();



				$post_type = isset($_GET['post_type']) ? $_GET['post_type'] : NULL ;



				$post_type = $post_id ? get_post_type($post_id) : $post_type ;



				if (isset($post_type))

				{

					return $post_type;

				}

				else

				{

					// because of the 'post.php' and 'post-new.php' checks above, we can default to 'post'

					return 'post';

				}

			}

		}



		return NULL;

	}



	/**

	 * Used to get the current post id.

	 *

	 * @static

	 * @since	1.4.8

	 * @return	int post ID

	 */

	static function _get_post_id()

	{

		global $post;



		$p_post_id = isset($_POST['post_ID']) ? $_POST['post_ID'] : null ;



		$g_post_id = isset($_GET['post']) ? $_GET['post'] : null ;



		$post_id = $g_post_id ? $g_post_id : $p_post_id ;



		$post_id = isset($post->ID) ? $post->ID : $post_id ;



		if (isset($post_id))

		{

			return (integer) $post_id;

		}

		

		return null;

	}



	/**

	 * @since	1.0

	 */

	function can_output()

	{

		$tax = $this->taxonomy;

		

		$can_output = true;

		

		if (isset($tax) AND ! in_array($tax, $this->types))

		{

			$can_output = FALSE;

		}



		// filter: output (can_output)

		if ($this->has_filter('output'))

		{

			$can_output = $this->apply_filters('output', $post_id);

		}



		return $can_output;

	}



	/**

	 * Used to insert global STYLE or SCRIPT tags into the head, called on

	 * WordPress admin_footer action.

	 *

	 * @static

	 * @since	1.3

	 * @access	private

	 * @see		_global_foot()

	 */

	static function _global_head()

	{

		// must be creating or editing a post or page

		if ( ! WPAlchemy_MetaBox::_is_post() AND ! WPAlchemy_MetaBox::_is_page()) return;



		// todo: you're assuming people will want to use this exact functionality

		// consider giving a developer access to change this via hooks/callbacks



		// include javascript for special functionality

		?><style type="text/css"> .wpa_group.tocopy { display:none; } </style>

		<script type="text/javascript">

		/* <![CDATA[ */

		jQuery(function($)

		{

			$(document).click(function(e)

			{		

				var elem = $(e.target);



				if (elem.attr('class') && elem.filter('[class*=dodelete]').length)

				{

					e.preventDefault();



					var p = elem.parents('.wpa_group:first');



					if(p.length <= 0)

						p = elem.parents('.postbox'); /*wp*/



					var the_name = elem.attr('class').match(/dodelete-([a-zA-Z0-9_-]*)/i);



					the_name = (the_name && the_name[1]) ? the_name[1] : null ;



					/* todo: expose and allow editing of this message */

					if (confirm('This action can not be undone, are you sure?'))

					{

						if (the_name)

						{

							$('.wpa_group-'+ the_name, p).not('.tocopy').remove();

						}

						else

						{

							elem.parents('.wpa_group:first').remove();

						}

						

						if(!the_name)

						{

							var the_group = elem.parents('.wpa_group');

							if(the_group && the_group.attr('class'))

							{

								the_name = the_group.attr('class').match(/wpa_group-([a-zA-Z0-9_-]*)/i);

								the_name = (the_name && the_name[1]) ? the_name[1] : null ;

							}

						}

						checkLoopLimit(the_name);



						$.wpalchemy.trigger('wpa_delete');

					}

				}

			});



			$(document).on('click', '[class*=docopy-]', function(e)

			{

				e.preventDefault();



				var p = $(this).parents('.wpa_group:first');



				if(p.length <= 0)

					p = $(this).parents('.postbox'); /*wp*/



				var the_name = $(this).attr('class').match(/docopy-([a-zA-Z0-9_-]*)/i)[1];



				var the_group = $('.wpa_group-'+ the_name +'.tocopy', p).first();



				var the_clone = the_group.clone().removeClass('tocopy last');



				var the_props = ['name', 'id', 'for', 'class'];



				the_group.find('*').each(function(i, elem)

				{

					for (var j = 0; j < the_props.length; j++)

					{

						var the_prop = $(elem).attr(the_props[j]);



						if (the_prop)

						{

							var reg = new RegExp('\\['+the_name+'\\]\\[(\\d+)\\]', 'i');

							var the_match = the_prop.match(reg);



							if (the_match)

							{

								the_prop = the_prop.replace(the_match[0], '['+ the_name + ']' + '['+ (+the_match[1]+1) +']');



								$(elem).attr(the_props[j], the_prop);

							}



							the_match = null;



							// todo: this may prove to be too broad of a search

							the_match = the_prop.match(/n(\d+)/i);



							if (the_match)

							{

								the_prop = the_prop.replace(the_match[0], 'n' + (+the_match[1]+1));



								$(elem).attr(the_props[j], the_prop);

							}

						}

					}

				});



				// increment the group id

				var reg       = new RegExp('\\[(\\d+)\\]$', 'i');

				var the_id    = the_group.attr("id");

				var the_match = the_id.match(reg);

				if (the_match)

				{

					the_group.attr("id", the_id.replace(the_match[0], '['+ (+the_match[1]+1) +']'));

				}



				if ($(this).hasClass('ontop'))

				{

					$('.wpa_group-'+ the_name, p).first().before(the_clone);

				}

				else

				{

					the_group.before(the_clone);

				}



				checkLoopLimit(the_name);



				$.wpalchemy.trigger('wpa_copy', [the_clone]);

			});



			function checkLoopLimit(name)

			{

				var elems = $('.docopy-' + name);



				$.each(elems, function(idx, elem){



					var p = $(this).parents('.wpa_group:first');



					if(p.length <= 0)

						p = $(this).parents('.postbox'); /*wp*/



					var the_class = $('.wpa_loop-' + name, p).attr('class');



					if (the_class)

					{

						var the_match = the_class.match(/wpa_loop_limit-([0-9]*)/i);



						if (the_match)

						{

							var the_limit = the_match[1];



							if ($('.wpa_group-' + name, p).not('.wpa_group.tocopy').length >= the_limit)

							{

								$(this).hide();

							}

							else

							{

								$(this).show();

							}

						}

					}



				});



			}

			

			/* do an initial limit check, show or hide buttons */

			$('[class*=docopy-]').each(function()

			{

				var the_name = $(this).attr('class').match(/docopy-([a-zA-Z0-9_-]*)/i)[1];



				checkLoopLimit(the_name);

			});

		});

		/* ]]> */

		</script>

		<?php

	}



	/**

	 * Used to insert global SCRIPT tags into the footer, called on WordPress

	 * admin_footer action.

	 *

	 * @static

	 * @since	1.3

	 * @access	private

	 * @see		_global_head()

	 */

	static function _global_foot()

	{

		// must be creating or editing a post or page

		if ( ! WPAlchemy_MetaBox::_is_post() AND ! WPAlchemy_MetaBox::_is_page()) return;



		?>

		<script type="text/javascript">

		/* <![CDATA[ */

		(function($){ /* not using jQuery ondomready, code runs right away in footer */



			/* use a global dom element to attach events to */

			$.wpalchemy = $('<div></div>').attr('id','wpalchemy').appendTo('body');



		})(jQuery);

		/* ]]> */

		</script>

		<?php

	}



	/**

	 * Gets the meta data for a meta box

	 *

	 * @since	1.0

	 * @access	public

	 * @param	int $post_id optional post ID for which to retrieve the meta data

	 * @return	array

	 * @see		_meta

	 */

	function the_meta($post_id = NULL)

	{

		return $this->_meta($post_id);

	}



	/**

	 * Gets the meta data for a meta box

	 *

	 * Internal method calls will typically bypass the data retrieval and will

	 * immediately return the current meta data

	 *

	 * @since	1.3

	 * @access	private

	 * @param	int $post_id optional post ID for which to retrieve the meta data

	 * @param	bool $internal optional boolean if internally calling

	 * @return	array

	 * @see		the_meta()

	 */

	function _meta($post_id = NULL, $internal = FALSE)

	{

		if ( ! is_numeric($post_id))

		{

			/*if ($internal AND $this->current_post_id)

			{

				$post_id = $this->current_post_id;

			}

			else

			{

				global $post;



				$post_id = $post->ID;

			}*/

		}

		$post_id = isset( $_GET['tag_ID'] ) ? $_GET['tag_ID'] : null;

		// this allows multiple internal calls to _meta() without having to fetch data everytime

		//if ($internal AND !empty($this->meta) AND $this->current_post_id == $post_id) return $this->meta;



		//$this->current_post_id = $post_id;



		// WPALCHEMY_MODE_ARRAY

		if( ! $post_id ) return;

		

		$meta = get_option( $this->id.$post_id );



		// var_dump($meta);



		// WPALCHEMY_MODE_EXTRACT



		/*$fields = get_post_meta($post_id, $this->id . '_fields', TRUE);



		if ( ! empty($fields) AND is_array($fields))

		{

			$meta = array();

			

			foreach ($fields as $field)

			{

				$field_noprefix = preg_replace('/^' . $this->prefix . '/i', '', $field);

				$meta[$field_noprefix] = get_post_meta($post_id, $field, TRUE);

			}

		}*/



		$this->meta = $meta;



		return $this->meta;

	}



	// user can also use the_ID(), php functions are case-insensitive

	/**

	 * @since	1.0

	 * @access	public

	 */

	function the_id()

	{

		echo $this->get_the_id();

	}



	/**

	 * @since	1.0

	 * @access	public

	 */

	function get_the_id()

	{

		return $this->id;

	}



	/**

	 * @since	1.0

	 * @access	public

	 */

	function the_field($n, $hint = NULL)

	{

		$this->name = $n;

		$this->hint = $hint;

	}



	/**

	 * @since	1.0

	 * @access	public

	 */

	function have_value($n = NULL)

	{

		if ($this->get_the_value($n)) return TRUE;

		

		return FALSE;

	}



	/**

	 * @since	1.0

	 * @access	public

	 */

	function the_value($n = NULL)

	{

		echo $this->get_the_value($n);

	}



	function get_the_value($n = NULL, $collection = FALSE)

	{

		$this->_meta(NULL, TRUE);



		$value = null;



		if ($this->is_in_loop())

		{

			$n = is_null($n) ? $this->name : $n ;



			if(!is_null($n))

			{

				if ($collection)

				{

					$keys   = $this->get_the_loop_group_name_array();

				}

				else

				{

					$keys   = $this->get_the_loop_group_name_array();

					$keys[] = $n;

				}

			}

			else

			{

				if ($collection)

				{

					$keys   = $this->get_the_loop_group_name_array();

					end($keys);

					$last   = key($keys);

					unset($keys[$last]);

				}

				else

				{

					$keys   = $this->get_the_loop_group_name_array();

				}

			}

			$value = $this->get_meta_by_array($keys);

		}

		else

		{

			$n = is_null($n) ? $this->name : $n ;



			if(isset($this->meta[$n]))

			{

				$value = $this->meta[$n];

			}

		}



		if (is_string($value) || is_numeric($value))

		{

			if ($this->in_template)

			{

				return $value;

			}

			else

			{

				// http://wordpress.org/support/topic/call-function-called-by-embed-shortcode-direct

				// http://phpdoc.wordpress.org/trunk/WordPress/Embed/WP_Embed.html#run_shortcode



				global $wp_embed;



				return do_shortcode($wp_embed->run_shortcode($value));

			}

		}

		else

		{

			// value can sometimes be an array

			return $value;

		}

	}



	/**

	 * @since	1.0

	 * @access	public

	 */

	function the_name($n = NULL)

	{

		echo $this->get_the_name($n);

	}



	/**

	 * @since	1.0

	 * @access	public

	 */

	function get_the_name($n = NULL)

	{

		if (!$this->in_template AND $this->mode == WPALCHEMY_MODE_EXTRACT)

		{

			return $this->prefix . str_replace($this->prefix, '', is_null($n) ? $this->name : $n);

		}



		if ($this->is_in_loop())

		{

			$n = is_null($n) ? $this->name : $n ;



			if (!is_null($n))

				$the_field = $this->get_the_loop_group_name(true) . '[' . $n . ']' ;

			else

				$the_field = $this->get_the_loop_group_name(true);

		}

		else

		{

			$n = is_null($n) ? $this->name : $n ;



			$the_field = $this->id . '[' . $n . ']';

		}



		$hints = array

		(

			WPALCHEMY_FIELD_HINT_CHECKBOX_MULTI,

			WPALCHEMY_FIELD_HINT_SELECT_MULTI,

			WPALCHEMY_FIELD_HINT_SELECT_MULTIPLE,

		);



		if (in_array($this->hint, $hints))

		{

			$the_field .= '[]';

		}



		return $the_field;

	}



	/**

	 * @since	1.1

	 * @access	public

	 */

	function the_index()

	{

		echo $this->get_the_index();

	}



	/**

	 * @since	1.1

	 * @access	public

	 */

	function get_the_index()

	{

		return $this->in_loop ? $this->get_the_current_group_current() : 0 ;

	}



	/**

	 * @since	1.0

	 * @access	public

	 */

	function is_first()

	{

		if ($this->in_loop AND $this->current == 0) return TRUE;



		return FALSE;

	}



	/**

	 * @since	1.0

	 * @access	public

	 */

	function is_last()

	{

		if ($this->in_loop AND ($this->current+1) == $this->length) return TRUE;



		return FALSE;

	}



	/**

	 * Used to check if a value is a match

	 *

	 * @since	1.1

	 * @access	public

	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)

	 * @param	string $v optional the value to check for

	 * @return	bool

	 * @see		is_value()

	 */

	function is_value($n, $v = NULL)

	{

		if (is_null($v))

		{

			$the_value = $this->get_the_value();



			$v = $n;

		}

		else

		{

			$the_value = $this->get_the_value($n);

		}



		if($v == $the_value) return TRUE;



		return FALSE;

	}



	/**

	 * Used to check if a value is selected, useful when working with checkbox,

	 * radio and select values.

	 *

	 * @since	1.3

	 * @access	public

	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)

	 * @param	string $v optional the value to check for

	 * @return	bool

	 * @see		is_value()

	 */

	function is_selected($n, $v = NULL)

	{

		if (is_null($v))

		{

			$the_value = $this->get_the_value(NULL);



			$v = $n;

		}

		else

		{

			$the_value = $this->get_the_value($n);

		}



		if (is_array($the_value))

		{

			if (in_array($v, $the_value)) return TRUE;

		}

		elseif($v == $the_value)

		{

			return TRUE;

		}



		return FALSE;

	}



	/**

	 * Prints the current state of a checkbox field and should be used inline

	 * within the INPUT tag.

	 *

	 * @since	1.3

	 * @access	public

	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)

	 * @param	string $v optional the value to check for

	 * @see		get_the_checkbox_state()

	 */

	function the_checkbox_state($n, $v = NULL)

	{

		echo $this->get_the_checkbox_state($n, $v);

	}



	/**

	 * Returns the current state of a checkbox field, the returned string is

	 * suitable to be used inline within the INPUT tag.

	 *

	 * @since	1.3

	 * @access	public

	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)

	 * @param	string $v optional the value to check for

	 * @return	string suitable to be used inline within the INPUT tag

	 * @see		the_checkbox_state()

	 */

	function get_the_checkbox_state($n, $v = NULL)

	{

		if ($this->is_selected($n, $v)) return ' checked="checked"';

	}



	/**

	 * Prints the current state of a radio field and should be used inline

	 * within the INPUT tag.

	 *

	 * @since	1.3

	 * @access	public

	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)

	 * @param	string $v optional the value to check for

	 * @see		get_the_radio_state()

	 */

	function the_radio_state($n, $v = NULL)

	{

		echo $this->get_the_checkbox_state($n, $v);

	}



	/**

	 * Returns the current state of a radio field, the returned string is

	 * suitable to be used inline within the INPUT tag.

	 *

	 * @since	1.3

	 * @access	public

	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)

	 * @param	string $v optional the value to check for

	 * @return	string suitable to be used inline within the INPUT tag

	 * @see		the_radio_state()

	 */

	function get_the_radio_state($n, $v = NULL)

	{

		return $this->get_the_checkbox_state($n, $v);

	}



	/**

	 * Prints the current state of a select field and should be used inline

	 * within the SELECT tag.

	 *

	 * @since	1.3

	 * @access	public

	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)

	 * @param	string $v optional the value to check for

	 * @see		get_the_select_state()

	 */

	function the_select_state($n, $v = NULL)

	{

		echo $this->get_the_select_state($n, $v);

	}



	/**

	 * Returns the current state of a select field, the returned string is

	 * suitable to be used inline within the SELECT tag.

	 *

	 * @since	1.3

	 * @access	public

	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)

	 * @param	string $v optional the value to check for

	 * @return	string suitable to be used inline within the SELECT tag

	 * @see		the_select_state()

	 */

	function get_the_select_state($n, $v = NULL)

	{

		if ($this->is_selected($n, $v)) return ' selected="selected"';

	}



	/**

	 * @since	1.1

	 * @access	public

	 */

	function the_group_open($t = 'div')

	{

		echo $this->get_the_group_open($t);

	}



	/**

	 * @since	1.1

	 * @access	public

	 */

	function get_the_group_open($t = 'div')

	{

		$this->group_tag = $t;



		$curr_loop = $this->get_the_current_loop();

		$the_name  = $curr_loop->name;



		$loop_open = NULL;



		$loop_open_classes = array('wpa_loop', 'wpa_loop-' . $the_name);

		

		$css_class = array('wpa_group', 'wpa_group-'. $the_name);





		if ($curr_loop->is_first())

		{

			array_push($css_class, 'first');



			$loop_open = '<div class="wpa_loop">';



			if (isset($this->_loop_data->limit))

			{

				array_push($loop_open_classes, 'wpa_loop_limit-' . $this->_loop_data->limit);

			}



			$loop_open = '<div id="wpa_loop-'. $the_name .'" class="' . implode(' ', $loop_open_classes) . '">';

		}



		if ($curr_loop->is_last())

		{

			array_push($css_class, 'last');



			if ($this->in_loop == 'multi')

			{

				array_push($css_class, 'tocopy');

			}

		}



		return $loop_open . '<' . $t . ' class="'. implode(' ', $css_class) . '">';

	}



	/**

	 * @since	1.1

	 * @access	public

	 */

	function the_group_close()

	{

		echo $this->get_the_group_close();

	}



	/**

	 * @since	1.1

	 * @access	public

	 */

	function get_the_group_close()

	{

		$loop_close = NULL;



		$curr_loop = $this->get_the_current_loop();



		if ($curr_loop->is_last())

		{

			$loop_close = '</div>';

		}

		

		return '</' . $this->group_tag . '>' . $loop_close;

	}



	/**

	 * @since	1.1

	 * @access	public

	 */

	function have_fields_and_multi($n, $options = NULL)

	{

		if (is_array($options))

		{

			// use as stdClass object

			$options = (object)$options;

			

			$length = @$options->length;



			$this->_loop_data->limit = @$options->limit;

		}

		else

		{

			// backward compatibility (bc)

			$length = $options;

		}



		$this->_meta(NULL, TRUE);



		$this->in_loop = 'multi';



		// push new loop or set loop to current name

		$this->push_or_set_current_loop($n, $length, $this->in_loop);



		return $this->_loop($n, $length, 2);

	}



	/**

	 * @deprecated

	 * @since	1.0

	 * @access	public

	 */

	function have_fields_and_one($n)

	{

		$this->_meta(NULL, TRUE);

		$this->in_loop = 'single';

		$this->push_or_set_current_loop($n, $length, $this->in_loop);

		return $this->_loop($n,NULL,1);

	}



	/**

	 * @since	1.0

	 * @access	public

	 */

	function have_fields($n,$length=NULL)

	{

		$this->_meta(NULL, TRUE);

		// push new loop or set loop to current name

		$this->in_loop = 'normal';

		$this->push_or_set_current_loop($n, $length, $this->in_loop);

		return $this->_loop($n,$length);

	}



	/**

	 * @since	1.0

	 * @access	private

	 */

	function _loop($n,$length=NULL,$and_one=0)

	{

		if (!$this->in_loop)

		{

			$this->in_loop = TRUE;

		}

		

		$cnt = $this->get_the_current_group_count();



		$length = is_null($length) ? $cnt : $length ;

		

		if ($this->in_loop == 'multi' AND $cnt > $length) $length = $cnt;



		$this->length = $length;



		if ($this->in_template AND $and_one)

		{

			if ($length == 0)

			{

				$this->length = $and_one;

			}

			else

			{

				$this->length = $length+1;

			}

		}



		$this->set_the_current_group_length($this->length);

		$this->increment_current_loop();

		$this->current++;



		if ($this->get_the_current_group_current() < $this->get_the_current_group_length())

		{

			$this->name      = NULL;

			$this->fieldtype = NULL;



			return TRUE;

		}

		else if ($this->get_the_current_group_current() == $this->get_the_current_group_length())

		{

			$this->name      = NULL;

			$this->set_the_current_group_current(-1);

			$this->prev_loop();

		}



		$this->in_loop = FALSE;



		$this->_loop_data = new stdClass;



		return FALSE;

	}



	/**

	 * @since	1.0

	 * @access	private

	 */

	function _save($term_id) 

	{

		printr($term);

		



		$real_post_id = isset($_POST['post_ID']) ? $_POST['post_ID'] : NULL ;

		

		// check autosave

		if (defined('DOING_AUTOSAVE') AND DOING_AUTOSAVE AND !$this->autosave) return $post_id;

	 

		// make sure data came from our meta box, verify nonce

		$nonce = isset($_POST[$this->id.'_nonce']) ? $_POST[$this->id.'_nonce'] : NULL ;

		if (!wp_verify_nonce($nonce, $this->id)) return $post_id;

	 

		// check user permissions

		if ($_POST['post_type'] == 'page') 

		{

			if (!current_user_can('edit_page', $post_id)) return $post_id;

		}

		else 

		{

			if (!current_user_can('edit_post', $post_id)) return $post_id;

		}

	 

		// authentication passed, save data

	 

		$new_data = isset( $_POST[$this->id] ) ? $_POST[$this->id] : NULL ;



		WPAlchemy_MetaBox::clean($new_data);



		if (empty($new_data))

		{

			$new_data = NULL;

		}



		// filter: save

		if ($this->has_filter('save'))

		{

			$new_data = $this->apply_filters('save', $new_data, $real_post_id);



			/**

			 * halt saving

			 * @since 1.3.4

			 */

			if (FALSE === $new_data) return $post_id;



			WPAlchemy_MetaBox::clean($new_data);

		}



		// get current fields, use $real_post_id (checked for in both modes)

		$current_fields = get_post_meta($real_post_id, $this->id . '_fields', TRUE);



		if ($this->mode == WPALCHEMY_MODE_EXTRACT)

		{

			$new_fields = array();



			if (is_array($new_data))

			{

				foreach ($new_data as $k => $v)

				{

					$field = $this->prefix . $k;

					

					array_push($new_fields,$field);



					$new_value = $new_data[$k];



					if (is_null($new_value))

					{

						delete_post_meta($post_id, $field);

					}

					else

					{

						update_post_meta($post_id, $field, $new_value);

					}

				}

			}



			$diff_fields = array_diff((array)$current_fields,$new_fields);



			if (is_array($diff_fields))

			{

				foreach ($diff_fields as $field)

				{

					delete_post_meta($post_id,$field);

				}

			}



			delete_post_meta($post_id, $this->id . '_fields');



			if ( ! empty($new_fields))

			{

				add_post_meta($post_id,$this->id . '_fields', $new_fields, TRUE);

			}



			// keep data tidy, delete values if previously using WPALCHEMY_MODE_ARRAY

			delete_post_meta($post_id, $this->id);

		}

		else

		{

			if (is_null($new_data))

			{

				delete_post_meta($post_id, $this->id);

			}

			else

			{

				update_post_meta($post_id, $this->id, $new_data);

			}



			// keep data tidy, delete values if previously using WPALCHEMY_MODE_EXTRACT

			if (is_array($current_fields))

			{

				foreach ($current_fields as $field)

				{

					delete_post_meta($post_id, $field);

				}



				delete_post_meta($post_id, $this->id . '_fields');

			}

		}



		// action: save

		if ($this->has_action('save'))

		{

			$this->do_action('save', $new_data, $real_post_id);

		}



		return $post_id;

	}



	/**

	 * Cleans an array, removing blank ('') values

	 *

	 * @static

	 * @since	1.0

	 * @access	public

	 * @param	array the array to clean (passed by reference)

	 */

	static function clean(&$arr)

	{

		if (is_array($arr))

		{

			foreach ($arr as $i => $v)

			{

				if (is_array($arr[$i])) 

				{

					WPAlchemy_MetaBox::clean($arr[$i]);

	 

					if (!count($arr[$i])) 

					{

						unset($arr[$i]);

					}

				}

				else 

				{

					if ('' == trim($arr[$i]) OR is_null($arr[$i])) 

					{

						unset($arr[$i]);

					}

				}

			}



			if (!count($arr)) 

			{

				$arr = array();

			}

			else

			{

				$keys = array_keys($arr);



				$is_numeric = TRUE;



				foreach ($keys as $key)

				{

					if (!is_numeric($key)) 

					{

						$is_numeric = FALSE;

						break;

					}

				}



				if ($is_numeric)

				{

					$arr = array_values($arr);

				}

			}

		}

	}



	/**

	 * Other than core modifications, here is the loop stack function and class to support

	 * nested repeatable group.

	 * author Vafpress

	 */



	var $_loop_stack = array();



	function push_loop($name, $length, $type)

	{

		$loop         = new WPA_Loop($name, $length, $type);

		$parent       = $this->get_the_current_loop();

		if($parent)

			$loop->parent = $parent->name;

		else

			$loop->parent = false;

		$this->_loop_stack[$name] = $loop;

		return $loop;

	}



	function push_or_set_current_loop($name, $length, $type)

	{

		if( !array_key_exists( $name, $this->_loop_stack ) )

		{

			$this->push_loop($name, $length, $type);

		}



		$this->set_current_loop($name);

	}



	function set_current_loop($name)

	{

		reset($this->_loop_stack);

		if(!array_key_exists($name, $this->_loop_stack)){

			return;

		}

		while(key($this->_loop_stack) !== $name)

			next($this->_loop_stack);

	}



	function next_loop()

	{

		return next($this->_loop_stack);

	}



	function prev_loop()

	{

		$parent = $this->get_the_current_loop()->parent;

		if($parent)

		{

			$this->set_current_loop($parent);

		}

		else

		{

			$this->_loop_stack = array();

			return false;

		}

	}



	function get_the_current_group_length()

	{

		return current($this->_loop_stack)->length;

	}



	function get_the_current_group_current()

	{

		return current($this->_loop_stack)->current;

	}



	function set_the_current_group_length($length)

	{

		current($this->_loop_stack)->length = $length;

	}



	function set_the_current_group_current($current)

	{

		current($this->_loop_stack)->current = $current;

	}



	function get_the_loop_collection($name = null)

	{

		$collection   = array();



		if(is_null($name))

		{

			$curr = $this->get_the_current_loop();

			if($curr)

			{

				$name         = $curr->name;

				$loop_stack   = $this->_loop_stack;

				$loop         = $loop_stack[$name];

				$collection[] = $loop;

				while ($loop)

				{

					$collection[] = $loop;

					if($loop->parent)

						$loop = $loop_stack[$loop->parent];

					else

						$loop = false;

				}

				$collection = array_reverse($collection);

			}

		}



		return $collection;

	}



	function get_the_loop_group_name($with_id = false)

	{

		$loop_name  = $with_id ? $this->id : '';

		$curr       = $this->get_the_current_loop();



		// copy _loop_stack to prevent internal pointer ruined

		$loop_stack = $this->get_the_loop_collection();

		// print_r($loop_stack);

		foreach ($loop_stack as $loop)

		{

			$loop_name .= '[' . $loop->name . '][' . $loop->current . ']';



			if($loop->name === $curr->name)

				break;

		}

		return $loop_name;

	}



	function get_the_loop_level()

	{

		$curr  = $this->get_the_current_loop();

		$depth = 0;



		// copy _loop_stack to prevent internal pointer ruined

		$loop_stack = $this->get_the_loop_collection();

		foreach ($loop_stack as $loop)

		{

			if($loop->name === $curr->name)

				break;

			$depth++;

		}

		return $depth;

	}



	function get_the_loop_group_id()

	{

		$loop_name  = '';

		$curr       = $this->get_the_current_loop();



		// copy _loop_stack to prevent internal pointer ruined

		$loop_stack = $this->get_the_loop_collection();

		foreach ($loop_stack as $key => $loop)

		{

			$is_first = false;

			$is_last  = false;



			reset($loop_stack);

			if ($key === key($loop_stack))

				$is_first = true;

			if ($loop->name === $curr->name)

				$is_last = true;



			$loop_name .= '[' . $loop->name . ']';



			if(!$is_last)

				$loop_name .= '[' . $loop->current . ']';



			if($loop->name === $curr->name)

				break;

		}

		return $loop_name;

	}



	function get_the_loop_group_name_array($with_id = false)

	{

		$loop_name   = array();

		$curr        = $this->get_the_current_loop();



		if($with_id)

		{	

			$loop_name[] = $this->id;

		}



		// copy _loop_stack to prevent internal pointer ruined

		$loop_stack = $this->get_the_loop_collection();

		foreach ($loop_stack as $loop)

		{

			$loop_name[] = $loop->name;

			$loop_name[] = $loop->current;



			if($loop->name === $curr->name)

				break;

		}

		return $loop_name;

	}



	function get_the_dotted_loop_group_name($with_id = false)

	{

		$loop_name  = $with_id ? $this->id : '';

		$curr       = $this->get_the_current_loop();



		// copy _loop_stack to prevent internal pointer ruined

		$loop_stack = $this->get_the_loop_collection();

		foreach ($loop_stack as $loop)

		{

			$loop_name .= ($loop_name === '' ? '' : '.') . $loop->name . '.' . $loop->current;



			if($loop->name === $curr->name)

				break;

		}

		return $loop_name;

	}



	function get_meta_by_dotted($dotted)

	{

		$keys = explode('.', $dotted);

		$meta = $this->meta;

		foreach ($keys as $key)

		{

			if(array_key_exists($key, $meta))

			{

				$meta = $meta[$key];

			}

			else

			{

				return null;

			}

		}

		return $meta;

	}



	function get_meta_by_array($arr)

	{

		$meta = $this->meta;



		if(!is_array($arr) || !is_array($meta) || is_null($meta))

			return null;

			

		foreach ($arr as $key)

		{

			if(is_array($meta) and array_key_exists($key, $meta))

			{

				$meta = $meta[$key];

			}

			else

			{

				return null;

			}

		}

		return $meta;

	}



	function get_the_current_group_count()

	{

		$arr  = $this->get_the_loop_group_name_array();

		end($arr);

		$last = key($arr);

		unset($arr[$last]);

		$meta = $this->get_meta_by_array($arr);

		return count($meta);

	}



	function increment_current_loop()

	{

		current($this->_loop_stack)->current++;

	}



	function get_the_current_loop()

	{

		return current($this->_loop_stack);

	}



	function is_in_multi_last()

	{

		// copy _loop_stack to prevent internal pointer ruined

		$loop_stack = $this->get_the_loop_collection();

		foreach ($loop_stack as $loop)

		{

			if($loop->type === 'multi' and $loop->is_last())

				return true;

		}

		return false;

	}



	function is_in_loop()

	{

		if(current($this->_loop_stack) === false)

			return false;

		return true;

	}



	function is_parent_multi()

	{

		// copy _loop_stack to prevent internal pointer ruined

		$loop_stack = $this->get_the_loop_collection();

		foreach ($loop_stack as $loop)

		{

			if($loop->type === 'multi')

				return true;

		}

		return false;

	}



	function the_copy_button_class()

	{

		$curr = $this->get_the_current_loop();

		return 'docopy-' . $curr->get_the_indexed_name();

	}



}



class Bunch_WPA_Loop

{



	public $length   = 0;



	public $parent   = NULL;



	public $current  = -1;



	public $name     = NULL;



	public $type     = false;



	function __construct($name, $length, $type)

	{

		$this->name   = $name;

		$this->length = $length;

		$this->type   = $type;

	}



	function the_indexed_name()

	{

		echo $this->get_the_indexed_name();

	}



	function get_the_indexed_name()

	{

		return $this->name . '[' . $this->current . ']';

	}



	function is_first()

	{

		if ( $this->current == 0 ) return TRUE;



		return FALSE;

	}



	function is_last()

	{

		if ( ( $this->current + 1 ) == $this->length ) return TRUE;



		return FALSE;

	}



}



/* eof */