File "model.php"

Full Path: /www/wwwroot/shphe-en.com/wp-content/plugins/admin-columns-pro/classes/inline-edit/classes/model.php
File size: 33.44 KB
MIME-type: --
Charset: utf-8

<?php
/**
 * Storage model for editability
 * This class can be extended for different storage models, such as post, user and taxonomy storage models
 *
 * @since 1.0
 * @abstract
 */
abstract class CACIE_Editable_Model {

	/**
	 * Main storage model class instance
	 *
	 * @since 1.0
	 * @var CPAC_Storage_Model
	 * @access protected
	 */
	public $storage_model;

	/**
	 * Enable inline edit for Custom Fields
	 *
	 * @since 3.1.2
	 */
	protected $is_custom_field_editable;

	/**
	 * Get default properties of editability of column types
	 * The array returned for each column type key contains information about the editability of a column type
	 * For example, it usually holds the editability type in $array['type'], which can be, for example, "text" or "select" or "email"
	 * For getting the editability information for an instance of a column, see CACIE_Editable_Model::get_editable()
	 *
	 * @since 1.0
	 * @abstract
	 *
	 * @return array List of editability information per column ([column_type] => (array) [editable])
	 */
	abstract function get_editables_data();

	/**
	 * Save a column for a certain entry
	 * Called on a succesful AJAX request
	 *
	 * @since 1.0
	 * @abstract
	 *
	 * @param int $id Post ID
	 * @param CPAC_Column $column Column object instance
	 * @param mixed $value Value to be saved
	 */
	abstract function column_save( $id, $column, $value );

	/**
	 * Get the available items on the current page for passing them to JS
	 *
	 * @since 1.0
	 * @abstract
	 *
	 * @return array Items on the current page ([entry_id] => (array) [entry_data])
	 */
	abstract function get_items();

	/**
	 * Output value for WP default column
	 *
	 * @since 1.0
	 * @abstract
	 *
	 * @param CPAC_Column $column Column Object
	 * @param int $id Entry ID
	 */
	abstract function manage_value( $column, $id );

	/**
	 * Constructor
	 *
	 * @since 1.0
	 *
	 * @param CPAC_Storage_Model $storage_model Main storage model class instance
	 */
	function __construct( $storage_model ) {

		$this->storage_model = $storage_model;

		$this->is_custom_field_editable = $this->storage_model->get_general_option( 'custom_field_editable' );

		// Enable inline edit per column
		add_action( "cac/columns/storage_key={$this->storage_model->key}", array( $this, 'enable_inlineedit' ) );

		// Add columns to javascript
		add_action( 'admin_enqueue_scripts', array( $this, 'scripts' ), 20 );

		// Save column value from inline edit
		add_action( 'wp_ajax_cacie_column_save', array( $this, 'ajax_column_save' ) );

		// Save user preference of the edititability state
		add_action( 'wp_ajax_cacie_editability_state_save', array( $this, 'ajax_editability_state_save' ) );

		// Get options for editable field by ajax
		add_action( 'wp_ajax_cacie_get_options', array( $this, 'ajax_get_options' ) );
	}

	/**
	 * Check whether a column is editable
	 *
	 * @since 1.0
	 *
	 * @param array $column Column options
	 * @return bool Whether the column is editable
	 */
	public function is_editable( $column ) {
		$is_editable = false;

		switch ( $column->properties->type ) {

			// ACF
			case 'column-acf_field':

				if ( ! method_exists( $column, 'get_field' ) ) {
					break;
				}

				$acf_field = $column->get_field();

				if ( ! isset( $acf_field['type'] ) ) {
					break;
				}

				switch ( $acf_field['type'] ) {
					case 'checkbox':
					case 'color_picker':
					case 'date_picker':
					//case 'date_time_picker':
					case 'email':
					case 'file':
					case 'gallery':
					//case 'google_map':
					case 'image':
					//case 'message':
					case 'number':
					case 'page_link':
					case 'password':
					case 'post_object':
					case 'radio':
					//case 'relationship':
				//case 'repeater':
					case 'select':
					case 'taxonomy':
					case 'text':
					case 'textarea':
					case 'true_false':
					case 'url':
					case 'user':
					case 'wysiwyg':
						$is_editable = true;
					break;
				}
				break;

			// Custom Fields
			case 'column-meta':

				/**
				 * Filter for making all custom fields editable. Only use this
				 * when you are well aware of any formatting and validating rules
				 * on how your custom field value are stored.
				 *
				 * @since 3.1.2
				 *
				 * @param bool $is_editable Set tot true to make all custom fields fields editable.
				 */
				if ( $this->is_custom_field_editable ) {
					switch ( $column->options->field_type ) {
						case '' :
						case 'checkmark' :
						case 'color' :
						case 'excerpt' :
						case 'image' :
						case 'library_id' :
						case 'numeric' :
						case 'title_by_id' :
						case 'user_by_id' :
							$is_editable = true;
						break;

						case 'date' :
							$is_editable = true;

							// @todo: the datepicker conflicts with ACF 'jquery-ui-datepicker' on upload.php
							if ( class_exists('acf') && 'media' === $column->storage_model->type ) {
								$is_editable = false;
							}
						break;
					}
				}

				break;
		}

		return $is_editable;
	}

	/**
	 * @since 3.5
	 */
	protected function get_list_selector() {
		return '#the-list';
	}

	/**
	 * Admin scripts
	 *
	 * @since 1.0
	 */
	public function scripts() {

		if ( ! $this->storage_model->is_columns_screen() ) {
			return;
		}

		// Allow JS to access the column and item data for this storage model on the edit page
		wp_localize_script( 'cacie-admin-edit', 'CACIE_List_Selector', $this->get_list_selector() );
		wp_localize_script( 'cacie-admin-edit', 'CACIE_Storage_Model', $this->storage_model->key );
		wp_localize_script( 'cacie-admin-edit', 'CACIE_Columns', $this->get_columns() );
		wp_localize_script( 'cacie-admin-edit', 'CACIE_Items', $this->get_items() );
		wp_localize_script( 'cacie-admin-edit', 'CACIE', array(
			'inline_edit' => array(
				'active' => $this->get_editability_preference()
			)
		) );
	}

	/**
	 * Get list of options for posts selection
	 *
	 * Results are formatted as an array of post types, the key being the post type name, the value
	 * being an array with two keys: label (the post type label) and options, an array of options (posts)
	 * for this post type, with the post IDs as keys and the post titles as values
	 *
	 * @since 1.0
	 * @uses WP_Query
	 *
	 * @param array $query_args Additional query arguments for WP_Query
	 * @return array List of options, grouped by posttype
	 */
	public function get_posts_options( $query_args = array() ) {

		$options = array();

		$args = wp_parse_args( $query_args, array(
			'posts_per_page' => 100, // max 100 records in case we get a very large db
			'post_type' => 'any',
			'orderby' => 'title',
			'order' => 'ASC'
		) );

		if ( $posts = get_posts( $args ) ) {
			foreach ( $posts as $post ) {
				if ( ! isset( $options[ $post->post_type ] ) ) {
					$options[ $post->post_type ] = array(
						'label' => $post->post_type,
						'options' => array()
					);
				}

				$options[ $post->post_type ]['options'][ $post->ID ] = $post->post_title;
			}
		}

		return $options;
	}

	/**
	 * Get list of options for users selection
	 *
	 * Results are formatted as an array of roles, the key being the role name, the value
	 * being an array with two keys: label (the role label) and options, an array of options (users)
	 * for this role, with the user IDs as keys and the user display names as values
	 *
	 * @since 1.0
	 * @uses WP_User_Query
	 *
	 * @param array $query_args Additional query arguments for WP_User_query
	 * @param object $column_object CPAC_Column object
	 * @return array List of options, grouped by author role
	 */
	public function get_users_options( $query_args = array(), $column_object = null ) {

		global $wp_roles;

		$options = array();

		$query_args = wp_parse_args( $query_args, array(
			'orderby' => 'display_name',
			'number' => 100 // max 100 records in case we get a very large db
		) );

		if ( isset( $query_args['search'] ) && ! isset( $query_args['search_columns'] ) ) {
			$query_args['search_columns'] = array( 'ID', 'user_login', 'user_nicename', 'user_email', 'user_url' );
		}

		// Get all users
		$users_query = new WP_User_Query( $query_args );
		$users = $users_query->get_results();

		// Get roles
        $roles = $wp_roles->roles;

		// Generate options by grouping users by role
		foreach ( $users as $user ) {
			$role = CACIE_Roles::get_user_role( $user->ID );

			// User name
			$name = $user->display_name;

			// If the column is an author name column, we use the name format set in the column
			// instead of the normal display name
			if ( is_a( $column_object, 'CPAC_Column_Post_Author_Name' ) ) {
				$name = $column_object->get_display_name( $user->ID );
			}

			if ( ! isset( $options[ $role ] ) ) {
				$options[ $role ] = array(
					'label' => translate_user_role( $roles[ $role ]['name'] ),
					'options' => array()
				);
			}

			$options[ $role ]['options'][ $user->ID ] = esc_attr( $name );
		}

		return $options;
	}

	/**
	 * AJAX callback for retrieving options for a column
	 * Results can be formatted in two ways: an array of options ([value] => [label]) or
	 * an array of option groups ([group key] => [group]) with [group] being an array with
	 * two keys: label (the label displayed for the group) and options (an array ([value] => [label])
	 * of options)
	 *
	 * @since 1.0
	 *
	 * @return array List of options, possibly grouped
	 */
	public function ajax_get_options() {

		if ( $this->storage_model->key != $_REQUEST['storage_model'] ) {
			return;
		}

		$options = array();

		if ( empty( $_GET['column'] ) ) {
			wp_send_json_error( __( 'Invalid request.', 'codepress-admin-columns' ) );
		}

		$column = $this->storage_model->get_column_by_name( $_GET['column'] );

		if ( empty( $column ) ) {
			wp_send_json_error( __( 'Invalid column.', 'codepress-admin-columns' ) );
		}

		$search = isset( $_GET['searchterm'] ) ? $_GET['searchterm'] : '';

		// Custom Field
		if ( 'column-meta' == $column->properties->type ) {

			switch ( $column->options->field_type ) {
				case 'title_by_id':
					$options = $this->get_posts_options( array( 's' => $search ) );
					break;

				case 'user_by_id':
					$options = $this->get_users_options( array(
						'search' => '*' . $search . '*'
					) );
					break;
			}
		}

		// ACF
		else if ( 'column-acf_field' == $column->properties->type ) {

			switch ( $column->get_field_type() ) {
				case 'page_link':
				case 'post_object':

					// ACF 5
					if ( function_exists( 'acf_get_setting' ) ) {
						$field = ( $column->get_field_type() == 'post_object' ) ? new acf_field_post_object() : new acf_field_page_link();
						$choices = $field->get_choices( array(
							's' => $search,
							'field_key' => $column->get_field_key(),
							'post_id' => $_GET['item_id']
						) );

						$options = array();

						foreach ( $choices as $choice ) {
							if ( ! isset( $choice['id'] ) ) {
								$options[ $choice['text'] ] = array(
									'label' => $choice['text'],
									'options' => array()
								);

								foreach ( $choice['children'] as $subchoice ) {
									$options[ $choice['text'] ]['options'][ $subchoice['id'] ] = $subchoice['text'];
								}
							}
							else {
								$options[ $choice['id'] ] = $choice['text'];
							}
						}
					}

					// ACF 4
					else {
						$field = $column->get_field();

						$post_type = 'any';
						if ( ! empty( $field['post_type'] ) ) {
							$post_type = $field['post_type'];
						}

						$options = $this->get_posts_options( array( 's' => $search, 'post_type' => $post_type ) );
					}

					break;
				case 'user':
					if ( function_exists( 'acf_get_setting' ) ) {

						$field = new acf_field_user();
						$choices = $field->get_choices( array(
							's' => $search,
							'field_key' => $column->get_field_key(),
							'post_id' => $_GET['item_id']
						) );

						$options = array();

						foreach ( $choices as $choice ) {
							if ( ! isset( $choice['id'] ) ) {
								$options[ $choice['text'] ] = array(
									'label' => $choice['text'],
									'options' => array()
								);

								foreach ( $choice['children'] as $subchoice ) {
									$options[ $choice['text'] ]['options'][ $subchoice['id'] ] = $subchoice['text'];
								}
							}
							else {
								$options[ $choice['id'] ] = $choice['text'];
							}
						}

					}
					else {
						$options = $this->get_users_options( array(
							'search' => '*' . $search . '*'
						) );
					}

					break;
			}
		}

		// Author
		else if ( in_array( $column->properties->type, array(
			'author',
			'column-author_name',
			'column-user' // comment column
			) ) ) {
			$options = $this->get_users_options( array(
				'search' => '*' . $search . '*'
			) );
		}

		// Post parent
		else if ( $column->properties->type == 'column-parent' ) {
			$options = $this->get_posts_options( array( 's' => $search, 'post_type' => $column->get_post_type() ) );
		}

		// WooCommerce: Upsells
		// WooCommerce: Crosssells
		// WooCommerce: Included Products
		// WooCommerce: Excluded Products
		else if ( in_array( $column->properties->type, array(
			'column-wc-upsells',
			'column-wc-crosssells',
			'column-wc-exclude_products',
			'column-wc-include_products'
			) ) ) {

			$args = array(
				'post_type'			=> 'product',
				'post_status' 		=> 'any',
				's' 				=> $search,
				'fields'			=> 'ids',
				'posts_per_page'	=> 60
			);

			$args2 = array(
				'post_type'			=> 'product',
				'post_status' 		=> 'any',
				'meta_query' 		=> array(
					array(
						'key' 	=> '_sku',
						'value' => $search,
						'compare' => 'LIKE'
					)
				),
				'fields'			=> 'ids',
				'posts_per_page'	=> 60
			);

			$posts = array_unique( array_merge( get_posts( $args ), get_posts( $args2 ) ) );

			$options = array();

			foreach ( $posts as $post ) {
				$product = get_product( $post );
				$options[ $post ] = $product->get_formatted_name();
			}
		}
		else if ( $column->properties->type == 'column-wc-parent' ) {

			$args = array(
				'post_type'			=> 'product',
				'post_status' 		=> 'any',
				'posts_per_page' 	=> 100,
				's' 				=> $search,
				'tax_query' 		=> array(
					array(
						'taxonomy' 	=> 'product_type',
						'field' => 'slug',
						'terms' => 'grouped'
					)
				),
				'fields' => 'ids',
			);

			$posts = get_posts( $args );

			$options = array();

			foreach ( $posts as $post ) {
				$product = get_product( $post );
				$name = str_replace( '&ndash; ', '', $product->get_formatted_name() ); // removes arrow
				$options[ $post ] = $name;
			}
		}

		// Third party
		else if ( method_exists( $column, 'get_editable_ajax_options' ) ) {
			$options = $column->get_editable_ajax_options( $_GET, $this );
		}

		wp_send_json_success( $this->format_options( $options ) );
	}

	/**
	 * Get total post count
	 * Used to determine whether post dropdowns should be populated directly or through AJAX
	 *
	 * @since 1.0
	 *
	 * @return int Total amount of published posts amongst all post types
	 */
	protected function get_total_post_count() {

		$count = 0;

		if ( $posttypes = get_post_types() ) {
			foreach ( $posttypes as $posttype ) {
				$counter = wp_count_posts( $posttype );
				$count += $counter->publish;
			}
		}

		return $count;
	}

	/**
	 * Get total user count
	 * Used to determine whether user dropdowns should be populated directly or through AJAX
	 *
	 * @since 1.0
	 *
	 * @return int Total number of users registered
	 */
	protected function get_total_user_count() {
		$count = count_users();

		return $count['total_users'];
	}

	/**
	 * Check if the columns is editable and if the user enabled editing for this column
	 *
	 * @since 1.1
	 *
	 * @param object CPAC_Column
	 * @return bool
	 */
	public function is_edit_enabled( $column ) {
		if ( ! isset( $column->properties->is_editable ) || ! $column->properties->is_editable || ! isset( $column->options ) || ! isset( $column->options->edit ) || $column->options->edit != 'on' ) {
			return false;
		}
		return true;
	}

	/**
	 * Settings based on ACF field type
	 * @since 3.6
	 */
	private function get_acf_editable_settings_by_field( $field ) {
		$editable = array();

		switch ( $field['type'] ) {
			case 'checkbox':
				$editable['type'] = 'checklist';
				break;
			case 'color_picker':
				$editable['type'] = 'text';
				break;
			case 'date_picker':
				$editable['type'] = 'date';
				break;
			case 'email':
				$editable['type'] = 'email';
				break;
			case 'file':
				// @todo Implement "attachment" type
				$editable['type'] = 'attachment';

				if ( empty( $field['required'] ) ) {
					$editable['clear_button'] = true;
				}
				break;
			case 'gallery':
				$editable['type'] = 'media';
				$editable['multiple'] = true;
				$editable['attachment']['disable_select_current'] = true;
				break;
			case 'image':
				$editable['type'] = 'media';
				$editable['attachment']['library']['type'] = 'image';

				if ( empty( $field['required'] ) ) {
					$editable['clear_button'] = true;
				}

				break;
			case 'number':
				$editable['type'] = 'number';
				$editable['range_step'] = 'any';
				break;
			case 'taxonomy':
				$editable['type'] = 'select';
				$editable['advanced_dropdown'] = true;
				break;
			case 'page_link':
			case 'post_object':
				if ( ! function_exists( 'acf_get_setting' ) || version_compare( acf_get_setting( 'version' ), '5.1.0' ) >= 0 ) {
					$editable['type'] = 'select2_dropdown';
					$editable['ajax_populate'] = true;
					$editable['advanced_dropdown'] = true;
					$editable['formatted_value'] = 'post';
				}

				if ( $field['multiple'] == 0 && $field['allow_null'] == 1 ) {
					$editable['clear_button'] = true;
				}
				break;
			case 'password':
				$editable['type'] = 'password';
				break;
			case 'radio':
				$editable['type'] = 'select';
				break;
			case 'select':
				$editable['type'] = 'select';
				$editable['advanced_dropdown'] = true;
				break;
			case 'text':
				$editable['type'] = 'text';
				break;
			case 'textarea':
				$editable['type'] = 'textarea';
				break;
			case 'true_false':
				$editable['type'] = 'togglable';
				$editable['options'] = array( '0', '1' );
				break;
			case 'url':
				$editable['type'] = 'url';
				break;
			case 'user':
				if ( ! function_exists( 'acf_get_setting' ) || version_compare( acf_get_setting( 'version' ), '5.1.0' ) >= 0 ) {
					$editable['type'] = 'select2_dropdown';
					$editable['ajax_populate'] = true;
					$editable['advanced_dropdown'] = true;
					$editable['formatted_value'] = 'user';
				}
				break;
			case 'wysiwyg':
				$editable['type'] = 'textarea';
				break;
			/*case 'repeater':
				if ( ! empty( $field['sub_fields'] ) ) {
					foreach ( $field['sub_fields'] as $sub_field ) {
						if ( $sub_field['key'] === $column['sub_field'] ) {
							$editable = $this->get_acf_editable_settings_by_field( $sub_field, $column );
						}
					}

				}
				break;*/
		}
		return $editable;
	}

	/**
	 * Get the editability options for a single column
	 * The array returned contains information about the editability of a column
	 * For example, it usually holds the editability type in $array['type'], which can be, for example, "text" or "select" or "email"
	 *
	 * @since 1.0
	 *
	 * @param array|string $column Column options or column name. In case a column name is provided, the column object is fetched based on the name
	 * @return bool|array Returns false if the column is not editable, an array with editability settings otherwise
	 */
	public function get_editable( $column ) {

		// Get column data by column name
		if ( is_string( $column ) ) {
			$columns = $this->storage_model->get_stored_columns();

			if ( empty( $columns[ $column ] ) ) {
				return false;
			}

			$column = $columns[ $column ];
		}

		// Edit possible for this column type
		if ( ! isset( $column['edit'] ) || $column['edit'] != 'on' ) {
			return false;
		}

		// Get default column editable data
		$editables = $this->get_editables_data();
		$editable = ! empty( $editables[ $column['type'] ] ) ? $editables[ $column['type'] ] : array();

		// ACF Field
		switch ( $column['type'] ) {

			// ACF Field
			case 'column-acf_field' :

				// make sure acf and the add-on are still active...
				if ( ! function_exists( 'acf' ) || ! function_exists( 'cpac_get_acf_field' ) ) {
					return false;
				}

				// Load field settings from ACF
				if ( $field = cpac_get_acf_field( $column['field'] ) ) {

					$editable['advanced_dropdown'] = false;

					// add acf editable settings
					$editable = array_merge( $editable, $this->get_acf_editable_settings_by_field( $field ) );

					// Create an advanced dropdown menu
					if ( $editable['advanced_dropdown'] ) {
						if ( ! empty( $field['multiple'] ) || ( ! empty( $field['field_type'] ) && in_array( $field['field_type'], array( 'checkbox', 'multi_select' ) ) ) ) {
							$editable['type'] = 'select2_dropdown';
							$editable['multiple'] = true;
						}
						else {
							if ( ! empty( $field['allow_null'] ) ) {
								if ( $field['type'] == 'taxonomy' ) {
									$option_null = array(
										'' => __( 'None' )
									);
								}
								else {
									$option_null = array(
										'null' => __( '- Select -', 'codepress-admin-columns' )
									);
								}

								if ( ! isset( $editable['options'] ) || ! is_array( $editable['options'] ) ) {
									$editable['options'] = array();
								}

								$editable['options'] = $option_null + $editable['options'];
							}
						}
					}

					if ( ! empty( $field['required'] ) ) {
						$editable['required'] = true;
					}

					if ( ! empty( $field['placeholder'] ) ) {
						$editable['placeholder'] = $field['placeholder'];
					}

					if ( ! empty( $field['maxlength'] ) ) {
						$editable['maxlength'] = $field['maxlength'];
					}

					if ( ! empty( $field['min'] ) ) {
						$editable['range_min'] = $field['min'];
					}

					if ( ! empty( $field['max'] ) ) {
						$editable['range_max'] = $field['max'];
					}

					if ( ! empty( $field['step'] ) ) {
						$editable['range_step'] = $field['step'];
					}

					if ( ! empty( $field['library'] ) ) {
						if ( $field['library'] == 'uploadedTo' ) {
							$editable['attachment']['library']['uploaded_to_post'] = true;
						}
					}

					if ( empty( $field['ajax_populate'] ) ) {
						// Options from ACF
						$fieldoptions = new CACIE_ACF_FieldOptions();
						$options = $fieldoptions->get_field_options( $field );

						if ( $options !== false ) {
							$editable['options'] = $options;
						}
					}
				}
			break;

			// Custom Fields
			case 'column-meta' :

				switch( $column['field_type'] ) {
					case 'excerpt' :
						$editable['type'] = 'textarea';
					break;
					case 'checkmark' :
						$editable['type'] = 'togglable';
						$editable['options'] = array( '0', '1' );
					break;
					case 'library_id' :
						$editable['type'] = 'attachment';
						$editable['clear_button'] = true;
					break;
					case 'title_by_id' :
						$editable['type'] = 'select2_dropdown';
						$editable['ajax_populate'] = true;
					break;
					case 'user_by_id' :
						$editable['type'] = 'select2_dropdown';
						$editable['ajax_populate'] = true;
					break;
					default :
						$editable['type'] = 'text';
				}
			break;

			// Taxonomy column
			case 'column-taxonomy' :
			case 'categories' :
			case 'tags' :

				if ( isset( $column['enable_term_creation'] ) && 'on' == $column['enable_term_creation'] ) {
					$editable['type'] = 'select2_tags';
				}
				else {
					$editable['type'] = 'select2_dropdown';
					$editable['multiple'] = true;
				}
			break;
		}

		// Developers can define editable settings with their column by using get_editable_settings();
		if ( empty( $editable ) ) {
			if ( $_column = $this->storage_model->get_column_by_name( $column['column-name'] ) ) {
				if ( method_exists( $_column, 'get_editable_settings' ) ) {
					$editable = $_column->get_editable_settings();
				}
			}
		}

		// Add all available options if we are not using ajax, and they haven't been set yet
		if ( empty( $editable['ajax_populate'] ) ) {
			// Fetch column options, and only use them if an array of options is passed
			$options = $this->get_column_options( $column );

			if ( $options !== false ) {
				$editable['options'] = $options;
			}
			if ( ! empty( $editable['options'] ) ) {
				$editable['options'] = $this->format_options( $editable['options'] );
			}
		}

		// deprecated
		$editable = apply_filters( 'cacie/inline_edit/options', $editable, $column );

		/**
		 * Filters the options for the editable field.
		 *
		 * @since 3.2.2
		 * @param array $editable List of edit options ([type] => [field_type] )
		 * @param array $column Stored column settings
		 */
		return apply_filters( 'cac/editable/options', $editable, $column, $this );
	}

	/**
	 * Get a list of editable columns, with the default column options and an
	 * addon_cacie array key, containing the add-on data
	 *
	 * @since 1.0
	 *
	 * @return array List of columns ([column_name] => [column_options])
	 */
	public function get_columns() {

		// Editable columns
		$columns = array();

		if ( $stored_columns = $this->storage_model->get_stored_columns() ) {
			foreach ( $stored_columns as $column_name => $column ) {
				if ( false !== ( $editable = $this->get_editable( $column ) ) ) {

					$columns[ $column_name ] = $column;
					$columns[ $column_name ]['addon_cacie'] = array( 'editable' => $editable );
				}
			}
		}

		return $columns;
	}

	/**
	* Get possible options for column with a defined set of possible options
	*
	* @since 1.0
	*
	* @param array $column Column array with column options
	* @return array List of options with option value as key and option label as value
	*/
	public function get_column_options( $column ) {

		return false;
	}

	/**
	 * Ajax callback for storing user preference of the default state of editability on an overview page
	 *
	 * @since 3.2.1
	 */
	public function ajax_editability_state_save() {

		if ( $this->storage_model->key != $_POST['storage_model'] ) {
			return;
		}

		$is_enabled = $_POST['value'] ? '1' : '0';

		update_user_meta( get_current_user_id(), 'cacie_editability_state' . $this->storage_model->key, $is_enabled );
		exit( $is_enabled );
	}

	/**
	 * Get editability preference
	 *
	 * @since 3.2.1
	 */
	public function get_editability_preference() {

		$is_active = '1' === get_user_meta( get_current_user_id(), 'cacie_editability_state' . $this->storage_model->key, true );

		/**
		 * Filters the default state of editability of cells on overview pages
		 *
		 * @since 3.0.9
		 *
		 * @param $is_active bool Whether the default state is active (true) or inactive (false)
		 */
		$is_active = apply_filters( 'cacie/inline_edit/active', $is_active );
		$is_active = apply_filters( 'cacie/inline_edit/active/storage_key=' . $this->storage_model->key, $is_active );

		return $is_active;
	}

	/**
	 * Ajax callback for saving a column
	 *
	 * @since 1.0
	 */
	public function ajax_column_save() {

		if ( $this->storage_model->key != $_POST['storage_model'] ) {
			return;
		}
		// Basic request validation
		if ( empty( $_POST['plugin_id'] ) || empty( $_POST['pk'] ) || empty( $_POST['column'] ) ) {
			wp_send_json_error( __( 'Required fields missing.', 'codepress-admin-columns' ) );
		}

		// Get ID of entry to edit
		if ( ! ( $id = intval( $_POST['pk'] ) ) ) {
			wp_send_json_error( __( 'Invalid item ID.', 'codepress-admin-columns' ) );
		}

		// Get column instance
		$column = $this->storage_model->get_column_by_name( $_POST['column'] );

		if ( ! $column ) {
			wp_send_json_error( __( 'Invalid column.', 'codepress-admin-columns' ) );
		}

		$value = isset( $_POST['value'] ) ? $_POST['value'] : '';

		/**
		 * Filter for changing the value before storing it to the DB
		 *
		 * @since 3.2.1
		 *
		 * @param mixed $value Value send from inline edit ajax callback
		 * @param object CPAC_Column instance
		 * @param int $id ID
		 */
		$value = apply_filters( 'cac/inline-edit/ajax-column-save/value', $value, $column, $id );

		// Store column
		$save_result = $this->column_save( $id, $column, $value );

		if ( is_wp_error( $save_result ) ) {
			status_header( 400 );
			echo $save_result->get_error_message();
			exit;
		}

		ob_start();

		// WP default column
		if ( $column->properties->default ) {
			$this->manage_value( $column, $id );
		}

		// Taxonomy
		else if ( 'taxonomy' == $this->storage_model->type ) {
			echo $this->storage_model->manage_value( '', $column->properties->name, $id );
		}

		// Custom Admin column
		else {
			echo $this->storage_model->manage_value( $column->properties->name, $id );
		}

		$contents = ob_get_clean();

		/**
		 * Fires after a inline-edit succesfully saved a value
		 *
		 * @since ????
		 *
		 * @param CPAC_Column $column Column instance
		 * @param int $id Item ID
		 * @param string $value User submitted input
		 * @param object $this CACIE_Editable_Model $editable_model_instance Editability model instance
		 */
		do_action( 'cac/inline-edit/after_ajax_column_save', $column, $id, $value, $this );

		$jsondata = array(
			'success' => true,
			'data' => array(
				'value' => $contents
			)
		);

		// We don't want a Nullable rawvalue  in our JSON because select2 will break
		$raw_value = $this->get_column_editability_value( $column, $id );
		if ( NULL !== $raw_value ) {
			$jsondata['data']['rawvalue'] = $this->get_column_editability_value( $column, $id );
		}

		if ( is_callable( array( $column, 'get_item_data' ) ) ) {
			$jsondata['data']['itemdata'] = $column->get_item_data( $id );
		}

		wp_send_json( $jsondata );
	}

	/**
	 * Add the option of inline editing to columns
	 *
	 * @since 1.0
	 */
	public function enable_inlineedit( $columns ) {

		foreach ( $columns as $column ) {
			if ( $this->is_editable( $column ) ) {
				// Enable editing
				$column->set_properties( 'is_editable', true );
			}
		}
	}

	/**
	 * Update Meta
	 *
	 * @since 1.0
	 */
	protected function update_meta( $id, $meta_key, $value ) {

		update_metadata( $this->storage_model->meta_type, $id, $meta_key, $value );
	}

	/**
	 * Format options to be in JS
	 *
	 * @since 1.0
	 *
	 * @param array $options List of options, possibly with option groups
	 * @return array Formatted option list
	 */
	public function format_options( $options ) {

		$newoptions = array();

		if ( $options ) {
			foreach ( $options as $index => $option ) {
				if ( is_array( $option ) && isset( $option['options'] ) ) {
					$option['options'] = $this->format_options( $option['options'] );
					$newoptions[] = $option;
				}
				else {
					$newoptions[] = array(
						'value' => $index,
						'label' => $option
					);
				}
			}
		}

		return $newoptions;
	}

	/**
	 * @since ?
	 */
	public function get_formatted_value( $column, $raw_value ) {
		$formattedvalues = NULL;

		$editable = $this->get_editable( $column->properties->name );
		$formatted_value = isset( $editable['formatted_value'] ) ? $editable['formatted_value'] : '';

		switch ( $formatted_value ) {
			case 'post' :
				$formattedvalues = array();
				if ( ! empty( $raw_value ) ) {
					foreach ( (array) $raw_value as $id ) {
						$formattedvalues[ $id ] = get_post_field( 'post_title', $id );
					}
				}
				break;
			case 'user' :
				$formattedvalues = array();
				if ( ! empty( $raw_value ) ) {
					foreach ( (array) $raw_value as $id ) {
						$user = get_user_by( 'id', $id );

						if ( is_a( $user, 'WP_User' ) ) {
							$formattedvalues[ $id ] = $user->display_name;
						}
					}
				}
				break;
			case 'wc_product' :
				$formattedvalues = array();
				if ( ! empty( $raw_value ) ) {
					foreach ( (array) $raw_value as $id ) {
						if ( $product = get_product( $id ) ) {
							$formattedvalues[ $id ] = $product->get_title();
						}
					}
				}
				break;
		}

		return $formattedvalues;
	}

	/**
	 * Get term options for a taxonomy
	 *
	 * @since 1.0
	 *
	 * @param string $taxonomy Taxonomy name
	 * @param string $default Default option ( key is always zero )
	 * @return List of term options (term_id => name)
	 */
	public function get_term_options( $taxonomy, $default = '' ) {
		$options = array();

		if ( $default ) {
			$options[0] = $default;
		}

		$terms = get_terms( $taxonomy, array(
		 	'hide_empty' => 0,
		));

		if ( $terms && ! is_wp_error( $terms ) ) {
			foreach ( $terms as  $term ) {
				$options[ $term->term_id ] = $term->name;
			}
		}

		return $options;
	}

	/**
	 * Update post terms
	 *
	 * @since 1.0
	 *
	 * @param mixed $post Post object or post ID. Pass NULL to use the current post in the loop.
	 * @param array $term_ids Term IDs (int) or names (string, will be added as new term if it doesn't exist yet)  to be stored
	 * @param string $taxonomy Taxonomy to set the terms for
	 */
	public function set_post_terms( $post, $term_ids, $taxonomy ) {

		$post = get_post( $post );

		if ( ! $post ) {
			return;
		}

		// Filter list of terms
		if ( empty( $term_ids ) ) {
			$term_ids = array();
		}

		$term_ids = array_unique( (array) $term_ids );

		// maybe create terms?
		$created_term_ids = array();

		foreach ( (array) $term_ids as $index => $term_id ) {
			if ( is_numeric( $term_id ) ) {
				continue;
			}

			if ( $term = get_term_by( 'name', $term_id, $taxonomy ) ) {
				$term_ids[ $index ] = $term->term_id;
			}
			else {
				$created_term = wp_insert_term( $term_id, $taxonomy );
				$created_term_ids[] = $created_term['term_id'];
			}
		}

		// merge
		$term_ids = array_merge( $created_term_ids, $term_ids );

		//to make sure the terms IDs is integers:
		$term_ids = array_map( 'intval', (array) $term_ids );
		$term_ids = array_unique( $term_ids );

		if ( $taxonomy == 'category' && is_object_in_taxonomy( $post->post_type, 'category' ) ) {
			wp_set_post_categories( $post->ID, $term_ids );
		}
		else if ( $taxonomy == 'post_tag' && is_object_in_taxonomy( $post->post_type, 'post_tag' ) ) {
			wp_set_post_tags( $post->ID, $term_ids );
		}
		else {
			wp_set_object_terms( $post->ID, $term_ids, $taxonomy );
		}
	}

	/**
	 * Get editability value for a column
	 *
	 * @since 3.4
	 *
	 * @param CPAC_Column $column Column
	 * @param integer $id Item ID
	 * @return mixed Raw value
	 */
	public function get_column_editability_value( $column, $id ) {
		if ( ! $column->properties->default ) {
			return $column->get_raw_value( $id );
		}
		return NULL;
	}
}