<?php
/*
	Custom Post Type Class
	used to help create custom post types for Wordpress
	http://github.com/jjgrainger/wp-custom-post-type-class/

	@author     jjgrainger
	@url        http://jjgrainger.co.uk
	@version    1.2.3
	@license    http://www.opensource.org/licenses/mit-license.html  MIT License
*/

class CPT {

	/*
		@var  string  $post_type_name
		Public variable that holds the name of the post type
		assigned on __contstruct()
	*/
	public $post_type_name;

	/*
		@var  string  $singular
		Public variable that holds the singular name of the post type
		This is a human friendly name, capitalized with spaces
		assigned on __contstruct()
	*/
	public $singular;

	/*
		@var  string  $singular
		Public variable that holds the plural name of the post type
		This is a human friendly name, capitalized with spaces
		assigned on __contstruct()
	*/
	public $plural;

	/*
		@var  string  $slug
		Public variable that holds the slug name of the post type
		This is a robot friendly name, all lowercase and uses hyphens
		assigned on __contstruct()
	*/
	public $slug;

	/*
		@var  array  $options
		public variable that holds the user submited options of the post type
		assigined on __construct()
	*/
	public $options;

	/*
		@var  array  $taxonomies
		public variable that holds an array of the taxonomies associated with the post type
		assigined on register_taxonomy()
	*/
	public $taxonomies;

	/*
		@var  array  $taxonomy_settings
		public variable that holds an array of the taxonomies associated with the post type and their options
		used when registering the taxonomies
		assigined on register_taxonomy()
	*/
	public $taxonomy_settings;

	/*
		@var  array  $filters
		use to define which filters are to appear on admin edit screen
		used in add_taxonmy_filters()
	*/
	public $filters;

	/*
		@var  array  $columns
		use to define which columns are to appear on admin edit screen
		used in add_admin_columns()
	*/
	public $columns;

	/*
		@var  array  $custom_populate_columns
		an array of user defined functions to populate admin columns
	*/
	public $custom_populate_columns;

	/*
		@var  array  $sortable
		use to define which columns are to sortable on admin edit screen
	*/
	public $sortable;

	/*
		@function __contructor(@post_type_name, @options)

		@param  mixed   $post_type_names    The name(s) of the post type, accepts (post type name, slug, plural, singluar)
		@param  array   $options            User submitted options
	*/
	function __construct($post_type_names, $options = array()) {

		// check if post type names is string or array
		if(is_array($post_type_names)) {

			// add names to object
			$names = array(
				'singular',
				'plural',
				'slug'
			);

			// set the post type name
			$this->post_type_name = $post_type_names['post_type_name'];

			// cycle through possible names
			foreach($names as $name) {

				// if the name has been set by user
				if(isset($post_type_names[$name])) {

					// use the user setting
					$this->$name = $post_type_names[$name];

				// else generate the name
				} else {

					// define the method to be used

					// generate the name
					$this->$name = $this->{'get_'.$name}();

				}

			}

		// else the post type name is only supplied
		} else {

			// apply to post type name
			$this->post_type_name = $post_type_names;

			// set the slug name
			$this->slug = $this->get_slug();

			// set the plural name label
			$this->plural = $this->get_plural();

			// set the singular name label
			$this->singular = $this->get_singular();

		}

		// set the user submitted options to the object
		$this->options = $options;

		// register the post type
		//$this->add_action('init', array(&$this, 'register_post_type'));
		$this->register_post_type();

		// register taxonomies
		//$this->add_action('init', array(&$this, 'register_taxonomies'));
		$this->register_taxonomies();

		// add taxonomy to admin edit columns
		$this->add_filter('manage_edit-' . $this->post_type_name . '_columns', array($this, 'add_admin_columns'));

		// populate the taxonomy columns with the posts terms
		$this->add_action('manage_' . $this->post_type_name . '_posts_custom_column', array($this, 'populate_admin_columns'), 10, 2);

		// add filter select option to admin edit
		$this->add_action('restrict_manage_posts', array($this, 'add_taxonomy_filters'));

	}



	/*
		helper function get
		used to get an object variable

		@param  string  $var        the variable you would like to retrieve
		@return mixed               returns the value on sucess, bool (false) when fails

	*/

	function get($var) {

		// if the variable exisits
		if($this->$var) {

			// on success return the value
			return $this->$var;

		} else {

			// on fail return false
			return false;

		}
	}

	/*
		helper function set
		used to set an object variable
		can overwrite exisiting variables and create new ones
		cannot overwrite reserved variables

		@param  mixed  $var         the variable you would like to create/overwrite
		@param  mixed  $value       the value you would like to set to the variable

	*/
	function set($var, $value) {

		// an array of reserved variables that cannot be overwritten
		$reserved = array(
			'config',
			'post_type_name',
			'singular',
			'plural',
			'slug',
			'options',
			'taxonomies'
		);

		// if the variable is not a reserved variable
		if(!in_array($var, $reserved,true)) {

			// write variable and value
			$this->$var = $value;

		}

	}

	/*
		helper function add_action
		used to create add_action wordpress filter

		see Wordpress Codex
		http://codex.wordpress.org/Function_Reference/add_action

		@param  string  $action            name of the action to hook to, e.g 'init'
		@param  string  $function          function to hook that will run on @action
		@param  int     $priority          order in which to execute the function, relation to other function hooked to this action
		@param  int     $accepted_args     the number of arguements the function accepts
	*/
	function add_action($action, $function, $priority = 10, $accepted_args = 1) {

		// pass variables into Wordpress add_action function
		add_action($action, $function, $priority, $accepted_args);

	}

	/*
		helper function add_filter
		used to create add_filter wordpress filter

		see Wordpress Codex
		http://codex.wordpress.org/Function_Reference/add_filter

		@param  string  $action           name of the action to hook to, e.g 'init'
		@param  string  $function         function to hook that will run on @action
		@param  int     $priority         order in which to execute the function, relation to other function hooked to this action
		@param  int     $accepted_args    the number of arguements the function accepts
	*/
	function add_filter($action, $function, $priority = 10, $accepted_args = 1) {

		// pass variables into Wordpress add_action function
		add_filter($action, $function, $priority, $accepted_args);

	}

	/*
		helper function get slug
		creates url friendly slug

		@param  string  $name           name to slugify
		@return string  $name           returns the slug
	*/
	function get_slug($name = null) {

		// if no name set use the post type name
		if(!isset($name)) {

			$name = $this->post_type_name;

		}

		// replace spaces with hyphen
		return str_replace(array(' ','_'), '-', strtolower($name));
	}

	/*
		helper function get_plural
		returns the friendly plural name

		ucwords      capitalize words
		strtolower   makes string lowercase before capitalizing
		str_replace  replace all instances of _ to space

		@param   string  $name      the slug name you want to pluralize
		@return  string             the friendly pluralized name
	*/
	function get_plural($name = null) {

		// if no name is passed the post_type_name is used
		if(!isset($name)) {

			$name = $this->post_type_name;

		}

		// return the plural name
		// add 's' to the end
		return $this->get_human_friendly($name) . 's';
	}

	/*
		helper function get_singular
		returns the friendly singular name

		ucwords      capitalize words
		strtolower   makes string lowercase before capitalizing
		str_replace  replace all instances of _ to space

		@param   string  $name      the slug name you want to unpluralize
		@return  string             the friendly singular name
	*/
	function get_singular($name = null) {

		// if no name is passed the post_type_name is used
		if(!isset($name)) {

			$name = $this->post_type_name;

		}

		// return the string
		return $this->get_human_friendly($name);

	}

	/*
		helper function get_human_friendly
		returns the human friendly name

		ucwords      capitalize words
		strtolower   makes string lowercase before capitalizing
		str_replace  replace all instances of hyphens and underscores to spaces

		@param   string  $name      the name you want to make friendly
		@return  string             the human friendly name
	*/
	function get_human_friendly($name = null) {

		// if no name is passed the post_type_name is used
		if(!isset($name)) {

			$name = $this->post_type_name;

		}

		// return human friendly name
		return ucwords(strtolower(str_replace('-', ' ', str_replace('_', ' ', $name))));

	}

	/*
		register_post_type function
		object function to register the post type

		see Wordpress Codex
		http://codex.wordpress.org/Function_Reference/register_post_type
	*/
	function register_post_type() {

		// friendly post type names
		$plural   = $this->plural;
		$singular = $this->singular;
		$slug     = $this->slug;

		// default labels
		$labels = array(
			'name' => $plural,
			'singular_name' => $singular,
			'menu_name' => $plural,
			'all_items' => $plural,
			'add_new' => __('Add New', 'themify'),
			'add_new_item' => sprintf( __('Add new %s', 'themify'), $singular ),
			'edit_item' => sprintf( __('Edit %s', 'themify'), $singular ),
			'new_item' => sprintf( __('New %s', 'themify'), $singular ),
			'view_item' => sprintf( __('View %s', 'themify'), $singular ),
			'search_items' => sprintf( __('Search %s', 'themify'), $plural ),
			'not_found' => sprintf( __('No %s found', 'themify'), $plural ),
			'not_found_in_trash' => sprintf( __('No %s found in Trash', 'themify'), $plural ),
			'parent_item_colon' => sprintf( __('Parent %s:', 'themify'), $singular )
		);

		// default options
		$defaults = array(
			'labels' => $labels,
			'public' => true,
			'rewrite' => array(
				'slug' => $slug,
				)
			);

		// merge user submitted options with defaults
		$options = wp_parse_args( $this->options, $defaults );

		// set the object options as full options passed
		$this->options = $options;

		// check that the post type doesn't already exist
		if(!post_type_exists($this->post_type_name)) {

			// register the post type
			register_post_type($this->post_type_name, $options);

		}

	}


	/*
		function register_taxonomy
		register a taxonomy to a post type

		@param  string          $taxonomy_name      the slug for the taxonomy
		@param  array           $options            taxonomy options

		see Wordpress codex
		http://codex.wordpress.org/Function_Reference/register_taxonomy
	*/

	function register_taxonomy($taxonomy_names, $options = array()) {


		// an array of the names required excluding taxonomy_name
		$names = array(
			'singular',
			'plural',
			'slug'
		);

		// if an array of names are passed
		if(is_array($taxonomy_names)) {

			// set the taxonomy name
			$taxonomy_name = $taxonomy_names['taxonomy_name'];

			// cycle through possible names
			foreach($names as $name) {

				// if the user has set the name
				if(isset($taxonomy_names[$name])) {

					// use user submitted name
					$$name = $taxonomy_names[$name];

				// else generate the name
				} else {

					// define the fnction to be used

					// generate the name
					$$name = $this->{'get_'.$name}( $taxonomy_name );

				}

			}

		// else if only the taxonomy_name has been supplied
		} else  {

			// create user friendly names
			$taxonomy_name = $taxonomy_names;
			$singular = $this->get_singular($taxonomy_name);
			$plural   = $this->get_plural($taxonomy_name);
			$slug     = $this->get_slug($taxonomy_name);

		}

		// default labels
		$labels = array(
			'name' => $plural,
			'singular_name' => $singular,
			'menu_name' => $plural,
			'all_items' => sprintf( __('All %s', 'themify'), $plural ),
			'edit_item' => sprintf( __('Edit %s', 'themify'), $singular ),
			'view_item' => sprintf( __('View %s', 'themify'), $singular ),
			'update_item' => sprintf( __('Update %s', 'themify'), $singular ),
			'add_new_item' => sprintf( __('Add New %s', 'themify'), $singular ),
			'new_item_name' => sprintf( __('New %s', 'themify'), $singular ),
			'parent_item' => sprintf( __('Parent %s', 'themify'), $plural ),
			'parent_item_colon' => sprintf( __('Parent %s:', 'themify'), $plural ),
			'search_items' => sprintf( __('Search %s', 'themify'), $plural ),
			'popular_items' => sprintf( __('Popular %s', 'themify'), $plural ),
			'separate_items_with_commas' => sprintf( __('Separate %s with commas', 'themify'), $plural ),
			'add_or_remove_items' => sprintf( __('Add or remove %s', 'themify'), $plural ),
			'choose_from_most_used' => sprintf( __('Choose from most used %s', 'themify'), $plural ),
			'not_found' => sprintf( __('No %s found', 'themify'), $plural )
		);

		// default options
		$defaults = array(
			'labels' => $labels,
			'hierarchical' => true,
			'rewrite' => array(
				'slug' => $slug
			)
		);

		// merge default options with user submitted options
		$options = array_replace_recursive($defaults, $options);

		// add the taxonomy to the object array
		// this is used to add columns and filters to admin pannel
		$this->taxonomies[] = $taxonomy_name;

		// create array used when registering taxonomies
		$this->taxonomy_settings[$taxonomy_name] = $options;

	}



	/*
		function register_taxonomies
		cycles through taxonomies added with the class and registers them

		function is used with add_action
	*/
	function register_taxonomies() {

		if(is_array($this->taxonomy_settings)) {
			// foreach taxonomy registered with the post type
			foreach($this->taxonomy_settings as $taxonomy_name => $options) {

				// register the taxonomy if it doesn't exist
				if(!taxonomy_exists($taxonomy_name)) {

					// register the taxonomy with Wordpress
					register_taxonomy($taxonomy_name, $this->post_type_name, $options);


				} else {

					// if taxonomy exists, attach exisiting taxonomy to post type
					register_taxonomy_for_object_type($taxonomy_name, $this->post_type_name);

				}
			}
		}

	}



	/*
		function add_admin_columns
		adds columns to the admin edit screen

		function is used with add_action
	*/

	function add_admin_columns($columns) {

		// if no user columns have been specified use following defaults
		if(!isset($this->columns)) {

			// default columns
			$columns = array(
				'cb' => '<input type="checkbox" />',
				'title' => __('Title', 'themify')
			);

			// if there are taxonomies registered to the post type
			if(is_array($this->taxonomies)) {

				// create a column for each taxonomy
				foreach($this->taxonomies as $tax) {

					// get the taxonomy object for labels
					$taxonomy_object = get_taxonomy($tax);

					// column key is the slug, value is friendly name
					$columns[$tax] = $taxonomy_object->labels->name;

				}

			}

			// if post type supports comments
			if(post_type_supports($this->post_type_name, 'comments')) {
				$columns['comments'] = '<img alt="Comments" src="'. admin_url() .'images/comment-grey-bubble.png">';
			}

			// add date of post to end of columns
			$columns['date'] = __('Date', 'themify');

		} else {

			// use user submitted columns
			// these are defined using the object columns() method
			$columns = $this->columns;

		}

		return $columns;

	}



	/*
		function populate_admin_columns
		populates custom columns on the admin edit screen

		function is used with add_action
	*/

	function populate_admin_columns($column, $post_id) {

		// get wordpress $post object
		global $post;

		// determine the column
		switch($column) {

			// if column is a taxonomy associated with the post type
			case (taxonomy_exists($column)) :

				// Get the taxonomy for the post
				$terms = get_the_terms($post_id, $column);

				// if we have terms
				if (!empty($terms)) {

					$output = array();

					// Loop through each term, linking to the 'edit posts' page for the specific term.
					foreach($terms as $term) {

						// output is an array of terms associated with the post
						$output[] = sprintf(

							// define link
							'<a href="%s">%s</a>',

							// create filter url
							esc_url(add_query_arg(array('post_type' => $post->post_type, $column => $term->slug), 'edit.php')),

							// create friendly term name
							esc_html(sanitize_term_field('name', $term->name, $term->term_id, $column, 'display'))

						);

					}

					// Join the terms, separating them with a comma
					echo join(', ', $output);

				// if no terms found
				} else {

					// get the taxonomy object for labels
					$taxonomy_object = get_taxonomy($column);

					// echo no terms
					echo sprintf( __('No %s', 'themify'), $taxonomy_object->labels->name );
				}


			break;

			// if column is for the post ID
			case 'post_id' :

				echo esc_attr( $post->ID );

			break;

			// if the column is prepended with 'meta_'
			// this will automagically retrieve the meta values and display them
			case (preg_match('/^meta_/', $column) ? true : false) :

				// meta_book_author (meta key = book_author)
				$x = substr($column, 5);

				$meta = get_post_meta($post->ID, $x);

				echo join(', ', $meta);

			break;

			// if the column is post thumbnail
			case 'icon' :

				// create the edit link
				$link = add_query_arg(array('post' => $post->ID, 'action' => 'edit'), 'post.php');

				// if it post has a featured image
				if(has_post_thumbnail()) {

					// display post featured image with edit link
					echo '<a href="'. esc_url( $link ) .'">';
						the_post_thumbnail(array(60, 60));
					echo '</a>';

				} else {

					// display default media image with link
					echo '<a href="'.esc_url( $link ).'"><img src="'. site_url('/wp-includes/images/crystal/default.png') .'" alt="'. $post->post_title .'" /></a>';

				}

			break;

			// default case checks if the column has a user function
			// this is most commonly used for custom fields
			default :

				// if there are user custom columns to populate
				if(isset($this->custom_populate_columns,$this->custom_populate_columns[$column]) && is_array($this->custom_populate_columns) && is_callable($this->custom_populate_columns[$column])) {

                                    // run the function
                                    //$this->custom_populate_columns[$column]($column, $post);
                                    call_user_func( $this->custom_populate_columns[$column], $column, $post );

				}

			break;

		} // end switch($column)

	}



	/*
		function filters
		user function to define which taxonomy filters to display on the admin page

		@param  array  $filters         an array of taxonomy filters to display

	*/

	function filters($filters = array()) {

		$this->filters = $filters;

	}




	/*
		function add_taxtonomy_filters
		creates select fields for filtering posts by taxonomies on admin edit screen

	*/

	function add_taxonomy_filters() {

		global $typenow;

		// must set this to the post type you want the filter(s) displayed on
		if($typenow === $this->post_type_name){

			// if custom filters are defined use those
			if(is_array($this->filters)) {
				$filters = $this->filters;
			// else default to use all taxonomies associated with the post
			} else {
				$filters = (array) $this->taxonomies;
			}

			// foreach of the taxonomies we want to create filters for
			foreach($filters as $tax_slug) {

				// object for taxonomy, doesn't contain the terms
				$tax = get_taxonomy($tax_slug);

				// get taxonomy terms and order by name
				$args = array(
					'orderby' => 'name',
					'hide_empty' => false
				);

				// get taxonomy terms
				$terms = get_terms($tax_slug, $args);

				// if we have terms
				if($terms) {

					// set up select box
					printf(' &nbsp;<select name="%s" class="postform">', $tax_slug);

					// default show all
					printf('<option value="0">%s</option>', 'Show all ' . $tax->label);

					// foreach term create an option field
					foreach ($terms as $term) {

						// if filtered by this term make it selected
						if(isset($_GET[$tax_slug]) && $_GET[$tax_slug] === $term->slug) {

							printf('<option value="%s" selected="selected">%s (%s)</option>', $term->slug, $term->name, $term->count);

						// create option for taxonomy
						} else {

							printf('<option value="%s">%s (%s)</option>', $term->slug, $term->name, $term->count);

						}

					}

					// end the select field
					print('</select>&nbsp;');

				}

			}

		}

	}



	/*
		function columns
		user function to choose columns to be displayed on the admin edit screen

		@param  array  $columns         an array of columns to be displayed

	*/

	function columns($columns) {

		// if columns is set
		if(isset($columns)) {

			// assign user submitted columns to object
			$this->columns = $columns;

		}

	}



	/*
		function populate_column
		user function to define what and how to populate a specific admin column

		@param  string  $column_name        the name of the column to populate
		@param  func    $function           an anonymous function to run when populating the column
	*/

	function populate_column($column_name, $function) {

		$this->custom_populate_columns[$column_name] = $function;

	}



	/*
		function sortable
		user function define what columns are sortable in admin edit screen

		@param  array  $columns         an array of the columns that are sortable
	*/

	function sortable($columns = array()) {

		// assign user defined sortable columns to object variable
		$this->sortable = $columns;

		// run filter to make columns sortable
		$this->add_filter('manage_edit-' . $this->post_type_name . '_sortable_columns', array($this, 'make_columns_sortable'));

		// run action that sorts columns on request
		$this->add_action('load-edit.php', array($this, 'load_edit'));

	}



	/*
		function make_columns_sortable
		internal function that adds any user defined sortable columns to wordpress default columns

	*/

	function make_columns_sortable($columns) {

		// for each sortable column
		foreach($this->sortable as $column => $values) {

			// make an array to merege into wordpress sortable columns
			$sortable_columns[$column] = $values[0];

		}


		// merge sortable columns array into wordpress sortable columns
		return array_merge($sortable_columns, $columns);

	}



	/*
		function load_edit
		only sort columns on the edit.php page when requested

	*/

	function load_edit() {

		// run filter to sort columns when requested
		$this->add_filter( 'request', array($this, 'sort_columns') );

	}



	/*
		function sort columns
		internal function that sorts columns on request

		run by load_edit() filter

		@param  array  $vars        the query vars submitted by user

	*/

	function sort_columns($vars) {

		// cycle through all sortable columns submitted by the user
		foreach($this->sortable as $values) {

			// retrieve the meta key from the user submitted array of sortable columns
			$meta_key = $values[0];


			// if the optional parameter is set and is set to true
			$orderby = isset($values[1]) && true === $values[1]?'meta_value_num':'meta_value';

			// Check if we're viewing this post type
			if (isset($vars['post_type'],$vars['orderby']) && $meta_key === $vars['orderby'] && $this->post_type_name === $vars['post_type']) {


                            // merge the query vars with our custom variables
                            $vars = array_merge($vars,
                                    array(
                                            'meta_key' => $meta_key,
                                            'orderby' => $orderby
                                    )
                            );

			}

		}

		return $vars;
	}



	/*
		function menu icon
		used to change the menu icon in the admin dashboard
		pass name of dashicon, list found here http://melchoyce.github.io/dashicons/

		@param  mixed  $icon        a string of the name of the icon to use
	*/

	function menu_icon($icon = 'dashicons-admin-page') {

		// WP 3.8 changed the icon system to use an icon font.
		// http://melchoyce.github.io/dashicons/

		$this->options['menu_icon'] = is_string($icon) && stripos($icon, 'dashicons') !== FALSE?$icon:'dashicons-admin-page';
	}
}