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