Last active
December 13, 2015 19:58
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> </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