PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.19
Booktics – Appointment Booking Calendar for Service Businesses v1.0.19
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 1.0.4 1.0.5 1.0.6 1.0.7 All 27 releases
booktics / base / abstracts / user-model.php

user-model.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.19, at base/abstracts/user-model.php

620 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * User Model Abstract for managing all wp_users and wp_usermeta
4 *
5 * @package Booktics/Abstracts
6 */
7
8 namespace Booktics\Abstracts;
9
10 if ( ! defined( 'ABSPATH' ) ) exit;
11
12 use JsonSerializable;
13 use WP_User_Query;
14
15 abstract class User_Model implements JsonSerializable {
16
17 /**
18 * Store specific user role
19 *
20 * @var string
21 */
22 protected $role = 'administrator';
23
24 /**
25 * Store specific meta prefix
26 *
27 * @var string
28 */
29 protected $meta_prefix = '';
30
31 /**
32 * Store user id
33 *
34 * @var integer
35 */
36 protected $id;
37
38 /**
39 * Store user data
40 *
41 * @var array
42 */
43 protected $fillable = [
44 'id' => '',
45 'first_name' => '',
46 'last_name' => '',
47 'user_email' => '',
48 'image' => '',
49 'user_login' => '',
50 'phone' => '',
51 'status' => 0,
52 'user_registered' => '',
53 ];
54
55 /**
56 * Store Filter conditions
57 *
58 * @var array
59 */
60 protected $filters = [];
61
62 /**
63 * Store error
64 *
65 * @var \WP_Error
66 */
67 protected $error;
68
69 /**
70 * User constructor
71 *
72 * @param $user
73 *
74 * return void
75 */
76 public function __construct( $user = 0 ) {
77 require_once( ABSPATH . 'wp-admin/includes/user.php' );
78
79 if ( $user instanceof self ) {
80 $this->set_id( $user->get_id() );
81 } elseif ( ! empty( $user->ID ) ) {
82 $this->set_id( $user->ID );
83 } elseif ( is_numeric( $user ) && $user > 0 ) {
84 $this->set_id( $user );
85 }
86 }
87
88 /**
89 * Get user id
90 *
91 * @return integer
92 */
93 public function get_id(): int {
94 return $this->id;
95 }
96
97 /**
98 * Set user id
99 *
100 * @param int $id
101 *
102 * @return void
103 */
104 public function set_id( int $id ) {
105 $this->id = $id;
106 }
107
108 /**
109 * Get user data
110 *
111 * @param string $name
112 *
113 * @return string|null
114 */
115 public function __get( string $name ) {
116 if ( $name === 'id' ) {
117 return $this->id;
118 }
119 if ( ! isset( $this->fillable[ $name ] ) ) {
120 return null;
121 }
122
123 $possible_function_name = 'get_' . booktics_snake_case( $name ) . '_attribute';
124 $data = $this->get_prop( $name, $this->fillable[ $name ] );
125 if ( method_exists( $this, $possible_function_name ) ) {
126 return $this->$possible_function_name( $this );
127 }
128
129 return $data;
130 }
131
132 /**
133 * Get either user data or default data
134 *
135 * @param string $prop
136 * @param string|array $default
137 *
138 * @return string|array
139 */
140 private function get_prop( $prop = '', $default = '' ) {
141 return $this->get_metadata( $prop ) ?? $default;
142 }
143
144 /**
145 * Get user metadata
146 *
147 * @param string $prop
148 *
149 * @return mixed
150 */
151 private function get_metadata( string $prop = '' ) {
152 $meta_key = $this->meta_prefix . $prop;
153
154 return get_user_meta( $this->id, $meta_key, true );
155 }
156
157 /**
158 * Set user values
159 *
160 * @param string $name
161 * @param string $value
162 *
163 * @return void
164 */
165 public function __set( string $name, string $value ) {
166 if ( strpos( $name, $this->meta_prefix ) !== false ) {
167 $name = str_replace( $this->meta_prefix, '', $name );
168 }
169
170 $this->fillable[ $name ] = $value;
171 }
172
173 /**
174 * Save or update user
175 *
176 * @param array $args
177 *
178 * @return mixed|string
179 */
180 public function save( array $args = [] ) {
181 if ( isset( $args['email'] ) ) {
182 $args['user_email'] = $args['email'];
183 unset( $args['email'] );
184 }
185
186 $user_id = $args['ID'] ?? '';
187
188 $user_data = [
189 'ID' => $user_id,
190 'role' => $this->role,
191 'password' => wp_generate_password(),
192 ];
193
194 // Add required user fields
195 foreach ( $args as $key => $value ) {
196 if ( ! isset( $this->fillable[ $key ] ) && ! in_array( $key, [
197 'email',
198 'user_email'
199 ] ) ) {
200 continue;
201 }
202 $user_data[ $key ] = $value;
203 }
204
205 $user_id = wp_insert_user( $user_data );
206 if ( is_wp_error( $user_id ) ) {
207 return $user_id;
208 }
209
210 $user = new \WP_User( $user_id );
211 $user->add_role( $this->role );
212 $this->save_metadata( $args );
213
214 if ( $user_id ) {
215 wp_update_user( $user_data );
216 } else {
217 $user_id = wp_insert_user( $user_data );
218 }
219
220 $user = get_userdata( $user_id );
221 $user->add_role( $this->role );
222 if ( ! is_wp_error( $user_id ) ) {
223 $this->set_id( $user_id );
224 $this->save_metadata( $user_data );
225 $this->retrieve_password();
226 }
227
228 return $user_id;
229 }
230
231 /**
232 * Update specific field in user or meta data
233 *
234 * @param array $data
235 *
236 * @return bool
237 */
238 public function update( array $data = [] ): bool {
239 if ( ! $this->id ) {
240 $this->error = new \WP_Error( 'no_id', __( 'User ID is required to update.', 'booktics' ) );
241
242 return false;
243 }
244
245 $user_data = [ 'ID' => $this->id ];
246 $meta_data = [];
247
248 foreach ( $data as $key => $value ) {
249 if ( in_array( $key, [ 'schedule', 'custom', 'holiday' ] ) ) {
250 continue;
251 }
252 if ( isset( $this->fillable[ $key ] ) && ! strpos( $key, $this->meta_prefix ) ) {
253 if ( in_array( $key, [ 'user_email', 'user_login' ] ) ) {
254 $user_data[ $key ] = $value;
255 }
256 $meta_data[ $key ] = $value;
257 }
258 }
259 $result = wp_update_user( $user_data );
260 if ( is_wp_error( $result ) ) {
261 $this->error = $result;
262
263 return false;
264 }
265
266 $this->save_metadata( $meta_data );
267
268 return true;
269 }
270
271 /**
272 * Store user meta data
273 *
274 * @param array $data
275 *
276 * @return void
277 */
278 public function save_metadata( array $data = [] ) {
279 foreach ( $data as $key => $value ) {
280 if ( ! isset( $this->fillable[ $key ] ) ) {
281 continue;
282 }
283 $meta_key = $this->meta_prefix . $key;
284
285 update_user_meta( $this->id, $meta_key, $value );
286 }
287 }
288
289 /**
290 * Send password reset email
291 *
292 * @return void
293 */
294 public function retrieve_password() {
295 if ( 0 === intval( $this->status ) ) {
296 retrieve_password( $this->email );
297 }
298 }
299
300 /**
301 * Delete user
302 *
303 * @return boolean
304 */
305 public function delete(): bool {
306 $user = get_userdata( $this->id );
307
308 if ( ! $user ) {
309 return false;
310 }
311
312 if ( count( $user->roles ) == 1 && in_array( $this->role, $user->roles ) ) {
313 return wp_delete_user( $this->id );
314 }
315 $user->remove_role( $this->role );
316
317 return true;
318 }
319
320 /**
321 * Append user filter condition
322 *
323 * @param string $field
324 * @param $value
325 * @param string $compare
326 *
327 * @return $this
328 */
329 public function where( string $field, $value, string $compare = '=' ): self {
330 $this->filters[] = [
331 'field' => $field,
332 'value' => $value,
333 'compare' => $compare,
334 ];
335
336 return $this;
337 }
338
339 /**
340 * Add OR conditions in where
341 *
342 * @param string $field
343 * @param $value
344 * @param string $compare
345 *
346 * @return $this
347 */
348 public function or_where( string $field, $value, string $compare = '=' ): self {
349 $this->filters[] = [
350 'field' => $field,
351 'value' => $value,
352 'compare' => $compare,
353 'relation' => 'OR',
354 ];
355
356 return $this;
357 }
358
359 /**
360 * Conditionally apply query filters
361 *
362 * @param mixed $value
363 * @param callable $callback
364 * @param callable|null $default
365 *
366 * @return $this
367 */
368 public function when( $value, callable $callback, callable $default = null ): self {
369 if ( $value ) {
370 $callback( $this, $value );
371 } elseif ( $default ) {
372 $default( $this, $value );
373 }
374
375 return $this;
376 }
377
378 /**
379 * Get all users paginated data
380 *
381 * @param array $args
382 *
383 * @return array
384 */
385 public function all( array $args = [] ): array {
386 $defaults = [
387 'role' => $this->role,
388 'number' => 20,
389 'paged' => 1,
390 'orderby' => 'ID',
391 'order' => 'DESC',
392 ];
393
394 $args = wp_parse_args( $args, $defaults );
395
396 if ( ! empty( $this->filters ) ) {
397 $meta_query = [];
398 $relation = 'AND';
399
400 foreach ( $this->filters as $filter ) {
401 if ( in_array( $filter['field'], [
402 'user_email',
403 'user_login'
404 ] ) ) {
405 $args['search'] = '*' . esc_attr( $filter['value'] ) . '*';
406 $args['search_columns'] = [ $filter['field'] ];
407 } else {
408 $meta_query[] = [
409 'key' => $this->meta_prefix . $filter['field'],
410 'value' => $filter['value'],
411 'compare' => $filter['compare'],
412 ];
413 }
414
415 if ( isset( $filter['relation'] ) && $filter['relation'] === 'OR' ) {
416 $relation = 'OR';
417 }
418 }
419 if ( ! empty( $meta_query ) ) {
420 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering users by meta fields
421 $args['meta_query'] = [
422 'relation' => $relation,
423 ...$meta_query
424 ];
425 }
426 }
427
428 $users = new WP_User_Query( $args );
429 $items = [];
430 foreach ( $users->get_results() as $result ) {
431 $cloneObj = clone $this;
432 $userMeta = get_user_meta( $result->ID );
433
434 $items[] = $cloneObj->hydrate( $result->ID, array_merge(
435 (array) $result->data,
436 $userMeta
437 ) )->to_array();
438 }
439
440 $this->filters = [];
441
442 return [
443 'total' => $users->get_total(),
444 'per_page' => $args['number'],
445 'current_page' => $args['paged'],
446 'last_page' => ceil( $users->get_total() / $args['number'] ),
447 'items' => $items,
448 ];
449 }
450
451 /**
452 * Find user by id
453 *
454 * @param $id
455 *
456 * @return $this
457 */
458 public function find( $id ) {
459 $user = get_user_by( 'ID', $id );
460
461 if ( ! $user ) {
462 return null;
463 }
464
465 $meta = array_map( function ( $value ) {
466 return is_array( $value ) && count( $value ) === 1 ? $value[0] : $value;
467 }, get_user_meta( $user->ID ) );
468
469 $user_data = (array) $user->data;
470
471 return $this->hydrate( $id, array_merge( $user_data, $meta ) );
472 }
473
474 /**
475 * Hydrate each result with this class
476 *
477 * @param $id
478 * @param $result
479 *
480 * @return $this
481 */
482 public function hydrate( $id, $result ): self {
483 $this->set_id( $id );
484 if ( ! is_array( $result ) ) {
485 return $this;
486 }
487
488 foreach ( $result as $key => $value ) {
489 $key = str_replace( $this->meta_prefix, '', $key );
490 if ( ! isset( $this->fillable[ $key ] ) && ! in_array( $key, [
491 'user_email',
492 'email'
493 ] ) ) {
494 continue;
495 }
496 if ( is_array( $value ) ) {
497 $value = implode( ',', $value );
498 }
499
500 if ( $key === 'user_registered' ) {
501 $this->fillable['user_registered'] = ! empty( $result['user_registered'] ) ?
502 gmdate( 'j F, Y', strtotime( $result['user_registered'] ) ) : '';
503 continue;
504 }
505
506 $this->$key = empty( $value ) ? $this->fillable[ $key ] : $value;
507 }
508
509 return $this;
510 }
511
512 /**
513 * Generate username from email
514 *
515 * @param string $email
516 *
517 * @return string
518 */
519 public static function generate_username( string $email ) {
520 $username = strtok( $email, '@' );
521
522 if ( username_exists( $username ) ) {
523 $username = $username . wp_rand( 10, 100 );
524 }
525
526 return $username;
527 }
528
529 /**
530 * Convert User model object to array
531 *
532 * @return array
533 */
534 public function to_array(): array {
535 $data = [];
536
537 foreach ( $this->fillable as $key => $default ) {
538 if ( $key === 'user_email' ) {
539 $data['email'] = $this->fillable[$key];
540 continue;
541 }
542
543 $value = $this->$key ?? $this->fillable[ $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;
556 }
557 }
558
559 // Cast specific keys to objects
560 if ( 'schedule' == $key ) {
561 $value = (object) $value;
562 }
563
564 // Cast specific keys to boolean
565 if ( in_array( $key, [
566 'enable_custom_schedule',
567 'enable_custom_holiday',
568 'enable_custom_availability',
569 ] ) ) {
570 $value = filter_var( $value, FILTER_VALIDATE_BOOLEAN );
571 }
572
573 if ( 'user_registered' == $key ) {
574 $value = $default;
575 }
576
577 $data[ $key ] = $value ?? $default;
578 }
579
580 $data['ID'] = $this->id;
581
582 return $data;
583 }
584
585 /**
586 * Check if a string is serialized data
587 *
588 * @param mixed $data Data to check for serialization
589 *
590 * @return bool True if data is serialized, false otherwise
591 */
592 function is_serialized( $data ) {
593 return is_string( $data ) && @unserialize( $data ) !== false;
594 }
595
596 /**
597 * Specify data which should be serialized to JSON.
598 *
599 * @return array
600 */
601 public function jsonSerialize(): array {
602 return $this->to_array();
603 }
604
605 /**
606 * Get the first user matching the current filters.
607 *
608 * @param array $args
609 * @return self|null
610 */
611 public function first(array $args = []): ?self {
612 $args['number'] = 1;
613 $args['paged'] = 1;
614 $result = $this->all($args);
615 if (!empty($result['items'][0])) {
616 return $this->hydrate($result['items'][0]['ID'], $result['items'][0]);
617 }
618 return null;
619 }
620 }