PluginProbe ʕ •ᴥ•ʔ
LatePoint – Calendar Booking Plugin for Appointments and Events / 5.4.2
LatePoint – Calendar Booking Plugin for Appointments and Events v5.4.2
5.6.5 5.6.4 5.6.3 5.6.2 5.6.1 5.6.0 5.5.2 5.5.1 5.5.0 5.4.2 trunk 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.1.91 5.1.92 5.1.93 5.1.94 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1
latepoint / lib / models / model.php
latepoint / lib / models Last commit date
activity_model.php 3 months ago agent_meta_model.php 3 months ago agent_model.php 3 months ago booking_meta_model.php 3 months ago booking_model.php 3 months ago bundle_meta_model.php 3 months ago bundle_model.php 3 months ago cart_item_model.php 3 months ago cart_meta_model.php 3 months ago cart_model.php 3 months ago connector_model.php 3 months ago customer_meta_model.php 3 months ago customer_model.php 3 months ago invoice_model.php 3 months ago join_bundles_services_model.php 3 months ago location_category_model.php 3 months ago location_model.php 3 months ago meta_model.php 3 months ago model.php 3 months ago off_period_model.php 3 months ago order_intent_meta_model.php 3 months ago order_intent_model.php 3 months ago order_item_model.php 3 months ago order_meta_model.php 3 months ago order_model.php 3 months ago otp_model.php 3 months ago payment_request_model.php 3 months ago process_job_model.php 3 months ago process_model.php 3 months ago recurrence_model.php 3 months ago service_category_model.php 3 months ago service_meta_model.php 3 months ago service_model.php 3 months ago session_model.php 3 months ago settings_model.php 3 months ago step_settings_model.php 3 months ago transaction_intent_model.php 3 months ago transaction_model.php 3 months ago transaction_refund_model.php 3 months ago work_period_model.php 3 months ago
model.php
1176 lines
1 <?php
2
3 #[AllowDynamicProperties]
4 class OsModel {
5
6 protected $error,
7 $db;
8
9 public $nice_names = [];
10 protected $comparisons = array( '>=', '<=', '<', '>', '!=', 'LIKE' );
11 protected $conditions = [];
12 protected $limit = false;
13 protected $offset = false;
14 protected $select_args = [];
15 protected $order_args = false;
16 protected $group_args = false;
17 protected $having_args = false;
18 protected $joins = [];
19 public $data_vars = [];
20 public $first_level_data_vars = [];
21 public $form_id = false;
22 public $last_query = '';
23 protected $meta_class = false;
24 public $meta = false;
25 public $table_name = '';
26 public $join_attributes = [];
27
28 function __construct( $id = false ) {
29 $this->error = false;
30 global $wpdb;
31 $this->db = $wpdb;
32 if ( $id ) {
33 $this->load_by_id( $id );
34 }
35 }
36
37 public function __get( $property ) {
38 $method = "get_$property";
39 if ( method_exists( $this, $method ) ) {
40 return $this->$method();
41 }
42 }
43
44 public function exists() {
45 return ( isset( $this->id ) && ! empty( $this->id ) );
46 }
47
48 public function formatted_created_date( $format = false, $default = 'n/a' ) {
49 if ( ! $format ) {
50 $format = OsSettingsHelper::get_readable_date_format();
51 }
52 if ( property_exists( $this, 'created_at' ) && isset( $this->created_at ) && ! empty( $this->created_at ) ) {
53 $date = new OsWpDateTime( $this->created_at, new DateTimeZone( 'UTC' ) );
54
55 return $date->format( $format );
56 } else {
57 return $default;
58 }
59 }
60 public function readable_created_date(): string {
61 try {
62 return OsTimeHelper::get_readable_date( new OsWpDateTime( $this->created_at, new DateTimeZone( 'UTC' ) ) );
63 } catch ( Exception $e ) {
64 return 'n/a';
65 }
66 }
67
68 public function prepare( $query, $values ) {
69 if ( empty( $values ) ) {
70 return $query;
71 } else {
72 return $this->db->prepare( $query, $values );
73 }
74 }
75
76
77 /**
78 *
79 * Clears all GROUP BY arguments
80 *
81 * @return $this OsModel
82 */
83 public function clear_group_by(): OsModel {
84 $this->group_args = '';
85
86 return $this;
87 }
88
89 public function group_by( $group_args ) {
90 if ( $this->group_args ) {
91 $this->group_args = implode( ',', array( $this->group_args, $group_args ) );
92 } else {
93 $this->group_args = $group_args;
94 }
95
96 return $this;
97 }
98
99 public function get_group_args() {
100 if ( $this->group_args ) {
101 return 'GROUP BY ' . $this->group_args;
102 } else {
103 return '';
104 }
105 }
106
107 public function clear_having(): OsModel {
108 $this->having_args = '';
109
110 return $this;
111 }
112
113
114 public function get_having_args(): string {
115 if ( $this->having_args ) {
116 return 'HAVING ' . $this->having_args;
117 }
118 return '';
119 }
120
121 public function having( $having_args ) {
122 if ( $this->having_args ) {
123 $this->having_args = implode( ',', array( $this->having_args, $having_args ) );
124 } else {
125 $this->having_args = $having_args;
126 }
127
128 return $this;
129 }
130
131 public function order_by( $order_args ) {
132 if ( $this->order_args ) {
133 $this->order_args = implode( ',', array( $this->order_args, $order_args ) );
134 } else {
135 $this->order_args = $order_args;
136 }
137
138 return $this;
139 }
140
141 public function get_order_args() {
142 if ( $this->order_args ) {
143 return 'ORDER BY ' . $this->order_args;
144 } else {
145 return '';
146 }
147 }
148
149 public static function where_in_array_to_string( $array_of_values ) {
150 $clean_string = '';
151 if ( is_array( $array_of_values ) ) {
152 $array_of_values = array_map(
153 function ( $v ) {
154 return "'" . esc_sql( $v ) . "'";
155 },
156 $array_of_values
157 );
158 $clean_string = ' (' . implode( ',', $array_of_values ) . ') ';
159 }
160
161 return $clean_string;
162 }
163
164
165 /**
166 * @param array $conditions
167 *
168 * @return $this
169 */
170 public function filter_where_conditions( array $allowed_conditions ): OsModel {
171 foreach ( $allowed_conditions as $condition_name => $allowed_condition_value ) {
172 if ( empty( $this->conditions[ $condition_name ] ) ) {
173 $this->conditions[ $condition_name ] = $allowed_condition_value;
174 } else {
175 // convert both to arrays to compare
176 $current_value = is_array( $this->conditions[ $condition_name ] ) ? $this->conditions[ $condition_name ] : OsUtilHelper::explode_and_trim( $this->conditions[ $condition_name ] );
177 $allowed_value = is_array( $allowed_condition_value ) ? $allowed_condition_value : OsUtilHelper::explode_and_trim( $allowed_condition_value );
178 $this->conditions[ $condition_name ] = array_intersect( $current_value, $allowed_value );
179 }
180 }
181
182 return $this;
183 }
184
185 public function where( $conditions ) {
186 if ( empty( $conditions ) ) {
187 return $this;
188 }
189 $this->conditions = array_merge( $this->conditions, $conditions );
190
191 return $this;
192 }
193
194 public function where_in( $column, $array_of_values ) {
195 $condition = array( "{$column} IN " => $array_of_values );
196 $this->conditions = array_merge( $this->conditions, $condition );
197
198 return $this;
199 }
200
201 public function where_not_in( $column, $array_of_values ) {
202 $condition = array( "{$column} NOT IN " => $array_of_values );
203 $this->conditions = array_merge( $this->conditions, $condition );
204
205 return $this;
206 }
207
208 public function join( $table, $on_args, $type = '' ) {
209 $this->joins[] = [
210 'join_table' => $table,
211 'join_on_args' => $on_args,
212 'join_type' => in_array( $type, [ 'left', 'right' ] ) ? $type : '',
213 ];
214
215 return $this;
216 }
217
218 public function get_join_string(): string {
219 $join_query = '';
220 if ( ! empty( $this->joins ) ) {
221 foreach ( $this->joins as $join_data ) {
222 if ( empty( $join_data['join_table'] ) || empty( $join_data['join_on_args'] ) ) {
223 continue;
224 }
225 $join_query .= $join_data['join_type'] . ' JOIN ' . $join_data['join_table'] . ' ON ' . $this->build_join_args_query( $join_data['join_table'], $join_data['join_on_args'] );
226 }
227 }
228
229 return $join_query;
230 }
231
232 private function build_join_args_query( $join_table, $join_on_args ) {
233 $join_args_query_arr = [];
234 foreach ( $join_on_args as $column_one => $column_two ) {
235 if ( is_array( $column_two ) ) {
236 $in_values = implode( ',', $column_two );
237 $join_args_query_arr[] = "{$join_table}.{$column_one} IN ({$in_values})";
238 } else {
239 $join_args_query_arr[] = "{$join_table}.{$column_one} = {$column_two}";
240 }
241 }
242
243 return implode( ' AND ', $join_args_query_arr );
244 }
245
246
247 /**
248 *
249 * Clears all SELECT arguments
250 *
251 * @return $this OsModel
252 */
253 public function clear_select(): OsModel {
254 $this->select_args = [];
255
256 return $this;
257 }
258
259 /**
260 *
261 * Adds arguments to SELECT query
262 *
263 * @param $select_args Array|string or comma separated String of arguments
264 *
265 * @return $this OsModel
266 */
267 public function select( $select_args ): OsModel {
268 if ( ! is_array( $select_args ) ) {
269 $select_args = OsUtilHelper::explode_and_trim( $select_args );
270 }
271 if ( ! empty( $select_args ) ) {
272 $this->select_args = array_merge( $this->select_args, $select_args );
273 }
274
275 return $this;
276 }
277
278 public function build_select_args_string(): string {
279 $select_args = $this->get_select_args();
280 if ( empty( $select_args ) ) {
281 return '*';
282 } else {
283 return implode( ',', array_unique( $this->select_args ) );
284 }
285 }
286
287 public function get_select_args(): array {
288 return $this->select_args;
289 }
290
291 /**
292 * Eager load meta key-value pairs associated with this model
293 *
294 * @param array $meta_keys
295 *
296 * @return $this
297 */
298 public function with_meta( array $meta_keys = [] ): OsModel {
299 $this->meta = [];
300 $meta_class = $this->meta_class;
301
302 if ( $this->exists() && $meta_class && class_exists( $meta_class ) ) {
303 /** @var OsMetaModel $meta_object */
304 $meta_object = new $meta_class();
305 if ( ! empty( $meta_keys ) ) {
306 foreach ( $meta_keys as $meta_key ) {
307 $this->meta[] = [ $meta_key => $meta_object->get_by_key( $meta_key, $this->id ) ];
308 }
309 } else {
310 $this->meta = $meta_object->get_by_object_id( $this->id );
311 }
312 }
313
314 return $this;
315 }
316
317 public function set_limit( $limit ) {
318 $this->limit = $limit;
319
320 return $this;
321 }
322
323 public function count() {
324 $count = $this->clear_select()->clear_group_by()->clear_having()->select( 'COUNT(DISTINCT(' . $this->table_name . '.id)) as total' )->set_limit( 1 )->get_results();
325 $total = ( $count ) ? $count->total : 0;
326
327 return $total;
328 }
329
330
331 public function set_offset( $offset ) {
332 $this->offset = $offset;
333
334 return $this;
335 }
336
337 protected function with_table_name( $column ) {
338 if ( ! is_numeric( $column ) && ! in_array(
339 $column,
340 [
341 'AND',
342 'OR',
343 ]
344 ) && ( strpos( $column, '(' ) === false ) && ( strpos( $column, '.' ) === false ) ) {
345 return $this->table_name . '.' . $column;
346 } else {
347 return $column;
348 }
349 }
350
351 protected function build_conditions_query( $conditions, $logical_operator = 'AND' ) {
352 $where_conditions = [];
353 $where_values = [];
354 $sql_query = '';
355 $index = 0;
356 if ( $conditions ) {
357 foreach ( $conditions as $column => $value ) {
358 $temp_query = false;
359 if ( $column == 'OR' || $column == 'AND' ) {
360 $sql_query .= '(';
361 $conditions_and_values = $this->build_conditions_query( $value, $column );
362 $sql_query .= $conditions_and_values[0];
363 $where_values = array_merge( $where_values, $conditions_and_values[1] );
364 $sql_query .= ')';
365 } else {
366 // Check if its a comparison condition e.g. <, >, <=, >= etc...
367 foreach ( $this->comparisons as $comparison ) {
368 if ( strpos( $column, $comparison ) ) {
369 $column = str_replace( $comparison, '', $column );
370 $temp_query = $this->with_table_name( $column ) . $comparison . ' %s';
371 }
372 }
373 // WHERE IN query
374 if ( strpos( $column, ' NOT IN' ) && is_array( $value ) ) {
375 $temp_query = $this->with_table_name( $column ) . OsModel::where_in_array_to_string( $value );
376
377 } elseif ( strpos( $column, ' IN' ) && is_array( $value ) ) {
378 $temp_query = $this->with_table_name( $column ) . OsModel::where_in_array_to_string( $value );
379 } elseif ( is_array( $value ) && ( isset( $value['OR'] ) || isset( $value['AND'] ) ) ) {
380 // IS ARRAY AND OR
381 foreach ( $value as $condition_and_or => $condition_values ) {
382
383 $temp_query .= '(';
384 $sub_queries = [];
385 foreach ( $condition_values as $condition_key => $condition_value ) {
386 if ( is_string( $condition_key ) && is_string( $column ) ) {
387 $temp_key = $this->with_table_name( $column ) . $condition_key;
388 $sub_conditions = [ $temp_key => $condition_value ];
389 } elseif ( is_string( $condition_key ) ) {
390 $sub_conditions = [ $this->with_table_name( $condition_key ) => $condition_value ];
391 } else {
392 $sub_conditions = [ $column => $condition_value ];
393 }
394 $conditions_and_values = $this->build_conditions_query( $sub_conditions, $condition_and_or );
395 $sub_queries[] = $conditions_and_values[0];
396 $where_values = array_merge( $where_values, $conditions_and_values[1] );
397 }
398 $temp_query .= implode( ' ' . $condition_and_or . ' ', $sub_queries );
399 $temp_query .= ')';
400 }
401 } elseif ( $value === 'IS NULL' ) {
402 // IS NULL
403 $temp_query = $this->with_table_name( $column ) . ' IS NULL ';
404 } elseif ( $value === 'IS NOT NULL' ) {
405 // IS NOT NULL
406 $temp_query = $this->with_table_name( $column ) . ' IS NOT NULL ';
407 } elseif ( is_array( $value ) && ! empty( $value ) ) {
408 $temp_query = $this->with_table_name( $column ) . ' IN ' . OsModel::where_in_array_to_string( $value );
409 } else {
410 // Add to list of query values
411 if ( is_array( $value ) ) {
412 $where_values[] = OsModel::where_in_array_to_string( $value );
413 } else {
414 $where_values[] = $value;
415 }
416 }
417 if ( $temp_query ) {
418 $sql_query .= $temp_query;
419 } else {
420 $sql_query .= $this->with_table_name( $column ) . '= %s';
421 }
422 }
423 $index++;
424 if ( $index < count( $conditions ) ) {
425 $sql_query .= ' ' . $logical_operator . ' ';
426 }
427 }
428 }
429
430 return array( $sql_query, $where_values );
431 }
432
433
434 public function escape_by_ref( &$string ) {
435 $this->db->escape_by_ref( $string );
436 }
437
438 public function get_results( $results_type = OBJECT ) {
439 $conditions_and_values = $this->build_conditions_query( $this->conditions );
440 if ( $conditions_and_values[0] ) {
441 $where_query = 'WHERE ' . $conditions_and_values[0];
442 } else {
443 $where_query = '';
444 }
445 if ( $this->limit ) {
446 $limit_query = ' LIMIT %d';
447 $conditions_and_values[1][] = $this->limit;
448 } else {
449 $limit_query = '';
450 }
451
452
453 if ( $this->offset ) {
454 $offset_query = ' OFFSET %d';
455 $conditions_and_values[1][] = $this->offset;
456 } else {
457 $offset_query = '';
458 }
459
460 $query = 'SELECT ' . $this->build_select_args_string() . ' FROM ' . $this->table_name . ' ' . $this->get_join_string() . ' ' . $where_query . ' ' . $this->get_group_args() . ' ' . $this->get_having_args() . ' ' . $this->get_order_args() . ' ' . $limit_query . ' ' . $offset_query;
461
462 $this->last_query = vsprintf( $query, $conditions_and_values[1] );
463 OsDebugHelper::log_query( $this->last_query );
464
465 $items = $this->db->get_results(
466 $this->prepare( $query, $conditions_and_values[1] ),
467 $results_type
468 );
469
470 if ( ( $this->limit == 1 ) && isset( $items[0] ) ) {
471 $items = $items[0];
472 }
473
474 return $items;
475 }
476
477
478 public function get_query_results( $query, $values = [], $results_type = OBJECT ) {
479 $this->last_query = $query;
480 $items = $this->db->get_results(
481 $this->prepare( $query, $values ),
482 $results_type
483 );
484 OsDebugHelper::log_query( $query );
485
486 return $items;
487 }
488
489
490 public function reset_conditions() {
491 $this->conditions = [];
492 }
493
494
495 /**
496 * @param $query
497 * @param $values
498 *
499 * @return static|static[]
500 */
501 public function get_results_as_models( $query = false, $values = [] ) {
502 if ( $query ) {
503 $items = $this->get_query_results( $query, $values );
504 } else {
505 $items = $this->get_results();
506 }
507 $models = [];
508 if ( empty( $items ) ) {
509 return [];
510 }
511 if ( $this->limit == 1 ) {
512 $items = [ $items ];
513 }
514 foreach ( $items as $item ) {
515 $current_class_name = get_class( $this );
516 $model = new $current_class_name();
517 foreach ( $item as $prop_name => $prop_value ) {
518 $model->$prop_name = $prop_value;
519 }
520 /**
521 * A child of <code>OsModel</code> is about to be added to the result set
522 *
523 * @param {OsModel} $model Instance of model that should be filtered
524 * @returns {OsModel} Instance of model that has been filtered
525 *
526 * @since 1.0.0
527 * @hook latepoint_get_results_as_models
528 *
529 */
530 $model = apply_filters( 'latepoint_get_results_as_models', $model );
531 if ( $model ) {
532 $models[] = $model;
533 }
534 }
535 $this->reset_conditions();
536 if ( $this->limit == 1 && isset( $models[0] ) ) {
537 $models = $models[0];
538 }
539
540 return $models;
541 }
542
543 public function filter_allowed_records(): OsModel {
544 return $this;
545 }
546
547 public function get_image_url( $size = 'thumbnail' ) {
548 $url = OsImageHelper::get_image_url_by_id( $this->image_id, $size );
549
550 return $url;
551 }
552
553 public function set_data( $data, $role = 'admin', $sanitize = true ) {
554 $data = $this->prepare_data_before_it_is_set( $data );
555 /**
556 * Data/Params are being prepared to be set on a child of <code>OsModel</code>
557 *
558 * @param {OsModel} $this Instance of model that data is to be set on
559 * @param {array} $data Array of data/params to be set
560 *
561 * @since 1.0.0
562 * @hook latepoint_model_prepare_set_data
563 *
564 */
565 do_action( 'latepoint_model_prepare_set_data', $this, $data );
566 if ( is_array( $data ) ) {
567 // array passed
568 // if ID is passed and model not loaded from db yet - load data from db
569 if ( isset( $data['id'] ) && is_numeric( $data['id'] ) && property_exists( $this, 'id' ) && $this->is_new_record() ) {
570 $this->load_by_id( $data['id'] );
571 }
572 foreach ( $this->get_allowed_params( $role ) as $param ) {
573 if ( isset( $data[ $param ] ) ) {
574 $this->$param = $sanitize ? $this->sanitize_param( $param, $data[ $param ] ) : $data[ $param ];
575 }
576 }
577 } else {
578 // object passed
579 // if ID is passed and model not loaded from db yet - load data from db
580 if ( isset( $data->id ) && is_numeric( $data->id ) && property_exists( $this, 'id' ) && $this->is_new_record() ) {
581 $this->load_by_id( $data->id );
582 }
583 foreach ( $this->get_allowed_params( $role ) as $param ) {
584 if ( isset( $data->$param ) ) {
585 $this->$param = $sanitize ? $this->sanitize_param( $param, $data->$param ) : $data->$param;
586 }
587 }
588 }
589 /**
590 * Data/Params have been set on a child of <code>OsModel</code>
591 *
592 * @param {OsModel} $this Instance of model that data was set on
593 * @param {array} $data Array of data/params that was set
594 *
595 * @since 1.0.0
596 * @hook latepoint_model_set_data
597 *
598 */
599 do_action( 'latepoint_model_set_data', $this, $data );
600 $this->after_data_was_set( $data );
601
602 return $this;
603 }
604
605 /**
606 * @return void
607 *
608 * Useful for child classes, to do something after a data is set
609 */
610 public function after_data_was_set( $data ) {
611 }
612
613
614 /**
615 * @return void
616 *
617 * Useful for child classes, to do something after a data is set
618 */
619 public function prepare_data_before_it_is_set( $data ) {
620 return $data;
621 }
622
623
624 public function delete_where( $where = false, $where_format = null ) {
625 if ( is_array( $where ) && $this->db->delete( $this->table_name, $where, $where_format ) ) {
626 return true;
627 } else {
628 return false;
629 }
630 }
631
632 public function delete( $id = false ) {
633 if ( ! $id && isset( $this->id ) ) {
634 $id = $this->id;
635 }
636 if ( $id && $this->db->delete( $this->table_name, array( 'id' => $id ), array( '%d' ) ) ) {
637 /**
638 * A child of <code>OsModel</code> has been deleted
639 *
640 * @param {OsModel} $this Instance of model that has been deleted
641 * @param {integer} $id ID of model instance that has been deleted
642 *
643 * @since 4.6.3
644 * @hook latepoint_model_deleted
645 *
646 */
647 do_action( 'latepoint_model_deleted', $this, $id );
648
649 return true;
650 } else {
651 return false;
652 }
653 }
654
655
656 public function load_from_row_data( $row_data ) {
657 foreach ( $row_data as $key => $field ) {
658 if ( property_exists( $this, $key ) ) {
659 $this->$key = $field;
660 }
661 }
662 }
663
664 public function load_by_id( $id ) {
665 if ( filter_var( $id, FILTER_VALIDATE_INT ) === false ) {
666 return false;
667 }
668 $query = $this->prepare( 'SELECT ' . $this->build_select_args_string() . ' FROM ' . $this->table_name . ' WHERE id = %d', $id );
669 $result_row = $this->db->get_row( $query, ARRAY_A );
670
671 if ( $result_row ) {
672 foreach ( $result_row as $row_key => $row_value ) {
673 if ( property_exists( $this, $row_key ) ) {
674 $this->$row_key = $row_value;
675 }
676 }
677
678 /**
679 * A child of <code>OsModel</code> has been loaded from the DB by its ID
680 *
681 * @param {OsModel} $this Instance of model that has been loaded
682 * @returns {OsModel} Instance of model that has been filtered
683 *
684 * @since 1.0.0
685 * @hook latepoint_model_loaded_by_id
686 *
687 */
688 return apply_filters( 'latepoint_model_loaded_by_id', $this );
689 } else {
690 return false;
691 }
692 }
693
694
695 /**
696 *
697 * Generates an ID that is used in a form for quick editing. Returns ID if exists or returns a "new_HASH" to be used
698 * as ID to indicate that it's a new record
699 *
700 * @return string
701 */
702 public function get_form_id(): string {
703 if ( $this->is_new_record() ) {
704 if ( empty( $this->form_id ) ) {
705 $this->form_id = OsUtilHelper::generate_form_id();
706 }
707 } else {
708 $this->form_id = $this->id;
709 }
710
711 return $this->form_id;
712 }
713
714
715 public function is_new_record() {
716 if ( $this->id ) {
717 return false;
718 } else {
719 return true;
720 }
721 }
722
723 public function get_field( $field_name ) {
724 return $this->$field_name;
725 }
726
727 public function set_field( $field_name, $field_value ) {
728 $this->$field_name = $field_value;
729 }
730
731 protected function before_save() {
732 }
733
734 protected function before_create() {
735 }
736
737 // updates array of attributes
738 public function update_attributes( $data, $sanitize = true ) {
739 if ( $this->is_new_record() ) {
740 return false;
741 }
742 $prepared_data = [];
743 foreach ( $data as $key => $value ) {
744 if ( property_exists( $this, $key ) ) {
745 if ( $sanitize && array_key_exists( $key, $this->params_to_sanitize() ) ) {
746 $value = OsParamsHelper::sanitize_param( $value, $this->params_to_sanitize()[ $key ] );
747 }
748 $this->$key = $value;
749 // encrypt value if it needs to be encrypted, however the model object itself stores an un-encrypted value
750 if ( in_array( $key, $this->encrypted_params() ) ) {
751 $value = OsEncryptHelper::encrypt_value();
752 }
753 $prepared_data[ $key ] = $value;
754 }
755 }
756 if ( empty( $prepared_data ) ) {
757 return false;
758 } else {
759 $now = OsTimeHelper::now_datetime_in_format( LATEPOINT_DATETIME_DB_FORMAT );
760 if ( property_exists( $this, 'updated_at' ) ) {
761 $prepared_data['updated_at'] = $now;
762 }
763 if ( false === $this->db->update( $this->table_name, $prepared_data, array( 'id' => $this->id ) ) ) {
764 $this->add_error( 'update_error', $this->db->last_error );
765
766 return false;
767 } else {
768 if ( property_exists( $this, 'updated_at' ) ) {
769 $this->updated_at = $now;
770 }
771 OsDebugHelper::log_query( $this->db->last_query );
772
773 return true;
774 }
775 }
776 }
777
778 protected function set_defaults() {
779 }
780
781 // searches list of params that need to be sanitised and returns sanitised value
782 protected function sanitize_param( $param_name, $value ) {
783 if ( $this->params_to_sanitize() && is_array( $this->params_to_sanitize() ) && array_key_exists( $param_name, $this->params_to_sanitize() ) ) {
784 $value = OsParamsHelper::sanitize_param( $value, $this->params_to_sanitize()[ $param_name ] );
785 }
786
787 return $value;
788 }
789
790 public function prepare_and_validate( $alternative_validation = false, $skip_properties = [] ): bool {
791 try {
792 $this->set_defaults();
793 $this->before_save();
794 if ( $this->validate( $alternative_validation, $skip_properties ) ) {
795 return true;
796 } else {
797 return false;
798 }
799 } catch ( Exception $e ) {
800 $this->add_error( 'validate_exception', $e->getMessage() );
801
802 return false;
803 }
804 }
805
806 public function save( $alternative_validation = false, $skip_validation = false ) {
807 try {
808 $this->set_defaults();
809 $this->before_save();
810 if ( $skip_validation || $this->validate( $alternative_validation ) ) {
811 if ( property_exists( $this, 'updated_at' ) ) {
812 $this->updated_at = OsTimeHelper::now_datetime_in_format( LATEPOINT_DATETIME_DB_FORMAT );
813 }
814 if ( $this->is_new_record() ) {
815 // New Record (insert)
816 $this->before_create();
817 if ( property_exists( $this, 'created_at' ) ) {
818 $this->created_at = OsTimeHelper::now_datetime_in_format( LATEPOINT_DATETIME_DB_FORMAT );
819 }
820 if ( false === $this->db->insert( $this->table_name, $this->get_params_to_save_with_values() ) && property_exists( $this, 'id' ) ) {
821 $this->add_error( 'insert_error', $this->db->last_error );
822
823 return false;
824 } else {
825 OsDebugHelper::log_query( $this->db->last_query );
826 $this->id = $this->db->insert_id;
827 }
828 } else {
829 // Existing record (update)
830 if ( false === $this->db->update( $this->table_name, $this->get_params_to_save_with_values(), array( 'id' => $this->id ) ) ) {
831 $this->add_error( 'update_error', $this->db->last_error );
832
833 return false;
834 } else {
835 OsDebugHelper::log_query( $this->db->last_query );
836 }
837 }
838 /**
839 * A child of <code>OsModel</code> has been saved to the DB
840 *
841 * @param {OsModel} $this Instance of model that has been saved
842 *
843 * @since 1.0.0
844 * @hook latepoint_model_save
845 *
846 */
847 do_action( 'latepoint_model_save', $this );
848 } else {
849 return false;
850 }
851
852 return true;
853 } catch ( Exception $e ) {
854 $this->add_error( 'save_exception', $e->getMessage() );
855
856 return false;
857 }
858 }
859
860
861 protected function get_property_nice_name( $property ) {
862 if ( isset( $this->nice_names[ $property ] ) ) {
863 return $this->nice_names[ $property ];
864 } else {
865 return ucwords( str_replace( '_', ' ', $property ) );
866 }
867 }
868
869 protected function get_params_to_save_with_values( $role = 'admin' ) {
870 $params_to_save = $this->get_params_to_save( $role );
871 $params_to_save_with_values = [];
872
873 foreach ( $params_to_save as $param_name ) {
874 if ( property_exists( $this, $param_name ) ) {
875 if ( $param_name == 'id' && empty( $this->id ) ) {
876 // ignore this param if its ID and is not set
877 } else {
878 $params_to_save_with_values[ $param_name ] = $this->prepare_param( $param_name, $this->$param_name );
879 }
880 }
881 }
882 if ( property_exists( $this, 'updated_at' ) && isset( $this->updated_at ) ) {
883 $params_to_save_with_values['updated_at'] = $this->updated_at;
884 }
885 if ( property_exists( $this, 'created_at' ) && isset( $this->created_at ) ) {
886 $params_to_save_with_values['created_at'] = $this->created_at;
887 }
888
889 return $params_to_save_with_values;
890 }
891
892
893 protected function is_encrypted_param( $param_name ) {
894 return in_array( $param_name, $this->encrypted_params( $param_name ) );
895 }
896
897 protected function prepare_param( $param_name, $value ) {
898 if ( ! empty( $value ) ) {
899 if ( $this->is_encrypted_param( $param_name ) ) {
900 $value = OsEncryptHelper::encrypt_value( $value );
901 } else {
902 $value = $value;
903 }
904 }
905
906 return $value;
907 }
908
909 protected function encrypted_params() {
910 return [];
911 }
912
913 protected function params_to_sanitize() {
914 return [];
915 }
916
917 public function generate_first_level_data_vars(): array {
918 return [];
919 }
920
921 public function generate_data_vars(): array {
922 return [];
923 }
924
925 public function get_data_vars( $force_regenerate = false ): array {
926 $data = ( $force_regenerate || empty( $this->data_vars ) ) ? $this->generate_data_vars() : $this->data_vars;
927
928 return apply_filters( 'latepoint_model_view_as_data', $data, $this );
929 }
930
931 public function get_first_level_data_vars( $force_regenerate = false ): array {
932 $data = ( $force_regenerate || empty( $this->first_level_data_vars ) ) ? $this->generate_first_level_data_vars() : $this->first_level_data_vars;
933
934 return apply_filters( 'latepoint_model_view_as_first_level_data', $data, $this );
935 }
936
937 protected function properties_to_query(): array {
938 $properties = [];
939
940 return $properties;
941 }
942
943 public function get_properties_to_query(): array {
944 $properties = $this->properties_to_query();
945
946 /**
947 * List of model properties that are allowed to be queried by the condition form in processes
948 *
949 * @param {array} $properties List of model properties allowed to be queried
950 * @param {OsModel} $this Instance of model that properties will be available for
951 * @returns {array} List of model properties that are allowed to be queried
952 *
953 * @since 4.7.0
954 * @hook latepoint_model_properties_to_query
955 *
956 */
957 return apply_filters( 'latepoint_model_properties_to_query', $properties, $this );
958 }
959
960 // params that are allowed to be mass assigned using set_data method
961 protected function allowed_params( $role = 'admin' ) {
962 $allowed_params = [];
963
964 return $allowed_params;
965 }
966
967 protected function params_to_save( $role = 'admin' ) {
968 $allowed_params = [];
969
970 return $allowed_params;
971 }
972
973 public function get_params_to_save( $role = 'admin' ) {
974 return $this->params_to_save( $role );
975 }
976
977 public function get_allowed_params( $role = 'admin' ) {
978 $allowed_params = $this->allowed_params( $role );
979
980 /**
981 * List of model params that are allowed to be mass assigned to a child of <code>OsModel</code>
982 *
983 * @param {array} $allowed_params List of model params being filtered
984 * @param {OsModel} $this Instance of model that the allowed params apply to
985 * @param {string} $role User role that the allowed params apply to
986 * @returns {array} List of model params that are allowed to be mass assigned
987 *
988 * @since 1.0.0
989 * @hook latepoint_model_allowed_params
990 *
991 */
992 return apply_filters( 'latepoint_model_allowed_params', $allowed_params, $this, $role );
993 }
994
995
996
997
998
999
1000 // -------------------------
1001 // Error handling
1002 // -------------------------
1003
1004
1005 // CLEAR
1006 protected function clear_error() {
1007 $this->error = false;
1008 }
1009
1010
1011 // ADD
1012 public function add_error( $code, $error_message = 'Field is not valid.', $data = '' ) {
1013 if ( is_array( $error_message ) ) {
1014 $error_message = implode( ', ', $error_message );
1015 }
1016 if ( is_wp_error( $this->get_error() ) ) {
1017 $this->get_error()->add( $code, $error_message, $data );
1018 } else {
1019 $this->error = new WP_Error( $code, $error_message, $data );
1020 }
1021 }
1022
1023
1024 // GET DATA
1025 public function get_error_data( $code ) {
1026 if ( is_wp_error( $this->get_error() ) ) {
1027 return $this->get_error()->get_error_data( $code );
1028 } else {
1029 return false;
1030 }
1031 }
1032
1033 // GET
1034 public function get_error() {
1035 return $this->error;
1036 }
1037
1038
1039 // CHECK
1040 public function has_validation_error() {
1041 if ( is_wp_error( $this->get_error() ) && $this->get_error()->get_error_messages( 'validation' ) ) {
1042 return true;
1043 } else {
1044 return false;
1045 }
1046 }
1047
1048
1049 // GET MESSAGES
1050 public function get_error_messages( $code = false ) {
1051 if ( is_wp_error( $this->get_error() ) ) {
1052 return $this->get_error()->get_error_messages( $code );
1053 } else {
1054 return [];
1055 }
1056 }
1057
1058
1059
1060
1061 // -------------------------
1062 // Validations
1063 // -------------------------
1064
1065 public function validate( $alternative_validation = false, $skip_properties = [] ): bool {
1066 $this->clear_error();
1067 $properties_to_validate = $this->properties_to_validate( $alternative_validation );
1068 foreach ( $properties_to_validate as $property_name => $validations ) {
1069 if ( $skip_properties && in_array( $property_name, $skip_properties ) ) {
1070 continue;
1071 }
1072 foreach ( $validations as $validation ) {
1073 $validation_function = 'validates_' . $validation;
1074 if ( ! method_exists( $this, $validation_function ) ) {
1075 continue;
1076 }
1077 $validation_result = $this->$validation_function( $property_name );
1078 if ( is_wp_error( $validation_result ) ) {
1079 $this->add_error( 'validation', $validation_result->get_error_message( $property_name ) );
1080 }
1081 }
1082 }
1083 /**
1084 * Custom validations to apply to a child of <code>OsModel</code>
1085 *
1086 * @param {OsModel} $this Instance of model to apply custom validations to
1087 * @param {bool} $alternative_validation True if applying alternative validations, false otherwise
1088 *
1089 * @since 1.0.0
1090 * @hook latepoint_model_validate
1091 *
1092 */
1093 do_action( 'latepoint_model_validate', $this, $alternative_validation, $skip_properties );
1094 if ( $this->has_validation_error() ) {
1095 return false;
1096 } else {
1097 return true;
1098 }
1099 }
1100
1101
1102 protected function properties_to_validate() {
1103 return [];
1104 }
1105
1106 protected function validates_email( $property ) {
1107 if ( empty( $this->$property ) || OsUtilHelper::is_valid_email( $this->$property ) ) {
1108 return true;
1109 } else {
1110 // translators: %s is the property name for a model
1111 return new WP_Error( $property, sprintf( __( '%s is not valid', 'latepoint' ), $this->get_property_nice_name( $property ) ) );
1112 }
1113 }
1114
1115 protected function validates_presence( $property ) {
1116 $validation_result = ( isset( $this->$property ) && ! empty( $this->$property ) );
1117 if ( $validation_result ) {
1118 return true;
1119 } else {
1120 // translators: %s is the property name for a model
1121 return new WP_Error( $property, sprintf( __( '%s can not be blank', 'latepoint' ), $this->get_property_nice_name( $property ) ) );
1122 }
1123 }
1124
1125 protected function validates_uniqueness( $property ) {
1126 if ( isset( $this->$property ) && ! empty( $this->$property ) ) {
1127 if ( $this->is_new_record() ) {
1128 $query = $this->prepare(
1129 'SELECT %i FROM %i WHERE %i = %s LIMIT 1',
1130 [
1131 $property,
1132 $this->table_name,
1133 $property,
1134 $this->$property,
1135 ]
1136 );
1137 } else {
1138 $query = $this->prepare(
1139 'SELECT %i FROM %i WHERE %i = %s AND id != %d LIMIT 1',
1140 [
1141 $property,
1142 $this->table_name,
1143 $property,
1144 $this->$property,
1145 $this->id,
1146 ]
1147 );
1148 }
1149 $items = $this->db->get_results( $query, ARRAY_A );
1150 if ( $items ) {
1151 // translators: %s is the property name for a model
1152 return new WP_Error( $property, sprintf( __( '%s has to be unique', 'latepoint' ), $this->get_property_nice_name( $property ) ) );
1153 }
1154 }
1155
1156 return true;
1157 }
1158
1159 public function get_validations_for_property( string $property ): array {
1160 $validations = $this->properties_to_validate();
1161
1162 return $validations[ $property ] ?? [];
1163 }
1164
1165
1166 public function format_created_datetime_rfc3339() {
1167 $datetime = OsTimeHelper::date_from_db( $this->created_at );
1168 if ( ! $datetime ) {
1169 return 'invalid date';
1170 }
1171 $datetime->setTimezone( new DateTimeZone( 'UTC' ) );
1172
1173 return $datetime->format( \DateTime::RFC3339 );
1174 }
1175 }
1176