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