Skip to content

Instantly share code, notes, and snippets.

@TCotton
Last active December 13, 2015 19:58
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save TCotton/4966697 to your computer and use it in GitHub Desktop.
Save TCotton/4966697 to your computer and use it in GitHub Desktop.
Creating a PHP class to create Wordpress custom post types with dynamic metaboxes
<?php namespace content;
/**
* See blog post for details: http://www.suburban-glory.com/blog?page=174
*
* Content_Type
*
* @package class for creating dynamic Wordpress metaboxes
* @author Andy Walpole
* @copyright AWalpole
* @version 2013
* @access public
* @license the same as Wordpress
*/
class Content_Type_Jobs_Template {
static $wpdb = null;
/**
* Content_Type::__construct()
*
* @return
*/
public function __construct() {
global $wpdb;
self::$wpdb = $wpdb;
add_action('init', array(&$this, 'content_type'));
// I have a habit of using sessions but forgetting to use session_start()
// So I place this in the __construct method and use session_regenerate_id() for security
if (session_id() == '') {
session_start();
session_regenerate_id(true);
}
}
/**
* Content_Type_Jobs_Template::metabox_attributes()
*
* This is the meat and bones of the meta box array and is used repeatedly in the code underneath
*
* Called in Content_Type::text_one_html() AND
*
* @return array
*/
public function metabox_attributes() {
return array(
'title' => array(
'name' => 'title',
'type' => 'text',
'title' => 'Title',
'description' => 'The job title',
),
'description' => array(
'name' => 'description',
'type' => 'text',
'title' => 'Description',
'description' => 'Short description',
),
'location' => array(
'name' => 'location',
'type' => 'text',
'title' => 'Location',
'description' => 'Location of job',
),
'date' => array(
'name' => 'date',
'type' => 'text',
'title' => 'Date',
'description' => 'Date posted',
),
);
} // end function metabox_attributes()
/**
* Content_Type_Jobs_Template::content_type()
*
* This is the to register the content type
*
*
*/
public function content_type() {
$labels = array(
'name' => _x('Jobs Layout', 'post type general name'),
'singular_name' => _x('Jobs Layout', 'post type singular name'),
'add_new' => _x('Add New', 'book'),
'add_new_item' => __('Jobs Layout'),
'edit_item' => __('Edit Jobs Layout type'),
'new_item' => __('New Jobs Layout type'),
'all_items' => __('All Jobs Layout types'),
'view_item' => __('View Jobs Layout types'),
'search_items' => __('Search Jobs Layout types'),
'not_found' => __('No Jobs Layout types found'),
'not_found_in_trash' => __('No Jobs Layout types found in Trash'),
'parent_item_colon' => '',
'menu_name' => __('Jobs Layout'),
);
$args = array(
'labels' => $labels,
'description' => 'Jobs',
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => true,
'capability_type' => 'post',
'has_archive' => true,
'hierarchical' => false,
'menu_position' => null,
'register_meta_box_cb' => array(&$this, 'add_metaboxes'),
'taxonomies' => array('category'),
'supports' => array(
'title',
'editor',
'author',
'thumbnail',
'excerpt',
));
add_action('save_post', array(&$this, 'save_postdata'), 10, 2);
add_action('admin_notices', array(&$this, 'my_post_admin_notices'), 10, 1);
register_post_type('jobs', $args);
}
/**
* Content_Type_Jobs_Template::add_metaboxes()
*
* Calls the add_meta_box hook
* Called in Content_Type_Jobs_Template::content_type()
*
*/
public function add_metaboxes() {
add_meta_box('jobs_metabox', 'Add individual jobs below', array(&$this, 'text_one_html'),
'jobs', 'normal', 'high');
}
/**
* Content_Type_Jobs_Template::text_one_html()
*
* This is the html of the meta_boxes
* Called in Content_Type_Jobs_Template::content_type()
*
* @return string
*/
public function text_one_html() {
$new_meta_boxes = $this->metabox_attributes();
global $post;
if (!is_null($post)) {
// creates the initial empty form fields to be populated by the user
$form = '<table id="list-table">';
$form .= '<tbody id="the-list" width="100%">';
foreach ($new_meta_boxes as $key => $result) {
if ($result['name'] == 'title') {
$form .= '<thead>';
$form .= '<tr>';
$form .= '<th class="left"><h4>Add a new job below</h4></th>';
$form .= '<th><h4>Values</h4></th>';
$form .= '</tr>';
$form .= '</thead>';
}
if ($result['type'] == 'text') {
$form .= '<tr>';
$form .= '<td class="left" width="30%">';
$form .= '<label for="'.$result['name'].'"><strong>'.$result['title'].
'</strong></label>';
$form .= '<div>'.$result['description'].'</div>';
$form .= '</td>';
$form .= '<td width="70%">';
$form .= '<input type="text" name="'.$result['name'].'" value="';
$form .= isset($_SESSION[$result['name']]) ? $_SESSION[$result['name']] : null;
$form .= '" class="regular-text"/>';
$form .= '</td>';
$form .= '</tr>';
}
} // end foreach
$form .= '</tbody>';
$form .= '</table>';
echo $form;
$meta = get_post_meta($post->ID);
$y = $x = 0;
// takes the multidimensional array from the database field and creates forms
// populated by the data
if (!empty($meta)) {
// remove the _edit_last and _edit_lock arrays from the returned multidimensional array
$remove = array('_edit_last', '_edit_lock');
$meta = array_diff_key($meta, array_flip($remove));
$form = '<table id="list-table">';
$form .= '<tbody id="the-list" width="100%">';
foreach ($meta as $key => $value) {
// remove all digits from the key. New form unique form attributes are created below
$key = preg_replace('/\d/', '', $key);
// make sure that all returned keys fit the original keys in the metabox_attributes method
if (!in_array($key, array_keys($this->metabox_attributes()))) continue;
// for every number of total keys in the metabox_attributes add an extra digit
// this is to ensure that all name attributes are unique
if ($x++ % count($this->metabox_attributes()) == 0) {
$y += 1;
}
// remove digit from the string
// and then use array_intersect_key() and array_flip() to find the array of
// the relevant corresponding key from the meta_attributes() method
// this is then used in the html
$key_array = array_intersect_key($this->metabox_attributes(), array_flip(array($key)));
if ($key_array[$key]['name'] == 'title') {
$form .= '<thead>';
$form .= '<tr>';
$form .= '<th class="left"><h4>'.implode('', $value).'</h4></th>';
$form .= '<th>&nbsp;</th>';
$form .= '</tr>';
$form .= '</thead>';
}
if ($key_array[$key]['type'] == 'text') {
$form .= '<tr>';
$form .= '<td class="left" width="30%">';
$form .= '<label for="'.$key.$y.'"><strong>'.$key_array[$key]['title'].
'</strong></label>';
$form .= '<div>'.$key_array[$key]['description'].'</div>';
$form .= '</td>';
$form .= '<td width="70%">';
$form .= '<input type="text" name="'.$key.$y.'" value="';
$form .= isset($_SESSION[$key.$y]) ? $_SESSION[$key.$y] : implode('', $value);
$form .= '" class="regular-text"/>';
$form .= '</td>';
$form .= '</tr>';
}
}
$form .= '</tbody>';
$form .= '</table>';
echo $form;
}
} // end if if (!is_null($post)) {
} // end private function text_one_html() {
/**
* Content_Type_Jobs_Template::validation()
*
* This is where all validation of form data takes place
* This checks for equal values but it would be easy to add other types
* of validation as the errors are built into an array
*
* @param array
* @return array
*/
protected function validation($form) {
$error = array();
// filter out unwanted parts of the $_POST
foreach ($form as $key => $value) {
if (!in_array(preg_replace('/\d/', '', $key), array_keys($this->metabox_attributes()))) {
unset($form[$key]);
} else {
// this session data is used if there is a form error to repopulate the form fields
// if the form has no error then the session data is destroyed
$_SESSION[$key] = $value;
}
}
// make sure that the the values are in proportion to that specified in the number
// of fields in the metabox_attributes. For instance if there are 4 fields in the method, then there must
// be no blank values in multiplications of 4, ie 4, 8 12
if (count(array_values(array_filter($form, 'strlen'))) % count($this->metabox_attributes()) ==
0) {
return true;
} else {
$error[] = 'Please make sure that all '.count($this->metabox_attributes()).
' fields are filled in or leave all form fields empty for the job listing to be deleted';
return $error;
}
}
/**
* Content_Type_Jobs_Template::save_postdata()
*
* Calls the validation() method
* If okay saves data
*
* If not okay it then proceeds to the next stage which is
* 1. To switch the form to pending rather than published
* 2. Call the add_filter hook and place error message into a session
*/
public function save_postdata() {
global $post;
if (!is_null($post)) {
// don't do on autosave or when new posts are first created
if ((defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) || $post->post_status == 'auto-draft')
return $post->ID;
// abort if not my custom type
if ($post->post_type != 'jobs') return $post->ID;
// sanitization here
$form = array_map('stripslashes_deep', $_POST);
$error = $this->validation($form);
// if there are no errors then save or delete data
if ($error === true) {
// destory session here and save meta data
foreach ($form as $key => $value) {
// filter out unwanted parts of the $_POST
if (!in_array(preg_replace('/\d/', '', $key), array_keys($this->
metabox_attributes()))) continue;
if ($value != '') {
update_post_meta($post->ID, $key, strip_tags($value), false);
} else {
delete_post_meta($post->ID, $key, false);
}
// destroy session
$_SESSION = array();
} // end foreach
} else {
// on attempting to publish - check for completion and intervene if necessary
if ((isset($_POST['publish']) || isset($_POST['save'])) && $_POST['post_status'] ==
'publish') {
self::$wpdb->update(self::$wpdb->posts, array('post_status' => 'pending'), array
('ID' => $post->ID));
} // end fi
add_filter('wp_insert_post_data', array(&$this, 'add_redirect_filter'), 102);
$this->add_redirect_filter();
$_SESSION['error'] = serialize($error);
} // end if($return == true) {
} // end if(!null($post)) {
} // public function ah_save_postdata() {
/**
* Content_Type::add_redirect_filter()
*
*/
public function add_redirect_filter() {
add_filter('redirect_post_location', array(&$this, 'my_redirect_post_location_filter'), 102);
} // end public function add_redirect_filter() {
/**
* Content_Type::my_redirect_post_location_filter()
*
* Adds 102 as a value for message in the query string
*
* @param mixed $location
* @return string
*/
public function my_redirect_post_location_filter($location) {
remove_filter('redirect_post_location', __function__, 102);
$location = add_query_arg('message', 102, $location);
return $location;
} // end public function my_redirect_post_location_filter($location) {
/**
* Content_Type::my_post_admin_notices()
*
* Diplays any error messages
*
* @return string
*/
public function my_post_admin_notices() {
if (!isset($_GET['message'])) return;
if ($_GET['message'] == '102') {
global $post;
// abort if not my custom type
if ($post->post_type != 'jobs') return $post->ID;
$message = '<div id="notice" class="error"><p>';
$message .= "Error: please check that all form values are correct - <br>";
$message .= isset($_SESSION['error']) ? implode('<br>', unserialize($_SESSION['error'])) : null;
$message .= '</p></div>';
echo $message;
unset($_SESSION['error']);
} // end if
} // end public function my_post_admin_notices() {
} // end class
new \content\Content_Type_Jobs_Template;
// It can be used in the theme like so:
if (class_exists('\\content\\Content_Type_Jobs_Template')) {
$meta = get_post_meta($id);
$table = '<table>
<caption>Current vacancies</caption>
<tr class="header-row">
<td id="job-title">Title</td>
<td id="job-description">Description</td>
<td id="job-location">Location</td>
<td id="job-date">Date posted</td>
</tr>';
$array_keys = array_keys(\content\Content_Type_Jobs_Template::metabox_attributes());
// loop through metabox jobs content to create the table data
foreach ($meta as $key => $value) {
// make sure that all returned keys fit the original keys in the metabox_attributes method
if (!in_array(preg_replace('/\d/', '', $key), $array_keys))
continue;
static $i;
$number = $i++;
if ($number % (count($array_keys)) == 0) {
$table .= '<tr>';
}
if (stristr($key, $array_keys['0'])) {
$table .= '<td headers="job-title">' . implode('', $value) . '</td>';
}
if (stristr($key, $array_keys['1'])) {
$table .= '<td headers="job-description">' . implode('', $value) . '</td>';
}
if (stristr($key, $array_keys['2'])) {
$table .= '<td headers="job-location">' . implode('', $value) . '</td>';
}
if (stristr($key, $array_keys['3'])) {
$table .= '<td headers="job-date">' . implode('', $value) . '</td>';
}
if (($number - (count($array_keys) - 1)) % (count($array_keys)) == 0) {
$table .= '</tr>';
}
} // end foreach
$table .= '</table>';
echo $table;
} // end if if (class_exists('\\menu\\Content_Type_Jobs_Template'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment