PluginProbe ʕ •ᴥ•ʔ
LatePoint – Calendar Booking Plugin for Appointments and Events / 5.1.8
LatePoint – Calendar Booking Plugin for Appointments and Events v5.1.8
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 1 year ago agent_meta_model.php 1 year ago agent_model.php 1 year ago booking_meta_model.php 1 year ago booking_model.php 1 year ago bundle_model.php 1 year 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 1 year ago invoice_model.php 1 year ago join_bundles_services_model.php 1 year ago location_category_model.php 1 year ago location_model.php 1 year ago meta_model.php 1 year ago model.php 1 year ago off_period_model.php 1 year ago order_intent_meta_model.php 1 year ago order_intent_model.php 1 year ago order_item_model.php 1 year ago order_meta_model.php 1 year ago order_model.php 1 year 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 1 year ago service_meta_model.php 1 year ago service_model.php 1 year 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
1147 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 save( $alternative_validation = false, $skip_validation = false ) {
787 try {
788 $this->set_defaults();
789 $this->before_save();
790 if ( $skip_validation || $this->validate( $alternative_validation ) ) {
791 if ( property_exists( $this, 'updated_at' ) ) {
792 $this->updated_at = OsTimeHelper::now_datetime_in_format( LATEPOINT_DATETIME_DB_FORMAT );
793 }
794 if ( $this->is_new_record() ) {
795 // New Record (insert)
796 $this->before_create();
797 if ( property_exists( $this, 'created_at' ) ) {
798 $this->created_at = OsTimeHelper::now_datetime_in_format( LATEPOINT_DATETIME_DB_FORMAT );
799 }
800 if ( false === $this->db->insert( $this->table_name, $this->get_params_to_save_with_values() ) && property_exists( $this, 'id' ) ) {
801 $this->add_error( 'insert_error', $this->db->last_error );
802
803 return false;
804 } else {
805 OsDebugHelper::log_query( $this->db->last_query );
806 $this->id = $this->db->insert_id;
807 }
808 } else {
809 // Existing record (update)
810 if ( false === $this->db->update( $this->table_name, $this->get_params_to_save_with_values(), array( 'id' => $this->id ) ) ) {
811 $this->add_error( 'update_error', $this->db->last_error );
812
813 return false;
814 } else {
815 OsDebugHelper::log_query( $this->db->last_query );
816 }
817 }
818 /**
819 * A child of <code>OsModel</code> has been saved to the DB
820 *
821 * @param {OsModel} $this Instance of model that has been saved
822 *
823 * @since 1.0.0
824 * @hook latepoint_model_save
825 *
826 */
827 do_action( 'latepoint_model_save', $this );
828 } else {
829 return false;
830 }
831
832 return true;
833 } catch ( Exception $e ) {
834 $this->add_error( 'save_exception', $e->getMessage() );
835
836 return false;
837 }
838 }
839
840
841 protected function get_property_nice_name( $property ) {
842 if ( isset( $this->nice_names[ $property ] ) ) {
843 return $this->nice_names[ $property ];
844 } else {
845 return ucwords( str_replace( "_", " ", $property ) );
846 }
847 }
848
849 protected function get_params_to_save_with_values( $role = 'admin' ) {
850 $params_to_save = $this->get_params_to_save( $role );
851 $params_to_save_with_values = [];
852
853 foreach ( $params_to_save as $param_name ) {
854 if ( property_exists( $this, $param_name ) ) {
855 if ( $param_name == 'id' && empty( $this->id ) ) {
856 // ignore this param if its ID and is not set
857 } else {
858 $params_to_save_with_values[ $param_name ] = $this->prepare_param( $param_name, $this->$param_name );
859 }
860 }
861 }
862 if ( property_exists( $this, 'updated_at' ) && isset( $this->updated_at ) ) {
863 $params_to_save_with_values['updated_at'] = $this->updated_at;
864 }
865 if ( property_exists( $this, 'created_at' ) && isset( $this->created_at ) ) {
866 $params_to_save_with_values['created_at'] = $this->created_at;
867 }
868
869 return $params_to_save_with_values;
870 }
871
872
873 protected function is_encrypted_param( $param_name ) {
874 return in_array( $param_name, $this->encrypted_params( $param_name ) );
875 }
876
877 protected function prepare_param( $param_name, $value ) {
878 if ( ! empty( $value ) ) {
879 if ( $this->is_encrypted_param( $param_name ) ) {
880 $value = OsEncryptHelper::encrypt_value( $value );
881 } else {
882 $value = $value;
883 }
884 }
885
886 return $value;
887 }
888
889 protected function encrypted_params() {
890 return [];
891 }
892
893 protected function params_to_sanitize() {
894 return [];
895 }
896
897 public function generate_first_level_data_vars() : array{
898 return [];
899 }
900
901 public function generate_data_vars(): array {
902 return [];
903 }
904
905 public function get_data_vars( $force_regenerate = false ): array {
906 $data = ( $force_regenerate || empty( $this->data_vars ) ) ? $this->generate_data_vars() : $this->data_vars;
907
908 return apply_filters( 'latepoint_model_view_as_data', $data, $this );
909 }
910
911 public function get_first_level_data_vars( $force_regenerate = false ): array {
912 $data = ( $force_regenerate || empty( $this->first_level_data_vars ) ) ? $this->generate_first_level_data_vars() : $this->first_level_data_vars;
913
914 return apply_filters( 'latepoint_model_view_as_first_level_data', $data, $this );
915 }
916
917 protected function properties_to_query(): array {
918 $properties = [];
919
920 return $properties;
921 }
922
923 public function get_properties_to_query(): array {
924 $properties = $this->properties_to_query();
925
926 /**
927 * List of model properties that are allowed to be queried by the condition form in processes
928 *
929 * @param {array} $properties List of model properties allowed to be queried
930 * @param {OsModel} $this Instance of model that properties will be available for
931 * @returns {array} List of model properties that are allowed to be queried
932 *
933 * @since 4.7.0
934 * @hook latepoint_model_properties_to_query
935 *
936 */
937 return apply_filters( 'latepoint_model_properties_to_query', $properties, $this );
938 }
939
940 // params that are allowed to be mass assigned using set_data method
941 protected function allowed_params( $role = 'admin' ) {
942 $allowed_params = [];
943
944 return $allowed_params;
945 }
946
947 protected function params_to_save( $role = 'admin' ) {
948 $allowed_params = [];
949
950 return $allowed_params;
951 }
952
953 public function get_params_to_save( $role = 'admin' ) {
954 return $this->params_to_save( $role );
955 }
956
957 public function get_allowed_params( $role = 'admin' ) {
958 $allowed_params = $this->allowed_params( $role );
959
960 /**
961 * List of model params that are allowed to be mass assigned to a child of <code>OsModel</code>
962 *
963 * @param {array} $allowed_params List of model params being filtered
964 * @param {OsModel} $this Instance of model that the allowed params apply to
965 * @param {string} $role User role that the allowed params apply to
966 * @returns {array} List of model params that are allowed to be mass assigned
967 *
968 * @since 1.0.0
969 * @hook latepoint_model_allowed_params
970 *
971 */
972 return apply_filters( 'latepoint_model_allowed_params', $allowed_params, $this, $role );
973 }
974
975
976
977
978
979
980 // -------------------------
981 // Error handling
982 // -------------------------
983
984
985 // CLEAR
986 protected function clear_error() {
987 $this->error = false;
988 }
989
990
991 // ADD
992 public function add_error( $code, $error_message = 'Field is not valid.', $data = '' ) {
993 if ( is_array( $error_message ) ) {
994 $error_message = implode( ', ', $error_message );
995 }
996 if ( is_wp_error( $this->get_error() ) ) {
997 $this->get_error()->add( $code, $error_message, $data );
998 } else {
999 $this->error = new WP_Error( $code, $error_message, $data );
1000 }
1001 }
1002
1003
1004 // GET DATA
1005 public function get_error_data( $code ) {
1006 if ( is_wp_error( $this->get_error() ) ) {
1007 return $this->get_error()->get_error_data( $code );
1008 } else {
1009 return false;
1010 }
1011 }
1012
1013 // GET
1014 public function get_error() {
1015 return $this->error;
1016 }
1017
1018
1019 // CHECK
1020 public function has_validation_error() {
1021 if ( is_wp_error( $this->get_error() ) && $this->get_error()->get_error_messages( 'validation' ) ) {
1022 return true;
1023 } else {
1024 return false;
1025 }
1026 }
1027
1028
1029 // GET MESSAGES
1030 public function get_error_messages( $code = false ) {
1031 if ( is_wp_error( $this->get_error() ) ) {
1032 return $this->get_error()->get_error_messages( $code );
1033 } else {
1034 return [];
1035 }
1036 }
1037
1038
1039
1040
1041 // -------------------------
1042 // Validations
1043 // -------------------------
1044
1045 public function validate( $alternative_validation = false, $skip_properties = [] ) : bool {
1046 $this->clear_error();
1047 foreach ( $this->properties_to_validate( $alternative_validation ) as $property_name => $validations ) {
1048 if($skip_properties && in_array( $property_name, $skip_properties )) continue;
1049 foreach ( $validations as $validation ) {
1050 $validation_function = 'validates_' . $validation;
1051 if ( ! method_exists( $this, $validation_function ) ) {
1052 continue;
1053 }
1054 $validation_result = $this->$validation_function( $property_name );
1055 if ( is_wp_error( $validation_result ) ) {
1056 $this->add_error( 'validation', $validation_result->get_error_message( $property_name ) );
1057 }
1058 }
1059 }
1060 /**
1061 * Custom validations to apply to a child of <code>OsModel</code>
1062 *
1063 * @param {OsModel} $this Instance of model to apply custom validations to
1064 * @param {bool} $alternative_validation True if applying alternative validations, false otherwise
1065 *
1066 * @since 1.0.0
1067 * @hook latepoint_model_validate
1068 *
1069 */
1070 do_action( 'latepoint_model_validate', $this, $alternative_validation, $skip_properties );
1071 if ( $this->has_validation_error() ) {
1072 return false;
1073 } else {
1074 return true;
1075 }
1076 }
1077
1078
1079 protected function properties_to_validate() {
1080 return [];
1081 }
1082
1083 protected function validates_email( $property ) {
1084 if ( isset( $this->$property ) && ! empty( $this->$property ) && OsUtilHelper::is_valid_email( $this->$property ) ) {
1085 return true;
1086 } else {
1087 // translators: %s is the property name for a model
1088 return new WP_Error( $property, sprintf( __( '%s is not valid', 'latepoint' ), $this->get_property_nice_name( $property ) ) );
1089 }
1090 }
1091
1092 protected function validates_presence( $property ) {
1093 $validation_result = ( isset( $this->$property ) && ! empty( $this->$property ) );
1094 if ( $validation_result ) {
1095 return true;
1096 } else {
1097 // translators: %s is the property name for a model
1098 return new WP_Error( $property, sprintf( __( '%s can not be blank', 'latepoint' ), $this->get_property_nice_name( $property ) ) );
1099 }
1100 }
1101
1102 protected function validates_uniqueness( $property ) {
1103 if ( isset( $this->$property ) && ! empty( $this->$property ) ) {
1104 if ( $this->is_new_record() ) {
1105 $query = $this->prepare( 'SELECT %i FROM %i WHERE %i = %s LIMIT 1', [
1106 $property,
1107 $this->table_name,
1108 $property,
1109 $this->$property
1110 ] );
1111 } else {
1112 $query = $this->prepare( 'SELECT %i FROM %i WHERE %i = %s AND id != %d LIMIT 1', [
1113 $property,
1114 $this->table_name,
1115 $property,
1116 $this->$property,
1117 $this->id
1118 ] );
1119 }
1120 $items = $this->db->get_results( $query, ARRAY_A );
1121 if ( $items ) {
1122 // translators: %s is the property name for a model
1123 return new WP_Error( $property, sprintf( __( '%s has to be unique', 'latepoint' ), $this->get_property_nice_name( $property ) ) );
1124 }
1125 }
1126
1127 return true;
1128 }
1129
1130 public function get_validations_for_property( string $property ): array {
1131 $validations = $this->properties_to_validate();
1132
1133 return $validations[ $property ] ?? [];
1134 }
1135
1136
1137 public function format_created_datetime_rfc3339() {
1138 $datetime = OsTimeHelper::date_from_db( $this->created_at );
1139 if ( ! $datetime ) {
1140 return 'invalid date';
1141 }
1142 $datetime->setTimezone( new DateTimeZone( "UTC" ) );
1143
1144 return $datetime->format( \DateTime::RFC3339 );
1145 }
1146
1147 }