post = $post;
} elseif ( is_numeric( $post ) ) {
$this->post = get_post( $post );
}
if ( $this->post && $this->post->post_type !== $this->get_post_type() ) {
$this->post = null;
}
if ( $this->post ) {
$this->load_attributes();
}
register_post_status( 'active', [
'label' => __( 'Active', 'booktics' ),
'public' => true,
'publicly_queryable' => true,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
// translators: %s: number of items active
'label_count' => _n_noop( 'Active (%s)', 'Active (%s)', 'booktics' ),
] );
register_post_status( 'deactive', [
'label' => __( 'Deactive', 'booktics' ),
'public' => true,
'publicly_queryable' => true,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
// translators: %s: number of items deactive
'label_count' => _n_noop( 'Deactive (%s)', 'Deactive (%s)', 'booktics' ),
] );
register_post_status( 'pending', [
'label' => __( 'Pending', 'booktics' ),
'public' => true,
'publicly_queryable' => true,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
// translators: %s: number of items pending
'label_count' => _n_noop( 'Pending (%s)', 'Pending (%s)', 'booktics' ),
] );
register_post_status( 'cancelled', [
'label' => __( 'Cancelled', 'booktics' ),
'public' => true,
'publicly_queryable' => true,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
// translators: %s: number of items cancelled
'label_count' => _n_noop( 'Cancelled (%s)', 'Cancelled (%s)', 'booktics' ),
] );
register_post_status( 'approved', [
'label' => __( 'Approved', 'booktics' ),
'public' => true,
'publicly_queryable' => true,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
// translators: %s: number of items approved
'label_count' => _n_noop( 'Approved (%s)', 'Approved (%s)', 'booktics' ),
] );
register_post_status( 'completed', [
'label' => __( 'Completed', 'booktics' ),
'public' => true,
'publicly_queryable' => true,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
// translators: %s: number of items Completed
'label_count' => _n_noop( 'Completed (%s)', 'Completed (%s)', 'booktics' ),
] );
register_post_status( 'not_show', [
'label' => __( 'Not Show', 'booktics' ),
'public' => true,
'publicly_queryable' => true,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
// translators: %s: number of items not_show
'label_count' => _n_noop( 'Not Show (%s)', 'Not Show (%s)', 'booktics' ),
] );
}
/**
* Get post type
*
* @return string Get post type name
*/
abstract protected function get_post_type();
/**
* Load attributes
*
* @return void Set all necessary attributes
*/
protected function load_attributes() {
$this->attributes = [
'id' => $this->post->ID,
'title' => $this->post->post_title,
'content' => $this->post->post_content,
'status' => $this->post->post_status,
'link' => $this->get_link( $this->post ),
];
$custom_meta = get_post_meta( $this->post->ID );
foreach ( $this->get_fillable() as $key => $value ) {
if ( isset( $this->attributes[ $key ] ) && ! empty( $this->attributes[ $key ] ) ) {
continue;
}
$this->attributes[ $key ] = get_post_meta( $this->post->ID, $key, true );
}
$this->original = $this->attributes;
}
/**
* Get the permalink for the post
*
* @param $post
*
* @return string
*/
public function get_link( $post ) {
return $post->post_type && get_post_type_object( $this->post->post_type ) ?
get_post_permalink( $this->post->ID ) : '';
}
/**
* Get attributes
*
* @param string $key Property name
*
* @return mixed Meta data from any model
* @throws Exception
*/
public function __get( $key ) {
if ( isset( $this->get_fillable()[ $key ] ) ) {
return $this->attributes[ $key ] ?? $this->get_fillable()[ $key ];
}
// translators: %s is the name of the undefined property.
throw new Exception( sprintf( esc_html__( 'Undefined property %s', 'booktics' ), esc_html( $key ) ) );
}
/**
* Set attributes
*
* @param string $key Model meta property
* @param mixed $value Post meta data
*
* @return void Set meta data
* @throws Exception
*/
public function __set( $key, $value ) {
if ( ! isset( $this->get_fillable()[ $key ] ) && ! in_array( $key, [ 'link' ] ) ) {
// translators: %s is the name of the undefined property.
throw new Exception( sprintf( esc_html__( 'Undefined property %s', 'booktics' ), esc_html( $key ) ) );
}
$this->attributes[ $key ] = $value;
}
/**
* Fillable getter
*
* @return array
*/
protected function get_fillable() {
$this->fillable = apply_filters( 'booktics_post_model_fillable', $this->fillable, $this->get_post_type() );
return $this->fillable;
}
/**
* Find any model by using id
*
* @param integer $id Post id
*
* @return null | PostModel
*/
public static function find( $id ) {
$post = get_post( $id );
if ( ! $post ) {
return null;
}
$instance = new static( $post );
return $instance;
}
/**
* Get all posts
*
* @param array $args Post arguments
*
* @return array collection of PostModel
*/
public static function all( $args = [] ) {
$args = array_merge( [
'post_type' => ( new static )->get_post_type(),
'post_status' => static::$accepted_statuses,
'numberposts' => - 1
], $args );
$posts = get_posts( $args );
return array_map( fn( $p ) => new static( $p ), $posts );
}
/**
* Pagenate all collections
*
* @param integer $per_page Number of items perpage
* @param integer $page Current page number
* @param array $args other args
*
* @return array collection of records
*/
public static function paginate( $per_page = 10, $page = 1, $args = [] ) {
$args = array_merge( [
'post_type' => ( new static )->get_post_type(),
'post_status' => static::$accepted_statuses,
'posts_per_page' => $per_page,
'paged' => $page,
], $args );
$query = new WP_Query( $args );
return [
'items' => array_map( function ( $post ) {
return new static( $post );
}, $query->posts ),
'total' => $query->found_posts,
'per_page' => $per_page,
'current_page' => $page,
'last_page' => ceil( $query->found_posts / $per_page ),
];
}
/**
* Filter post by using condition
*
* @param string $meta_key Meta key for the post model
* @param mixed $meta_value Post meta value
*
* @return array collection of post model
*/
public static function where( $meta_key, $meta_value ) {
$args = [
'post_type' => ( new static )->get_post_type(),
'post_status' => static::$accepted_statuses,
'meta_query' => [
[
'key' => $meta_key,
'value' => $meta_value,
]
]
];
$posts = get_posts( $args );
return array_map( function ( $p ) {
return new static( $p );
}, $posts );
}
/**
* Create a single row
*
* @param array $attributes collection of attributes
*
* @return null | Post_Model | WP_Error
*/
public static function create( array $attributes ) {
$validation = self::validate_attributes( $attributes );
if ( is_wp_error( $validation ) ) {
return $validation;
}
$post_data = [
'post_type' => ( new static )->get_post_type(),
'post_status' => $attributes['status'] ?? 'deactive',
'post_title' => $attributes['title'] ?? '',
'post_content' => $attributes['content'] ?? $attributes['description'] ?? '',
];
$post_id = wp_insert_post( $post_data );
if ( is_wp_error( $post_id ) ) {
return null;
}
$instance = new static( $post_id );
if ( ! is_array( $attributes ) ) {
return $instance;
}
foreach ( $attributes as $key => $value ) {
if ( ! in_array( $key, [ 'title', 'content', 'status' ] ) ) {
update_post_meta( $post_id, $key, $value );
$instance->$key = $value;
}
}
$instance->link = $instance->get_link( $instance->post );
return $instance;
}
/**
* Update a certain record
*
* @param array $attributes Collection of attributes
*
* @return WP_Error
*/
public function update( array $attributes ) {
if ( ! $this->post ) {
return new WP_Error( 'post_not_found', esc_html__( 'Cannot update a non-existent post.', 'booktics' ) );
}
$validation = self::validate_attributes( $attributes );
if ( is_wp_error( $validation ) ) {
return $validation;
}
$post_data = [ 'ID' => $this->post->ID ];
if ( isset( $attributes['title'] ) ) {
$this->title = $attributes['title'];
$post_data['post_title'] = $attributes['title'];
}
if ( isset( $attributes['content'] ) ) {
$this->content = $attributes['content'];
$post_data['post_content'] = $attributes['content'];
}
if ( isset( $attributes['status'] ) ) {
$this->status = $attributes['status'];
$post_data['post_status'] = $attributes['status'];
}
wp_update_post( $post_data );
// Update custom meta fields
foreach ( $attributes as $key => $value ) {
if ( ! in_array( $key, [ 'title', 'content', 'status' ] ) ) {
update_post_meta( $this->post->ID, $key, $value );
$this->attributes[ $key ] = $value;
}
}
return $this;
}
/**
* Save a single record
*
* @return Post_Model instance of the PostModel
* @throws Exception
*/
public function save() {
if ( ! $this->post ) {
return static::create( $this->attributes );
}
$post_data = [
'ID' => $this->post->ID,
'post_title' => $this->title,
'post_content' => $this->content,
];
wp_update_post( $post_data );
foreach ( $this->attributes as $key => $value ) {
if ( ! in_array( $key, [
'ID',
'title',
'content'
] ) && $value !== ( $this->original[ $key ] ?? null ) ) {
update_post_meta( $this->post->ID, $key, $value );
}
}
return $this;
}
/**
* Delete a record
*
* @return WP_Post|false|null Post data on success, false or null on failure.
*/
public function delete() {
if ( $this->post ) {
return wp_delete_post( $this->post->ID, true );
}
return false;
}
/**
* Query records from posts
*
* @return Object
*/
public static function query() {
return new Query_Builder( new static );
}
/**
* Validate attributes
*
* @param array $attributes Post data
*
* @return WP_Error|true
*/
protected static function validate_attributes( $attributes = [] ) {
if ( ! is_array( $attributes ) ) {
return new WP_Error( 'invalid_attributes', esc_html__( 'Attributes must be an array.', 'booktics' ) );
}
foreach ( $attributes as $key => $value ) {
if ( ! isset( ( new static )->get_fillable()[ $key ] ) && ! in_array( $key, [
'ID',
'team_member_details',
'category_details',
'shortcode',
'service_details',
'duration_unit',
] ) ) {
// translators: %s: Property name
return new WP_Error( 'invalid_property', sprintf( esc_html__( 'Invalid property: %s', 'booktics' ), $key ) );
}
}
return true;
}
/**
* Convert the model instance to an array.
*
* @return array
*/
public function to_array(): array {
$data = [];
foreach ( $this->get_fillable() as $key => $default ) {
if ( $key === 'user_email' ) {
$data['email'] = $this->$key;
continue;
}
$value = $this->$key;
// Unserialize if necessary
if ( is_string( $value ) && $this->is_serialized( $value ) ) {
$value = maybe_unserialize( $value );
}
// Normalize value to default type
if ( gettype( $value ) !== gettype( $default ) ) {
if ( gettype( $default ) === 'array' && is_string( $value ) && $this->is_serialized( $value ) ) {
$value = maybe_unserialize( $value );
} elseif ( gettype( $default ) === 'array' ) {
$value = $default; // Set to default empty array if the value doesn't match
}
}
// Cast specific keys to objects or arrays if needed
if ( in_array( $key, [
'team_member',
'holiday',
'categories',
'additional_durations'
] ) ) {
$value = is_array( $value ) ? $value : [];
}
if ( in_array( $key, [ 'schedule', 'custom_field' ] ) ) {
$value = empty( $value ) ? (object) [] : $value;
}
// Cast specific keys to boolean
if ( in_array( $key, [
'enable_custom_schedule',
'enable_custom_holiday',
] ) ) {
$value = filter_var( $value, FILTER_VALIDATE_BOOLEAN );
}
$data[ $key ] = $value ?? $default;
}
return $data;
}
/**
* Specify data which should be serialized to JSON.
*
* @return array
*/
public function jsonSerialize(): array {
return $this->to_array();
}
/**
* Check if a string is serialized
*
* @param string $data The data to check.
*
* @return bool True if the data is serialized, false otherwise.
*/
protected function is_serialized( $data ) {
// If it isn't a string, return false
if ( ! is_string( $data ) ) {
return false;
}
// Check if it's a serialized string (Array or Object)
$data = trim( $data );
if ( 'N;' === $data || ! preg_match( '/^([adObis]):/', $data ) ) {
return false;
}
$result = @unserialize( $data );
return $result !== false || $data === 'b:0;'; // Return false for failed unserialization and true for successful
}
}