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