Skip to content

Instantly share code, notes, and snippets.

@jonathonbyrdziak
Created February 22, 2012 02:27
Show Gist options
  • Star 37 You must be signed in to star a gist
  • Fork 17 You must be signed in to fork a gist
  • Save jonathonbyrdziak/1880770 to your computer and use it in GitHub Desktop.
Save jonathonbyrdziak/1880770 to your computer and use it in GitHub Desktop.
Wordpress Metabox, stand alone class for multiple metabox abilities
<?php
/*
Here's a couple of metaboxes that I've recently created using this system
*/
$subpostings = redrokk_metabox_class::getInstance('subpostings', array(
'title' => '(optional) Subscription for Postings',
'description' => "As an optional feature, you have a complete api at your disposal which will allow you the ability to offer and manage member posts. In addition to these settings, you may need to create the associated pages for accepting posts from your frontend.",
'_object_types' => 'page',
'priority' => 'high',
//this combination allows you to include the metabox on a single post,
//or exclude a single post
'include_exclude' => true,
'object_ids' => 306,
'_fields' => array(
array(
'name' => 'Maximum Posts',
'id' => 'max_posts',
'type' => 'text',
'class' => 'small-text',
'desc' => "The maximum number of posts that this subscriber is allowed to make.
<br/>-1 allows an infinite number of posts
<br/> 0 does not allow the subscriber to post",
'default' => '0',
),
array(
'name' => 'New Posting Defaults',
'type' => 'title',
),
array(
'name' => 'New Post Email',
'id' => 'new_post_email',
'type' => 'checkbox',
'desc' => "Would you like an email when a subscriber makes a new post?"
),
array(
'name' => 'Post Type',
'id' => 'postnew__post_type',
'type' => 'post_type',
),
array(
'name' => 'New Post Status',
'id' => 'postnew__post_status',
'type' => 'post_status',
'desc' => "When a subscriber makes a new post it will have this status",
),
array(
'name' => 'Default Category',
'id' => 'postnew__post_category',
'type' => 'category',
),
array(
'name' => 'Tagged with',
'id' => 'postnew__tags_input',
'type' => 'text',
'desc' => "Leave a comma deliminated list of tags to add to new member posts"
),
array(
'name' => 'Edit Posting Defaults',
'type' => 'title',
),
array(
'name' => 'Edit Post Email',
'id' => 'edit_post_email',
'type' => 'checkbox',
'desc' => "Would you like an email when a subscriber makes an edit?"
),
array(
'name' => 'Can User Edit?',
'id' => 'can_user_edit',
'type' => 'checkbox',
'desc' => "Leaving unchecked will not allow users to edit their posts"
),
array(
'name' => 'Edit Post Status',
'id' => 'postedit__post_status',
'type' => 'post_status',
'desc' => "When a subscriber edits their posts, this will be the new status",
),
array(
'name' => 'Tagged with',
'id' => 'postedit__tags_input',
'type' => 'text',
'desc' => "Leave a comma deliminated list of tags to add to new member posts"
),
)
));
$subroles = redrokk_metabox_class::getInstance('subroles', array(
'title' => '(optional) Role Management',
'description' => "As an optional feature, you can use these settings to manage the users roles during certain subscription triggers.",
'_object_types' => $sub,
'priority' => 'high',
'_fields' => array(
array(
'name' => 'Role',
'id' => 'role',
'type' => 'role',
'desc' => "New subscribers will be assigned to this role.",
),
array(
'name' => 'Expired to Role',
'id' => 'downgrade_role',
'type' => 'role',
'desc' => "Terminated subscriptions will be assigned to this role.",
),
)
));
$subdisplay = redrokk_metabox_class::getInstance('subdisplay', array(
'title' => 'Subscription Listing Preview',
'_object_types' => 'post',
'priority' => 'default',
'callback' => 'ms_subdisplay', //use a valid callback to display a custom template
));
//some example fields to use in your form
$metabox->set('_fields', array(
array(
'name' => 'Title',
'type' => 'title',
),
array(
'name' => 'Title',
'desc' => '',
'id' => 'title',
'type' => 'text',
'std' => ''
),
array(
'name' => 'image',
'desc' => '',
'id' => 'imagefile',
'type' => 'image',
'std' => ''
),
array(
'name' => 'Textarea',
'desc' => 'Enter big text here',
'id' => 'textarea_id',
'type' => 'textarea',
'std' => 'Default value 2'
),
array(
'name' => 'Select box',
'id' => 'select_id',
'type' => 'select',
'options'=> array(
'value1' => 'Value 1',
'value2' => 'Value 2',
'value3' => 'Value 3',
'value4' => 'Value 4',
)
),
array(
'name' => 'Radio',
'id' => 'radio_id',
'type' => 'radio',
'value' => 'test',
'desc' => 'Check this box if you want its value saved',
),
array(
'name' => '',
'id' => 'radio_id',
'type' => 'radio',
'value' => 'test2',
'desc' => 'Check this box if you want its value saved',
),
array(
'name' => 'Checkbox',
'id' => 'checkbox_id',
'type' => 'checkbox',
'desc' => 'Check this box if you want its value saved',
),
));
<?php
/**
* @Author Anonymous
* @link http://www.redrokk.com
* @Package Wordpress
* @SubPackage RedRokk Library
*
* @version 2.0
*/
//security
defined('ABSPATH') or die('You\'re not supposed to be here.');
/**
*
*
* @author Anonymous
* @example
$gallery = redrokk_metabox_class::getInstance('gallery');
*/
if (!class_exists('redrokk_metabox_class')):
class redrokk_metabox_class
{
/**
* HTML 'id' attribute of the edit screen section
*
* @var string
*/
var $_id;
/**
* Save the form fields here that will be displayed to the user
*
* @var array
*/
protected $_fields;
protected $fields;
/**
* Title of the edit screen section, visible to user
* Default: None
*
* @var string
*/
var $title;
/**
* Function that prints out the HTML for the edit screen section. Pass
* function name as a string. Within a class, you can instead pass an
* array to call one of the class's methods. See the second example under
* Example below.
* Default: None
*
* @var callback
*/
var $callback = null;
/**
* The part of the page where the edit screen section should be shown
* ('normal', 'advanced', or 'side'). (Note that 'side' doesn't exist before 2.7)
* Default: 'advanced'
*
* @var string
*/
var $context = 'normal';
/**
* The priority within the context where the boxes should show
* ('high', 'core', 'default' or 'low')
* Default: 'default'
*
* @var string
*/
var $priority = 'default';
/**
* Arguments to pass into your callback function. The callback will receive the
* $post object and whatever parameters are passed through this variable.
* Default: null
*
* @var array
*/
var $callback_args;
/**
* Prebuilt metaboxes can be activated by using this type
* Default: default
*
* (options:)
* default
* images
*
* @var string
*/
var $_type;
/**
*
* @var unknown_type
*/
var $_category_name;
/**
* The type of Write screen on which to show the edit screen section
* ('post', 'page', 'link', or 'custom_post_type' where custom_post_type
* is the custom post type slug)
* Default: None
*
* @var array
*/
var $_object_types = array();
/**
* Whether or not to make the fields available as wp-options
*
* @var bool
*/
var $_isAdminPage = false;
/**
* Constructor.
*
*/
function __construct( $options = array() )
{
//initializing
$this->setProperties($options);
$this->setOptionHooks();
if (!$this->callback) {
$this->callback = array(&$this, 'show');
}
if (!$this->title) {
$this->title = ucfirst($this->_id);
}
//registering this metabox
add_action( 'add_meta_boxes', array(&$this, '_register') );
// backwards compatible (before WP 3.0)
// add_action( 'admin_init', array($this, '_register'), 1 );
add_action( 'save_post', array(&$this, '_save') );
add_filter( 'wp_redirect', array(&$this, '_redirectIntervention'), 40, 1 );
}
/**
* Method properly inturprets the given parameter and sets it accordingly
*
* @param string|object $value
*/
function setObjectTypes( $value )
{
if (is_a($value, 'redrokk_post_class')) {
$value = $value->_post_type;
}
if (is_a($value, 'redrokk_admin_class')) {
$value = $value->id;
$this->_isAdminPage = $value;
}
if (is_array($value)) {
foreach($value as $v)
$this->_object_types[] = $v;
return $this;
}
$this->_object_types[] = $value;
return $this;
}
/**
* Method is designed to return the currently visible post type
*/
function getCurrentPostType()
{
$post_type = false;
if (isset($_REQUEST['post_type'])) {
$post_type = $_REQUEST['post_type'];
}
elseif (isset($_REQUEST['post'])) {
$post = get_post($_REQUEST['post']);
$post_type = $post->post_type;
}
elseif (isset($_REQUEST['page'])) {
$post_type = $_REQUEST['page'];
}
return $post_type;
}
/**
* Method properly prepares the metabox type by binding the necessary hooks
*
* @param mixed $value
*/
function setType( $value = 'default' )
{
$this->_type = $value;
switch ($this->_type)
{
default:
case 'default':
add_action('metabox-show-'.$this->_id, array(&$this, '_renderForm'), 20 );
add_action('metabox-save-'.$this->_id, array(&$this, 'saveAsPostMeta'), 10, 2);
break;
case 'image':
case 'images':
$this->_fields = array(
array(
'name' => 'New Image',
'type' => 'title',
),
array(
'name' => 'Image Title',
'id' => $this->_id.'_post_title',
'type' => 'text',
),
array(
'name' => 'Description',
'id' => $this->_id.'_post_content',
'type' => 'textarea',
),
array(
'name' => 'Image File',
'id' => $this->_id.'_image',
'type' => 'image',
),
array(
'name' => '_metaboxed',
'id' => $this->_id.'_metaboxed',
'type' => 'hidden',
'default' => $this->_id
),
array(
'name' => 'Save Image',
'type' => 'submit',
),
);
add_action('metabox-show-'.$this->_id, array(&$this, '_renderListImageAttachments'), 20 );
add_action('metabox-show-'.$this->_id, array(&$this, '_renderForm'), 20 );
add_action('metabox-save-'.$this->_id, array(&$this, 'saveAsAttachment'), 1, 2);
break;
case 'video':
case 'videos':
$this->_fields = array(
array(
'name' => 'New Video',
'type' => 'title',
),
array(
'name' => 'Video Title',
'id' => $this->_id.'_post_title',
'type' => 'text',
),
array(
'name' => 'Description',
'id' => $this->_id.'_post_content',
'type' => 'textarea',
),
array(
'name' => 'Video File',
'id' => $this->_id.'_image',
'type' => 'image',
),
array(
'name' => 'Video Link',
'id' => $this->_id.'_link',
'type' => 'text',
),
array(
'name' => '_videocat',
'id' => $this->_id.'_videocat',
'default'=>$this->getCategory(),
'type' => 'hidden',
),
array(
'name' => '_metaid',
'id' => $this->_id.'_metaid',
'type' => 'hidden',
),
array(
'name' => 'Save Video',
'type' => 'submit',
),
);
add_action('metabox-show-'.$this->_id, array(&$this, '_renderListAttachments'), 20 );
add_action('metabox-show-'.$this->_id, array(&$this, '_renderListVideoAttachments'), 20 );
add_action('metabox-show-'.$this->_id, array(&$this, '_renderForm'), 20 );
add_action('metabox-save-'.$this->_id, array(&$this, 'saveAsPostMeta'), 1, 2);
break;
}
}
/**
* Returns the category to use
*/
function getCategory()
{
return isset($this->_category_name)
? $this->_category_name
: '_videocat';
}
/**
* Method will save the posted content as an image attachment
*
*/
function saveAsAttachment( $source, $post_id )
{
if (empty($_FILES) || !isset($_REQUEST[$this->_id.'files'])) return $source;
// initializing
$property = $_REQUEST[$this->_id.'files'];
$post_data = array();
if (isset($source[$this->_id.'_post_title']) && $source[$this->_id.'_post_title']) {
$post_data['post_title'] = $source[$this->_id.'_post_title'];
}
if (isset($source[$this->_id.'_post_content']) && $source[$this->_id.'_post_content']) {
$post_data['post_content'] = $source[$this->_id.'_post_content'];
}
$id = media_handle_upload($property, $post_id, $post_data);
$source[$property] = $id;
$type = 'post';
if ($this->getCurrentPostType() && $this->getCurrentPostType() !='page') {
$type = $this->getCurrentPostType();
}
//saving the attachment ID to the taxonomy
if (!in_array($type, get_post_types(array('public' => false)))) {
$old = get_metadata($type, $post_id, $property, true);
if ($id && $id != $old) {
wp_delete_attachment( $old, true );
update_metadata($type, $post_id, $property, $id);
}
}
foreach ((array)$source as $property => $new)
{
//skip everything but the specially prefixed
if (strpos($property, $this->_id) !== 0) continue;
if (in_array($property, array(
$this->_id.'_post_title',
$this->_id.'_post_content',
))) continue;
$old = get_metadata($type, $id, $property, true);
if ($new && $new != $old) {
update_metadata($type, $id, $property, $new);
}
elseif (!$new) {
delete_metadata($type, $id, $property, $old);
}
}
return $source;
}
/**
* Method saves the data provided as post meta values
*
* @param array $source
* @param integer $post_id
*/
function saveAsPostMeta( $source, $post_id )
{
$type = 'post';
if (!$this->getCurrentPostType()) {
$type = $this->_table;
}
//save as a file
//if there's no FILES then we save as a meta
$source = $this->saveAsAttachment( $source, $post_id );
//get the ID of this meta set
$id = false;
if (isset($source[$this->_id.'_metaid']) && $source[$this->_id.'_metaid']) {
$id = $source[$this->_id.'_metaid'];
}
// if this is a built in metabox
if ($this->_type != 'default'
&& (!isset($source[$this->_id.'_image']) || !$source[$this->_id.'_image']))
return false;
// Saving only the specially prefixed items
foreach ((array)$source as $property => $new)
{
//skip everything but the specially prefixed
if (strpos($property, $this->_id) !== 0) continue;
//each meta set has it's own ID
$property = str_replace($this->_id, $this->_category_name.'_'.$id, $property);
$old = get_metadata($type, $post_id, $property, true);
if ($new && $new != $old) {
update_metadata($type, $post_id, $property, $new);
}
elseif (!$new) {
delete_metadata($type, $post_id, $property, $old);
}
}
// maybe there's a last id
if (!$id) {
if (!$id = get_metadata($type, $post_id, '_metaidlast', true)) {
$id = 0;
}
$id++;
update_metadata($type, $post_id, '_metaidlast', $id);
}
// saving all of the standard items
foreach ((array)$source as $property => $new)
{
//skip special properties that are prefixed with the id
if (strpos($property, $this->_id) === 0) continue;
$old = get_metadata($type, $post_id, $property, true);
update_metadata($type, $post_id, $property, $new);
// if ($new && $new != $old) {
// update_metadata($type, $post_id, $property, $new);
// }
// elseif (!$new) {
// delete_metadata($type, $post_id, $property, $old);
// }
}
return true;
}
/**
* Do something with the data entered
*
* @param integer $post_id
*/
function _save( $post_id )
{
//initializing
$post = get_post($post_id);
// verify if this is an auto save routine.
// If it is our form has not been submitted, so we dont want to do anything
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
return;
// verify this came from the our screen and with proper authorization,
// because save_post can be triggered at other times
if ( !isset($_REQUEST[ get_class().$this->_id ]) )
return;
if ( !wp_verify_nonce( $_REQUEST[ get_class().$this->_id ], plugin_basename( __FILE__ ) ) )
return;
// this metabox is to be displayed for a certain object type only
if ( !in_array($post->post_type, $this->_object_types) )
return;
// Check permissions
if ( 'page' == $post->post_type )
{
if ( !current_user_can( 'edit_page', $post->ID ) )
return;
}
else
{
if ( !current_user_can( 'edit_post', $post->ID ) )
return;
}
//saving the request data
if (!$this->_type) $this->setType();
do_action('metabox-save-'.$this->_id, $this->getRequestPostMetas(), $post->ID, $this );
return true;
}
/**
* Method returns the post meta
*
*/
function getRequestPostMetas()
{
$ignores = array('post_title', 'post_name', 'post_content', 'post_excerpt', 'post',
'post_status', 'post_type', 'post_author', 'ping_status', 'post_parent', 'message',
'post_category', 'comment_status', 'menu_order', 'to_ping', 'pinged', 'post_password',
'guid', 'post_content_filtered', 'import_id', 'post_date', 'post_date_gmt', 'tags_input',
'action');
$fields = array();
foreach ((array)$this->getFields() as $field) {
if (!array_key_exists('id', $field)) continue;
$fields[] = $field['id'];
}
$requests = $_REQUEST;
foreach ((array)$requests as $k => $request)
{
if ((!empty($fields) && !in_array($k, $fields))
|| (in_array($k, $ignores) || strpos($k, 'nounce') !== false))
{
unset($requests[$k]);
}
}
return apply_filters('metabox-requests-'.$this->_id, $requests);
}
/**
* Display the inner contents of the metabox
*
* @param object $post
*/
function show( $post )
{
// Use nonce for verification
wp_nonce_field( plugin_basename( __FILE__ ), get_class().$this->_id );
do_action('metabox-show-'.$this->_id, $this);
}
/**
*
*/
function getImages( $post_id = false )
{
if (!$post_id) {
global $post;
$post_id = $post->ID;
}
//
return get_children(array(
'post_parent' => $post_id,
'post_type' => 'attachment',
'post_mime_type'=> 'image',
'meta_query' => array(
array(
'key' => $this->_id.'_metaboxed',
'value' => $this->_id,
'compare' => '='
)
)
));
}
/**
* Method displays a list of attached images
*
*/
function _renderListImageAttachments()
{
global $post, $current_screen;
$images = $this->getImages();
// no images to render
if (empty($images)) {
?><p>No images have been saved.</p><?php
// rendering the images
} else {
?>
<table class="wp-list-table form-table widefat" style="border:none;">
<?php foreach ((array)$images as $post_id => $image): ?>
<?php $image_attributes = wp_get_attachment_image_src( $image->ID, 'thumbnail' ); ?>
<tbody id="the-list">
<tr>
<th scope="row" style="width:20%!important">
<div style="padding:10px;background:whiteSmoke;">
<img src="<?php echo wp_get_attachment_thumb_url( $image->ID ); ?>" /></div>
</th>
<td>
<b><?php echo $image->post_title; ?></b>
<p><?php echo get_the_content($image->ID); ?></p>
<div class="row-actions">
<span class="inline">
<a href="<?php echo wp_nonce_url(
"media.php?attachment_id=$image->ID"
."&action=edit&_redirect="
.urlencode( $this->_currentPageURL() )
); ?>">
Edit</a> |
</span>
<span class="trash">
<a class="submitdelete"
onclick="return showNotice.warn();"
href="<?php echo wp_nonce_url(
"post.php?action=delete&_redirect="
.urlencode( $this->_currentPageURL() )
."&amp;post=$image->ID",
'delete-attachment_' . $image->ID ); ?>">
Delete Permanently</a> |
</span>
<span class="inline">
<a target="_blank"
href="<?php echo get_attachment_link($image->ID); ?>">
View</a>
</span>
</div>
</td>
</tr>
</tbody>
<?php endforeach; ?>
</table>
<?php
}
return;
}
/**
* Return a clean list of meta listings created by this system
*
* @param string $category
* $param object $post
*/
public static function getMetaListings( $category, $post = null )
{
// initializing
if ($post === NULL) {
global $post;
}
$custom = get_post_custom($post->ID);
$return = array();
//looping all values to build our return array
foreach((array)$custom as $property => $value)
{
$parts = explode('_',$property);
if (!isset($parts[0]) || !isset($parts[1]) || !isset($parts[2])) continue;
if ($parts[0] != $category) continue;
$pro = str_replace($parts[0].'_'.$parts[1].'_', '', $property);
$return[$parts[1]][$pro] = $value;
}
return $return;
}
/**
* Function removes a specific category meta
*
* @param string $category
* $param string $meta_id
* $param object $post
*/
public static function deleteMetaListing( $category, $meta_id, $post = null )
{
// initializing
if ($post === NULL) {
global $post;
}
$listings = redrokk_metabox_class::getMetaListings( $category, $post );
if (!isset($listings[$meta_id])) return false;
$type = 'post';
foreach((array)$listings[$meta_id] as $property => $value) {
$pro = $category.'_'.$meta_id.'_'.$property;
delete_metadata($type, $post->ID, $pro, $value[0]);
}
return true;
}
/**
* Method displays a list of meta attachments
*
*/
function _renderListAttachments()
{
global $post;
//delete action prior to pulling new listings
if (isset($_REQUEST['redrokkdelete']) && $_REQUEST['redrokkdelete']) {
redrokk_metabox_class::deleteMetaListing($this->_category_name, $_REQUEST['redrokkdelete'], $post);
}
//pull new listings
$metaListings = redrokk_metabox_class::getMetaListings($this->_category_name, $post);
if (!empty($metaListings)) {
?>
<table class="wp-list-table form-table widefat" style="border:none;">
<tbody id="the-list">
<?php foreach ((array)$metaListings as $meta_id => $video): ?>
<?php $video = apply_filters('redrokk_metabox_class::_renderListAttachments', $video, $meta_id);?>
<tr id="<?php echo $this->_category_name; ?>_<?php echo $meta_id; ?>">
<th scope="row" style="width:20%!important">
<div style="padding:10px;background:whiteSmoke;">
<?php if(isset($video['link'])) echo apply_filters('the_content', $video['link'][0]); ?>
</div>
</th>
<td>
<b><?php if(isset($video['post_title'])) echo $video['post_title'][0]; ?></b>
<p><?php if(isset($video['post_content'])) echo $video['post_content'][0]; ?></p>
<div class="row-actions">
<span class="inline">
<a href="#" id="edit_<?php echo $this->_category_name; ?>_<?php echo $meta_id; ?>">
Edit</a> |
</span>
<span class="trash">
<a class="submitdelete"
onclick="return showNotice.warn();"
href="<?php echo site_url( "wp-admin/post.php?post={$post->ID}"
."&action=edit"
."&redrokkdelete=$meta_id"
); ?>">
Delete Permanently</a>
</span>
</div>
<script type="application/javascript">
jQuery('#edit_<?php echo $this->_category_name; ?>_<?php echo $meta_id; ?>').click(function(){
var data = {
<?php
$data = array();
//making sure all fields will be cleared
foreach ((array)$this->getFields() as $field) {
if (!isset($field['id']) || !isset($field['type'])) continue;
if (!in_array($field['type'], array('text','file','image','textarea','hidden')))
continue;
$id = str_replace($this->_id.'_', '', $field['id']);
$data[$id] = "'$id':''";
}
//adding our values to the array
foreach((array)$video as $vp => $vv)
{
if (isset($vv[0])) $vv = $vv[0];
$data[$vp] = "'$vp':'$vv'";
}
//adding the meta ID to the array
$data[$id] = "'metaid':'$meta_id'";
echo implode(',',$data);
?>
};
jQuery.each(data, function(key, value){
jQuery('#<?php echo $this->_id; ?>_'+key).val( value );
});
return false;
});
</script>
</td>
<?php do_action('redrokk_metabox_class::_renderListAttachments::rows', $video, $meta_id, $this); ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php
}
}
/**
* Method displays a list of attached videos
*
*/
function _renderListVideoAttachments()
{
global $post;
//pull new listings
$videos =& get_children( "post_parent=$post->ID&post_type=attachment&post_mime_type=video/mp4" );
// no images to render
if (!empty($videos)) {
?>
<table class="wp-list-table form-table widefat" style="border:none;">
<tbody id="the-list">
<?php foreach ((array)$videos as $post_id => $video): ?>
<?php $image_attributes = wp_get_attachment_link( $video->ID ); ?>
<tr>
<th scope="row" style="width: 140px">
<div style="padding:10px;background:whiteSmoke;">
<?php echo $image_attributes; ?>
</div>
</th>
<td>
<b><?php echo $video->post_title; ?></b>
<p><?php echo get_the_content($video->ID); ?></p>
<div class="row-actions">
<span class="inline">
<a href="<?php echo wp_nonce_url(
"media.php?attachment_id=$meta_id"
."&action=edit&_redirect="
.urlencode( $this->_currentPageURL() )
); ?>">
Edit</a> |
</span>
<span class="trash">
<a class="submitdelete"
onclick="return showNotice.warn();"
href="<?php echo wp_nonce_url(
"post.php?action=delete&_redirect="
.urlencode( $this->_currentPageURL() )
."&amp;post=$video->ID",
'delete-attachment_' . $video->ID ); ?>">
Delete Permanently</a>
</span>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php
}
}
/**
*
*/
function getFields()
{
// initializing
if (!$this->fields)
{
$custom = get_post_custom($this->_id);
$defaults = array(
'name' => '',
'desc' => '',
'id' => '',
'type' => 'text',
'options' => array(),
'default' => '',
'value' => '',
'class' => '',
'multiple' => '',
'args' => array(
'hide_empty' => 0,
'name' => 'element_name',
'hierarchical' => true
),
'attributes' => '',
'value' => '',
);
foreach ((array)$this->_fields as $key => $field)
{
$field = wp_parse_args($field, $defaults);
$field['args'] = wp_parse_args($field['args'], $defaults['args']);
// GET PREDEFINED OPTIONS
if (empty($field['options']))
{
$method = 'get_options'.substr($field['type'],6);
if (method_exists($this, $method))
{
$field['options'] = call_user_method($method, $this) +$field['options'];
}
}
// SAVED VALUE
if (is_array($custom) && array_key_exists($field['id'], $custom))
{
if (isset($custom[$field['id']][0])) {
$field['value'] = esc_attr( $custom[$field['id']][0] );
} else {
$field['value'] = esc_attr( $custom[$field['id']] );
}
}
elseif ($field['default'] || is_bool($field['default']))
{
if (strtolower($field['default']) == 'true') {
$field['value'] = true;
}
elseif (strtolower($field['default']) == 'false') {
$field['value'] = false;
}
else {
$field['value'] = $field['default'];
}
}
else
{
// SELECT BOX
if (substr($field['type'],0,6) == 'select')
{
if (is_array($field['options']))
$field['value'] = key($field['options']);
}
// CHECKBOX TO BOOLEAN
elseif (in_array($field['type'], array('checkbox','radio')))
{
if (!$field['default']) {
$field['value'] = false;
}
}
}
// rebuild the fields
$this->fields[$key] = $field;
}
}
return $this->fields;
}
/**
* Method renders the form from any source
*
* @param array $fields
*/
function _renderForm()
{
//initializing
global $post;
$fields = $this->getFields();
// no fields to render
if (empty($fields)) {
?>
<p>No form fields have been defined. Use <pre>
$metabox->set('_fields', array(
array(
'name' => 'Title',
'type' => 'title',
),
array(
'name' => 'Title',
'desc' => '',
'id' => 'title',
'type' => 'text',
'std' => ''
),
array(
'name' => 'image',
'desc' => '',
'id' => 'imagefile',
'type' => 'image',
'std' => ''
),
array(
'name' => 'Textarea',
'desc' => 'Enter big text here',
'id' => 'textarea_id',
'type' => 'textarea',
'std' => 'Default value 2'
),
array(
'name' => 'Select box',
'id' => 'select_id',
'type' => 'select',
'options'=> array(
'value1' => 'Value 1',
'value2' => 'Value 2',
'value3' => 'Value 3',
'value4' => 'Value 4',
)
),
array(
'name' => 'Radio',
'id' => 'radio_id',
'type' => 'radio',
'value' => 'test',
'desc' => 'Check this box if you want its value saved',
),
array(
'name' => '',
'id' => 'radio_id',
'type' => 'radio',
'value' => 'test2',
'desc' => 'Check this box if you want its value saved',
),
array(
'name' => 'Checkbox',
'id' => 'checkbox_id',
'type' => 'checkbox',
'desc' => 'Check this box if you want its value saved',
),
));</pre>
</p>
<?php
// rendering the fields
} else {
?>
<table class="form-table">
<?php
do_action("{$this->_class}_before");
$custom = get_post_custom($this->_id);
foreach ((array)$fields as $field):
extract($field);
$field['args']['name'] = $element_name = $id;
// grabbing the meta value
if (array_key_exists($id, $custom)) {
if (isset($custom[$id][0]))
$meta = esc_attr( $custom[$id][0] );
else
$meta = esc_attr( $custom[$id] );
} else {
$meta = $default;
}
$id = sanitize_title($id);
if (array_key_exists('deleteattachment', $_GET)
&& $id == $_GET['fileproperty']
&& $meta == $_GET['deleteattachment'])
{
wp_delete_attachment( $_GET['deleteattachment'], $force_delete = true );
update_post_meta($post->ID, $id, '');
}
?>
<?php switch ($type){ default: ?>
<?php if (is_callable($type) && function_exists($type)) : ?>
<tr>
<th scope="row" style="width:20%!important">
<label for="<?php echo $id; ?>"><?php echo $name; ?></label>
</th>
<td>
<?php call_user_func($type, $args); ?>
<span class="description"><?php echo $desc; ?></span>
</td>
</tr>
<?php break; endif; ?>
<?php case 'text': ?>
<tr>
<th scope="row" style="width:20%!important">
<label for="<?php echo $id; ?>"><?php echo $name; ?></label>
</th>
<td>
<input <?php echo $attributes ?>
id="<?php echo $id; ?>"
value="<?php echo $meta; ?>"
type="<?php echo $type; ?>"
name="<?php echo $id; ?>"
class="text large-text <?php echo $class; ?>" />
<span class="description"><?php echo $desc; ?></span>
</td>
</tr>
<?php break; ?>
<?php case 'submit': ?>
<?php case 'button': ?>
<tr>
<td colspan="2">
<input <?php echo $attributes ?>
id="<?php echo $id; ?>"
value="<?php echo $name; ?>"
type="submit"
name="submit"
class="button-primary <?php echo $class; ?>" />
<span class="description"><?php echo $desc; ?></span>
</td>
</tr>
<?php break; ?>
<?php case 'file': ?>
<?php case 'image': ?>
<tr>
<th scope="row" style="width:20%!important">
<label for="<?php echo $id; ?>"><?php echo $name; ?></label>
</th>
<td>
<?php if ($meta && wp_get_attachment_url( $meta ) && !in_array($this->_type, array('image','images','video','videos'))): ?>
<?php echo wp_get_attachment_image( $meta ); ?>
<span><a href="<?php echo add_query_arg('fileproperty', $id, add_query_arg('deleteattachment', $meta, $this->_currentPageURL())); ?>">
Delete Image</a></span>
<?php else: ?>
<input type="hidden" name="<?php echo $this->_id; ?>files" value="<?php echo $id; ?>" />
<!-- first hidden input forces this item to be submitted when it is not checked -->
<input <?php echo $attributes ?>
id="<?php echo $id; ?>"
type="file"
name="<?php echo $id; ?>"
onChange="jQuery(this).closest('form').attr('enctype', 'multipart/form-data');"
class="<?php echo $class; ?>" />
<?php endif; ?>
<span class="description"><?php echo $desc; ?></span>
</td>
</tr>
<?php break; ?>
<?php case 'title': ?>
<tr>
<th colspan="2" scope="row">
<h3 <?php echo $attributes ?> style="border: 1px solid #ddd;
padding: 10px;
background: #eee;
border-radius: 2px;
color: #666;
margin: 0;"><?php echo $name; ?>
</h3>
</th>
</tr>
<?php break; ?>
<?php case 'checkbox': ?>
<tr>
<th scope="row" style="width:20%!important">
<label for="<?php echo $id; ?>"><?php echo $name; ?></label>
</th>
<td>
<input type="hidden" name="<?php echo $id; ?>" value="" />
<!-- first hidden input forces this item to be submitted when it is not checked -->
<?php foreach ((array)$options as $_value => $_name): ?>
<input value="<?php echo $_value; ?>" type="checkbox" <?php echo $attributes ?>
name="<?php echo $element_name; ?>" id="<?php echo $id; ?>"
<?php echo $meta == $_value? 'checked="checked"' :''; ?>
class="<?php echo $class; ?>" />
<?php echo $_name; ?>
<?php endforeach; ?>
<span class="description"><?php echo $desc; ?></span>
</td>
</tr>
<?php break; ?>
<?php case 'radio': ?>
<tr>
<th scope="row" style="width:20%!important">
<label for="<?php echo $id; ?>"><?php echo $name; ?></label>
</th>
<td>
<?php foreach ((array)$options as $_value => $_name): ?>
<input name="<?php echo $element_name; ?>" id="<?php echo $id; ?>"
value="<?php echo $_value; ?>" type="<?php echo $type; ?>"
<?php echo $meta == $_value?'checked="checked"' :''; ?>
<?php echo $attributes ?> class="<?php echo $class; ?>" />
<?php echo $_name; ?>
<?php endforeach; ?>
<span class="description"><?php echo $desc; ?></span>
</td>
</tr>
<?php break; ?>
<?php case 'textarea': ?>
<tr>
<th scope="row" style="width:20%!important">
<label for="<?php echo $id; ?>"><?php echo $name; ?></label>
</th>
<td>
<textarea <?php echo $attributes ?>
id="<?php echo $id; ?>"
name="<?php echo $id; ?>"
class="large-text <?php echo $class; ?>"
><?php echo $meta; ?></textarea>
<span class="description"><?php echo $desc; ?></span>
</td>
</tr>
<?php break; ?>
<?php case 'wpeditor': ?>
<tr>
<th scope="row" style="width:20%!important">
<label for="<?php echo $id; ?>"><?php echo $name; ?></label>
</th>
<td>
<?php wp_editor( $meta, $id, $settings = array() ); ?>
<span class="description"><?php echo $desc; ?></span>
</td>
</tr>
<?php break; ?>
<?php case 'select_capabilities': ?>
<?php case 'select_roles': ?>
<?php case 'select_menu': ?>
<?php case 'select_pages': ?>
<?php case 'select_users': ?>
<?php case 'select_categories': ?>
<?php case 'select': ?>
<tr>
<th scope="row" style="width:20%!important">
<label for="<?php echo $id; ?>"><?php echo $name; ?></label>
</th>
<td>
<?php if ($type == 'select_categories'): ?>
<?php wp_dropdown_categories($args); ?>
<?php else: ?>
<select <?php echo $attributes ?>
id="<?php echo $id; ?>"
name="<?php echo $id; ?>"
class="<?php echo $class; ?>"
<?php echo $multiple ?"MULTIPLE SIZE='$multiple'" :''; ?>
><?php foreach ((array)$options as $_value => $_name): ?>
<option
value="<?php echo $_value; ?>"
<?php echo $meta == $_value ?' selected="selected"' :''; ?>
><?php echo $_name; ?></option>
<?php endforeach; ?></select>
<?php endif; ?>
<span class="description"><?php echo $desc; ?></span>
</td>
</tr>
<?php break; ?>
<?php case 'hidden': ?>
<tr>
<td colspan="2">
<input <?php echo $attributes ?>
id="<?php echo $id; ?>"
value="<?php echo $meta ?$meta :$default; ?>"
type="<?php echo $type; ?>"
name="<?php echo $id; ?>"
style="visibility:hidden;" />
</td>
</tr>
<?php break; ?>
<?php case 'custom': ?>
<tr>
<td colspan="2">
<?php echo $desc.$default; ?>
</td>
</tr>
<?php } ?>
<?php endforeach; ?>
</table>
<?php
}
return $this;
}
/**
* Returns an options list of menus
*/
function get_options_pages()
{
// initializing
$options = array('0'=>' -- ');
$pages = get_pages(array('post_type' => 'page', 'post_status' => 'publish'));
foreach($pages as $page) {
$options[$page->ID] = $page->post_title;
}
return $options;
}
/**
* Returns an options list of menus
*/
function get_options_menu()
{
// initializing
$options = array('0'=>' -- ');
$menus = get_terms('nav_menu', array(
'hide_empty' => 0
));
foreach($menus as $menu) {
$options[$menu->slug] = $menu->name;
}
return $options;
}
/**
* Returns an options list of users
*/
function get_options_users()
{
// initializing
global $wpdb;
$options = array('0'=>' -- ');
$query = $wpdb->prepare("SELECT $wpdb->users.ID, $wpdb->users.display_name FROM $wpdb->users");
$results = $wpdb->get_results( $query );
foreach ((array)$results as $result)
{
$options[$result->ID] = $result->display_name;
}
return $options;
}
/**
* Returns an options list of capabilities
*/
function get_options_capabilities()
{
// initializing
global $wpdb;
$options = array();
$roles = get_option($wpdb->prefix . 'user_roles');
foreach ((array)$roles as $role)
{
if(!isset($role['capabilities'])) continue;
foreach ((array)$role['capabilities'] as $cap => $v)
{
$options[$role['name']."::$cap"] = $role['name']."::$cap";
}
}
return $options;
}
/**
* Returns an options list of roles
*/
function get_options_roles()
{
// initializing
global $wpdb;
$options = array(
'read' => 'Public'
);
$roles = get_option($wpdb->prefix . 'user_roles');
foreach ((array)$roles as $role)
{
$options[strtolower($role['name'])] = $role['name'];
}
return $options;
}
/**
* Adds a box to the main column on the Post and Page edit screens
*
*/
function _register()
{
// this metabox is to be displayed for a certain object type only
if (!empty($this->_object_types) && !in_array($this->getCurrentPostType(), $this->_object_types) )
return;
if (!$this->callback_args) {
$this->callback_args = $this;
}
// if the user has not already set the type of this metabox,
// then we need to do that now
if (!$this->_type) {
$this->setType();
}
add_meta_box(
$this->_id,
$this->title,
$this->callback,
$this->getCurrentPostType(),
$this->context,
$this->priority,
$this->callback_args
);
}
/**
* Method set's the hooks for the options creted by this metabox
*
*/
function setOptionHooks()
{
foreach ((array)$this->getFields() as $field)
{
if (!isset($field['id'])) continue;
//creating the callback for the admin page
$function = create_function('$default','
if (!$default) {
$default = "'. $field['value'] .'";
}
return redrokk_admin_class::getInstance("'.$this->_isAdminPage.'")
->getOption("'.$field['id'].'", $default, true);
');
add_filter("pre_option_{$field['id']}", $function, 20, 2);
}
}
/**
* Method redirects the user if we have added a request redirect
* in the url
*
* @param string $location
*/
function _redirectIntervention( $location )
{
if (isset($_GET['_redirect'])) {
$location = urldecode($_GET['_redirect']);
}
return $location;
}
/**
* Get the current page url
*/
function _currentPageURL()
{
$pageURL = 'http';
if (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") {$pageURL .= "s";}
$pageURL .= "://";
if ($_SERVER["SERVER_PORT"] != "80") {
$pageURL .= $_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"];
} else {
$pageURL .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
}
return $pageURL;
}
/**
* Method to bind an associative array or object to the JTable instance.This
* method only binds properties that are publicly accessible and optionally
* takes an array of properties to ignore when binding.
*
* @param mixed $src An associative array or object to bind to the JTable instance.
* @param mixed $ignore An optional array or space separated list of properties to ignore while binding.
*
* @return boolean True on success.
*
* @link http://docs.joomla.org/JTable/bind
* @since 11.1
*/
public function bind($src, $ignore = array())
{
// If the source value is not an array or object return false.
if (!is_object($src) && !is_array($src))
{
trigger_error('Bind failed as the provided source is not an array.');
return false;
}
// If the source value is an object, get its accessible properties.
if (is_object($src))
{
$src = get_object_vars($src);
}
// If the ignore value is a string, explode it over spaces.
if (!is_array($ignore))
{
$ignore = explode(' ', $ignore);
}
// Bind the source value, excluding the ignored fields.
foreach ($this->getProperties() as $k => $v)
{
// Only process fields not in the ignore array.
if (!in_array($k, $ignore))
{
if (isset($src[$k]))
{
$this->$k = $src[$k];
}
}
}
return true;
}
/**
* Set the object properties based on a named array/hash.
*
* @param mixed $properties Either an associative array or another object.
*
* @return boolean
*
* @since 11.1
*
* @see set()
*/
public function setProperties($properties)
{
if (is_array($properties) || is_object($properties))
{
foreach ((array) $properties as $k => $v)
{
// Use the set function which might be overridden.
$this->set($k, $v);
}
return true;
}
return false;
}
/**
* Modifies a property of the object, creating it if it does not already exist.
*
* @param string $property The name of the property.
* @param mixed $value The value of the property to set.
*
* @return mixed Previous value of the property.
*
* @since 11.1
*/
public function set($property, $value = null)
{
$_property = 'set'.str_replace(' ', '', ucwords(str_replace('_', ' ', $property)));
if (method_exists($this, $_property)) {
return $this->$_property($value);
}
$previous = isset($this->$property) ? $this->$property : null;
$this->$property = $value;
return $previous;
}
/**
* Returns an associative array of object properties.
*
* @param boolean $public If true, returns only the public properties.
*
* @return array
*
* @see get()
*/
public function getProperties($public = true)
{
$vars = get_object_vars($this);
if ($public)
{
foreach ($vars as $key => $value)
{
if ('_' == substr($key, 0, 1))
{
unset($vars[$key]);
}
}
}
return $vars;
}
/**
* Method returns the called class
*
*/
public static function get_called_class()
{
if (function_exists('get_called_class')) {
return get_called_class();
}
$called_class = false;
$objects = array();
$traces = debug_backtrace();
foreach ($traces as $trace)
{
if (isset($trace['object'])) {
if (is_object($trace['object'])) {
$objects[] = $trace['object'];
}
}
}
if (count($objects)) {
$called_class = get_class($objects[0]);
}
return $called_class;
}
/**
*
* contains the current instance of this class
* @var object
*/
static $_instances = null;
/**
* Method is called when we need to instantiate this class
*
* @param array $options
*/
public static function getInstance( $id, $options = array() )
{
if (!isset(self::$_instances[$id]))
{
$class = self::get_called_class();
$options['_class'] = $class;
$options['_id'] = $id;
self::$_instances[$id] =& new $class($options);
}
return self::$_instances[$id];
}
}
endif;
@lgobatto
Copy link

Hey man, can you give me some help? i'm trying to create a metabox only for a specific custom post, how can i do that?
Thanks

@jonathonbyrdziak
Copy link
Author

jonathonbyrdziak commented Mar 15, 2012 via email

@jonathonbyrdziak
Copy link
Author

I added a bunch of examples for you Igobatto. I will most likely program the option to assign metaboxes to single posts in a few days.

@lgobatto
Copy link

Thanks man, that's what i'm looking for. also congrats for the great class.

@jonathonbyrdziak
Copy link
Author

There you go bro, I added the functionality to include a metabox on a single post, or exclude it from a single post. This will work on any post_type.

@lgobatto
Copy link

Hey man, sorry for so many questions, but i'm having another issue on my end, i just updated the class with your latest version and the metabox isnt rendering the form anymore, here are my code:

set('_single', 'Slider'); $slider->set('_plural', 'Sliders'); $slider->set('menu_icon', get_template_directory_uri().'/images/icons/slider.png'); $slider->set('supports', array('title')); $link_box = redrokk_metabox_class::getInstance('link_box'); $link_box->set('title', 'Slider Details'); $link_box->set('_object_types', 'slider'); $link_box->set('_fields', array( array('name' => 'Link Url', 'type' => 'text', 'id' => 'link_uri', 'desc' => 'insert the url that the slide will point to.'), array('name' => 'Slider Image', 'type' => 'image', 'id' => 'slider_image', 'desc' => 'select the image, best size: 940px X 370px.'), )); ?> also here are the link for a print of my wp-admin: www.lgobatto.com.br/slider-ss.png

@jonathonbyrdziak
Copy link
Author

jonathonbyrdziak commented Mar 21, 2012 via email

@lgobatto
Copy link

Hey man, how are you, so back to my bug, the code are stopping on line 187
add_action( 'plugins_loaded', array(&$this, 'setType') );
this action is not calling the setType function.

quick question, there is some dependence to use this class? I'm also using Master Widget, Post Class and Taxonomy(this one isn't working for me also, but isn't my priority).

Thanks again for your help man,

@lgobatto
Copy link

i just enable wordpress debug mode, here are some infos:

Notice: Undefined offset: 0 in D:\Jobs\Vera Roca Webgroup\DjInkognito\Site v_2\wp-includes\plugin.php on line 764

Notice: Undefined offset: 0 in D:\Jobs\Vera Roca Webgroup\DjInkognito\Site v_2\wp-includes\plugin.php on line 782

Notice: add_option was called with an argument that is deprecated since version 2.3 with no alternative available. in D:\Jobs\Vera Roca Webgroup\DjInkognito\Site v_2\wp-includes\functions.php on line 3553

@lgobatto
Copy link

Hey man, i found a solution to make it work again, comparing with the older versions i found that:
// if the user has not already set the type of this metabox,
// then we need to do that now
if (!$this->_type) {
$this->setType();
}
so what i did was:
remove this: add_action( 'plugins_loaded', array(&$this, 'setType') ); from line 189
add the older code into _register functions just after this (line 944 i think):
if (!is_array($this->callback_args)) {
$this->callback_args = array();
}
$this->callback_args[] = $this;
now everything is working.

thanks

@jonathonbyrdziak
Copy link
Author

jonathonbyrdziak commented Mar 22, 2012 via email

@lgobatto
Copy link

Sure one sec

@jonathonbyrdziak
Copy link
Author

@changlog 6/13/2012 : Made metabox class compatible with redrokk_admin_class for administrative pages.

Copy link

ghost commented Jul 23, 2012

Probably a lame question, but how could I import your class in order to use it on my WP installation?

Thanks

@michael-cannon
Copy link

I recently hooked redrokk_metabox_class into my Testimonials Widget plugin at http://wordpress.org/extend/plugins/testimonials-widget/. The final result is really slick and wonderful time saver. To help you out implementation, take a look at http://plugins.svn.wordpress.org/testimonials-widget/tags/2.1.4/testimonials-widget.php.

Basically, in my plugin constructor, I have an admin_init add_action which in turns calls the method add_meta_box_testimonials_widget. Now add_meta_box_testimonials_widget() itself only includes the metabox.class.php file and a simple redrokk_metabox_class::getInstance for my 3 post meta fields.

That's it and the meta data integration without any further effort is working well. Do note that the id shouldn't contain hyphens. If you do, the meta data boxes show up, but no data is saved.

@gasatrya
Copy link

I got this error message

Notice: Trying to get property of non-object in D:\xampp\htdocs\dev\wp-includes\post-template.php on line 30 in my local development

@emad-elsaid
Copy link

WOW, this is prefect man,
thank you.

@pbiron
Copy link

pbiron commented Mar 10, 2013

@Satrya: I'm getting the same error. (with define (WP_DEBUG, true) in my wp-config.php).

I've tracked it down to line 834 (in getFields(), which is called by setOptionHooks() which is called by __constructor()):

$custom = get_post_custom($this->_id);

I just started using this class yesterday, so obviously, I don't know the code very well, but it seems there are two problems with this line:

  1. get_post_custom() expects a post ID as its arg, but this is passing the id of the metabox. There is one other place where get_post_custom() is called in this way (line 997).

So, one question is: what is supposed to be accomplished by passing $this->_id?

  1. If the arg passed to get_post_custom() doesn't eval to an int, then it internally uses global $post (in a call to get_the_ID()). If I create a custom metabox with this class in my plugin's admin_init hook (as suggested by @michael-cannon above) then global $post is not yet set, and hence the "Trying to get property of non-object" error.

Now, changing line 834 to:

        global $post ;
        $custom = isset ($post) ? get_post_custom ($post->ID) : null ;

makes the error go away, but I'm not sure it's the right thing to do (again, I've only been working with this class for a few hours). So, I'm NOT suggesting the above as a patch, only as a hint to the author about one possible fix.

@pbiron
Copy link

pbiron commented Mar 10, 2013

another question I have has to do with metaboxes not rendering if I create them, not in my plugin's admin_init hook.

My plugin registers a custom post type. In my call to register_post_type() I use the register_meta_box_cb parameter. If I try to create a metabox with this class in that callback (which to me seems like the natural place to do it), then that metabox does not render (although, other metaboxes that I create using add_meta_box() do render).

Why don't they render in that case?

@pbiron
Copy link

pbiron commented Mar 11, 2013

maybe I'm being dense, but I also can't seem to get array valued fields to work, e.g.

redrokk_metabox_class::getInstance ('tmp', array (
    'title'         => 'Tmp',
    '_object_types' => 'post',
    'context'       => 'normal',
    'priority'      => 'high',
    '_fields'       =>
        array (
            array (
                'name'  => 'Post Types',
                'id'    => 'post_types[]',
                'type'  => 'checkbox',
                'options'=> array (
                    'post' => 'Post',
                    'page' => 'Page',
                    'attachment' => 'Attachment',
                    ),
                'desc' => 'Select all post types that apply',
                ),
            )
    )) ;

The above renders properly, but when I save the post, the post_types meta-key is not saved.

[note: if I use 'id' => 'post_types' in the _fields array then only the last checked checkbox gets saved]

If I modify line 508 to be:

if ((!empty($fields) && !(in_array($k, $fields) || in_array($k . '[]', $fields)))

then the post_types meta-key does get saved. However, upon editing the post again, the previously saved value(s) for the field are not checked :-(

The culprit here seems to be the call to get_post_custom() on line 997 (mentioned in my 1st comment). But the problem isn't in passing $this->_id, it's what could be argued is a bug in the way get_post_custom() is implemented in core (actually, it's in get_metadata(), which is indirectly called by get_post_custom()).

That is, get_post_custom() doesn't unserialize the meta-values, whereas get_post_meta($post_id, $metakey, true) does :-( You can see this by inserting the following after line 997:

echo '<pre>' . var_export ($custom, true) . '</pre>' ;
die () ;

And because the meta-values for array valued fields aren't being unserialized, the check on line 1134 doesn't result in checked='checked' being output:

<?php echo $meta == $_value?'checked="checked"' :''; ?>

From my little time looking over the code in this class, modifying it to get around what I think is a bug in get_post_custom() seems like ALOT of work. One way would be to replace the call to get_post_custom() with individual calls to get_post_meta(), which seems like a real hassle. Another would be explicitly call $meta = maybe_unserialze($meta) in the appropriate places.

One UGLY implementation of the 2nd alternative is to replace the lines 1005-1012 with:

if (array_key_exists($id, $custom)) {
    if (isset($custom[$id][0]))
        $meta = esc_attr( $custom[$id][0] );
    else
        $meta = esc_attr( $custom[$id] );
} else {
    $tmpid = str_replace ('[]', '', $id) ;
    if (array_key_exists (str_replace ('[]', '', $id), $custom)) {
        if (isset($custom[$tmpid][0]))
            $meta = maybe_unserialize ($custom[$tmpid][0]);
        else
            $meta = maybe_unserialize ($custom[$tmpid]);
    }
else {
    $meta = $default;
    }
}

and line 1117 with:

<?php echo (is_array ($meta) && in_array ($_value, $meta)) || $meta == $_value? 'checked="checked"' :''; ?>

[I told you it was UGLY! But it does work for the cases I've tried. As in my 1st comment, I'm NOT proposing this as a patch, I'm sure there are cleaner ways to work around the problem.]

Does the above analysis seem correct to you?

[p.s. I plan on opening a ticket on http://core.trac.wordpress.org about what I think is the bug in get_post_custom() after I've explored that issue a little more. If/when I do open that ticket, I'd appreciate your commenting on it about how the bug impacts your code (assuming you agree with my analysis above).]

[p.p.s. all the above applies equally well with select's where multiple='multiple', with an equivalent change to line 1194 as I have above for 1117]

@pbiron
Copy link

pbiron commented Mar 11, 2013

having explored the issue of get_post_custom() returning serialized values, I see that it is documented in the Codex (http://codex.wordpress.org/Function_Reference/get_post_custom#Retrieving_data_from_the_array) as having this behavior:

Note: not only does the function return a multi-dimensional array (ie: always be
prepared to deal with an array of   arrays, even if expecting array of single values),
but if also returns serialized values of any arrays stored as meta values. If you
expect that possibly an array may be stored as a metavalue, then be prepared to 
'maybe_unserialize'. 

And there has been on open ticket on it (http://core.trac.wordpress.org/ticket/20853) for 9 months.

So, while I still think it's a "bug", it appears that it is by design and that they are unlikely to "fix" it :-(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment