PageRenderTime 54ms CodeModel.GetById 17ms app.highlight 20ms RepoModel.GetById 0ms app.codeStats 1ms

/WPAlchemy/MetaBox.php

https://github.com/volpeo/wpalchemy
PHP | 2298 lines | 1211 code | 343 blank | 744 comment | 188 complexity | fa35473b7d6ff8d3b886da01f7ec8d36 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.8
   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		$post_type = WPAlchemy_MetaBox::_get_current_post_type();
 992
 993		if (isset($post_type))
 994		{
 995			if ('page' == $post_type)
 996			{
 997				return 'page';
 998			}
 999			else
1000			{
1001				return 'post';
1002			}
1003		}
1004
1005		return NULL;
1006	}
1007
1008	/**
1009	 * Used to check for the current post type, works when creating or editing a
1010	 * new post, page or custom post type.
1011	 *
1012	 * @static
1013	 * @since	1.4.6
1014	 * @return	string [custom_post_type], page or post
1015	 */
1016	function _get_current_post_type()
1017	{
1018		$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : NULL ;
1019
1020		$uri_parts = parse_url($uri);
1021
1022		$file = basename($uri_parts['path']);
1023
1024		if ($uri AND in_array($file, array('post.php', 'post-new.php')))
1025		{
1026			$post_id = WPAlchemy_MetaBox::_get_post_id();
1027
1028			$post_type = isset($_GET['post_type']) ? $_GET['post_type'] : NULL ;
1029
1030			$post_type = $post_id ? get_post_type($post_id) : $post_type ;
1031
1032			if (isset($post_type))
1033			{
1034				return $post_type;
1035			}
1036			else
1037			{
1038				// because of the 'post.php' and 'post-new.php' checks above, we can default to 'post'
1039				return 'post';
1040			}
1041		}
1042
1043		return NULL;
1044	}
1045
1046	/**
1047	 * Used to get the current post id.
1048	 *
1049	 * @static
1050	 * @since	1.4.8
1051	 * @return	int post ID
1052	 */
1053	function _get_post_id()
1054	{
1055		global $post;
1056
1057		$p_post_id = isset($_POST['post_ID']) ? $_POST['post_ID'] : null ;
1058
1059		$g_post_id = isset($_GET['post']) ? $_GET['post'] : null ;
1060
1061		$post_id = $g_post_id ? $g_post_id : $p_post_id ;
1062
1063		$post_id = isset($post->ID) ? $post->ID : $post_id ;
1064
1065		if (isset($post_id))
1066		{
1067			return (integer) $post_id;
1068		}
1069		
1070		return null;
1071	}
1072
1073	/**
1074	 * @since	1.0
1075	 */
1076	function can_output()
1077	{
1078		$post_id = WPAlchemy_MetaBox::_get_post_id();
1079
1080		if (!empty($this->exclude_template) OR !empty($this->include_template))
1081		{
1082			$template_file = get_post_meta($post_id,'_wp_page_template',TRUE);
1083		}
1084
1085		if 
1086		(
1087			!empty($this->exclude_category) OR 
1088			!empty($this->exclude_category_id) OR 
1089			!empty($this->include_category) OR
1090			!empty($this->include_category_id)
1091		)
1092		{
1093			$categories = wp_get_post_categories($post_id,'fields=all');
1094		}
1095
1096		if 
1097		(
1098			!empty($this->exclude_tag) OR 
1099			!empty($this->exclude_tag_id) OR 
1100			!empty($this->include_tag) OR
1101			!empty($this->include_tag_id)
1102		)
1103		{
1104			$tags = wp_get_post_tags($post_id);
1105		}
1106
1107		// processing order: "exclude" then "include"
1108		// processing order: "template" then "category" then "post"
1109
1110		$can_output = TRUE; // include all
1111
1112		if 
1113		(
1114			!empty($this->exclude_template) OR 
1115			!empty($this->exclude_category_id) OR 
1116			!empty($this->exclude_category) OR 
1117			!empty($this->exclude_tag_id) OR
1118			!empty($this->exclude_tag) OR
1119			!empty($this->exclude_post_id) OR
1120			!empty($this->include_template) OR 
1121			!empty($this->include_category_id) OR 
1122			!empty($this->include_category) OR 
1123			!empty($this->include_tag_id) OR 
1124			!empty($this->include_tag) OR 
1125			!empty($this->include_post_id)
1126		)
1127		{
1128			if (!empty($this->exclude_template))
1129			{
1130				if (in_array($template_file,$this->exclude_template)) 
1131				{
1132					$can_output = FALSE;
1133				}
1134			}
1135
1136			if (!empty($this->exclude_category_id))
1137			{
1138				foreach ($categories as $cat)
1139				{
1140					if (in_array($cat->term_id,$this->exclude_category_id)) 
1141					{
1142						$can_output = FALSE;
1143						break;
1144					}
1145				}
1146			}
1147
1148			if (!empty($this->exclude_category))
1149			{
1150				foreach ($categories as $cat)
1151				{
1152					if 
1153					(
1154						in_array($cat->slug,$this->exclude_category) OR
1155						in_array($cat->name,$this->exclude_category)
1156					) 
1157					{
1158						$can_output = FALSE;
1159						break;
1160					}
1161				}
1162			}
1163
1164			if (!empty($this->exclude_tag_id))
1165			{
1166				foreach ($tags as $tag)
1167				{
1168					if (in_array($tag->term_id,$this->exclude_tag_id)) 
1169					{
1170						$can_output = FALSE;
1171						break;
1172					}
1173				}
1174			}
1175
1176			if (!empty($this->exclude_tag))
1177			{
1178				foreach ($tags as $tag)
1179				{
1180					if 
1181					(
1182						in_array($tag->slug,$this->exclude_tag) OR 
1183						in_array($tag->name,$this->exclude_tag)
1184					) 
1185					{
1186						$can_output = FALSE;
1187						break;
1188					}
1189				}
1190			}
1191
1192			if (!empty($this->exclude_post_id))
1193			{
1194				if (in_array($post_id,$this->exclude_post_id)) 
1195				{
1196					$can_output = FALSE;
1197				}
1198			}
1199
1200			// excludes are not set use "include only" mode
1201
1202			if 
1203			(
1204				empty($this->exclude_template) AND 
1205				empty($this->exclude_category_id) AND 
1206				empty($this->exclude_category) AND 
1207				empty($this->exclude_tag_id) AND 
1208				empty($this->exclude_tag) AND 
1209				empty($this->exclude_post_id)
1210			)
1211			{
1212				$can_output = FALSE;
1213			}
1214
1215			if (!empty($this->include_template))
1216			{
1217				if (in_array($template_file,$this->include_template)) 
1218				{
1219					$can_output = TRUE;
1220				}
1221			}
1222
1223			if (!empty($this->include_category_id))
1224			{
1225				foreach ($categories as $cat)
1226				{
1227					if (in_array($cat->term_id,$this->include_category_id)) 
1228					{
1229						$can_output = TRUE;
1230						break;
1231					}
1232				}
1233			}
1234
1235			if (!empty($this->include_category))
1236			{
1237				foreach ($categories as $cat)
1238				{
1239					if 
1240					(
1241						in_array($cat->slug,$this->include_category) OR
1242						in_array($cat->name,$this->include_category)
1243					)
1244					{
1245						$can_output = TRUE;
1246						break;
1247					}
1248				}
1249			}
1250
1251			if (!empty($this->include_tag_id))
1252			{
1253				foreach ($tags as $tag)
1254				{
1255					if (in_array($tag->term_id,$this->include_tag_id)) 
1256					{
1257						$can_output = TRUE;
1258						break;
1259					}
1260				}
1261			}
1262
1263			if (!empty($this->include_tag))
1264			{
1265				foreach ($tags as $tag)
1266				{
1267					if 
1268					(
1269						in_array($tag->slug,$this->include_tag) OR
1270						in_array($tag->name,$this->include_tag)
1271					) 
1272					{
1273						$can_output = TRUE;
1274						break;
1275					}
1276				}
1277			}
1278
1279			if (!empty($this->include_post_id))
1280			{
1281				if (in_array($post_id,$this->include_post_id)) 
1282				{
1283					$can_output = TRUE;
1284				}
1285			}
1286		}
1287
1288		$post_type = WPAlchemy_MetaBox::_get_current_post_type();
1289
1290		if (isset($post_type) AND ! in_array($post_type, $this->types))
1291		{
1292			$can_output = FALSE;
1293		}
1294
1295		// filter: output (can_output)
1296		if ($this->has_filter('output'))
1297		{
1298			$can_output = $this->apply_filters('output', $post_id);
1299		}
1300
1301		return $can_output;
1302	}
1303
1304	/**
1305	 * Used to insert global STYLE or SCRIPT tags into the head, called on
1306	 * WordPress admin_footer action.
1307	 *
1308	 * @static
1309	 * @since	1.3
1310	 * @access	private
1311	 * @see		_global_foot()
1312	 */
1313	function _global_head()
1314	{
1315		// must be creating or editing a post or page
1316		if ( ! WPAlchemy_MetaBox::_is_post() AND ! WPAlchemy_MetaBox::_is_page()) return;
1317
1318		// todo: you're assuming people will want to use this exact functionality
1319		// consider giving a developer access to change this via hooks/callbacks
1320
1321		// include javascript for special functionality
1322		?><style type="text/css"> .wpa_group.tocopy { display:none; } </style>
1323		<script type="text/javascript">
1324		/* <![CDATA[ */
1325		jQuery(function($)
1326		{
1327			$(document).click(function(e)
1328			{		
1329				var elem = $(e.target);
1330
1331				if (elem.attr('class') && elem.filter('[class*=dodelete]').length)
1332				{
1333					e.preventDefault();
1334
1335					var p = elem.parents('.postbox'); /*wp*/
1336
1337					var the_name = elem.attr('class').match(/dodelete-([a-zA-Z0-9_-]*)/i);
1338
1339					the_name = (the_name && the_name[1]) ? the_name[1] : null ;
1340
1341					/* todo: expose and allow editing of this message */
1342					if (confirm('This action can not be undone, are you sure?'))
1343					{
1344						if (the_name)
1345						{
1346							$('.wpa_group-'+ the_name, p).not('.tocopy').remove();
1347						}
1348						else
1349						{
1350							elem.parents('.wpa_group').remove();
1351						}
1352
1353						the_name = elem.parents('.wpa_group').attr('class').match(/wpa_group-([a-zA-Z0-9_-]*)/i)[1];
1354
1355						checkLoopLimit(the_name);
1356
1357						$.wpalchemy.trigger('wpa_delete');
1358					}
1359				}
1360			});
1361
1362			$('[class*=docopy-]').click(function(e)
1363			{
1364				e.preventDefault();
1365
1366				var p = $(this).parents('.postbox'); /*wp*/
1367
1368				var the_name = $(this).attr('class').match(/docopy-([a-zA-Z0-9_-]*)/i)[1];
1369
1370				var the_group = $('.wpa_group-'+ the_name +':first.tocopy', p);
1371				
1372				var the_clone = the_group.clone().removeClass('tocopy last');
1373
1374				var the_props = ['name', 'id', 'for', 'class'];
1375
1376				the_group.find('*').each(function(i, elem)
1377				{
1378					for (var j = 0; j < the_props.length; j++)
1379					{
1380						var the_prop = $(elem).attr(the_props[j]);
1381
1382						if (the_prop)
1383						{
1384							var the_match = the_prop.match(/\[(\d+)\]/i);
1385
1386							if (the_match)
1387							{
1388								the_prop = the_prop.replace(the_match[0],'['+ (+the_match[1]+1) +']');
1389
1390								$(elem).attr(the_props[j], the_prop);
1391							}
1392
1393							the_match = null;
1394
1395							// todo: this may prove to be too broad of a search
1396							the_match = the_prop.match(/n(\d+)/i);
1397
1398							if (the_match)
1399							{
1400								the_prop = the_prop.replace(the_match[0], 'n' + (+the_match[1]+1));
1401
1402								$(elem).attr(the_props[j], the_prop);
1403							}
1404						}
1405					}
1406				});
1407
1408				if ($(this).hasClass('ontop'))
1409				{
1410					$('.wpa_group-'+ the_name +':first', p).before(the_clone);
1411				}
1412				else
1413				{
1414					the_group.before(the_clone);
1415				}
1416
1417				checkLoopLimit(the_name);
1418
1419				$.wpalchemy.trigger('wpa_copy', [the_clone]);
1420			});
1421
1422			function checkLoopLimit(name)
1423			{
1424				var elem = $('.docopy-' + name);
1425
1426				var the_match = $('.wpa_loop-' + name).attr('class').match(/wpa_loop_limit-([0-9]*)/i);
1427
1428				if (the_match)
1429				{
1430					var the_limit = the_match[1];
1431
1432					if ($('.wpa_group-' + name).not('.wpa_group.tocopy').length >= the_limit)
1433					{
1434						elem.hide();
1435					}
1436					else
1437					{
1438						elem.show();
1439					}
1440				}
1441			}
1442			
1443			/* do an initial limit check, show or hide buttons */
1444			$('[class*=docopy-]').each(function()
1445			{
1446				var the_name = $(this).attr('class').match(/docopy-([a-zA-Z0-9_-]*)/i)[1];
1447
1448				checkLoopLimit(the_name);
1449			});
1450		});
1451		/* ]]> */
1452		</script>
1453		<?php
1454	}
1455
1456	/**
1457	 * Used to insert global SCRIPT tags into the footer, called on WordPress
1458	 * admin_footer action.
1459	 *
1460	 * @static
1461	 * @since	1.3
1462	 * @access	private
1463	 * @see		_global_head()
1464	 */
1465	function _global_foot()
1466	{
1467		// must be creating or editing a post or page
1468		if ( ! WPAlchemy_MetaBox::_is_post() AND ! WPAlchemy_MetaBox::_is_page()) return;
1469
1470		?>
1471		<script type="text/javascript">
1472		/* <![CDATA[ */
1473		(function($){ /* not using jQuery ondomready, code runs right away in footer */
1474
1475			/* use a global dom element to attach events to */
1476			$.wpalchemy = $('<div></div>').attr('id','wpalchemy').appendTo('body');
1477
1478		})(jQuery);
1479		/* ]]> */
1480		</script>
1481		<?php
1482	}
1483
1484	/**
1485	 * Gets the meta data for a meta box
1486	 *
1487	 * @since	1.0
1488	 * @access	public
1489	 * @param	int $post_id optional post ID for which to retrieve the meta data
1490	 * @return	array
1491	 * @see		_meta
1492	 */
1493	function the_meta($post_id = NULL)
1494	{
1495		return $this->_meta($post_id);
1496	}
1497
1498	/**
1499	 * Gets the meta data for a meta box
1500	 *
1501	 * Internal method calls will typically bypass the data retrieval and will
1502	 * immediately return the current meta data
1503	 *
1504	 * @since	1.3
1505	 * @access	private
1506	 * @param	int $post_id optional post ID for which to retrieve the meta data
1507	 * @param	bool $internal optional boolean if internally calling
1508	 * @return	array
1509	 * @see		the_meta()
1510	 */
1511	function _meta($post_id = NULL, $internal = FALSE)
1512	{
1513		if ( ! is_numeric($post_id))
1514		{
1515			if ($internal AND $this->current_post_id)
1516			{
1517				$post_id = $this->current_post_id;
1518			}
1519			else
1520			{
1521				global $post;
1522
1523				$post_id = $post->ID;
1524			}
1525		}
1526
1527		// this allows multiple internal calls to _meta() without having to fetch data everytime
1528		if ($internal AND !empty($this->meta) AND $this->current_post_id == $post_id) return $this->meta;
1529
1530		$this->current_post_id = $post_id;
1531
1532		// WPALCHEMY_MODE_ARRAY
1533
1534		$meta = get_post_meta($post_id, $this->id, TRUE);
1535
1536		// WPALCHEMY_MODE_EXTRACT
1537
1538		$fields = get_post_meta($post_id, $this->id . '_fields', TRUE);
1539
1540		if ( ! empty($fields) AND is_array($fields))
1541		{
1542			$meta = array();
1543			
1544			foreach ($fields as $field)
1545			{
1546				$field_noprefix = preg_replace('/^' . $this->prefix . '/i', '', $field);
1547				$meta[$field_noprefix] = get_post_meta($post_id, $field, TRUE);
1548			}
1549		}
1550
1551		$this->meta = $meta;
1552
1553		return $this->meta;
1554	}
1555
1556	// user can also use the_ID(), php functions are case-insensitive
1557	/**
1558	 * @since	1.0
1559	 * @access	public
1560	 */
1561	function the_id()
1562	{
1563		echo $this->get_the_id();
1564	}
1565
1566	/**
1567	 * @since	1.0
1568	 * @access	public
1569	 */
1570	function get_the_id()
1571	{
1572		return $this->id;
1573	}
1574
1575	/**
1576	 * @since	1.0
1577	 * @access	public
1578	 */
1579	function the_field($n, $hint = NULL)
1580	{
1581		if ($this->in_loop) $this->subname = $n;
1582		else $this->name = $n;
1583
1584		$this->hint = $hint;
1585	}
1586
1587	/**
1588	 * @since	1.0
1589	 * @access	public
1590	 */
1591	function have_value($n = NULL)
1592	{
1593		if ($this->get_the_value($n)) return TRUE;
1594		
1595		return FALSE;
1596	}
1597
1598	/**
1599	 * @since	1.0
1600	 * @access	public
1601	 */
1602	function the_value($n = NULL)
1603	{
1604		echo $this->get_the_value($n);
1605	}
1606
1607	/**
1608	 * @since	1.0
1609	 * @access	public
1610	 */
1611	function get_the_value($n = NULL, $collection = FALSE)
1612	{
1613		$this->_meta(NULL, TRUE);
1614
1615		if ($this->in_loop)
1616		{
1617			if(isset($this->meta[$this->name]))
1618			{
1619				$n = is_null($n) ? $this->subname : $n ;
1620
1621				if(!is_null($n))
1622				{
1623					if ($collection)
1624					{
1625						if(isset($this->meta[$this->name][$this->current]))
1626						{
1627							return $this->meta[$this->name][$this->current];
1628						}
1629					}
1630					else
1631					{
1632						if(isset($this->meta[$this->name][$this->current][$n]))
1633						{
1634							return $this->meta[$this->name][$this->current][$n];
1635						}
1636					}
1637				}
1638				else
1639				{
1640					if ($collection)
1641					{
1642						if(isset($this->meta[$this->name]))
1643						{
1644							return $this->meta[$this->name];
1645						}
1646					}
1647					else
1648					{
1649						if(isset($this->meta[$this->name][$this->current]))
1650						{
1651							return $this->meta[$this->name][$this->current];
1652						}
1653					}
1654				}
1655			}
1656		}
1657		else
1658		{
1659			$n = is_null($n) ? $this->name : $n ;
1660
1661			if(isset($this->meta[$n])) return $this->meta[$n];
1662		}
1663
1664		return NULL;
1665	}
1666
1667	/**
1668	 * @since	1.0
1669	 * @access	public
1670	 */
1671	function the_name($n = NULL)
1672	{
1673		echo $this->get_the_name($n);
1674	}
1675
1676	/**
1677	 * @since	1.0
1678	 * @access	public
1679	 */
1680	function get_the_name($n = NULL)
1681	{
1682		if (!$this->in_template AND $this->mode == WPALCHEMY_MODE_EXTRACT)
1683		{
1684			return $this->prefix . str_replace($this->prefix, '', is_null($n) ? $this->name : $n);
1685		}
1686
1687		if ($this->in_loop)
1688		{
1689			$n = is_null($n) ? $this->subname : $n ;
1690
1691			if (!is_null($n)) return $this->id . '[' . $this->name . '][' . $this->current . '][' . $n . ']' ;
1692
1693			$the_field = $this->id . '[' . $this->name . '][' . $this->current . ']' ;	
1694		}
1695		else
1696		{
1697			$n = is_null($n) ? $this->name : $n ;
1698
1699			$the_field = $this->id . '[' . $n . ']';
1700		}
1701
1702		$hints = array
1703		(
1704			WPALCHEMY_FIELD_HINT_CHECKBOX_MULTI,
1705			WPALCHEMY_FIELD_HINT_SELECT_MULTI,
1706			WPALCHEMY_FIELD_HINT_SELECT_MULTIPLE,
1707		);
1708		
1709		if (in_array($this->hint, $hints))
1710		{
1711			$the_field .= '[]';
1712		}
1713
1714		return $the_field;
1715	}
1716
1717	/**
1718	 * @since	1.1
1719	 * @access	public
1720	 */
1721	function the_index()
1722	{
1723		echo $this->get_the_index();
1724	}
1725
1726	/**
1727	 * @since	1.1
1728	 * @access	public
1729	 */
1730	function get_the_index()
1731	{
1732		return $this->in_loop ? $this->current : 0 ;
1733	}
1734
1735	/**
1736	 * @since	1.0
1737	 * @access	public
1738	 */
1739	function is_first()
1740	{
1741		if ($this->in_loop AND $this->current == 0) return TRUE;
1742
1743		return FALSE;
1744	}
1745
1746	/**
1747	 * @since	1.0
1748	 * @access	public
1749	 */
1750	function is_last()
1751	{
1752		if ($this->in_loop AND ($this->current+1) == $this->length) return TRUE;
1753
1754		return FALSE;
1755	}
1756
1757	/**
1758	 * Used to check if a value is a match
1759	 *
1760	 * @since	1.1
1761	 * @access	public
1762	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1763	 * @param	string $v optional the value to check for
1764	 * @return	bool
1765	 * @see		is_value()
1766	 */
1767	function is_value($n, $v = NULL)
1768	{
1769		if (is_null($v))
1770		{
1771			$the_value = $this->get_the_value();
1772
1773			$v = $n;
1774		}
1775		else
1776		{
1777			$the_value = $this->get_the_value($n);
1778		}
1779
1780		if($v == $the_value) return TRUE;
1781
1782		return FALSE;
1783	}
1784
1785	/**
1786	 * Used to check if a value is selected, useful when working with checkbox,
1787	 * radio and select values.
1788	 *
1789	 * @since	1.3
1790	 * @access	public
1791	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1792	 * @param	string $v optional the value to check for
1793	 * @return	bool
1794	 * @see		is_value()
1795	 */
1796	function is_selected($n, $v = NULL)
1797	{
1798		if (is_null($v))
1799		{
1800			$the_value = $this->get_the_value(NULL, TRUE);
1801
1802			$v = $n;
1803		}
1804		else
1805		{
1806			$the_value = $this->get_the_value($n, TRUE);
1807		}
1808
1809		if (is_array($the_value))
1810		{
1811			if (in_array($v, $the_value)) return TRUE;
1812		}
1813		elseif($v == $the_value)
1814		{
1815			return TRUE;
1816		}
1817
1818		return FALSE;
1819	}
1820
1821	/**
1822	 * Prints the current state of a checkbox field and should be used inline
1823	 * within the INPUT tag.
1824	 *
1825	 * @since	1.3
1826	 * @access	public
1827	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1828	 * @param	string $v optional the value to check for
1829	 * @see		get_the_checkbox_state()
1830	 */
1831	function the_checkbox_state($n, $v = NULL)
1832	{
1833		echo $this->get_the_checkbox_state($n, $v);
1834	}
1835
1836	/**
1837	 * Returns the current state of a checkbox field, the returned string is
1838	 * suitable to be used inline within the INPUT tag.
1839	 *
1840	 * @since	1.3
1841	 * @access	public
1842	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1843	 * @param	string $v optional the value to check for
1844	 * @return	string suitable to be used inline within the INPUT tag
1845	 * @see		the_checkbox_state()
1846	 */
1847	function get_the_checkbox_state($n, $v = NULL)
1848	{
1849		if ($this->is_selected($n, $v)) return ' checked="checked"';
1850	}
1851
1852	/**
1853	 * Prints the current state of a radio field and should be used inline
1854	 * within the INPUT tag.
1855	 *
1856	 * @since	1.3
1857	 * @access	public
1858	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1859	 * @param	string $v optional the value to check for
1860	 * @see		get_the_radio_state()
1861	 */
1862	function the_radio_state($n, $v = NULL)
1863	{
1864		echo $this->get_the_checkbox_state($n, $v);
1865	}
1866
1867	/**
1868	 * Returns the current state of a radio field, the returned string is
1869	 * suitable to be used inline within the INPUT tag.
1870	 *
1871	 * @since	1.3
1872	 * @access	public
1873	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1874	 * @param	string $v optional the value to check for
1875	 * @return	string suitable to be used inline within the INPUT tag
1876	 * @see		the_radio_state()
1877	 */
1878	function get_the_radio_state($n, $v = NULL)
1879	{
1880		return $this->get_the_checkbox_state($n, $v);
1881	}
1882
1883	/**
1884	 * Prints the current state of a select field and should be used inline
1885	 * within the SELECT tag.
1886	 *
1887	 * @since	1.3
1888	 * @access	public
1889	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1890	 * @param	string $v optional the value to check for
1891	 * @see		get_the_select_state()
1892	 */
1893	function the_select_state($n, $v = NULL)
1894	{
1895		echo $this->get_the_select_state($n, $v);
1896	}
1897
1898	/**
1899	 * Returns the current state of a select field, the returned string is
1900	 * suitable to be used inline within the SELECT tag.
1901	 *
1902	 * @since	1.3
1903	 * @access	public
1904	 * @param	string $n the field name to check or the value to check for (if the_field() is used prior)
1905	 * @param	string $v optional the value to check for
1906	 * @return	string suitable to be used inline within the SELECT tag
1907	 * @see		the_select_state()
1908	 */
1909	function get_the_select_state($n, $v = NULL)
1910	{
1911		if ($this->is_selected($n, $v)) return ' selected="selected"';
1912	}
1913
1914	/**
1915	 * @since	1.1
1916	 * @access	public
1917	 */
1918	function the_group_open($t = 'div')
1919	{
1920		echo $this->get_the_group_open($t);
1921	}
1922
1923	/**
1924	 * @since	1.1
1925	 * @access	public
1926	 */
1927	function get_the_group_open($t = 'div')
1928	{
1929		$this->group_tag = $t;
1930
1931		$loop_open = NULL;
1932
1933		$loop_open_classes = array('wpa_loop', 'wpa_loop-' . $this->name);
1934		
1935		$css_class = array('wpa_group', 'wpa_group-'. $this->name);
1936
1937		if ($this->is_first())
1938		{
1939			array_push($css_class, 'first');
1940
1941			$loop_open = '<div class="wpa_loop">';
1942
1943			if (isset($this->_loop_data->limit))
1944			{
1945				array_push($loop_open_classes, 'wpa_loop_limit-' . $this->_loop_data->limit);
1946			}
1947
1948			$loop_open = '<div id="wpa_loop-'. $this->name .'" class="' . implode(' ', $loop_open_classes) . '">';
1949		}
1950
1951		if ($this->is_last())
1952		{
1953			array_push($css_class, 'last');
1954
1955			if ($this->in_loop == 'multi')
1956			{
1957				array_push($css_class, 'tocopy');
1958			}
1959		}
1960
1961		return $loop_open . '<' . $t . ' class="'. implode(' ', $css_class) . '">';
1962	}
1963
1964	/**
1965	 * @since	1.1
1966	 * @access	public
1967	 */
1968	function the_group_close()
1969	{
1970		echo $this->get_the_group_close();
1971	}
1972
1973	/**
1974	 * @since	1.1
1975	 * @access	public
1976	 */
1977	function get_the_group_close()
1978	{
1979		$loop_close = NULL;
1980		
1981		if ($this->is_last())
1982		{
1983			$loop_close = '</div>';
1984		}
1985		
1986		return '</' . $this->group_tag . '>' . $loop_close;
1987	}
1988
1989	/**
1990	 * @since	1.1
1991	 * @access	public
1992	 */
1993	function have_fields_and_multi($n, $options = NULL)
1994	{
1995		if (is_array($options))
1996		{
1997			// use as stdClass object
1998			$options = (object)$options;
1999			
2000			$length = @$options->length;
2001
2002			$this->_loop_data->limit = @$options->limit;
2003		}
2004		else
2005		{
2006			// backward compatibility (bc)
2007			$length = $options;
2008		}
2009
2010		$this->_meta(NULL, TRUE);
2011
2012		$this->in_loop = 'multi';
2013
2014		return $this->_loop($n, $length, 2);
2015	}
2016
2017	/**
2018	 * @deprecated
2019	 * @since	1.0
2020	 * @access	public
2021	 */
2022	function have_fields_and_one($n)
2023	{
2024		$this->_meta(NULL, TRUE);
2025		$this->in_loop = 'single';
2026		return $this->_loop($n,NULL,1);
2027	}
2028
2029	/**
2030	 * @since	1.0
2031	 * @access	public
2032	 */
2033	function have_fields($n,$length=NULL)
2034	{
2035		$this->_meta(NULL, TRUE);
2036		$this->in_loop = 'normal';
2037		return $this->_loop($n,$length);
2038	}
2039
2040	/**
2041	 * @since	1.0
2042	 * @access	private
2043	 */
2044	function _loop($n,$length=NULL,$and_one=0)
2045	{
2046		if (!$this->in_loop)
2047		{
2048			$this->in_loop = TRUE;
2049		}
2050		
2051		$this->name = $n;
2052
2053		$cnt = count(!empty($this->meta[$n])?$this->meta[$n]:NULL);
2054
2055		$length = is_null($length) ? $cnt : $length ;
2056		
2057		if ($this->in_loop == 'multi' AND $cnt > $length) $length = $cnt;
2058
2059		$this->length = $length;
2060
2061		if ($this->in_template AND $and_one)
2062		{
2063			if ($length == 0)
2064			{
2065				$this->length = $and_one;
2066			}
2067			else
2068			{
2069				$this->length = $length+1;
2070			}
2071		}
2072
2073		$this->current++;
2074
2075		if ($this->current < $this->length)
2076		{
2077			$this->subname = NULL;
2078
2079			$this->fieldtype = NULL;
2080
2081			return TRUE;
2082		}
2083		else if ($this->current == $this->length)
2084		{
2085			$this->name = NULL;
2086
2087			$this->current = -1;
2088		}
2089
2090		$this->in_loop = FALSE;
2091
2092		$this->_loop_data = new stdClass;
2093
2094		return FALSE;
2095	}
2096
2097	/**
2098	 * @since	1.0
2099	 * @access	private
2100	 */
2101	function _save($post_id) 
2102	{
2103		/**
2104		 * note: the "save_post" action fires for saving revisions and post/pages, 
2105		 * when saving a post this function fires twice, once for a revision save, 
2106		 * and again for the post/page save ... the $post_id is different for the
2107		 * revision save, this means that "get_post_meta()" will not work if trying
2108		 * to get values for a revision (as it has no post meta data)
2109		 * see http://alexking.org/blog/2008/09/06/wordpress-26x-duplicate-custom-field-issue
2110		 *
2111		 * why let the code run twice? wordpress does not currently save post meta
2112		 * data per revisions (I think it should, so users can do a complete revert),
2113		 * so in the case that this functionality changes, let it run twice
2114		 */
2115
2116		$real_post_id = isset($_POST['post_ID']) ? $_POST['post_ID'] : NULL ;
2117		
2118		// check autosave
2119		if (defined('DOING_AUTOSAVE') AND DOING_AUTOSAVE AND !$this->autosave) return $post_id;
2120	 
2121		// make sure data came from our meta box, verify nonce
2122		$nonce = isset($_POST[$this->id.'_nonce']) ? $_POST[$this->id.'_nonce'] : NULL ;
2123		if (!wp_verify_nonce($nonce, $this->id)) return $post_id;
2124	 
2125		// check user permissions
2126		if ($_POST['post_type'] == 'page') 
2127		{
2128			if (!current_user_can('edit_page', $post_id)) return $post_id;
2129		}
2130		else 
2131		{
2132			if (!current_user_can('edit_post', $post_id)) return $post_id;
2133		}
2134	 
2135		// authentication passed, save data
2136	 
2137		$new_data = $_POST[$this->id];
2138	 
2139		WPAlchemy_MetaBox::clean($new_data);
2140
2141		if (empty($new_data))
2142		{
2143			$new_data = NULL;
2144		}
2145
2146		// filter: save
2147		if ($this->has_filter('save'))
2148		{
2149			$new_data = $this->apply_filters('save', $new_data, $real_post_id);
2150
2151			/**
2152			 * halt saving
2153			 * @since 1.3.4
2154			 */
2155			if (FALSE === $new_data) return $post_id;
2156		}
2157
2158		// get current fields, use $real_post_id (checked for in both modes)
2159		$current_fields = get_post_meta($real_post_id, $this->id . '_fields', TRUE);
2160
2161		if ($this->mode == WPALCHEMY_MODE_EXTRACT)
2162		{
2163			$new_fields = array();
2164
2165			if (is_array($new_data))
2166			{
2167				foreach ($new_data as $k => $v)
2168				{
2169					$field = $this->prefix . $k;
2170					
2171					array_push($new_fields,$field);
2172
2173					$new_value = $new_data[$k];
2174
2175					if (is_null($new_value))
2176					{
2177						delete_post_meta($post_id, $field);
2178					}
2179					else
2180					{
2181						update_post_meta($post_id, $field, $new_value);
2182					}
2183				}
2184			}
2185
2186			$diff_fields = array_diff((array)$current_fields,$new_fields);
2187
2188			if (is_array($diff_fields))
2189			{
2190				foreach ($diff_fields as $field)
2191				{
2192					delete_post_meta($post_id,$field);
2193				}
2194			}
2195
2196			delete_post_meta($post_id, $this->id . '_fields');
2197
2198			if ( ! empty($new_fields))
2199			{
2200				add_post_meta($post_id,$this->id . '_fields', $new_fields, TRUE);
2201			}
2202
2203			// keep data tidy, delete values if previously using WPALCHEMY_MODE_ARRAY
2204			delete_post_meta($post_id, $this->id);
2205		}
2206		else
2207		{
2208			if (is_null($new_data))
2209			{
2210				delete_post_meta($post_id, $this->id);
2211			}
2212			else
2213			{
2214				update_post_meta($post_id, $this->id, $new_data);
2215			}
2216
2217			// keep data tidy, delete values if previously using WPALCHEMY_MODE_EXTRACT
2218			if (is_array($current_fields))
2219			{
2220				foreach ($current_fields as $field)
2221				{
2222					delete_post_meta($post_id, $field);
2223				}
2224
2225				delete_post_meta($post_id, $this->id . '_fields');
2226			}
2227		}
2228
2229		// action: save
2230		if ($this->has_action('save'))
2231		{
2232			$this->do_action('save', $new_data, $real_post_id);
2233		}
2234
2235		return $post_id;
2236	}
2237
2238	/**
2239	 * Cleans an array, removing blank ('') values
2240	 *
2241	 * @static
2242	 * @since	1.0
2243	 * @access	public
2244	 * @param	array the array to clean (passed by reference)
2245	 */
2246	function clean(&$arr)
2247	{
2248		if (is_array($arr))
2249		{
2250			foreach ($arr as $i => $v)
2251			{
2252				if (is_array($arr[$i])) 
2253				{
2254					WPAlchemy_MetaBox::clean($arr[$i]);
2255	 
2256					if (!count($arr[$i])) 
2257					{
2258						unset($arr[$i]);
2259					}
2260				}
2261				else 
2262				{
2263					if ('' == trim($arr[$i]) OR is_null($arr[$i])) 
2264					{
2265						unset($arr[$i]);
2266					}
2267				}
2268			}
2269
2270			if (!count($arr)) 
2271			{
2272				$arr = array();
2273			}
2274			else
2275			{
2276				$keys = array_keys($arr);
2277
2278				$is_numeric = TRUE;
2279
2280				foreach ($keys as $key)
2281				{
2282					if (!is_numeric($key)) 
2283					{
2284						$is_numeric = FALSE;
2285						break;
2286					}
2287				}
2288
2289				if ($is_numeric)
2290				{
2291					$arr = array_values($arr);
2292				}
2293			}
2294		}
2295	}
2296}
2297
2298/* End of file */