PageRenderTime 62ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/WPAlchemy/MetaBox.php

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