PageRenderTime 15ms CodeModel.GetById 4ms app.highlight 18ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/farinspace/WPAlchemy/WPAlchemy/MetaBox.php

http://github.com/beingzoe/wpplugin-kitchen-sink-html5-base
PHP | 2237 lines | 1176 code | 329 blank | 732 comment | 175 complexity | ccabf4dd81f89e09fc701d4113978666 MD5 | raw file
   1<?php
   2
   3/**
   4 * @author		Dimas Begunoff
   5 * @copyright	Copyright (c) 2009, Dimas Begunoff, http://farinspace.com
   6 * @license		http://en.wikipedia.org/wiki/MIT_License The MIT License
   7 * @package		WPAlchemy
   8 * @version		1.4.2
   9 * @link		http://github.com/farinspace/wpalchemy
  10 * @link		http://farinspace.com
  11 */
  12
  13// todo: perhaps move _global_head and _global_foot locally, when first run
  14// define a constant to prevent other instances from running again ...
  15
  16add_action('admin_head', array('WPAlchemy_MetaBox', '_global_head'));
  17
  18add_action('admin_footer', array('WPAlchemy_MetaBox', '_global_foot'));
  19
  20define('WPALCHEMY_MODE_ARRAY', 'array');
  21
  22define('WPALCHEMY_MODE_EXTRACT', 'extract');
  23
  24define('WPALCHEMY_FIELD_HINT_TEXT', 'text');
  25
  26define('WPALCHEMY_FIELD_HINT_TEXTAREA', 'textarea');
  27
  28define('WPALCHEMY_FIELD_HINT_CHECKBOX', 'checkbox');
  29
  30define('WPALCHEMY_FIELD_HINT_CHECKBOX_MULTI', 'checkbox_multi');
  31
  32define('WPALCHEMY_FIELD_HINT_RADIO', 'radio');
  33
  34define('WPALCHEMY_FIELD_HINT_SELECT', 'select');
  35
  36define('WPALCHEMY_FIELD_HINT_SELECT_MULTI', 'select_multi');
  37
  38// depreciated, use WPALCHEMY_FIELD_HINT_SELECT_MULTI instead
  39define('WPALCHEMY_FIELD_HINT_SELECT_MULTIPLE', 'select_multiple');
  40
  41define('WPALCHEMY_LOCK_TOP', 'top');
  42
  43define('WPALCHEMY_LOCK_BOTTOM', 'bottom');
  44
  45define('WPALCHEMY_LOCK_BEFORE_POST_TITLE', 'before_post_title');
  46
  47define('WPALCHEMY_LOCK_AFTER_POST_TITLE', 'after_post_title');
  48
  49define('WPALCHEMY_VIEW_START_OPENED', 'opened');
  50
  51define('WPALCHEMY_VIEW_START_CLOSED', 'closed');
  52
  53define('WPALCHEMY_VIEW_ALWAYS_OPENED', 'always_opened');
  54
  55class WPAlchemy_MetaBox
  56{
  57	/**
  58	 * User defined identifier for the meta box, prefix with an underscore to
  59	 * prevent option(s) form showing up in the custom fields meta box, this
  60	 * option should be used when instantiating the class.
  61	 *
  62	 * @since	1.0
  63	 * @access	public
  64	 * @var		string required
  65	 */
  66	var $id;
  67
  68	/**
  69	 * Used to set the title of the meta box, this option should be used when
  70	 * instantiating the class.
  71	 *
  72	 * @since	1.0
  73	 * @access	public
  74	 * @var		string required
  75	 * @see		$hide_title
  76	 */
  77	var $title = 'Custom Meta';
  78
  79	/**
  80	 * Used to set the meta box content, the contents of your meta box should be
  81	 * defined within this file, this option should be used when instantiating
  82	 * the class.
  83	 *
  84	 * @since	1.0
  85	 * @access	public
  86	 * @var		string required
  87	 */
  88	var $template;
  89
  90	/**
  91	 * Used to set the post types that the meta box can appear in, this option
  92	 * should be used when instantiating the class.
  93	 *
  94	 * @since	1.0
  95	 * @access	public
  96	 * @var		array
  97	 */
  98	var $types;
  99
 100	/**
 101	 * @since	1.0
 102	 * @access	public
 103	 * @var		bool
 104	 */
 105	var $context = 'normal';
 106
 107	/**
 108	 * @since	1.0
 109	 * @access	public
 110	 * @var		bool
 111	 */
 112	var $priority = 'high';
 113
 114	/**
 115	 * @since	1.0
 116	 * @access	public
 117	 * @var		bool
 118	 */
 119	var $autosave = TRUE;
 120
 121	/**
 122	 * Used to set how the class does its data storage, data will be stored as
 123	 * an associative array in a single meta entry in the wp_postmeta table or
 124	 * data can be set and individual entries in the wp_postmeta table, the
 125	 * following constants should be used when setting this option,
 126	 * WPALCHEMY_MODE_ARRAY (default) and WPALCHEMY_MODE_EXTRACT, this option
 127	 * should be used when instantiating the class.
 128	 *
 129	 * @since	1.2
 130	 * @access	public
 131	 * @var		string
 132	 */
 133	var $mode = WPALCHEMY_MODE_ARRAY;
 134
 135	/**
 136	 * When the mode option is set to WPALCHEMY_MODE_EXTRACT, you have to take
 137	 * care to avoid name collisions with other meta entries. Use this option to
 138	 * automatically add a prefix to your variables, this option should be used
 139	 * when instantiating the class.
 140	 *
 141	 * @since	1.2
 142	 * @access	public
 143	 * @var		array
 144	 */
 145	var $prefix;
 146
 147	/**
 148	 * @since	1.0
 149	 * @access	public
 150	 * @var		bool
 151	 */
 152	var $exclude_template;
 153
 154	/**
 155	 * @since	1.0
 156	 * @access	public
 157	 * @var		bool
 158	 */
 159	var $exclude_category_id;
 160
 161	/**
 162	 * @since	1.0
 163	 * @access	public
 164	 * @var		bool
 165	 */
 166	var $exclude_category;
 167
 168	/**
 169	 * @since	1.0
 170	 * @access	public
 171	 * @var		bool
 172	 */
 173	var $exclude_tag_id;
 174
 175	/**
 176	 * @since	1.0
 177	 * @access	public
 178	 * @var		bool
 179	 */
 180	var $exclude_tag;
 181
 182	/**
 183	 * @since	1.0
 184	 * @access	public
 185	 * @var		bool
 186	 */
 187	var $exclude_post_id;
 188
 189	/**
 190	 * @since	1.0
 191	 * @access	public
 192	 * @var		bool
 193	 */
 194	var $include_template;
 195
 196	/**
 197	 * @since	1.0
 198	 * @access	public
 199	 * @var		bool
 200	 */
 201	var $include_category_id;
 202
 203	/**
 204	 * @since	1.0
 205	 * @access	public
 206	 * @var		bool
 207	 */
 208	var $include_category;
 209
 210	/**
 211	 * @since	1.0
 212	 * @access	public
 213	 * @var		bool
 214	 */
 215	var $include_tag_id;
 216
 217	/**
 218	 * @since	1.0
 219	 * @access	public
 220	 * @var		bool
 221	 */
 222	var $include_tag;
 223
 224	/**
 225	 * @since	1.0
 226	 * @access	public
 227	 * @var		bool
 228	 */
 229	var $include_post_id;
 230
 231	/**
 232	 * Callback used on the WordPress "admin_init" action, the main benefit is
 233	 * that this callback is executed only when the meta box is present, this
 234	 * option should be used when instantiating the class.
 235	 *
 236	 * @since	1.3.4
 237	 * @access	public
 238	 * @var		string|array optional
 239	 */
 240	var $init_action;
 241
 242	/**
 243	 * Callback used to override when the meta box gets displayed, must return
 244	 * true or false to determine if the meta box should or should not be
 245	 * displayed, this option should be used when instantiating the class.
 246	 *
 247	 * @since	1.3
 248	 * @access	public
 249	 * @var		string|array optional
 250	 * @param	array $post_id first variable passed to the callback function
 251	 * @see		can_output()
 252	 */
 253	var $output_filter;
 254
 255	/**
 256	 * Callback used to override or insert meta data before saving, you can halt
 257	 * saving by passing back FALSE (return FALSE), this option should be used
 258	 * when instantiating the class.
 259	 *
 260	 * @since	1.3
 261	 * @access	public
 262	 * @var		string|array optional
 263	 * @param	array $meta meta box data, first variable passed to the callback function
 264	 * @param	string $post_id second variable passed to the callback function
 265	 * @see		$save_action, add_filter()
 266	 */
 267	var $save_filter;
 268
 269	/**
 270	 * Callback used to execute custom code after saving, this option should be
 271	 * used when instantiating the class.
 272	 *
 273	 * @since	1.3
 274	 * @access	public
 275	 * @var		string|array optional
 276	 * @param	array $meta meta box data, first variable passed to the callback function
 277	 * @param	string $post_id second variable passed to the callback function
 278	 * @see		$save_filter, add_filter()
 279	 */
 280	var $save_action;
 281
 282	/**
 283	 * Callback used to override or insert STYLE or SCRIPT tags into the head,
 284	 * this option should be used when instantiating the class.
 285	 *
 286	 * @since	1.3
 287	 * @access	public
 288	 * @var		string|array optional
 289	 * @param	array $content current head content, first variable passed to the callback function
 290	 * @see		$head_action, add_filter()
 291	 */
 292	var $head_filter;
 293
 294	/**
 295	 * Callback used to insert STYLE or SCRIPT tags into the head,
 296	 * this option should be used when instantiating the class.
 297	 *
 298	 * @since	1.3
 299	 * @access	public
 300	 * @var		string|array optional
 301	 * @see		$head_filter, add_action()
 302	 */
 303	var $head_action;
 304
 305	/**
 306	 * Callback used to override or insert SCRIPT tags into the footer, this
 307	 * option should be used when instantiating the class.
 308	 *
 309	 * @since	1.3
 310	 * @access	public
 311	 * @var		string|array optional
 312	 * @param	array $content current foot content, first variable passed to the callback function
 313	 * @see		$foot_action, add_filter()
 314	 */
 315	var $foot_filter;
 316
 317	/**
 318	 * Callback used to insert SCRIPT tags into the footer, this option should
 319	 * be used when instantiating the class.
 320	 *
 321	 * @since	1.3
 322	 * @access	public
 323	 * @var		string|array optional
 324	 * @see		$foot_filter, add_action()
 325	 */
 326	var $foot_action;
 327
 328	/**
 329	 * Used to hide the default content editor in a page or post, this option
 330	 * should be used when instantiating the class.
 331	 *
 332	 * @since	1.3
 333	 * @access	public
 334	 * @var		bool optional
 335	 */
 336	var $hide_editor = FALSE;
 337
 338	/**
 339	 * Used to hide the meta box title, this option should be used when
 340	 * instantiating the class.
 341	 *
 342	 * @since	1.3
 343	 * @access	public
 344	 * @var		bool optional
 345	 * @see		$title
 346	 */
 347	var $hide_title = FALSE;
 348
 349	/**
 350	 * Used to lock a meta box in place, possible values are: top, bottom,
 351	 * before_post_title, after_post_title, this option should be used when
 352	 * instantiating the class.
 353	 *
 354	 * @since		1.3.3
 355	 * @access		public
 356	 * @var			string optional possible values are: top, bottom, before_post_title, after_post_title
 357	 */
 358	var $lock;
 359
 360	/**
 361	 * Used to lock a meta box at top (below the default content editor), this
 362	 * option should be used when instantiating the class.
 363	 *
 364	 * @deprecated	deprecated since version 1.3.3
 365	 * @since		1.3
 366	 * @access		public
 367	 * @var			bool optional
 368	 * @see			$lock
 369	 */
 370	var $lock_on_top = FALSE;
 371
 372	/**
 373	 * Used to lock a meta box at bottom, this option should be used when
 374	 * instantiating the class.
 375	 *
 376	 * @deprecated	deprecated since version 1.3.3
 377	 * @since		1.3
 378	 * @access		public
 379	 * @var			bool optional
 380	 * @see			$lock
 381	 */
 382	var $lock_on_bottom = FALSE;
 383
 384	/**
 385	 * Used to set the initial view state of the meta box, possible values are:
 386	 * opened, closed, always_opened, this option should be used when
 387	 * instantiating the class.
 388	 *
 389	 * @since	1.3.3
 390	 * @access	public
 391	 * @var		string optional possible values are: opened, closed, always_opened
 392	 */
 393	var $view;
 394
 395	/**
 396	 * Used to hide the show/hide checkbox option from the screen options area,
 397	 * this option should be used when instantiating the class.
 398	 *
 399	 * @since		1.3.4
 400	 * @access		public
 401	 * @var			bool optional
 402	 */
 403	var $hide_screen_option = FALSE;
 404
 405	// private
 406
 407	var $meta;
 408	var $name;
 409	var $subname;
 410
 411	/**
 412	 * Used to provide field type hinting
 413	 *
 414	 * @since	1.3
 415	 * @access	private
 416	 * @var		string
 417	 * @see		the_field()
 418	 */
 419	var $hint;
 420
 421	var $length = 0;
 422	var $current = -1;
 423	var $in_loop = FALSE;
 424	var $in_template = FALSE;
 425	var $group_tag;
 426	var $current_post_id;
 427
 428	/**
 429	 * Used to store current loop details, cleared after loop ends
 430	 *
 431	 * @since	1.4
 432	 * @access	private
 433	 * @var		stdClass
 434	 * @see		have_fields_and_multi(), have_fields()
 435	 */
 436	var $_loop_data;
 437
 438	function WPAlchemy_MetaBox($arr)
 439	{
 440		$this->_loop_data = new stdClass;
 441
 442		$this->meta = array();
 443
 444		$this->types = array('post', 'page');
 445
 446		if (is_array($arr))
 447		{
 448			foreach ($arr as $n => $v)
 449			{
 450				$this->$n = $v;
 451			}
 452
 453			if (empty($this->id)) die('Meta box ID required');
 454
 455			if (is_numeric($this->id)) die('Meta box ID must be a string');
 456
 457			if (empty($this->template)) die('Meta box template file required');
 458
 459			// check for nonarray values
 460
 461			$exc_inc = array
 462			(
 463				'exclude_template',
 464				'exclude_category_id',
 465				'exclude_category',
 466				'exclude_tag_id',
 467				'exclude_tag',
 468				'exclude_post_id',
 469
 470				'include_template',
 471				'include_category_id',
 472				'include_category',
 473				'include_tag_id',
 474				'include_tag',
 475				'include_post_id'
 476			);
 477
 478			foreach ($exc_inc as $v)
 479			{
 480				// ideally the exclude and include values should be in array form, convert to array otherwise
 481				if (!empty($this->$v) AND !is_array($this->$v))
 482				{
 483					$this->$v = array_map('trim',explode(',',$this->$v));
 484				}
 485			}
 486
 487			// convert depreciated variables
 488			if ($this->lock_on_top) $this->lock = WPALCHEMY_LOCK_TOP;
 489			elseif ($this->lock_on_bottom) $this->lock = WPALCHEMY_LOCK_BOTTOM;
 490
 491			add_action('admin_init', array($this,'_init'));
 492
 493			// uses the default wordpress-importer plugin hook
 494			add_action('import_post_meta', array($this, '_import'), 10, 3);
 495		}
 496		else
 497		{
 498			die('Associative array parameters required');
 499		}
 500	}
 501
 502	/**
 503	 * Used to correct double serialized data during post/page export/import
 504	 *
 505	 * @since	1.3.16
 506	 * @access	private
 507	 */
 508	function _import($post_id, $key, $value)
 509	{
 510		if (WPALCHEMY_MODE_ARRAY == $this->mode AND $key == $this->id)
 511		{
 512			// maybe_unserialize fixes a wordpress bug which double serializes already serialized data during export/import
 513			update_post_meta($post_id, $key, maybe_unserialize(stripslashes($value)));
 514		}
 515	}
 516
 517	/**
 518	 * Used to initialize the meta box, runs on WordPress admin_init action,
 519	 * properly calls internal WordPress methods
 520	 *
 521	 * @since	1.0
 522	 * @access	private
 523	 */
 524	function _init()
 525	{
 526		// must be creating or editing a post or page
 527		if ( ! WPAlchemy_MetaBox::_is_post() AND ! WPAlchemy_MetaBox::_is_page()) return;
 528
 529		if ( ! empty($this->output_filter))
 530		{
 531			$this->add_filter('output', $this->output_filter);
 532		}
 533
 534		if ($this->can_output())
 535		{
 536			foreach ($this->types as $type)
 537			{
 538				add_meta_box($this->id . '_metabox', $this->title, array($this, '_setup'), $type, $this->context, $this->priority);
 539			}
 540
 541			add_action('save_post', array($this,'_save'));
 542
 543			$filters = array('save', 'head', 'foot');
 544
 545			foreach ($filters as $filter)
 546			{
 547				$var = $filter . '_filter';
 548
 549				if (!empty($this->$var))
 550				{
 551					if ('save' == $filter)
 552					{
 553						$this->add_filter($filter, $this->$var, 10, 2);
 554					}
 555					else
 556					{
 557						$this->add_filter($filter, $this->$var);
 558					}
 559				}
 560			}
 561
 562			$actions = array('save', 'head', 'foot', 'init');
 563
 564			foreach ($actions as $action)
 565			{
 566				$var = $action . '_action';
 567
 568				if (!empty($this->$var))
 569				{
 570					if ('save' == $action)
 571					{
 572						$this->add_action($action, $this->$var, 10, 2);
 573					}
 574					else
 575					{
 576						$this->add_action($action, $this->$var);
 577					}
 578				}
 579			}
 580
 581			add_action('admin_head', array($this,'_head'), 11);
 582
 583			add_action('admin_footer', array($this,'_foot'), 11);
 584
 585			// action: init
 586			if ($this->has_action('init'))
 587			{
 588				$this->do_action('init');
 589			}
 590		}
 591	}
 592
 593	/**
 594	 * Used to insert STYLE or SCRIPT tags into the head, called on WordPress
 595	 * admin_head action.
 596	 *
 597	 * @since	1.3
 598	 * @access	private
 599	 * @see		_foot()
 600	 */
 601	function _head()
 602	{
 603		$content = NULL;
 604
 605		ob_start();
 606
 607		?>
 608		<style type="text/css">
 609			<?php if ($this->hide_editor): ?> #postdiv, #postdivrich { display:none; } <?php endif; ?>
 610		</style>
 611		<?php
 612
 613		$content = ob_get_contents();
 614
 615		ob_end_clean();
 616
 617		// filter: head
 618		if ($this->has_filter('head'))
 619		{
 620			$content = $this->apply_filters('head', $content);
 621		}
 622
 623		echo $content;
 624
 625		// action: head
 626		if ($this->has_action('head'))
 627		{
 628			$this->do_action('head');
 629		}
 630	}
 631
 632	/**
 633	 * Used to insert SCRIPT tags into the footer, called on WordPress
 634	 * admin_footer action.
 635	 *
 636	 * @since	1.3
 637	 * @access	private
 638	 * @see		_head()
 639	 */
 640	function _foot()
 641	{
 642		$content = NULL;
 643
 644		if
 645		(
 646			$this->lock OR
 647			$this->hide_title OR
 648			$this->view OR
 649			$this->hide_screen_option
 650		)
 651		{
 652			ob_start();
 653
 654			?>
 655			<script type="text/javascript">
 656			/* <![CDATA[ */
 657			(function($){ /* not using jQuery ondomready, code runs right away in footer */
 658
 659				var mb_id = '<?php echo $this->id; ?>';
 660				var mb = $('#' + mb_id + '_metabox');
 661
 662				<?php if (WPALCHEMY_LOCK_TOP == $this->lock): ?>
 663				<?php if ('side' == $this->context): ?>
 664				var id = 'wpalchemy-side-top';
 665				if ( ! $('#'+id).length)
 666				{
 667					$('<div></div>').attr('id',id).prependTo('#side-info-column');
 668				}
 669				<?php else: ?>
 670				var id = 'wpalchemy-content-top';
 671				if ( ! $('#'+id).length)
 672				{
 673					$('<div></div>').attr('id',id).insertAfter('#postdiv, #postdivrich');
 674				}
 675				<?php endif; ?>
 676				$('#'+id).append(mb);
 677				<?php elseif (WPALCHEMY_LOCK_BOTTOM == $this->lock): ?>
 678				<?php if ('side' == $this->context): ?>
 679				var id = 'wpalchemy-side-bottom';
 680				if ( ! $('#'+id).length)
 681				{
 682					$('<div></div>').attr('id',id).appendTo('#side-info-column');
 683				}
 684				<?php else: ?>
 685				if ( ! $('#advanced-sortables').children().length)
 686				{
 687					$('#advanced-sortables').css('display','none');
 688				}
 689
 690				var id = 'wpalchemy-content-bottom';
 691				if ( ! $('#'+id).length)
 692				{
 693					$('<div></div>').attr('id',id).insertAfter('#advanced-sortables');
 694				}
 695				<?php endif; ?>
 696				$('#'+id).append(mb);
 697				<?php elseif (WPALCHEMY_LOCK_BEFORE_POST_TITLE == $this->lock): ?>
 698				<?php if ('side' != $this->context): ?>
 699				var id = 'wpalchemy-content-bpt';
 700				if ( ! $('#'+id).length)
 701				{
 702					$('<div></div>').attr('id',id).prependTo('#post-body-content');
 703				}
 704				$('#'+id).append(mb);
 705				<?php endif; ?>
 706				<?php elseif (WPALCHEMY_LOCK_AFTER_POST_TITLE == $this->lock): ?>
 707				<?php if ('side' != $this->context): ?>
 708				var id = 'wpalchemy-content-apt';
 709				if ( ! $('#'+id).length)
 710				{
 711					$('<div></div>').attr('id',id).insertAfter('#titlediv');
 712				}
 713				$('#'+id).append(mb);
 714				<?php endif; ?>
 715				<?php endif; ?>
 716
 717				<?php if ( ! empty($this->lock)): ?>
 718				$('.hndle', mb).css('cursor','pointer');
 719				$('.handlediv', mb).remove();
 720				<?php endif; ?>
 721
 722				<?php if ($this->hide_title): ?>
 723				$('.hndle', mb).remove();
 724				$('.handlediv', mb).remove();
 725				mb.removeClass('closed'); /* start opened */
 726				<?php endif; ?>
 727
 728				<?php if (WPALCHEMY_VIEW_START_OPENED == $this->view): ?>
 729				mb.removeClass('closed');
 730				<?php elseif (WPALCHEMY_VIEW_START_CLOSED == $this->view): ?>
 731				mb.addClass('closed');
 732				<?php elseif (WPALCHEMY_VIEW_ALWAYS_OPENED == $this->view): ?>
 733				/* todo: need to find a way to add this script block below, load-scripts.php?... */
 734				var h3 = mb.children('h3');
 735				setTimeout(function(){ h3.unbind('click'); }, 1000);
 736				$('.handlediv', mb).remove();
 737				mb.removeClass('closed'); /* start opened */
 738				$('.hndle', mb).css('cursor','auto');
 739				<?php endif; ?>
 740
 741				<?php if ($this->hide_screen_option): ?>
 742					$('.metabox-prefs label[for='+ mb_id +'_metabox-hide]').remove();
 743				<?php endif; ?>
 744
 745				mb = null;
 746
 747			})(jQuery);
 748			/* ]]> */
 749			</script>
 750			<?php
 751
 752			$content = ob_get_contents();
 753
 754			ob_end_clean();
 755		}
 756
 757		// filter: foot
 758		if ($this->has_filter('foot'))
 759		{
 760			$content = $this->apply_filters('foot', $content);
 761		}
 762
 763		echo $content;
 764
 765		// action: foot
 766		if ($this->has_action('foot'))
 767		{
 768			$this->do_action('foot');
 769		}
 770	}
 771
 772	/**
 773	 * Used to setup the meta box content template
 774	 *
 775	 * @since	1.0
 776	 * @access	private
 777	 * @see		_init()
 778	 */
 779	function _setup()
 780	{
 781		$this->in_template = TRUE;
 782
 783		// also make current post data available
 784		global $post;
 785
 786		// shortcuts
 787		$mb =& $this;
 788		$metabox =& $this;
 789		$id = $this->id;
 790		$meta = $this->_meta(NULL, TRUE);
 791
 792		// use include because users may want to use one templete for multiple meta boxes
 793		include $this->template;
 794
 795		// create a nonce for verification
 796		echo '<input type="hidden" name="'. $this->id .'_nonce" value="' . wp_create_nonce($this->id) . '" />';
 797
 798		$this->in_template = FALSE;
 799	}
 800
 801	/**
 802	 * Used to properly prefix the filter tag, the tag is unique to the meta
 803	 * box instance
 804	 *
 805	 * @since	1.3
 806	 * @access	private
 807	 * @param	string $tag name of the filter
 808	 * @return	string uniquely prefixed tag name
 809	 */
 810	function _get_filter_tag($tag)
 811	{
 812		$prefix = 'wpalchemy_filter_' . $this->id . '_';
 813		$prefix = preg_replace('/_+/', '_', $prefix);
 814
 815		$tag = preg_replace('/^'. $prefix .'/i', '', $tag);
 816		return $prefix . $tag;
 817	}
 818
 819	/**
 820	 * Uses WordPress add_filter() function, see WordPress add_filter()
 821	 *
 822	 * @since	1.3
 823	 * @access	public
 824	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L65
 825	 */
 826	function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1)
 827	{
 828		$tag = $this->_get_filter_tag($tag);;
 829		add_filter($tag, $function_to_add, $priority, $accepted_args);
 830	}
 831
 832	/**
 833	 * Uses WordPress has_filter() function, see WordPress has_filter()
 834	 *
 835	 * @since	1.3
 836	 * @access	public
 837	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L86
 838	 */
 839	function has_filter($tag, $function_to_check = FALSE)
 840	{
 841		$tag = $this->_get_filter_tag($tag);
 842		return has_filter($tag, $function_to_check);
 843	}
 844
 845	/**
 846	 * Uses WordPress apply_filters() function, see WordPress apply_filters()
 847	 *
 848	 * @since	1.3
 849	 * @access	public
 850	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L134
 851	 */
 852	function apply_filters($tag, $value)
 853	{
 854		$args = func_get_args();
 855		$args[0] = $this->_get_filter_tag($tag);
 856		return call_user_func_array('apply_filters', $args);
 857	}
 858
 859	/**
 860	 * Uses WordPress remove_filter() function, see WordPress remove_filter()
 861	 *
 862	 * @since	1.3
 863	 * @access	public
 864	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L250
 865	 */
 866	function remove_filter($tag, $function_to_remove, $priority = 10, $accepted_args = 1)
 867	{
 868		$tag = $this->_get_filter_tag($tag);
 869		return remove_filter($tag, $function_to_remove, $priority, $accepted_args);
 870	}
 871
 872	/**
 873	 * Used to properly prefix the action tag, the tag is unique to the meta
 874	 * box instance
 875	 *
 876	 * @since	1.3
 877	 * @access	private
 878	 * @param	string $tag name of the action
 879	 * @return	string uniquely prefixed tag name
 880	 */
 881	function _get_action_tag($tag)
 882	{
 883		$prefix = 'wpalchemy_action_' . $this->id . '_';
 884		$prefix = preg_replace('/_+/', '_', $prefix);
 885
 886		$tag = preg_replace('/^'. $prefix .'/i', '', $tag);
 887		return $prefix . $tag;
 888	}
 889
 890	/**
 891	 * Uses WordPress add_action() function, see WordPress add_action()
 892	 *
 893	 * @since	1.3
 894	 * @access	public
 895	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L324
 896	 */
 897	function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1)
 898	{
 899		$tag = $this->_get_action_tag($tag);
 900		add_action($tag, $function_to_add, $priority, $accepted_args);
 901	}
 902
 903	/**
 904	 * Uses WordPress has_action() function, see WordPress has_action()
 905	 *
 906	 * @since	1.3
 907	 * @access	public
 908	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L492
 909	 */
 910	function has_action($tag, $function_to_check = FALSE)
 911	{
 912		$tag = $this->_get_action_tag($tag);
 913		return has_action($tag, $function_to_check);
 914	}
 915
 916	/**
 917	 * Uses WordPress remove_action() function, see WordPress remove_action()
 918	 *
 919	 * @since	1.3
 920	 * @access	public
 921	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L513
 922	 */
 923	function remove_action($tag, $function_to_remove, $priority = 10, $accepted_args = 1)
 924	{
 925		$tag = $this->_get_action_tag($tag);
 926		return remove_action($tag, $function_to_remove, $priority, $accepted_args);
 927	}
 928
 929	/**
 930	 * Uses WordPress do_action() function, see WordPress do_action()
 931	 * @since	1.3
 932	 * @access	public
 933	 * @link	http://core.trac.wordpress.org/browser/trunk/wp-includes/plugin.php#L352
 934	 */
 935	function do_action($tag, $arg = '')
 936	{
 937		$args = func_get_args();
 938		$args[0] = $this->_get_action_tag($tag);
 939		return call_user_func_array('do_action', $args);
 940	}
 941
 942	/**
 943	 * Used to check if creating a new post or editing one
 944	 *
 945	 * @static
 946	 * @since	1.3.7
 947	 * @access	private
 948	 * @return	bool
 949	 * @see		_is_page()
 950	 */
 951	function _is_post()
 952	{
 953		if ('post' == WPAlchemy_MetaBox::_is_post_or_page())
 954		{
 955			return TRUE;
 956		}
 957
 958		return FALSE;
 959	}
 960
 961	/**
 962	 * Used to check if creating a new page or editing one
 963	 *
 964	 * @static
 965	 * @since	1.3.7
 966	 * @access	private
 967	 * @return	bool
 968	 * @see		_is_post()
 969	 */
 970	function _is_page()
 971	{
 972		if ('page' == WPAlchemy_MetaBox::_is_post_or_page())
 973		{
 974			return TRUE;
 975		}
 976
 977		return FALSE;
 978	}
 979
 980	/**
 981	 * Used to check if creating or editing a post or page
 982	 *
 983	 * @static
 984	 * @since	1.3.8
 985	 * @access	private
 986	 * @return	string "post" or "page"
 987	 * @see		_is_post(), _is_page()
 988	 */
 989	function _is_post_or_page()
 990	{
 991		$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : NULL ;
 992
 993		$file = basename(parse_url($uri, PHP_URL_PATH));
 994
 995		if ($uri AND in_array($file, array('post.php', 'post-new.php')))
 996		{
 997			$post_id = isset($_GET['post']) ? $_GET['post'] : NULL ;
 998
 999			$post_type = isset($_GET['post_type']) ? $_GET['post_type'] : NULL ;
1000
1001			$post_type = $post_id ? get_post_type($post_id) : $post_type ;
1002
1003			if ('page' == $post_type)
1004			{
1005				return 'page';
1006			}
1007			else
1008			{
1009				return 'post';
1010			}
1011		}
1012
1013		return NULL;
1014	}
1015
1016	/**
1017	 * @since	1.0
1018	 */
1019	function can_output()
1020	{
1021		global $post;
1022
1023		$p_post_id = isset($_POST['post_ID']) ? $_POST['post_ID'] : '' ;
1024		$g_post_id = isset($_GET['post']) ? $_GET['post'] : '' ;
1025
1026		$post_id = $g_post_id ? $g_post_id : $p_post_id ;
1027		$post_id = (!empty($post) AND $post->ID) ? $post->ID : $post_id ;
1028
1029		if (!empty($this->exclude_template) OR !empty($this->include_template))
1030		{
1031			$template_file = get_post_meta($post_id,'_wp_page_template',TRUE);
1032		}
1033
1034		if
1035		(
1036			!empty($this->exclude_category) OR
1037			!empty($this->exclude_category_id) OR
1038			!empty($this->include_category) OR
1039			!empty($this->include_category_id)
1040		)
1041		{
1042			$categories = wp_get_post_categories($post_id,'fields=all');
1043		}
1044
1045		if
1046		(
1047			!empty($this->exclude_tag) OR
1048			!empty($this->exclude_tag_id) OR
1049			!empty($this->include_tag) OR
1050			!empty($this->include_tag_id)
1051		)
1052		{
1053			$tags = wp_get_post_tags($post_id);
1054		}
1055
1056		// processing order: "exclude" then "include"
1057		// processing order: "template" then "category" then "post"
1058
1059		$can_output = TRUE; // include all
1060
1061		if
1062		(
1063			!empty($this->exclude_template) OR
1064			!empty($this->exclude_category_id) OR
1065			!empty($this->exclude_category) OR
1066			!empty($this->exclude_tag_id) OR
1067			!empty($this->exclude_tag) OR
1068			!empty($this->exclude_post_id) OR
1069			!empty($this->include_template) OR
1070			!empty($this->include_category_id) OR
1071			!empty($this->include_category) OR
1072			!empty($this->include_tag_id) OR
1073			!empty($this->include_tag) OR
1074			!empty($this->include_post_id)
1075		)
1076		{
1077			if (!empty($this->exclude_template))
1078			{
1079				if (in_array($template_file,$this->exclude_template))
1080				{
1081					$can_output = FALSE;
1082				}
1083			}
1084
1085			if (!empty($this->exclude_category_id))
1086			{
1087				foreach ($categories as $cat)
1088				{
1089					if (in_array($cat->term_id,$this->exclude_category_id))
1090					{
1091						$can_output = FALSE;
1092						break;
1093					}
1094				}
1095			}
1096
1097			if (!empty($this->exclude_category))
1098			{
1099				foreach ($categories as $cat)
1100				{
1101					if
1102					(
1103						in_array($cat->slug,$this->exclude_category) OR
1104						in_array($cat->name,$this->exclude_category)
1105					)
1106					{
1107						$can_output = FALSE;
1108						break;
1109					}
1110				}
1111			}
1112
1113			if (!empty($this->exclude_tag_id))
1114			{
1115				foreach ($tags as $tag)
1116				{
1117					if (in_array($tag->term_id,$this->exclude_tag_id))
1118					{
1119						$can_output = FALSE;
1120						break;
1121					}
1122				}
1123			}
1124
1125			if (!empty($this->exclude_tag))
1126			{
1127				foreach ($tags as $tag)
1128				{
1129					if
1130					(
1131						in_array($tag->slug,$this->exclude_tag) OR
1132						in_array($tag->name,$this->exclude_tag)
1133					)
1134					{
1135						$can_output = FALSE;
1136						break;
1137					}
1138				}
1139			}
1140
1141			if (!empty($this->exclude_post_id))
1142			{
1143				if (in_array($post_id,$this->exclude_post_id))
1144				{
1145					$can_output = FALSE;
1146				}
1147			}
1148
1149			// excludes are not set use "include only" mode
1150
1151			if
1152			(
1153				empty($this->exclude_template) AND
1154				empty($this->exclude_category_id) AND
1155				empty($this->exclude_category) AND
1156				empty($this->exclude_tag_id) AND
1157				empty($this->exclude_tag) AND
1158				empty($this->exclude_post_id)
1159			)
1160			{
1161				$can_output = FALSE;
1162			}
1163
1164			if (!empty($this->include_template))
1165			{
1166				if (in_array($template_file,$this->include_template))
1167				{
1168					$can_output = TRUE;
1169				}
1170			}
1171
1172			if (!empty($this->include_category_id))
1173			{
1174				foreach ($categories as $cat)
1175				{
1176					if (in_array($cat->term_id,$this->include_category_id))
1177					{
1178						$can_output = TRUE;
1179						break;
1180					}
1181				}
1182			}
1183
1184			if (!empty($this->include_category))
1185			{
1186				foreach ($categories as $cat)
1187				{
1188					if
1189					(
1190						in_array($cat->slug,$this->include_category) OR
1191						in_array($cat->name,$this->include_category)
1192					)
1193					{
1194						$can_output = TRUE;
1195						break;
1196					}
1197				}
1198			}
1199
1200			if (!empty($this->include_tag_id))
1201			{
1202				foreach ($tags as $tag)
1203				{
1204					if (in_array($tag->term_id,$this->include_tag_id))
1205					{
1206						$can_output = TRUE;
1207						break;
1208					}
1209				}
1210			}
1211
1212			if (!empty($this->include_tag))
1213			{
1214				foreach ($tags as $tag)
1215				{
1216					if
1217					(
1218						in_array($tag->slug,$this->include_tag) OR
1219						in_array($tag->name,$this->include_tag)
1220					)
1221					{
1222						$can_output = TRUE;
1223						break;
1224					}
1225				}
1226			}
1227
1228			if (!empty($this->include_post_id))
1229			{
1230				if (in_array($post_id,$this->include_post_id))
1231				{
1232					$can_output = TRUE;
1233				}
1234			}
1235		}
1236
1237		// $_GET['post_type'] used with post-new.php
1238		$post_type = isset($_GET['post_type']) ? $_GET['post_type'] : NULL ;
1239
1240		// get_post_type() works only with existing posts or pages get_post_type($post_id);
1241		$post_type = $post_type ? $post_type : get_post_type($post_id) ;
1242
1243		if (! empty($post_type) AND ! in_array($post_type, $this->types))
1244		{
1245			$can_output = FALSE;
1246		}
1247
1248		// filter: output (can_output)
1249		if ($this->has_filter('output'))
1250		{
1251			$can_output = $this->apply_filters('output', $post_id);
1252		}
1253
1254		return $can_output;
1255	}
1256
1257	/**
1258	 * Used to insert global STYLE or SCRIPT tags into the head, called on
1259	 * WordPress admin_footer action.
1260	 *
1261	 * @static
1262	 * @since	1.3
1263	 * @access	private
1264	 * @see		_global_foot()
1265	 */
1266	function _global_head()
1267	{
1268		// must be creating or editing a post or page
1269		if ( ! WPAlchemy_MetaBox::_is_post() AND ! WPAlchemy_MetaBox::_is_page()) return;
1270
1271		// todo: you're assuming people will want to use this exact functionality
1272		// consider giving a developer access to change this via hooks/callbacks
1273
1274		// include javascript for special functionality
1275		?><style type="text/css"> .wpa_group.tocopy { display:none; } </style>
1276		<script type="text/javascript">
1277		/* <![CDATA[ */
1278		jQuery(function($)
1279		{
1280			$(document).click(function(e)
1281			{
1282				var elem = $(e.target);
1283
1284				if (elem.attr('class') && elem.filter('[class*=dodelete]').length)
1285				{
1286					e.preventDefault();
1287
1288					var p = elem.parents('.postbox'); /*wp*/
1289
1290					var the_name = elem.attr('class').match(/dodelete-([a-zA-Z0-9_-]*)/i);
1291
1292					the_name = (the_name && the_name[1]) ? the_name[1] : null ;
1293
1294					/* todo: expose and allow editing of this message */
1295					if (confirm('This action can not be undone, are you sure?'))
1296					{
1297						if (the_name)
1298						{
1299							$('.wpa_group-'+ the_name, p).not('.tocopy').remove();
1300						}
1301						else
1302						{
1303							elem.parents('.wpa_group').remove();
1304						}
1305
1306						the_name = elem.parents('.wpa_group').attr('class').match(/wpa_group-([a-zA-Z0-9_-]*)/i)[1];
1307
1308						checkLoopLimit(the_name);
1309
1310						$.wpalchemy.trigger('wpa_delete');
1311					}
1312				}
1313			});
1314
1315			$('[class*=docopy-]').click(function(e)
1316			{
1317				e.preventDefault();
1318
1319				var p = $(this).parents('.postbox'); /*wp*/
1320
1321				var the_name = $(this).attr('class').match(/docopy-([a-zA-Z0-9_-]*)/i)[1];
1322
1323				var the_group = $('.wpa_group-'+ the_name +':first.tocopy', p);
1324
1325				var the_clone = the_group.clone().removeClass('tocopy');
1326
1327				var the_props = ['name', 'id', 'for'];
1328
1329				the_group.find('input, textarea, select, button, label').each(function(i,elem)
1330				{
1331					for (var j = 0; j < the_props.length; j++)
1332					{
1333						var the_prop = $(elem).attr(the_props[j]);
1334
1335						if (the_prop)
1336						{
1337							var the_match = the_prop.match(/\[(\d+)\]/i);
1338							the_prop = the_prop.replace(the_match[0],'['+(+the_match[1]+1)+']');
1339							$(elem).attr(the_props[j], the_prop);
1340						}
1341					}
1342				});
1343
1344				if ($(this).hasClass('ontop'))
1345				{
1346					$('.wpa_group-'+ the_name +':first', p).before(the_clone);
1347				}
1348				else
1349				{
1350					the_group.before(the_clone);
1351				}
1352
1353				checkLoopLimit(the_name);
1354
1355				$.wpalchemy.trigger('wpa_copy', [the_clone]);
1356			});
1357
1358			function checkLoopLimit(name)
1359			{
1360				var elem = $('.docopy-' + name);
1361
1362				var the_match = $('.wpa_loop-' + name).attr('class').match(/wpa_loop_limit-([0-9]*)/i);
1363
1364				if (the_match)
1365				{
1366					var the_limit = the_match[1];
1367
1368					if ($('.wpa_group-' + name).not('.wpa_group.tocopy').length >= the_limit)
1369					{
1370						elem.hide();
1371					}
1372					else
1373					{
1374						elem.show();
1375					}
1376				}
1377			}
1378
1379			/* do an initial limit check, show or hide buttons */
1380			$('[class*=docopy-]').each(function()
1381			{
1382				var the_name = $(this).attr('class').match(/docopy-([a-zA-Z0-9_-]*)/i)[1];
1383
1384				checkLoopLimit(the_name);
1385			});
1386		});
1387		/* ]]> */
1388		</script>
1389		<?php
1390	}
1391
1392	/**
1393	 * Used to insert global SCRIPT tags into the footer, called on WordPress
1394	 * admin_footer action.
1395	 *
1396	 * @static
1397	 * @since	1.3
1398	 * @access	private
1399	 * @see		_global_head()
1400	 */
1401	function _global_foot()
1402	{
1403		// must be creating or editing a post or page
1404		if ( ! WPAlchemy_MetaBox::_is_post() AND ! WPAlchemy_MetaBox::_is_page()) return;
1405
1406		?>
1407		<script type="text/javascript">
1408		/* <![CDATA[ */
1409		(function($){ /* not using jQuery ondomready, code runs right away in footer */
1410
1411			/* use a global dom element to attach events to */
1412			$.wpalchemy = $('<div></div>').attr('id','wpalchemy').appendTo('body');
1413
1414		})(jQuery);
1415		/* ]]> */
1416		</script>
1417		<?php
1418	}
1419
1420	/**
1421	 * Gets the meta data for a meta box
1422	 *
1423	 * @since	1.0
1424	 * @access	public
1425	 * @param	int $post_id optional post ID for which to retrieve the meta data
1426	 * @return	array
1427	 * @see		_meta
1428	 */
1429	function the_meta($post_id = NULL)
1430	{
1431		return $this->_meta($post_id);
1432	}
1433
1434	/**
1435	 * Gets the meta data for a meta box
1436	 *
1437	 * Internal method calls will typically bypass the data retrieval and will
1438	 * immediately return the current meta data
1439	 *
1440	 * @since	1.3
1441	 * @access	private
1442	 * @param	int $post_id optional post ID for which to retrieve the meta data
1443	 * @param	bool $internal optional boolean if internally calling
1444	 * @return	array
1445	 * @see		the_meta()
1446	 */
1447	function _meta($post_id = NULL, $internal = FALSE)
1448	{
1449		if ( ! is_numeric($post_id))
1450		{
1451			if ($internal AND $this->current_post_id)
1452			{
1453				$post_id = $this->current_post_id;
1454			}
1455			else
1456			{
1457				global $post;
1458
1459				$post_id = $post->ID;
1460			}
1461		}
1462
1463		// this allows multiple internal calls to _meta() without having to fetch data everytime
1464		if ($internal AND !empty($this->meta) AND $this->current_post_id == $post_id) return $this->meta;
1465
1466		$this->current_post_id = $post_id;
1467
1468		// WPALCHEMY_MODE_ARRAY
1469
1470		$meta = get_post_meta($post_id, $this->id, TRUE);
1471
1472		// WPALCHEMY_MODE_EXTRACT
1473
1474		$fields = get_post_meta($post_id, $this->id . '_fields', TRUE);
1475
1476		if ( ! empty($fields) AND is_array($fields))
1477		{
1478			$meta = array();
1479
1480			foreach ($fields as $field)
1481			{
1482				$field_noprefix = preg_replace('/^' . $this->prefix . '/i', '', $field);
1483				$meta[$field_noprefix] = get_post_meta($post_id, $field, TRUE);
1484			}
1485		}
1486
1487		$this->meta = $meta;
1488
1489		return $this->meta;
1490	}
1491
1492	// user can also use the_ID(), php functions are case-insensitive
1493	/**
1494	 * @since	1.0
1495	 * @access	public
1496	 */
1497	function the_id()
1498	{
1499		echo $this->get_the_id();
1500	}
1501
1502	/**
1503	 * @since	1.0
1504	 * @access	public
1505	 */
1506	function get_the_id()
1507	{
1508		return $this->id;
1509	}
1510
1511	/**
1512	 * @since	1.0
1513	 * @access	public
1514	 */
1515	function the_field($n, $hint = NULL)
1516	{
1517		if ($this->in_loop) $this->subname = $n;
1518		else $this->name = $n;
1519
1520		$this->hint = $hint;
1521	}
1522
1523	/**
1524	 * @since	1.0
1525	 * @access	public
1526	 */
1527	function have_value($n = NULL)
1528	{
1529		if ($this->get_the_value($n)) return TRUE;
1530
1531		return FALSE;
1532	}
1533
1534	/**
1535	 * @since	1.0
1536	 * @access	public
1537	 */
1538	function the_value($n = NULL)
1539	{
1540		echo $this->get_the_value($n);
1541	}
1542
1543	/**
1544	 * @since	1.0
1545	 * @access	public
1546	 */
1547	function get_the_value($n = NULL, $collection = FALSE)
1548	{
1549		$this->_meta(NULL, TRUE);
1550
1551		if ($this->in_loop)
1552		{
1553			if(isset($this->meta[$this->name]))
1554			{
1555				$n = is_null($n) ? $this->subname : $n ;
1556
1557				if(!is_null($n))
1558				{
1559					if ($collection)
1560					{
1561						if(isset($this->meta[$this->name][$this->current]))
1562						{
1563							return $this->meta[$this->name][$this->current];
1564						}
1565					}
1566					else
1567					{
1568						if(isset($this->meta[$this->name][$this->current][$n]))
1569						{
1570							return $this->meta[$this->name][$this->current][$n];
1571						}
1572					}
1573				}
1574				else
1575				{
1576					if ($collection)
1577					{
1578						if(isset($this->meta[$this->name]))
1579						{
1580							return $this->meta[$this->name];
1581						}
1582					}
1583					else
1584					{
1585						if(isset($this->meta[$this->name][$this->current]))
1586						{
1587							return $this->meta[$this->name][$this->current];
1588						}
1589					}
1590				}
1591			}
1592		}
1593		else
1594		{
1595			$n = is_null($n) ? $this->name : $n ;
1596
1597			if(isset($this->meta[$n])) return $this->meta[$n];
1598		}
1599
1600		return NULL;
1601	}
1602
1603	/**
1604	 * @since	1.0
1605	 * @access	public
1606	 */
1607	function the_name($n = NULL)
1608	{
1609		echo $this->get_the_name($n);
1610	}
1611
1612	/**
1613	 * @since	1.0
1614	 * @access	public
1615	 */
1616	function get_the_name($n = NULL)
1617	{
1618		if (!$this->in_template AND $this->mode == WPALCHEMY_MODE_EXTRACT)
1619		{
1620			return $this->prefix . str_replace($this->prefix, '', is_null($n) ? $this->name : $n);
1621		}
1622
1623		if ($this->in_loop)
1624		{
1625			$n = is_null($n) ? $this->subname : $n ;
1626
1627			if (!is_null($n)) return $this->id . '[' . $this->name . '][' . $this->current . '][' . $n . ']' ;
1628
1629			$the_field = $this->id . '[' . $this->name . '][' . $this->current . ']' ;
1630		}
1631		else
1632		{
1633			$n = is_null($n) ? $this->name : $n ;
1634
1635			$the_field = $this->id . '[' . $n . ']';
1636		}
1637
1638		$hints = array
1639		(
1640			WPALCHEMY_FIELD_HINT_CHECKBOX_MULTI,
1641			WPALCHEMY_FIELD_HINT_SELECT_MULTI,
1642			WPALCHEMY_FIELD_HINT_SELECT_MULTIPLE,
1643		);
1644
1645		if (in_array($this->hint, $hints))
1646		{
1647			$the_field .= '[]';
1648		}
1649
1650		return $the_field;
1651	}
1652
1653	/**
1654	 * @since	1.1
1655	 * @access	public
1656	 */
1657	function the_index()
1658	{
1659		echo $this->get_the_index();
1660	}
1661
1662	/**
1663	 * @since	1.1
1664	 * @access	public
1665	 */
1666	function get_the_index()
1667	{
1668		return $this->in_loop ? $this->current : 0 ;
1669	}
1670
1671	/**
1672	 * @since	1.0
1673	 * @access	public
1674	 */
1675	function is_first()
1676	{
1677		if ($this->in_loop AND $this->current == 0) return TRUE;
1678
1679		return FALSE;
1680	}
1681
1682	/**
1683	 * @since	1.0
1684	 * @access	public
1685	 */
1686	function is_last()
1687	{
1688		if ($this->in_loop AND ($this->current+1) == $this->length) return TRUE;
1689
1690		return FALSE;
1691	}
1692
1693	/**
1694	 * Used to check if a value is a match
1695	 *
1696	 * @since	1.1
1697	 * @access	public
1698	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1699	 * @param	string $v optional the value to check for
1700	 * @return	bool
1701	 * @see		is_value()
1702	 */
1703	function is_value($n, $v = NULL)
1704	{
1705		if (is_null($v))
1706		{
1707			$the_value = $this->get_the_value();
1708
1709			$v = $n;
1710		}
1711		else
1712		{
1713			$the_value = $this->get_the_value($n);
1714		}
1715
1716		if($v == $the_value) return TRUE;
1717
1718		return FALSE;
1719	}
1720
1721	/**
1722	 * Used to check if a value is selected, useful when working with checkbox,
1723	 * radio and select values.
1724	 *
1725	 * @since	1.3
1726	 * @access	public
1727	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1728	 * @param	string $v optional the value to check for
1729	 * @return	bool
1730	 * @see		is_value()
1731	 */
1732	function is_selected($n, $v = NULL)
1733	{
1734		if (is_null($v))
1735		{
1736			$the_value = $this->get_the_value(NULL, TRUE);
1737
1738			$v = $n;
1739		}
1740		else
1741		{
1742			$the_value = $this->get_the_value($n, TRUE);
1743		}
1744
1745		if (is_array($the_value))
1746		{
1747			if (in_array($v, $the_value)) return TRUE;
1748		}
1749		elseif($v == $the_value)
1750		{
1751			return TRUE;
1752		}
1753
1754		return FALSE;
1755	}
1756
1757	/**
1758	 * Prints the current state of a checkbox field and should be used inline
1759	 * within the INPUT tag.
1760	 *
1761	 * @since	1.3
1762	 * @access	public
1763	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1764	 * @param	string $v optional the value to check for
1765	 * @see		get_the_checkbox_state()
1766	 */
1767	function the_checkbox_state($n, $v = NULL)
1768	{
1769		echo $this->get_the_checkbox_state($n, $v);
1770	}
1771
1772	/**
1773	 * Returns the current state of a checkbox field, the returned string is
1774	 * suitable to be used inline within the INPUT tag.
1775	 *
1776	 * @since	1.3
1777	 * @access	public
1778	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1779	 * @param	string $v optional the value to check for
1780	 * @return	string suitable to be used inline within the INPUT tag
1781	 * @see		the_checkbox_state()
1782	 */
1783	function get_the_checkbox_state($n, $v = NULL)
1784	{
1785		if ($this->is_selected($n, $v)) return ' checked="checked"';
1786	}
1787
1788	/**
1789	 * Prints the current state of a radio field and should be used inline
1790	 * within the INPUT tag.
1791	 *
1792	 * @since	1.3
1793	 * @access	public
1794	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1795	 * @param	string $v optional the value to check for
1796	 * @see		get_the_radio_state()
1797	 */
1798	function the_radio_state($n, $v = NULL)
1799	{
1800		echo $this->get_the_checkbox_state($n, $v);
1801	}
1802
1803	/**
1804	 * Returns the current state of a radio field, the returned string is
1805	 * suitable to be used inline within the INPUT tag.
1806	 *
1807	 * @since	1.3
1808	 * @access	public
1809	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1810	 * @param	string $v optional the value to check for
1811	 * @return	string suitable to be used inline within the INPUT tag
1812	 * @see		the_radio_state()
1813	 */
1814	function get_the_radio_state($n, $v = NULL)
1815	{
1816		return $this->get_the_checkbox_state($n, $v);
1817	}
1818
1819	/**
1820	 * Prints the current state of a select field and should be used inline
1821	 * within the SELECT tag.
1822	 *
1823	 * @since	1.3
1824	 * @access	public
1825	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1826	 * @param	string $v optional the value to check for
1827	 * @see		get_the_select_state()
1828	 */
1829	function the_select_state($n, $v = NULL)
1830	{
1831		echo $this->get_the_select_state($n, $v);
1832	}
1833
1834	/**
1835	 * Returns the current state of a select field, the returned string is
1836	 * suitable to be used inline within the SELECT tag.
1837	 *
1838	 * @since	1.3
1839	 * @access	public
1840	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1841	 * @param	string $v optional the value to check for
1842	 * @return	string suitable to be used inline within the SELECT tag
1843	 * @see		the_select_state()
1844	 */
1845	function get_the_select_state($n, $v = NULL)
1846	{
1847		if ($this->is_selected($n, $v)) return ' selected="selected"';
1848	}
1849
1850	/**
1851	 * @since	1.1
1852	 * @access	public
1853	 */
1854	function the_group_open($t = 'div')
1855	{
1856		echo $this->get_the_group_open($t);
1857	}
1858
1859	/**
1860	 * @since	1.1
1861	 * @access	public
1862	 */
1863	function get_the_group_open($t = 'div')
1864	{
1865		$this->group_tag = $t;
1866
1867		$loop_open = NULL;
1868
1869		$loop_open_classes = array('wpa_loop', 'wpa_loop-' . $this->name);
1870
1871		$css_class = array('wpa_group', 'wpa_group-'. $this->name);
1872
1873		if ($this->is_first())
1874		{
1875			array_push($css_class, 'first');
1876
1877			$loop_open = '<div class="wpa_loop">';
1878
1879			if (isset($this->_loop_data->limit))
1880			{
1881				array_push($loop_open_classes, 'wpa_loop_limit-' . $this->_loop_data->limit);
1882			}
1883
1884			$loop_open = '<div class="' . implode(' ', $loop_open_classes) . '">';
1885		}
1886
1887		if ($this->is_last())
1888		{
1889			array_push($css_class, 'last');
1890
1891			if ($this->in_loop == 'multi')
1892			{
1893				array_push($css_class, 'tocopy');
1894			}
1895		}
1896
1897		return $loop_open . '<' . $t . ' class="'. implode(' ', $css_class) . '">';
1898	}
1899
1900	/**
1901	 * @since	1.1
1902	 * @access	public
1903	 */
1904	function the_group_close()
1905	{
1906		echo $this->get_the_group_close();
1907	}
1908
1909	/**
1910	 * @since	1.1
1911	 * @access	public
1912	 */
1913	function get_the_group_close()
1914	{
1915		$loop_close = NULL;
1916
1917		if ($this->is_last())
1918		{
1919			$loop_close = '</div>';
1920		}
1921
1922		return '</' . $this->group_tag . '>' . $loop_close;
1923	}
1924
1925	/**
1926	 * @since	1.1
1927	 * @access	public
1928	 */
1929	function have_fields_and_multi($n, $options = NULL)
1930	{
1931		if (is_array($options))
1932		{
1933			// use as stdClass object
1934			$options = (object)$options;
1935
1936			$length = @$options->length;
1937
1938			$this->_loop_data->limit = @$options->limit;
1939		}
1940		else
1941		{
1942			// backward compatibility (bc)
1943			$length = $options;
1944		}
1945
1946		$this->_meta(NULL, TRUE);
1947
1948		$this->in_loop = 'multi';
1949
1950		return $this->_loop($n, $length, 2);
1951	}
1952
1953	/**
1954	 * @deprecated
1955	 * @since	1.0
1956	 * @access	public
1957	 */
1958	function have_fields_and_one($n)
1959	{
1960		$this->_meta(NULL, TRUE);
1961		$this->in_loop = 'single';
1962		return $this->_loop($n,NULL,1);
1963	}
1964
1965	/**
1966	 * @since	1.0
1967	 * @access	public
1968	 */
1969	function have_fields($n,$length=NULL)
1970	{
1971		$this->_meta(NULL, TRUE);
1972		//print_r($this->_meta(NULL, TRUE));
1973		//echo "<br />";
1974		$this->in_loop = 'normal';
1975		//echo "<br />this->_loop({$n}, {$length})" . $this->_loop($n,$length) . "<br />";
1976		return $this->_loop($n,$length);
1977	}
1978
1979	/**
1980	 * @since	1.0
1981	 * @access	private
1982	 */
1983	function _loop($n,$length=NULL,$and_one=0)
1984	{
1985		if (!$this->in_loop)
1986		{
1987			$this->in_loop = TRUE;
1988		}
1989
1990		$this->name = $n;
1991
1992		$cnt = count(!empty($this->meta[$n])?$this->meta[$n]:NULL);
1993
1994		$length = is_null($length) ? $cnt : $length ;
1995
1996		if ($this->in_loop == 'multi' AND $cnt > $length) $length = $cnt;
1997
1998		$this->length = $length;
1999
2000		if ($this->in_template AND $and_one)
2001		{
2002			if ($length == 0)
2003			{
2004				$this->length = $and_one;
2005			}
2006			else
2007			{
2008				$this->length = $length+1;
2009			}
2010		}
2011
2012		$this->current++;
2013
2014		if ($this->current < $this->length)
2015		{
2016			$this->subname = NULL;
2017
2018			$this->fieldtype = NULL;
2019
2020			return TRUE;
2021		}
2022		else if ($this->current == $this->length)
2023		{
2024			$this->name = NULL;
2025
2026			$this->current = -1;
2027		}
2028
2029		$this->in_loop = FALSE;
2030
2031		$this->_loop_data = new stdClass;
2032
2033		return FALSE;
2034	}
2035
2036	/**
2037	 * @since	1.0
2038	 * @access	private
2039	 */
2040	function _save($post_id)
2041	{
2042		/**
2043		 * note: the "save_post" action fires for saving revisions and post/pages,
2044		 * when saving a post this function fires twice, once for a revision save,
2045		 * and again for the post/page save ... the $post_id is different for the
2046		 * revision save, this means that "get_post_meta()" will not work if trying
2047		 * to get values for a revision (as it has no post meta data)
2048		 * see http://alexking.org/blog/2008/09/06/wordpress-26x-duplicate-custom-field-issue
2049		 *
2050		 * why let the code run twice? wordpress does not currently save post meta
2051		 * data per revisions (I think it should, so users can do a complete revert),
2052		 * so in the case that this functionality changes, let it run twice
2053		 */
2054
2055		$real_post_id = isset($_POST['post_ID']) ? $_POST['post_ID'] : NULL ;
2056
2057		// check autosave
2058		if (defined('DOING_AUTOSAVE') AND DOING_AUTOSAVE AND !$this->autosave) return $post_id;
2059
2060		// make sure data came from our meta box, verify nonce
2061		$nonce = isset($_POST[$this->id.'_nonce']) ? $_POST[$this->id.'_nonce'] : NULL ;
2062		if (!wp_verify_nonce($nonce, $this->id)) return $post_id;
2063
2064		// check user permissions
2065		if ($_POST['post_type'] == 'page')
2066		{
2067			if (!current_user_can('edit_page', $post_id)) return $post_id;
2068		}
2069		else
2070		{
2071			if (!current_user_can('edit_post', $post_id)) return $post_id;
2072		}
2073
2074		// authentication passed, save data
2075
2076		$new_data = $_POST[$this->id];
2077
2078		WPAlchemy_MetaBox::clean($new_data);
2079
2080		if (empty($new_data))
2081		{
2082			$new_data = NULL;
2083		}
2084
2085		// filter: save
2086		if ($this->has_filter('save'))
2087		{
2088			$new_data = $this->apply_filters('save', $new_data, $real_post_id);
2089
2090			/**
2091			 * halt saving
2092			 * @since 1.3.4
2093			 */
2094			if (FALSE === $new_data) return $post_id;
2095		}
2096
2097		// get current fields, use $real_post_id (checked for in both modes)
2098		$current_fields = get_post_meta($real_post_id, $this->id . '_fields', TRUE);
2099
2100		if ($this->mode == WPALCHEMY_MODE_EXTRACT)
2101		{
2102			$new_fields = array();
2103
2104			if (is_array($new_data))
2105			{
2106				foreach ($new_data as $k => $v)
2107				{
2108					$field = $this->prefix . $k;
2109
2110					array_push($new_fields,$field);
2111
2112					$new_value = $new_data[$k];
2113
2114					if (is_null($new_value) || (is_integer($new_value) && $new_value <= 0))
2115					{
2116						delete_post_meta($post_id, $field);
2117					}
2118					else
2119					{
2120						update_post_meta($post_id, $field, $new_value);
2121					}
2122				}
2123			}
2124
2125			$diff_fields = array_diff((array)$current_fields,$new_fields);
2126
2127			if (is_array($diff_fields))
2128			{
2129				foreach ($diff_fields as $field)
2130				{
2131					delete_post_meta($post_id,$field);
2132				}
2133			}
2134
2135			delete_post_meta($post_id, $this->id . '_fields');
2136
2137			if ( ! empty($new_fields))
2138			{
2139				add_post_meta($post_id,$this->id . '_fields', $new_fields, TRUE);
2140			}
2141
2142			// keep data tidy, delete values if previously using WPALCHEMY_MODE_ARRAY
2143			delete_post_meta($post_id, $this->id);
2144		}
2145		else
2146		{
2147			if (is_null($new_data))
2148			{
2149				delete_post_meta($post_id, $this->id);
2150			}
2151			else
2152			{
2153				update_post_meta($post_id, $this->id, $new_data);
2154			}
2155
2156			// keep data tidy, delete values if previously using WPALCHEMY_MODE_EXTRACT
2157			if (is_array($current_fields))
2158			{
2159				foreach ($current_fields as $field)
2160				{
2161					delete_post_meta($post_id, $field);
2162				}
2163
2164				delete_post_meta($post_id, $this->id . '_fields');
2165			}
2166		}
2167
2168		// action: save
2169		if ($this->has_action('save'))
2170		{
2171			$this->do_action('save', $new_data, $real_post_id);
2172		}
2173
2174		return $post_id;
2175	}
2176
2177	/**
2178	 * Cleans an array, removing blank ('') values
2179	 *
2180	 * @static
2181	 * @since	1.0
2182	 * @access	public
2183	 * @param	array the array to clean (passed by reference)
2184	 */
2185	function clean(&$arr)
2186	{
2187		if (is_array($arr))
2188		{
2189			foreach ($arr as $i => $v)
2190			{
2191				if (is_array($arr[$i]))
2192				{
2193					WPAlchemy_MetaBox::clean($arr[$i]);
2194
2195					if ( isset($arr[$i]) && !count($arr[$i]))
2196					{
2197						unset($arr[$i]);
2198					}
2199				}
2200				else
2201				{
2202					if ( '' == trim($arr[$i]) OR is_null($arr[$i]) )
2203					{
2204						unset($arr[$i]);
2205					}
2206				}
2207			}
2208
2209			if (!count($arr))
2210			{
2211				$arr = array();
2212			}
2213			else
2214			{
2215				$keys = array_keys($arr);
2216
2217				$is_numeric = TRUE;
2218
2219				foreach ($keys as $key)
2220				{
2221					if (!is_numeric($key))
2222					{
2223						$is_numeric = FALSE;
2224						break;
2225					}
2226				}
2227
2228				if ($is_numeric)
2229				{
2230					$arr = array_values($arr);
2231				}
2232			}
2233		}
2234	}
2235}
2236
2237/* End of file */