'',
'title' => __( 'Additional Options', 'ski' ),
'desc' => __( '', 'ski' ),
'screens' => array( 'post' ),
'context' => 'advanced',
'priority' => 'default',
'controls' => array()
);
$this->args = (object)wp_parse_args( $args, $defaults );
// Respect the WordPress timing for creation and saving of the metabox.
add_action( 'add_meta_boxes', array( $this, 'add' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
add_action( 'save_post', array( $this, 'save' ) );
foreach ( $this->args->controls as $control )
{
$control->hook();
}
}
/**
* The main entry into creation of metaboxes - calling code defines the
* boxes through a custom hook.
*/
function add()
{
// Make the call to WP for the metabox.
foreach ( $this->args->screens as $screen )
{
add_meta_box(
$this->args->slug,
$this->args->title,
array( $this, 'render' ),
$screen,
$this->args->context,
$this->args->priority
);
}
}
/**
* Queues up scripts and styles.
*/
function enqueue()
{
// Run through the controls.
foreach ( $this->args->controls as $control )
{
$control->enqueue();
}
wp_enqueue_script( 'ski.wp.metabox.js', path::get_ski_directory_uri().'/controls/ski.wp.metabox.js', array(), time() );
wp_enqueue_style( 'ski.wp.metabox.css', path::get_ski_directory_uri().'/controls/ski.wp.metabox.css', array(), time() );
wp_enqueue_style( 'jquery-ui.css', 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/smoothness/jquery-ui.css', array(), time() );
}
/**
* Displays the contents of this metabox -
* the controls should have been created in the constructor.
* @param type $post An object representing the current post
*
* Note: A second parameter is optional that contains the ID, title, callback, and any args
* passed from the add_meta_box call; however, this info is already stored on this object
*/
function render( $post )
{
// Display the overall description.
echo $this->args->desc;
// Run through the controls.
foreach ( $this->args->controls as $control )
{
// Every control gets a nonce - just do it here to avoid duplicating everywhere else.
wp_nonce_field( 'ski_metabox', 'ski_metabox_nonce' );
// Retrieve the value of the control - if not present and there is a starting value, use it.
$value = get_post_meta( $post->ID, $control->get_slug(), true );
// Write the markup for the control - unless otherwise noted, all controls
// have some text on the left and the actual control on the right.
//
// Note: When setting up the $class_type_name, do not use 'basename' without ensuring
// there are forward slashes (e.g. no backslashes) - it appears that different
// results happen based on the PHP version. For example, with these cases:
//
// Example: $test_1 = ski\mb_text_ctl
// 5.4.12: basename( $test_1 ) -> mb_text_ctl
// 5.4.19: basename( $test_1 ) -> ski\mb_text_ctl
//
// Example: $test_2 = ski/mb_text_ctl
// 5.4.12: basename( $test_2 ) -> mb_text_ctl
// 5.4.19: basename( $test_2 ) -> mb_text_ctl
//
// It looks like the official documentation states that Windows servers can handle
// both kinds of slashes, but other environments can only handle the forward slash.
// This must mean that 5.4.12 was broken in this regard:
//
// http://php.net/manual/en/function.basename.php
//
$class_type_name = basename( \ski\path::force_forward_slashes( get_class( $control ) ) );
$class_slug_name = 'container-'.$control->get_slug();
echo
'
'.
$control->get_label_markup().
'
'.
$control->get_desc_markup().
'
'.
'
';
$control->render( $value );
echo
'
'.
'
';
}
}
/**
* The validation checking is pretty much verbatim from the WP codex. This function
* does a bunch of security-checking, then runs through the controls to ask for the
* POST value from each, then places that value in the database.
* @param integer $post_id The ID of the current post.
* @link http://codex.wordpress.org/Function_Reference/sanitize_text_field
*/
function save( $post_id )
{
// Check if our nonce is set.
if ( ! isset( $_POST['ski_metabox_nonce'] ) ) return;
// Verify that the nonce is valid.
if ( !wp_verify_nonce( $_POST['ski_metabox_nonce'], 'ski_metabox' ) ) return;
// If this is an autosave, our form has not been submitted, so we don't want to do anything.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
// Check the user's permissions.
if ( isset( $_POST['post_type'] ) && 'page' == $_POST['post_type'] )
{
if ( !current_user_can( 'edit_page', $post_id ) ) return;
}
else
{
if ( !current_user_can( 'edit_post', $post_id ) ) return;
}
// Making it here means it is cool to run through the controls -
// do the following:
// 1. Ask the control for the prepared POST value to save.
// 2. Save that value to the database using the control's slug and metabox's post ID.
//
// If the control's value is not in the POST, then expect an exception to be thrown
// from prepare_post_value() that indicates nothing should be updated in the database.
foreach ( $this->args->controls as $control )
{
try
{
$value = $control->prepare_post_value();
update_post_meta( $post_id, $control->get_slug(), $value );
}
catch ( \Exception $e )
{
// Nothing to save - something could be wrong with the POST data.
continue;
}
}
}
}
/**
* Acts as an abstract class for displaying and saving controls in metaboxes.
*/
class mb_ctl
{
/**
* @var array The arguments passed in when this control was initially created.
*/
protected $args = array();
/**
* Constructor.
* @param array $args Can be any of the following arguments:
* string $slug The unique name to be used as both the control ID and the key in the database.
* string $label Text that describes a potential group of desc/control combos; usually bolded and above.
* string $desc Text that describes the purpose of the control; sits on the same line as the control.
* string $starting_value Used in the case there is no database value.
*
* string $placeholder Displayed when textbox-like controls have no value.
* string $text Main text that can be used next to check/radio controls, as the 'add' button in the media library, etc
* array $choices Options for dropdown and radio controls
* boolean $multiple Allow for multiple selections in dropdown (TBD) and media library controls
* integer $range_min Minimum value allowed typically for constrained controls
* integer $range_max Maximum value allowed typically for constrained controls
* integer $range_step Amount to change the value typically for constrained controls
* string $status Can be one of the following values: info, caution, fail
* boolean $allow_html Setting to false not permit HTML to make it into the control value
*
* string $repeat_ctl The name of the mb_ctl class that will be repeated one under the other
* integer $repeat_num The maximum number of times the $repeat_ctl can be repeated; use -1 for infinite
*/
public function __construct( $args = array() )
{
// Fill in missing arguments and store for later use.
$defaults = array(
'slug' => 'mb_ctl_dummy',
'label' => __( '', 'ski' ),
'desc' => __( '', 'ski' ),
'starting_value' => '',
'placeholder' => __( '', 'ski' ),
'text' => __( '', 'ski' ),
'choices' => array(),
'multiple' => false,
'range_min' => 0,
'range_max' => 100,
'range_step' => 1,
'status' => 'info',
'allow_html' => false,
'repeat_ctl' => '',
'repeat_num' => 2,
);
$this->args = (object)wp_parse_args( $args, $defaults );
}
/**
* Offers an opportunity to hook into actions/filter - expecting child classes to implement.
*/
public function hook()
{
// Intentionally nothing here - expecting child classes to implement.
}
/**
* Offers an opportunity to queue up CSS/JS - this is a stub for derived classes.
*/
public function enqueue()
{
// Intentionally nothing here - expecting child classes to implement.
}
/**
* Displays this control - this is a stub for derived classes.
* @param type $post_id The ID of the current post.
*/
public function render( $post_id )
{
// Intentionally nothing here - expecting child classes to implement.
}
/**
* Preps the corresponding POST value.
* @return string The prepared value from the POST
* @link http://codex.wordpress.org/Function_Reference/sanitize_text_field
*/
public function prepare_post_value()
{
// Default is to not save - an exception is the trigger for that.
throw new \Exception();
}
/**
* Accessor for the unqiue slug.
* @return string This object's slug value
*/
public function get_slug()
{
return $this->args->slug;
}
/**
* The label should be uniform for every control - so just lay
* it out once and reference it from the render callbacks everywhere.
*/
public function get_label_markup()
{
$label = isset( $this->args->label ) ? esc_html( $this->args->label ) : '';
if ( empty( $label ) ) return '';
return '
'.$this->args->label.'
';
}
/**
* The description should be uniform for every control - so just lay
* it out once and reference it from the render callbacks everywhere.
*/
public function get_desc_markup()
{
$desc = isset( $this->args->desc ) ? esc_html( $this->args->desc ) : '';
if ( empty( $desc ) ) return '';
return ''.$desc.'';
}
}
/**
* Concrete class for a textbox that can be displayed inside of a metabox.
*/
class mb_checkbox_ctl extends mb_ctl
{
/**
* Displays this control.
* @param string $value Raw database value to assign
*/
public function render( $value )
{
$id = esc_attr( $this->args->slug );
$value = esc_attr( $value );
$text = esc_html( $this->args->text );
if ( empty( $value ) ) $value = $this->args->starting_value;
echo
'';
}
/**
* Saves the contents of this control.
* @param type $post_id The ID of the current post.
*/
public function prepare_post_value()
{
// Convert to a meaningful value.
$value = isset( $_POST[$this->args->slug] ) && $_POST[$this->args->slug] ? 'on' : 'off';
return $value;
}
}
/**
* Concrete class for a date picker that can be displayed inside of a metabox.
*/
class mb_date_picker_ctl extends mb_ctl
{
/**
* Offers an opportunity to queue up CSS/JS.
*/
public function enqueue()
{
wp_enqueue_script( 'jquery-ui-datepicker' );
}
/**
* Displays this control.
* @param string $value Raw database value to assign
*/
public function render( $value )
{
$id = esc_attr( $this->args->slug );
$value = esc_attr( $value );
if ( empty( $value ) ) $value = $this->args->starting_value;
echo
'';
}
/**
* Preps the corresponding POST value.
* @return string The prepared value from the POST
*/
public function prepare_post_value()
{
// Sanitize user input.
$value = $_POST[$this->args->slug];
if ( !$this->args->allow_html ) $value = sanitize_text_field( $value );
return $value;
}
}
/**
* Concrete class for a heading label and control description that can be displayed inside of a metabox.
*/
class mb_detail_ctl extends mb_ctl
{
// Intentionally nothing to do since the metabox class handles display of label/desc.
}
/**
* Concrete class for a horizontal divider.
*/
class mb_divider_ctl extends mb_ctl
{
/**
* Displays this control.
* @param string $value Raw database value to assign
*/
public function render( $value )
{
echo
'';
}
}
/**
* Concrete class for a dropdown that can be displayed inside of a metabox.
*/
class mb_drop_ctl extends mb_ctl
{
/**
* Displays this control.
* @param string $value Raw database value to assign
*/
public function render( $value )
{
$id = esc_attr( $this->args->slug );
$value = esc_attr( $value );
if ( empty( $value ) ) $value = $this->args->starting_value;
echo
'';
}
/**
* Preps the corresponding POST value.
* @return string The prepared value from the POST
*/
public function prepare_post_value()
{
// Make sure that it is set.
if ( !isset( $_POST[$this->args->slug] ) ) throw new \Exception();
// Sanitize user input.
$value = $_POST[$this->args->slug];
if ( !$this->args->allow_html ) $value = sanitize_text_field( $value );
return $value;
}
}
/**
* Concrete class for a media library selector that can be displayed inside of a metabox.
*/
class mb_media_library_ctl extends mb_ctl
{
/**
* Offers an opportunity to queue up CSS/JS.
*/
public function enqueue()
{
wp_enqueue_media();
}
/**
* Displays this control.
* @param string $value Raw database value to assign
*/
public function render( $value )
{
$id = esc_attr( $this->args->slug );
$value = esc_attr( $value );
$multiple = empty( $this->args->multiple ) ? 'false' : 'true';
$text = esc_attr( $this->args->text );
if ( empty( $value ) ) $value = $this->args->starting_value;
// Note: The preview images, select button, and remove buttons are all generated through JS.
echo
''.
'
';
}
/**
* Preps the corresponding POST value.
* @return string The prepared value from the POST
*/
public function prepare_post_value()
{
// Make sure that it is set.
if ( !isset( $_POST[$this->args->slug] ) ) throw new \Exception();
// Sanitize user input.
$value = $_POST[$this->args->slug];
if ( !$this->args->allow_html ) $value = sanitize_text_field( $value );
return $value;
}
}
/**
* Concrete class for a media library selector that can be displayed inside of a metabox.
*/
class mb_number_bar_ctl extends mb_ctl
{
/**
* Offers an opportunity to queue up CSS/JS.
*/
public function enqueue()
{
wp_enqueue_script( 'jquery-ui-slider' );
}
/**
* Displays this control.
* @param string $value Raw database value to assign
*/
public function render( $value )
{
$id = esc_attr( $this->args->slug );
$value = intval( esc_attr( $value ) );
$min = intval( $this->args->range_min );
$max = intval( $this->args->range_max );
$step = $this->args->range_step;
if ( $min > $max ) math::swap( $min, $max );
if ( empty( $value ) ) $value = $this->args->starting_value;
$value = math::clamp( $value, $min, $max );
echo
''.
''.$min.''.
'
'.
''.
'
'.
''.$max.'';
}
/**
* Preps the corresponding POST value.
* @return string The prepared value from the POST
*/
public function prepare_post_value()
{
// Make sure that it is set.
if ( !isset( $_POST[$this->args->slug] ) ) throw new \Exception();
// Ensure it is a number.
if ( !is_numeric( $_POST[$this->args->slug] ) ) return;
// Sanitize user input.
$value = $_POST[$this->args->slug];
if ( !$this->args->allow_html ) $value = sanitize_text_field( $value );
return $value;
}
}
/**
* Concrete class for a set of radio image options that can be displayed inside of a metabox.
*/
class mb_radio_image_ctl extends mb_ctl
{
/**
* Displays this control.
* @param string $value Raw database value to assign
*/
public function render( $value )
{
if ( empty( $this->args->choices ) ) return;
$id = esc_attr( $this->args->slug );
$value = esc_attr( $value );
if ( empty( $value ) ) $value = $this->args->starting_value;
if ( empty( $value ) ) $value = array_keys( $this->args->choices )[0];
foreach ( $this->args->choices as $choice => $url )
{
$checked = checked( $value, $choice, false );
echo
'';
}
}
/**
* Preps the corresponding POST value.
* @return string The prepared value from the POST
*/
public function prepare_post_value()
{
// Make sure that it is set.
if ( !isset( $_POST[$this->args->slug] ) ) throw new \Exception();
// Sanitize user input.
$value = $_POST[$this->args->slug];
if ( !$this->args->allow_html ) $value = sanitize_text_field( $value );
return $value;
}
}
/**
* Represents sidebar options: 0, l1, l2, l1r1, r2, r1
* @since 2.1
*/
class mb_radio_image_sidebar_layout_ctl extends mb_radio_image_ctl
{
/**
* Constructor. If no layouts are passed in, initialize with all site span layouts.
*
* @param array $args
*/
public function __construct( $args = array() )
{
if ( !isset( $args['layouts'] ) ) $args['layouts'] = array( '0', 'l1', 'l2', 'l1r1', 'r2', 'r1' );
foreach ( $args['layouts'] as $layout )
{
$args['choices'][$layout] = path::get_ski_directory_uri().'/assets/customizer/sidebar-'.$layout.'.png';
}
parent::__construct( $args );
}
}
/**
* Represents span options: bleed-wide, bleed-narrow, boxed-narrow
* @since 2.1
*/
class mb_radio_image_site_span_layout_ctl extends mb_radio_image_ctl
{
/**
* Constructor. If no layouts are passed in, initialize with all site span layouts.
*
* @param array $args
*/
public function __construct( $args = array() )
{
if ( !isset( $args['layouts'] ) ) $args['layouts'] = array( 'bleed-wide', 'bleed-narrow', 'boxed-narrow' );
foreach ( $args['layouts'] as $layout )
{
$args['choices'][$layout] = path::get_ski_directory_uri().'/assets/customizer/span-'.$layout.'.png';
}
parent::__construct( $args );
}
}
/**
* Concrete class for a set of radio options that can be displayed inside of a metabox.
*/
class mb_radio_text_ctl extends mb_ctl
{
/**
* Displays this control.
* @param string $value Raw database value to assign
*/
public function render( $value )
{
if ( empty( $this->args->choices ) ) return;
$id = esc_attr( $this->args->slug );
$value = esc_attr( $value );
if ( empty( $value ) ) $value = $this->args->starting_value;
if ( empty( $value ) ) $value = array_keys( $this->args->choices )[0];
foreach ( $this->args->choices as $choice => $label )
{
echo
'';
}
}
/**
* Preps the corresponding POST value.
* @return string The prepared value from the POST
*/
public function prepare_post_value()
{
// Make sure that it is set.
if ( !isset( $_POST[$this->args->slug] ) ) throw new \Exception();
// Sanitize user input.
$value = $_POST[$this->args->slug];
if ( !$this->args->allow_html ) $value = sanitize_text_field( $value );
return $value;
}
}
/**
* Concrete class for repeating any of the other mb_ctl
* classes one under the other.
*/
class mb_repeater_ctl extends mb_ctl
{
/**
* The maximum number of controls this repeater supports.
*/
const MAX_CTLS = 16;
/**
* @var array Representation of the controls currently in play.
*/
protected $ctls = array();
/**
* Constructor.
* @param array $args See the parent comments for a description of the arguments.
*/
public function __construct( $args )
{
// Parent setup.
parent::__construct( $args );
// If a bogus number was passed in as the max, then set to infinity. If a number
// larger than the number supported was passed in, then clamp it to the max.
if ( $this->args->repeat_num < 0 ) $this->args->repeat_num = self::MAX_CTLS;
$this->args->repeat_num = min( $this->args->repeat_num, self::MAX_CTLS );
// Create all possible controls - the max number requested.
for ( $i = 0; $i < $this->args->repeat_num; $i++ )
{
$ctl_args = clone $this->args;
$ctl_args->slug = $this->get_index_slug( $i );
$this->ctls[] = new $this->args->repeat_ctl( $ctl_args );
}
}
/**
* Displays this control.
* @param string $value Raw database value to assign
*/
public function render( $value )
{
// Run through all controls, assign values, and render.
$ctl_count = count( $this->ctls );
$value_count = count( $value );
for( $i = 0; $i < $ctl_count; $i++ )
{
$child_value = $this->args->starting_value;
if ( is_array( $value ) && ( $value_count > $i ) )
{
$child_value = $value[$i];
}
echo
'
';
}
}
/**
* Preps the corresponding POST value.
* @return string The prepared value from the POST
*/
public function prepare_post_value()
{
// Collect the values from each control and place into an array.
$values = array();
foreach ( $this->ctls as $ctl )
{
$values[] = $ctl->prepare_post_value();
}
return $values;
}
/**
* Generates a unique name for a child control, given it's index.
* @param integer $index 0-based index
* @return string The unqiue slug
*/
protected function get_index_slug( $index )
{
return $this->args->slug.'__'.$index;
}
}
/**
* Concrete class for plain text.
*/
class mb_text_ctl extends mb_ctl
{
/**
* Displays this control.
* @param string $value Raw database value to assign
*/
public function render( $value )
{
echo
$this->args->text;
}
}
/**
* Concrete class for a textarea that can be displayed inside of a metabox.
*/
class mb_text_area_ctl extends mb_ctl
{
/**
* Displays this control.
* @param string $value Raw database value to assign
*/
public function render( $value )
{
$id = esc_attr( $this->args->slug );
$value = esc_textarea( $value );
$placeholder = esc_attr( $this->args->placeholder );
if ( empty( $value ) ) $value = $this->args->starting_value;
echo
'';
}
/**
* Preps the corresponding POST value.
* @return string The prepared value from the POST
*/
public function prepare_post_value()
{
// Make sure that it is set.
if ( !isset( $_POST[$this->args->slug] ) ) throw new \Exception();
// Sanitize user input.
$value = $_POST[$this->args->slug];
if ( !$this->args->allow_html ) $value = sanitize_text_field( $value );
return $value;
}
}
/**
* Concrete class for stylized text.
*/
class mb_text_notice_ctl extends mb_ctl
{
/**
* Displays this control.
* @param string $value Raw database value to assign
*/
public function render( $value )
{
echo
'
'.
$this->args->text.
'
';
}
}
/**
* Concrete class for a textbox that can be displayed inside of a metabox.
*/
class mb_textbox_ctl extends mb_ctl
{
/**
* Displays this control.
* @param string $value Raw database value to assign
*/
public function render( $value )
{
$id = esc_attr( $this->args->slug );
$value = esc_attr( $value );
$placeholder = esc_attr( $this->args->placeholder );
if ( empty( $value ) ) $value = $this->args->starting_value;
echo
'';
}
/**
* Preps the corresponding POST value.
* @return string The prepared value from the POST
*/
public function prepare_post_value()
{
// Make sure that it is set.
if ( !isset( $_POST[$this->args->slug] ) ) throw new \Exception();
// Sanitize user input.
$value = $_POST[$this->args->slug];
if ( !$this->args->allow_html ) $value = sanitize_text_field( $value );
return $value;
}
}
/**
* From your theme, hook into this function like so:
* if ( defined( 'WP_DEBUG' ) ) add_action( 'admin_init', 'ski\metabox_sample' );
*
* This function will add a new section to the page/post edit screens with examplesof each control.
* Note that doing this and clicking "Publish" will append these bogus settings to your database.
*/
function metabox_sample( $wp_customize )
{
// Sample metabox.
$args = array (
'slug' => 'ski_metabox_sample',
'title' => __( 'Ski.Web Metabox Tests', 'ski' ),
'desc' => __( 'Demonstrates each Ski.Web control that can be used in a metabox.', 'ski' ),
'screens' => array( 'page', 'post' ),
'context' => 'advanced',
'priority' => 'low',
'controls' => array(
// Detail
new mb_detail_ctl(
array(
'slug' => 'ski_meta_detail',
'label' => __( 'detail_ctl (args = label)', 'ski' ),
'desc' => __( 'detail_ctl (args = desc)', 'ski' )
)),
// Divider
new mb_divider_ctl(
array(
// None
)),
// Checkbox
new mb_checkbox_ctl(
array(
'slug' => 'ski_meta_checkbox',
'desc' => __( 'checkbox_ctl', 'ski' ),
'text' => __( 'additional text', 'ski' )
)),
// Date Picker
new mb_date_picker_ctl(
array(
'slug' => 'ski_meta_date_picker',
'desc' => __( 'date_picker_ctl', 'ski' )
)),
// Dropdown
new mb_drop_ctl(
array(
'slug' => 'ski_meta_dropdown',
'desc' => __( 'drop_ctl', 'ski' ),
'choices' =>
array(
'' => '- Select -',
'1' => 'first',
'2' => 'second',
'3' => 'third'
),
)),
// Media Library
new mb_media_library_ctl(
array(
'slug' => 'ski_meta_media_library',
'desc' => __( 'media_library_ctl', 'ski' ),
'multiple' => true,
'text' => __( 'Select an image', 'ski' )
)),
// Number Bar
new mb_number_bar_ctl(
array(
'slug' => 'ski_meta_number_bar',
'desc' => __( 'number_bar_ctl', 'ski' ),
'range_min' => 50,
'range_max' => 40,
'range_step' => 5
)),
// Radio Image
new mb_radio_image_ctl(
array(
'slug' => 'ski_meta_radio_image',
'desc' => __( 'radio_image_ctl', 'ski' ),
'choices' =>
array(
'4' => path::get_ski_directory_uri().'/assets/customizer/logo-1.png',
'5' => path::get_ski_directory_uri().'/assets/customizer/logo-2.png',
'6' => path::get_ski_directory_uri().'/assets/customizer/logo-3.png'
),
)),
// Radio Text
new mb_radio_text_ctl(
array(
'slug' => 'ski_meta_radio_text',
'desc' => __( 'radio_text_ctl', 'ski' ),
'choices' =>
array(
'1' => 'first',
'2' => 'second',
'3' => 'third'
),
)),
// Repeater
new mb_repeater_ctl(
array(
'slug' => 'ski_meta_repeater',
'desc' => __( 'repeater_ctl', 'ski' ),
'placeholder' => __( 'placeholder', 'ski' ),
'repeat_ctl' => 'ski\mb_textbox_ctl',
'repeat_num' => 3,
)),
// Text
new mb_text_ctl(
array(
'slug' => 'ski_meta_text',
'desc' => __( 'text_ctl', 'ski' ),
'text' => __( 'text only', 'ski' )
)),
// Text area
new mb_text_area_ctl(
array(
'slug' => 'ski_meta_text_area',
'desc' => __( 'text_area_ctl', 'ski' ),
'placeholder' => __( 'placeholder', 'ski' )
)),
// Text notice
new mb_text_notice_ctl(
array(
'slug' => 'ski_meta_text_notice',
'desc' => __( 'text_notice_ctl', 'ski' ),
'text' => __( 'text notice', 'ski' ),
'status' => 'fail'
)),
// Textbox
new mb_textbox_ctl(
array(
'slug' => 'ski_meta_textbox',
'desc' => __( 'textbox_ctl', 'ski' ),
'placeholder' => __( 'placeholder', 'ski' )
)),
)
);
// Create the metabox with the plentiful arguments.
new metabox( $args );
}
}
?>