PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.3
Booktics – Appointment Booking Calendar for Service Businesses v1.0.3
1.0.29 1.0.28 1.0.27 1.0.26 1.0.25 1.0.24 1.0.23 1.0.22 1.0.21 1.0.20 1.0.19 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.2 1.0.3 All 31 releases
booktics / trunk / base / abstracts / post-model.php

post-model.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.3, at trunk/base/abstracts/post-model.php

624 lines 18.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Booktics\Abstracts;
4
5 use Booktics\Query_Builders\Query_Builder;
6 use Exception;
7 use JsonSerializable;
8 use WP_Error;
9 use WP_Post;
10 use WP_Query;
11
12 /**
13 * PostModel class
14 */
15 abstract class Post_Model implements JsonSerializable {
16 /**
17 * List of accepted post statuses
18 *
19 * @var array
20 */
21 public static $accepted_statuses = [
22 'active',
23 'deactive',
24 'draft',
25 'publish',
26 'cancelled',
27 'pending'
28 ];
29
30 /**
31 * Store post
32 *
33 * @var Object
34 */
35 protected $post;
36
37 /**
38 * Store meta property
39 *
40 * @var array
41 */
42 protected $fillable = [];
43
44 /**
45 * Store post attributes
46 *
47 * @var array
48 */
49 protected $attributes = [];
50
51 /**
52 * Store origianl data
53 *
54 * @var array
55 */
56 protected $original = [];
57
58 /**
59 * Store post type
60 *
61 * @var string
62 */
63 protected $post_type;
64
65 /**
66 * Constructor for post model class
67 *
68 * @param Object $post Post model
69 *
70 * @return void
71 */
72 public function __construct( $post = null ) {
73 if ( $post instanceof WP_Post ) {
74 $this->post = $post;
75 } elseif ( is_numeric( $post ) ) {
76 $this->post = get_post( $post );
77 }
78
79 if ( $this->post && $this->post->post_type !== $this->get_post_type() ) {
80 $this->post = null;
81 }
82
83 if ( $this->post ) {
84 $this->load_attributes();
85 }
86
87 register_post_status( 'active', [
88 'label' => __( 'Active', 'booktics' ),
89 'public' => true,
90 'publicly_queryable' => true,
91 'exclude_from_search' => false,
92 'show_in_admin_all_list' => true,
93 'show_in_admin_status_list' => true,
94 // translators: %s: number of items active
95 'label_count' => _n_noop( 'Active <span class="count">(%s)</span>', 'Active <span class="count">(%s)</span>', 'booktics' ),
96 ] );
97
98 register_post_status( 'deactive', [
99 'label' => __( 'Deactive', 'booktics' ),
100 'public' => true,
101 'publicly_queryable' => true,
102 'exclude_from_search' => false,
103 'show_in_admin_all_list' => true,
104 'show_in_admin_status_list' => true,
105 // translators: %s: number of items deactive
106 'label_count' => _n_noop( 'Deactive <span class="count">(%s)</span>', 'Deactive <span class="count">(%s)</span>', 'booktics' ),
107 ] );
108
109 register_post_status( 'pending', [
110 'label' => __( 'Pending', 'booktics' ),
111 'public' => true,
112 'publicly_queryable' => true,
113 'exclude_from_search' => false,
114 'show_in_admin_all_list' => true,
115 'show_in_admin_status_list' => true,
116 // translators: %s: number of items pending
117 'label_count' => _n_noop( 'Pending <span class="count">(%s)</span>', 'Pending <span class="count">(%s)</span>', 'booktics' ),
118 ] );
119
120 register_post_status( 'cancelled', [
121 'label' => __( 'Cancelled', 'booktics' ),
122 'public' => true,
123 'publicly_queryable' => true,
124 'exclude_from_search' => false,
125 'show_in_admin_all_list' => true,
126 'show_in_admin_status_list' => true,
127 // translators: %s: number of items cancelled
128 'label_count' => _n_noop( 'Cancelled <span class="count">(%s)</span>', 'Cancelled <span class="count">(%s)</span>', 'booktics' ),
129 ] );
130
131 register_post_status( 'approved', [
132 'label' => __( 'Approved', 'booktics' ),
133 'public' => true,
134 'publicly_queryable' => true,
135 'exclude_from_search' => false,
136 'show_in_admin_all_list' => true,
137 'show_in_admin_status_list' => true,
138 // translators: %s: number of items approved
139 'label_count' => _n_noop( 'Approved <span class="count">(%s)</span>', 'Approved <span class="count">(%s)</span>', 'booktics' ),
140 ] );
141
142 register_post_status( 'completed', [
143 'label' => __( 'Completed', 'booktics' ),
144 'public' => true,
145 'publicly_queryable' => true,
146 'exclude_from_search' => false,
147 'show_in_admin_all_list' => true,
148 'show_in_admin_status_list' => true,
149 // translators: %s: number of items Completed
150 'label_count' => _n_noop( 'Completed <span class="count">(%s)</span>', 'Completed <span class="count">(%s)</span>', 'booktics' ),
151 ] );
152
153 register_post_status( 'not_show', [
154 'label' => __( 'Not Show', 'booktics' ),
155 'public' => true,
156 'publicly_queryable' => true,
157 'exclude_from_search' => false,
158 'show_in_admin_all_list' => true,
159 'show_in_admin_status_list' => true,
160 // translators: %s: number of items not_show
161 'label_count' => _n_noop( 'Not Show <span class="count">(%s)</span>', 'Not Show <span class="count">(%s)</span>', 'booktics' ),
162 ] );
163 }
164
165 /**
166 * Get post type
167 *
168 * @return string Get post type name
169 */
170 abstract protected function get_post_type();
171
172 /**
173 * Load attributes
174 *
175 * @return void Set all necessary attributes
176 */
177 protected function load_attributes() {
178 $this->attributes = [
179 'id' => $this->post->ID,
180 'title' => $this->post->post_title,
181 'content' => $this->post->post_content,
182 'status' => $this->post->post_status,
183 'link' => $this->get_link( $this->post ),
184 ];
185
186 $custom_meta = get_post_meta( $this->post->ID );
187
188 foreach ( $this->get_fillable() as $key => $value ) {
189 if ( isset( $this->attributes[ $key ] ) && ! empty( $this->attributes[ $key ] ) ) {
190 continue;
191 }
192 $this->attributes[ $key ] = get_post_meta( $this->post->ID, $key, true );
193 }
194 $this->original = $this->attributes;
195 }
196
197 /**
198 * Get the permalink for the post
199 *
200 * @param $post
201 *
202 * @return string
203 */
204 public function get_link( $post ) {
205 return $post->post_type && get_post_type_object( $this->post->post_type ) ?
206 get_post_permalink( $this->post->ID ) : '';
207 }
208
209 /**
210 * Get attributes
211 *
212 * @param string $key Property name
213 *
214 * @return mixed Meta data from any model
215 * @throws Exception
216 */
217 public function __get( $key ) {
218 if ( isset( $this->get_fillable()[ $key ] ) ) {
219 return $this->attributes[ $key ] ?? $this->get_fillable()[ $key ];
220 }
221
222 // translators: %s is the name of the undefined property.
223 throw new Exception( sprintf( esc_html__( 'Undefined property %s', 'booktics' ), esc_html( $key ) ) );
224
225 }
226
227 /**
228 * Set attributes
229 *
230 * @param string $key Model meta property
231 * @param mixed $value Post meta data
232 *
233 * @return void Set meta data
234 * @throws Exception
235 */
236 public function __set( $key, $value ) {
237
238 if ( ! isset( $this->get_fillable()[ $key ] ) && ! in_array( $key, [ 'link' ] ) ) {
239
240 // translators: %s is the name of the undefined property.
241 throw new Exception( sprintf( esc_html__( 'Undefined property %s', 'booktics' ), esc_html( $key ) ) );
242 }
243
244 $this->attributes[ $key ] = $value;
245 }
246
247 /**
248 * Fillable getter
249 *
250 * @return array
251 */
252 protected function get_fillable() {
253 $this->fillable = apply_filters( 'booktics_post_model_fillable', $this->fillable, $this->get_post_type() );
254
255 return $this->fillable;
256 }
257
258 /**
259 * Find any model by using id
260 *
261 * @param integer $id Post id
262 *
263 * @return null | PostModel
264 */
265 public static function find( $id ) {
266 $post = get_post( $id );
267 if ( ! $post ) {
268 return null;
269 }
270
271 $instance = new static( $post );
272
273 return $instance;
274 }
275
276 /**
277 * Get all posts
278 *
279 * @param array $args Post arguments
280 *
281 * @return array collection of PostModel
282 */
283 public static function all( $args = [] ) {
284 $args = array_merge( [
285 'post_type' => ( new static )->get_post_type(),
286 'post_status' => static::$accepted_statuses,
287 'numberposts' => - 1
288 ], $args );
289
290 $posts = get_posts( $args );
291
292 return array_map( fn( $p ) => new static( $p ), $posts );
293 }
294
295 /**
296 * Pagenate all collections
297 *
298 * @param integer $per_page Number of items perpage
299 * @param integer $page Current page number
300 * @param array $args other args
301 *
302 * @return array collection of records
303 */
304 public static function paginate( $per_page = 10, $page = 1, $args = [] ) {
305 $args = array_merge( [
306 'post_type' => ( new static )->get_post_type(),
307 'post_status' => static::$accepted_statuses,
308 'posts_per_page' => $per_page,
309 'paged' => $page,
310 ], $args );
311
312 $query = new WP_Query( $args );
313
314 return [
315 'items' => array_map( function ( $post ) {
316 return new static( $post );
317 }, $query->posts ),
318 'total' => $query->found_posts,
319 'per_page' => $per_page,
320 'current_page' => $page,
321 'last_page' => ceil( $query->found_posts / $per_page ),
322 ];
323 }
324
325
326 /**
327 * Filter post by using condition
328 *
329 * @param string $meta_key Meta key for the post model
330 * @param mixed $meta_value Post meta value
331 *
332 * @return array collection of post model
333 */
334 public static function where( $meta_key, $meta_value ) {
335 $args = [
336 'post_type' => ( new static )->get_post_type(),
337 'post_status' => static::$accepted_statuses,
338 'meta_query' => [
339 [
340 'key' => $meta_key,
341 'value' => $meta_value,
342 ]
343 ]
344 ];
345
346 $posts = get_posts( $args );
347
348 return array_map( function ( $p ) {
349 return new static( $p );
350 }, $posts );
351 }
352
353 /**
354 * Create a single row
355 *
356 * @param array $attributes collection of attributes
357 *
358 * @return null | Post_Model | WP_Error
359 */
360 public static function create( array $attributes ) {
361 $validation = self::validate_attributes( $attributes );
362 if ( is_wp_error( $validation ) ) {
363 return $validation;
364 }
365
366 $post_data = [
367 'post_type' => ( new static )->get_post_type(),
368 'post_status' => $attributes['status'] ?? 'deactive',
369 'post_title' => $attributes['title'] ?? '',
370 'post_content' => $attributes['content'] ?? $attributes['description'] ?? '',
371 ];
372
373 $post_id = wp_insert_post( $post_data );
374
375 if ( is_wp_error( $post_id ) ) {
376 return null;
377 }
378
379 $instance = new static( $post_id );
380
381 if ( ! is_array( $attributes ) ) {
382 return $instance;
383 }
384
385 foreach ( $attributes as $key => $value ) {
386 if ( ! in_array( $key, [ 'title', 'content', 'status' ] ) ) {
387
388 update_post_meta( $post_id, $key, $value );
389
390 $instance->$key = $value;
391 }
392 }
393 $instance->link = $instance->get_link( $instance->post );
394
395 return $instance;
396 }
397
398 /**
399 * Update a certain record
400 *
401 * @param array $attributes Collection of attributes
402 *
403 * @return WP_Error
404 */
405 public function update( array $attributes ) {
406 if ( ! $this->post ) {
407 return new WP_Error( 'post_not_found', esc_html__( 'Cannot update a non-existent post.', 'booktics' ) );
408 }
409
410 $validation = self::validate_attributes( $attributes );
411 if ( is_wp_error( $validation ) ) {
412 return $validation;
413 }
414
415 $post_data = [ 'ID' => $this->post->ID ];
416
417 if ( isset( $attributes['title'] ) ) {
418 $this->title = $attributes['title'];
419 $post_data['post_title'] = $attributes['title'];
420 }
421
422 if ( isset( $attributes['content'] ) ) {
423 $this->content = $attributes['content'];
424 $post_data['post_content'] = $attributes['content'];
425 }
426
427 if ( isset( $attributes['status'] ) ) {
428 $this->status = $attributes['status'];
429 $post_data['post_status'] = $attributes['status'];
430 }
431
432 wp_update_post( $post_data );
433
434 // Update custom meta fields
435 foreach ( $attributes as $key => $value ) {
436 if ( ! in_array( $key, [ 'title', 'content', 'status' ] ) ) {
437 update_post_meta( $this->post->ID, $key, $value );
438 $this->attributes[ $key ] = $value;
439 }
440 }
441
442 return $this;
443 }
444
445 /**
446 * Save a single record
447 *
448 * @return Post_Model instance of the PostModel
449 * @throws Exception
450 */
451 public function save() {
452
453 if ( ! $this->post ) {
454 return static::create( $this->attributes );
455 }
456
457 $post_data = [
458 'ID' => $this->post->ID,
459 'post_title' => $this->title,
460 'post_content' => $this->content,
461 ];
462
463 wp_update_post( $post_data );
464
465 foreach ( $this->attributes as $key => $value ) {
466 if ( ! in_array( $key, [
467 'ID',
468 'title',
469 'content'
470 ] ) && $value !== ( $this->original[ $key ] ?? null ) ) {
471 update_post_meta( $this->post->ID, $key, $value );
472 }
473 }
474
475 return $this;
476 }
477
478 /**
479 * Delete a record
480 *
481 * @return WP_Post|false|null Post data on success, false or null on failure.
482 */
483 public function delete() {
484 if ( $this->post ) {
485 return wp_delete_post( $this->post->ID, true );
486 }
487
488 return false;
489 }
490
491 /**
492 * Query records from posts
493 *
494 * @return Object
495 */
496 public static function query() {
497 return new Query_Builder( new static );
498 }
499
500 /**
501 * Validate attributes
502 *
503 * @param array $attributes Post data
504 *
505 * @return WP_Error|true
506 */
507 protected static function validate_attributes( $attributes = [] ) {
508 if ( ! is_array( $attributes ) ) {
509 return new WP_Error( 'invalid_attributes', esc_html__( 'Attributes must be an array.', 'booktics' ) );
510 }
511
512 foreach ( $attributes as $key => $value ) {
513 if ( ! isset( ( new static )->get_fillable()[ $key ] ) && ! in_array( $key, [
514 'ID',
515 'team_member_details',
516 'category_details',
517 'shortcode',
518 'service_details',
519 'duration_unit',
520 ] ) ) {
521 // translators: %s: Property name
522 return new WP_Error( 'invalid_property', sprintf( esc_html__( 'Invalid property: %s', 'booktics' ), $key ) );
523 }
524 }
525
526 return true;
527 }
528
529 /**
530 * Convert the model instance to an array.
531 *
532 * @return array
533 */
534 public function to_array(): array {
535 $data = [];
536
537 foreach ( $this->get_fillable() as $key => $default ) {
538 if ( $key === 'user_email' ) {
539 $data['email'] = $this->$key;
540 continue;
541 }
542
543 $value = $this->$key;
544
545 // Unserialize if necessary
546 if ( is_string( $value ) && $this->is_serialized( $value ) ) {
547 $value = maybe_unserialize( $value );
548 }
549
550 // Normalize value to default type
551 if ( gettype( $value ) !== gettype( $default ) ) {
552 if ( gettype( $default ) === 'array' && is_string( $value ) && $this->is_serialized( $value ) ) {
553 $value = maybe_unserialize( $value );
554 } elseif ( gettype( $default ) === 'array' ) {
555 $value = $default; // Set to default empty array if the value doesn't match
556 }
557 }
558
559 // Cast specific keys to objects or arrays if needed
560 if ( in_array( $key, [
561 'team_member',
562 'holiday',
563 'categories',
564 'additional_durations'
565 ] ) ) {
566 $value = is_array( $value ) ? $value : [];
567 }
568
569 if ( in_array( $key, [ 'schedule', 'custom_field' ] ) ) {
570 $value = empty( $value ) ? (object) [] : $value;
571 }
572
573 // Cast specific keys to boolean
574 if ( in_array( $key, [
575 'enable_custom_schedule',
576 'enable_custom_holiday',
577 ] ) ) {
578 $value = filter_var( $value, FILTER_VALIDATE_BOOLEAN );
579 }
580
581 $data[ $key ] = $value ?? $default;
582 }
583
584 return $data;
585 }
586
587
588 /**
589 * Specify data which should be serialized to JSON.
590 *
591 * @return array
592 */
593 public function jsonSerialize(): array {
594 return $this->to_array();
595 }
596
597 /**
598 * Check if a string is serialized
599 *
600 * @param string $data The data to check.
601 *
602 * @return bool True if the data is serialized, false otherwise.
603 */
604 protected function is_serialized( $data ) {
605 // If it isn't a string, return false
606 if ( ! is_string( $data ) ) {
607 return false;
608 }
609
610 // Check if it's a serialized string (Array or Object)
611 $data = trim( $data );
612
613 if ( 'N;' === $data || ! preg_match( '/^([adObis]):/', $data ) ) {
614 return false;
615 }
616
617 $result = @unserialize( $data );
618
619 return $result !== false || $data === 'b:0;'; // Return false for failed unserialization and true for successful
620 }
621
622 }
623
624