PluginProbe ʕ •ᴥ•ʔ
LatePoint – Calendar Booking Plugin for Appointments and Events / 5.5.2
LatePoint – Calendar Booking Plugin for Appointments and Events v5.5.2
5.6.7 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 / helpers / settings_helper.php
latepoint / lib / helpers Last commit date
activities_helper.php 3 months ago agent_helper.php 3 months ago analytics_helper.php 2 months ago auth_helper.php 2 months ago blocks_helper.php 3 months ago booking_helper.php 2 months ago bricks_helper.php 3 months ago bundles_helper.php 3 months ago calendar_helper.php 3 months ago carts_helper.php 3 months ago connector_helper.php 3 months ago csv_helper.php 3 months ago customer_helper.php 1 month ago customer_import_helper.php 3 months ago database_helper.php 3 months ago debug_helper.php 3 months ago defaults_helper.php 3 months ago elementor_helper.php 3 months ago email_helper.php 3 months ago encrypt_helper.php 3 months ago events_helper.php 3 months ago form_helper.php 3 months ago icalendar_helper.php 3 months ago image_helper.php 3 months ago invoices_helper.php 2 months ago license_helper.php 3 months ago location_helper.php 3 months ago marketing_systems_helper.php 3 months ago meeting_systems_helper.php 3 months ago menu_helper.php 2 months ago meta_helper.php 3 months ago migrations_helper.php 3 months ago money_helper.php 3 months ago notifications_helper.php 3 months ago nps_survey_helper.php 3 months ago order_intent_helper.php 2 months ago orders_helper.php 3 months ago otp_helper.php 3 months ago pages_helper.php 3 months ago params_helper.php 3 months ago payments_helper.php 2 months ago plugin_version_update_helper.php 2 months ago price_breakdown_helper.php 3 months ago process_jobs_helper.php 3 months ago processes_helper.php 3 months ago replacer_helper.php 3 months ago resource_helper.php 3 months ago roles_helper.php 3 months ago router_helper.php 3 months ago service_helper.php 3 months ago sessions_helper.php 3 months ago settings_helper.php 2 months ago short_links_systems_helper.php 3 months ago shortcodes_helper.php 2 months ago sms_helper.php 3 months ago steps_helper.php 2 months ago stripe_connect_helper.php 2 months ago styles_helper.php 3 months ago support_topics_helper.php 3 months ago time_helper.php 2 months ago timeline_helper.php 2 months ago transaction_helper.php 3 months ago transaction_intent_helper.php 3 months ago util_helper.php 3 months ago version_specific_updates_helper.php 3 months ago whatsapp_helper.php 3 months ago work_periods_helper.php 3 months ago wp_datetime.php 3 months ago wp_user_helper.php 3 months ago
settings_helper.php
956 lines
1 <?php
2
3 class OsSettingsHelper {
4
5 public static $loaded_values;
6
7
8 private static $encrypted_settings = [
9 'license',
10 'google_calendar_client_secret',
11 'facebook_app_secret',
12 'google_client_secret',
13 'braintree_secret_key',
14 'braintree_merchant_id',
15 'paypal_client_secret',
16 ];
17
18 private static $settings_to_autoload = [
19 'enable_google_login',
20 'time_system',
21 'date_format',
22 'currency_symbol_before',
23 'currency_symbol_after',
24 'phone_format',
25 'steps_show_timezone_selector',
26 'show_booking_end_time',
27 'stripe_publishable_key',
28 'enable_facebook_login',
29 'earliest_possible_booking',
30 'enable_payments_local',
31 'color_scheme_for_booking_form',
32 'latest_possible_booking',
33 'facebook_app_id',
34 'google_client_id',
35 'paypal_client_id',
36 'paypal_currency_iso_code',
37 'paypal_use_braintree_api',
38 'stripe_secret_key',
39 'steps_hide_agent_info',
40 'booking_hash',
41 'payments_environment',
42 'list_of_phone_countries',
43 'included_phone_countries',
44 'wp_users_as_customers',
45 'thousand_separator',
46 'decimal_separator',
47 'number_of_decimals',
48 'default_phone_country',
49 ];
50
51 private static $defaults = [
52 'date_format' => LATEPOINT_DEFAULT_DATE_FORMAT,
53 'time_system' => LATEPOINT_DEFAULT_TIME_SYSTEM,
54 'currency_symbol_before' => '$',
55 ];
56
57 public static function get_business_logo_url() {
58 $default_logo_url = LATEPOINT_IMAGES_URL . 'logo.png';
59
60 return OsImageHelper::get_image_url_by_id( OsSettingsHelper::get_settings_value( 'business_logo' ), 'thumbnail', $default_logo_url );
61 }
62
63 public static function get_business_logo_image( $height = '50px' ) {
64 $url = self::get_business_logo_url();
65
66 return '<img src="' . $url . '" style="height: ' . $height . '; width: auto"/>';
67 }
68
69 public static function get_encrypted_settings() {
70 $encrypted_settings = apply_filters( 'latepoint_encrypted_settings', self::$encrypted_settings );
71
72 return $encrypted_settings;
73 }
74
75
76 public static function get_active_addons(): array {
77 return json_decode( OsSettingsHelper::get_settings_value( 'active_addons', '' ) ) ?? [];
78 }
79
80 public static function get_earliest_possible_booking_restriction( $service_id = 0 ) {
81 if ( ! empty( $service_id ) ) {
82 $service = new OsServiceModel( $service_id );
83 if ( ! $service->is_new_record() && ! empty( $service->earliest_possible_booking ) && OsTimeHelper::is_valid_date( $service->earliest_possible_booking ) ) {
84 return $service->earliest_possible_booking;
85 }
86 }
87 $restriction_from_general_settings = OsSettingsHelper::get_settings_value( 'earliest_possible_booking', '' );
88 if ( OsTimeHelper::is_valid_date( $restriction_from_general_settings ) ) {
89 return $restriction_from_general_settings;
90 } else {
91 return '';
92 }
93 }
94
95 public static function get_latest_possible_booking_restriction( $service_id = 0 ) {
96 if ( ! empty( $service_id ) ) {
97 $service = new OsServiceModel( $service_id );
98 if ( ! $service->is_new_record() && ! empty( $service->latest_possible_booking ) && OsTimeHelper::is_valid_date( $service->latest_possible_booking ) ) {
99 return $service->latest_possible_booking;
100 }
101 }
102 $restriction_from_general_settings = OsSettingsHelper::get_settings_value( 'latest_possible_booking', '' );
103 if ( OsTimeHelper::is_valid_date( $restriction_from_general_settings ) ) {
104 return $restriction_from_general_settings;
105 } else {
106 return '';
107 }
108 }
109
110 /**
111 * @param array $tables
112 *
113 * @return string
114 */
115 public static function export_data( array $tables = [] ): string {
116
117 if ( empty( $tables ) ) {
118 $tables = OsDatabaseHelper::get_all_latepoint_tables();
119 }
120
121 global $wpdb;
122
123 $output = [];
124
125 foreach ( $tables as $table ) {
126 // Check if table exists
127 $table_exists = $wpdb->get_var( "SHOW TABLES LIKE '{$table}'" );
128
129 if ( ! $table_exists ) {
130 continue; // Skip this table if it doesn't exist
131 }
132 // Get all rows from the table
133 $rows = $wpdb->get_results( "SELECT * FROM {$table}", ARRAY_A );
134
135 if ( $rows ) {
136 // Get table create statement
137 $create_table = $wpdb->get_row( "SHOW CREATE TABLE {$table}", ARRAY_N );
138 $var_table = str_replace( $wpdb->prefix, '{{TABLE_PREFIX}}', $table );
139 $create_table = str_replace( $table, $var_table, $create_table );
140
141 $output[ $var_table ] = [
142 'create' => $create_table[1],
143 'prefix' => $wpdb->prefix,
144 'data' => $rows,
145 ];
146 }
147 }
148
149 // Generate filename
150 $filename = 'latepoint_backup_' . date( 'Y-m-d_H-i-s' ) . '.json';
151
152 $json = wp_json_encode( $output, JSON_PRETTY_PRINT );
153
154 // Send headers for file download
155 header( 'Content-Type: application/json' );
156 header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
157 header( 'Content-Length: ' . strlen( $json ) );
158 header( 'Pragma: no-cache' );
159
160 return $json;
161 }
162
163 /**
164 * @param string $data
165 *
166 * @return bool
167 * @throws Exception
168 */
169 public static function import_data( string $data ): bool {
170 global $wpdb;
171
172 if ( empty( $data ) ) {
173 throw new Exception( __( 'Error reading uploaded file', 'latepoint' ) );
174 }
175
176 // update table name from original to new prefix
177 $data = str_replace( '{{TABLE_PREFIX}}', $wpdb->prefix, $data );
178
179 $data = json_decode( $data, true );
180 if ( json_last_error() !== JSON_ERROR_NONE ) {
181 throw new Exception( __( 'Invalid JSON file format', 'latepoint' ) );
182 }
183
184 // Get whitelist of allowed LatePoint tables.
185 $allowed_tables = OsDatabaseHelper::get_all_latepoint_tables();
186
187 // Validate all tables before processing to ensure security.
188 foreach ( $data as $table => $table_data ) {
189 // Security check: Ensure table is in whitelist.
190 if ( ! in_array( $table, $allowed_tables, true ) ) {
191 throw new Exception( sprintf( __( 'Security: Table "%s" is not allowed for import', 'latepoint' ), esc_html( $table ) ) );
192 }
193
194 // Security check: Validate required fields exist.
195 if ( ! isset( $table_data['create'] ) || ! isset( $table_data['data'] ) ) {
196 throw new Exception( sprintf( __( 'Invalid data structure for table "%s"', 'latepoint' ), esc_html( $table ) ) );
197 }
198
199 // Security check: Validate CREATE statement only creates the expected table.
200 $create_statement = $table_data['create'];
201 if ( ! preg_match( '/^\s*CREATE\s+TABLE\s+/i', $create_statement ) ) {
202 throw new Exception( sprintf( __( 'Security: Invalid CREATE statement for table "%s"', 'latepoint' ), esc_html( $table ) ) );
203 }
204
205 // Extract table name from CREATE statement and validate it matches.
206 if ( ! preg_match( '/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`\'"]?(' . preg_quote( $table, '/' ) . ')[`\'"]?\s+/i', $create_statement ) ) {
207 throw new Exception( sprintf( __( 'Security: CREATE statement table name mismatch for "%s"', 'latepoint' ), esc_html( $table ) ) );
208 }
209
210 // Security check: Ensure no dangerous SQL keywords in CREATE statement (whole-word match to avoid false positives on column names like "selection_image_id").
211 $dangerous_keywords = array( 'SELECT', 'UNION', 'EXEC', 'EXECUTE', 'CALL', 'LOAD_FILE', 'INTO\s+OUTFILE', 'INTO\s+DUMPFILE' );
212 foreach ( $dangerous_keywords as $keyword ) {
213 if ( preg_match( '/\b' . $keyword . '\b/i', $create_statement ) ) {
214 $display_keyword = str_replace( '\s+', ' ', $keyword );
215 throw new Exception( sprintf( __( 'Security: Dangerous SQL keyword "%s" detected in CREATE statement', 'latepoint' ), esc_html( $display_keyword ) ) );
216 }
217 }
218 }
219
220 // Process each table after validation.
221 foreach ( $data as $table => $table_data ) {
222 // Drop table if exists
223 $wpdb->query( $wpdb->prepare( 'DROP TABLE IF EXISTS %i', $table ) );
224
225 // Create table
226 $result = $wpdb->query( $table_data['create'] );
227 if ( $result === false ) {
228 throw new Exception( sprintf( __( 'Error creating table "%s": %s', 'latepoint' ), esc_html( $table ), $wpdb->last_error ) );
229 }
230
231 // Insert data
232 if ( is_array( $table_data['data'] ) ) {
233 foreach ( $table_data['data'] as $row ) {
234 if ( is_array( $row ) ) {
235 // Security fix: Prevent mass assignment of wordpress_user_id during import.
236 if ( $table === $wpdb->prefix . 'latepoint_customers' ) {
237 unset( $row['wordpress_user_id'] );
238 }
239
240 $insert_result = $wpdb->insert( $table, $row );
241 if ( $insert_result === false ) {
242 // Log error but continue with other rows.
243 error_log( sprintf( 'LatePoint Import: Error inserting data into %s: %s', $table, $wpdb->last_error ) );
244 }
245 }
246 }
247 }
248
249 // Find auto-increment columns and their max values
250 $columns = $wpdb->get_results( $wpdb->prepare( "SHOW COLUMNS FROM %i WHERE Extra = 'auto_increment'", $table ) );
251 if ( is_array( $columns ) ) {
252 foreach ( $columns as $column ) {
253 if ( isset( $column->Field ) ) {
254 // Sanitize column name to prevent SQL injection.
255 $column_name = preg_replace( '/[^a-zA-Z0-9_]/', '', $column->Field );
256
257 // Get the maximum value for this column.
258 $max_id = $wpdb->get_var( $wpdb->prepare( 'SELECT MAX(%i) FROM %i', $column_name, $table ) );
259
260 // Set the auto_increment value to max + 1.
261 if ( $max_id ) {
262 $next_id = intval( $max_id ) + 1;
263 $wpdb->query( $wpdb->prepare( 'ALTER TABLE %i AUTO_INCREMENT = %d', $table, $next_id ) );
264 }
265 }
266 }
267 }
268 }
269
270 return true;
271 }
272
273 public static function run_autoload() {
274 /**
275 * Default settings to be used in SettingsHelper when no value exists in DB
276 *
277 * @param {array} $default_settings Array of key => value pairs of setting names and their default values
278 * @returns {array} The filtered array of default settings
279 *
280 * @since 4.7.2
281 * @hook latepoint_settings_defaults
282 *
283 */
284 self::$defaults = apply_filters( 'latepoint_settings_defaults', self::$defaults );
285 foreach ( self::$defaults as $name => $default ) {
286 self::$loaded_values[ $name ] = $default;
287 }
288
289 $settings_model = new OsSettingsModel();
290
291 /**
292 * Settings to autoload in SettingsHelper on every page load, this is done to reduce the number of queries to DB
293 *
294 * @param {array} $settings Array of setting names to autoload
295 * @returns {array} The filtered array of setting names
296 *
297 * @since 4.7.2
298 * @hook latepoint_settings_to_autoload
299 *
300 */
301 self::$settings_to_autoload = apply_filters( 'latepoint_settings_to_autoload', self::$settings_to_autoload );
302 $settings_arr = $settings_model->select( 'name, value' )->where( array( 'name' => self::$settings_to_autoload ) )->get_results();
303
304
305 if ( $settings_arr && is_array( $settings_arr ) ) {
306 foreach ( $settings_arr as $setting ) {
307 if ( in_array( $setting->name, self::get_encrypted_settings() ) ) {
308 self::$loaded_values[ $setting->name ] = OsEncryptHelper::decrypt_value( $setting->value );
309 } else {
310 self::$loaded_values[ $setting->name ] = $setting->value;
311 }
312 }
313 }
314 }
315
316
317 // ENVIRONMENT SETTINGS
318
319 // BASE ENVIRONMENT
320 public static function is_env_live() {
321 return ( LATEPOINT_ENV == LATEPOINT_ENV_LIVE );
322 }
323
324 public static function is_env_dev() {
325 return ( LATEPOINT_ENV == LATEPOINT_ENV_DEV );
326 }
327
328 public static function is_env_demo() {
329 return ( LATEPOINT_ENV == LATEPOINT_ENV_DEMO );
330 }
331
332 // SMS, EMAILS
333
334 public static function is_sms_allowed() {
335 return LATEPOINT_ALLOW_SMS;
336 }
337
338 public static function is_whatsapp_allowed() {
339 return LATEPOINT_ALLOW_WHATSAPP;
340 }
341
342 public static function is_email_allowed() {
343 return LATEPOINT_ALLOW_EMAILS;
344 }
345
346 // PAYMENTS ENVIRONMENT
347 public static function is_env_payments_live() {
348 return ( self::get_payments_environment() == LATEPOINT_PAYMENTS_ENV_LIVE );
349 }
350
351 public static function is_env_payments_dev() {
352 return ( self::get_payments_environment() == LATEPOINT_PAYMENTS_ENV_DEV );
353 }
354
355 public static function is_env_payments_demo() {
356 return ( self::get_payments_environment() == LATEPOINT_PAYMENTS_ENV_DEMO );
357 }
358
359 public static function get_payments_environment() {
360 return self::get_settings_value( 'payments_environment', LATEPOINT_PAYMENTS_ENV_LIVE );
361 }
362
363
364 public static function append_payment_env_key( string $string, string $force_env = '' ): string {
365 if ( $force_env ) {
366 if ( $force_env == LATEPOINT_PAYMENTS_ENV_DEV ) {
367 $string .= LATEPOINT_PAYMENTS_DEV_SUFFIX;
368 }
369 } else {
370 if ( self::is_env_payments_dev() ) {
371 $string .= LATEPOINT_PAYMENTS_DEV_SUFFIX;
372 }
373 }
374
375 return $string;
376 }
377
378 public static function set_menu_layout_style( $layout ) {
379 $layout = in_array( $layout, [ 'compact', 'full' ] ) ? $layout : 'full';
380 update_user_meta( get_current_user_id(), 'latepoint_admin_menu_style', $layout );
381 }
382
383 public static function get_menu_layout_style(): string {
384 $menu_style = get_user_meta( get_current_user_id(), 'latepoint_admin_menu_style', true );
385
386 // Set default if not set
387 if ( empty( $menu_style ) ) {
388 $menu_style = 'full'; // default value
389 }
390 return $menu_style;
391 }
392
393 public static function get_time_system() {
394 return self::get_settings_value( 'time_system', LATEPOINT_DEFAULT_TIME_SYSTEM );
395 }
396
397 public static function get_date_format() {
398 return self::get_settings_value( 'date_format', LATEPOINT_DEFAULT_DATE_FORMAT );
399 }
400
401 public static function get_date_format_for_js() {
402 $format = strtolower( self::get_date_format() );
403
404 return str_replace( [ 'd', 'm', 'y' ], [ 'dd', 'mm', 'yyyy' ], $format );
405 }
406
407 public static function get_readable_datetime_format( $no_year = false, $no_seconds = true ) {
408 return self::get_readable_date_format( $no_year ) . ', ' . self::get_readable_time_format( $no_seconds );
409 }
410
411 public static function get_order_types_list_for_any_agent_logic(): array {
412 $order_types = [
413 LATEPOINT_ANY_AGENT_ORDER_RANDOM => __( 'Randomly picked agent', 'latepoint' ),
414 LATEPOINT_ANY_AGENT_ORDER_PRICE_HIGH => __( 'Most expensive agent', 'latepoint' ),
415 LATEPOINT_ANY_AGENT_ORDER_PRICE_LOW => __( 'Least expensive agent', 'latepoint' ),
416 LATEPOINT_ANY_AGENT_ORDER_BUSY_HIGH => __( 'Agent with the most bookings on that day', 'latepoint' ),
417 LATEPOINT_ANY_AGENT_ORDER_BUSY_LOW => __( 'Agent with the least bookings on that day', 'latepoint' ),
418 ];
419
420 /**
421 * Get the list of order types for agent selection logic when "ANY" agent is pre-selected in the booking form
422 *
423 * @param {array} $order_types Array of order types
424 *
425 * @returns {array} Filtered array of order types
426 * @since 4.7.6
427 * @hook latepoint_get_order_types_list_for_any_agent_logic
428 *
429 */
430 return apply_filters( 'latepoint_get_order_types_list_for_any_agent_logic', $order_types );
431 }
432
433 public static function get_readable_time_format( $no_seconds = true ) {
434 $seconds = $no_seconds ? '' : ':s';
435 $format = ( self::get_time_system() == '12' ) ? "g:i$seconds a" : "G:i$seconds";
436
437 return $format;
438 }
439
440 public static function get_readable_date_format( $no_year = false, $short_month = false ) {
441 if ( OsSettingsHelper::is_on( 'disable_verbose_date_output' ) ) {
442 return self::get_date_format();
443 }
444 $format = ( $no_year ) ? 'F j' : 'M j, Y';
445 switch ( self::get_date_format() ) {
446 case 'm/d/Y':
447 case 'm.d.Y':
448 $format = ( $no_year ) ? 'F j' : 'M j, Y';
449 break;
450 case 'd.m.Y':
451 case 'd/m/Y':
452 $format = ( $no_year ) ? 'j F' : 'j M, Y';
453 break;
454 case 'Y-m-d':
455 $format = ( $no_year ) ? 'F j' : 'Y, M j';
456 break;
457 }
458 if ( $short_month ) {
459 // if short month is requested - use month shorthand;
460 $format = str_replace( 'F', 'M', $format );
461 }
462
463 return $format;
464 }
465
466
467 public static function get_booking_template_for_calendar() {
468 return OsSettingsHelper::get_settings_value( 'booking_template_for_calendar', '{{service_name}}' );
469 }
470
471
472 public static function get_selected_columns_for_bookings_table() {
473 return OsSettingsHelper::get_settings_value( 'bookings_table_columns', [] );
474 }
475
476 public static function get_available_columns_for_bookings_table() {
477 $available_columns = [];
478
479 $available_columns['customer'] = [
480 'email' => __( 'Email', 'latepoint' ),
481 'phone' => __( 'Phone', 'latepoint' ),
482 ];
483
484 $available_columns['booking'] = [
485 'booking_code' => __( 'Code', 'latepoint' ),
486 'duration' => __( 'Duration', 'latepoint' ),
487 'source_id' => __( 'Source ID', 'latepoint' ),
488 'payment_method' => __( 'Payment Method', 'latepoint' ),
489 'payment_portion' => __( 'Payment Portion', 'latepoint' ),
490 'formatted_price' => __( 'Price', 'latepoint' ),
491 ];
492
493 $available_columns = apply_filters( 'latepoint_bookings_table_columns', $available_columns );
494
495 return $available_columns;
496 }
497
498 /**
499 * Returns a registry of ALL bookings table columns (fixed + extra), in default display order.
500 * Each entry: [ 'label', 'type' (fixed|extra), 'always_visible'?, 'condition'?, 'extra_type'?, 'extra_key'? ]
501 */
502 public static function get_all_bookings_table_columns(): array {
503 $columns = [
504 'id' => [
505 'label' => __( 'ID', 'latepoint' ),
506 'type' => 'fixed',
507 'always_visible' => true,
508 ],
509 'service' => [
510 'label' => __( 'Service', 'latepoint' ),
511 'type' => 'fixed',
512 'condition' => 'multi_service',
513 ],
514 'datetime' => [
515 'label' => __( 'Date/Time', 'latepoint' ),
516 'type' => 'fixed',
517 'always_visible' => true,
518 ],
519 'time_left' => [
520 'label' => __( 'Time Left', 'latepoint' ),
521 'type' => 'fixed',
522 'always_visible' => true,
523 ],
524 'agent' => [
525 'label' => __( 'Agent', 'latepoint' ),
526 'type' => 'fixed',
527 'condition' => 'multi_agent',
528 ],
529 'location' => [
530 'label' => __( 'Location', 'latepoint' ),
531 'type' => 'fixed',
532 'condition' => 'multi_location',
533 ],
534 'customer' => [
535 'label' => __( 'Customer', 'latepoint' ),
536 'type' => 'fixed',
537 'always_visible' => true,
538 ],
539 'status' => [
540 'label' => __( 'Status', 'latepoint' ),
541 'type' => 'fixed',
542 'always_visible' => true,
543 ],
544 'payment_status' => [
545 'label' => __( 'Payment Status', 'latepoint' ),
546 'type' => 'fixed',
547 'always_visible' => true,
548 ],
549 'created_on' => [
550 'label' => __( 'Created On', 'latepoint' ),
551 'type' => 'fixed',
552 'always_visible' => true,
553 ],
554 ];
555
556 // Append extra columns from the existing registry (includes addon filter results).
557 foreach ( self::get_available_columns_for_bookings_table() as $extra_type => $type_columns ) {
558 foreach ( $type_columns as $extra_key => $extra_label ) {
559 $columns[ $extra_type . '.' . $extra_key ] = [
560 'label' => $extra_label,
561 'type' => 'extra',
562 'extra_type' => $extra_type,
563 'extra_key' => $extra_key,
564 ];
565 }
566 }
567
568 return apply_filters( 'latepoint_all_bookings_table_columns', $columns );
569 }
570
571 /**
572 * Returns the saved column order array (flat list of column keys).
573 * Returns [] if no order has been saved yet.
574 */
575 public static function get_bookings_table_columns_order(): array {
576 $order = self::get_settings_value( 'bookings_table_columns_order', [] );
577 return is_array( $order ) ? $order : [];
578 }
579
580 /**
581 * Returns all columns sorted by the user-saved order.
582 * Falls back to default order when no order is saved (backward compat).
583 * Columns added by addons after the order was saved are appended at the end.
584 */
585 public static function get_ordered_bookings_table_columns(): array {
586 $all_columns = self::get_all_bookings_table_columns();
587 $saved_order = self::get_bookings_table_columns_order();
588
589 if ( empty( $saved_order ) ) {
590 return $all_columns;
591 }
592
593 $ordered = [];
594 foreach ( $saved_order as $key ) {
595 if ( 'id' === $key ) continue;
596 if ( isset( $all_columns[ $key ] ) ) {
597 $ordered[ $key ] = $all_columns[ $key ];
598 }
599 }
600 // Append any columns not in saved order (e.g. newly installed addon columns).
601 foreach ( $all_columns as $key => $def ) {
602 if ( 'id' === $key ) continue;
603 if ( ! isset( $ordered[ $key ] ) ) {
604 $ordered[ $key ] = $def;
605 }
606 }
607
608 // ID column is always first regardless of saved order.
609 if ( isset( $all_columns['id'] ) ) {
610 $ordered = array_merge( [ 'id' => $all_columns['id'] ], $ordered );
611 }
612
613 return $ordered;
614 }
615
616 /**
617 * Determines whether a given column should be visible in the table.
618 */
619 public static function is_bookings_column_visible( array $col_def, array $selected_columns, int $services_count, int $agents_count, int $locations_count ): bool {
620 if ( ! empty( $col_def['always_visible'] ) ) {
621 return true;
622 }
623 if ( ! empty( $col_def['condition'] ) ) {
624 switch ( $col_def['condition'] ) {
625 case 'multi_service':
626 return $services_count > 1;
627 case 'multi_agent':
628 return $agents_count > 1;
629 case 'multi_location':
630 return $locations_count > 1;
631 }
632 }
633 if ( 'extra' === $col_def['type'] ) {
634 $extra_type = $col_def['extra_type'];
635 $extra_key = $col_def['extra_key'];
636 return isset( $selected_columns[ $extra_type ] ) && in_array( $extra_key, $selected_columns[ $extra_type ], true );
637 }
638 return false;
639 }
640
641 public static function force_bite( $request ) {
642 }
643
644 public static function read_encoded( $str ) {
645 return base64_decode( $str );
646 }
647
648 public static function get_db_version() {
649 return get_option( 'latepoint_db_version' );
650 }
651
652 public static function force_release( $request ) {
653 }
654
655 public static function is_enabled_show_dial_code_with_flag() {
656 return ( self::get_settings_value( 'show_dial_code_with_flag', LATEPOINT_VALUE_ON ) == LATEPOINT_VALUE_ON );
657 }
658
659 public static function is_using_google_login() {
660 return self::is_on( 'enable_google_login' );
661 }
662
663 public static function is_using_facebook_login() {
664 return self::is_on( 'enable_facebook_login' );
665 }
666
667 public static function is_using_social_login() {
668 return ( self::is_using_google_login() || self::is_using_facebook_login() );
669 }
670
671 public static function get_steps_support_text() {
672 $default = '<h5>Questions?</h5><p>Call (858) 939-3746 for help</p>';
673
674 return self::get_settings_value( 'steps_support_text', $default );
675 }
676
677 public static function get_default_fields_for_customer() {
678 $default_fields = [
679 'first_name' => [
680 'label' => __( 'First Name', 'latepoint' ),
681 'required' => true,
682 'width' => 'os-col-6',
683 'active' => true,
684 ],
685 'last_name' => [
686 'label' => __( 'Last Name', 'latepoint' ),
687 'required' => true,
688 'width' => 'os-col-6',
689 'active' => true,
690 ],
691 'email' => [
692 'label' => __( 'Email Address', 'latepoint' ),
693 'required' => true,
694 'width' => 'os-col-6',
695 'active' => true,
696 ],
697 'phone' => [
698 'label' => __( 'Phone Number', 'latepoint' ),
699 'required' => false,
700 'width' => 'os-col-6',
701 'active' => true,
702 ],
703 'notes' => [
704 'label' => __( 'Comments', 'latepoint' ),
705 'required' => false,
706 'width' => 'os-col-12',
707 'active' => true,
708 ],
709 ];
710
711 $fields_from_db = OsSettingsHelper::get_settings_value( 'default_fields_for_customer', '' );
712 $fields_from_db_arr = json_decode( $fields_from_db, true );
713 if ( $fields_from_db_arr ) {
714 foreach ( $fields_from_db_arr as $name => $field_from_db ) {
715 if ( isset( $default_fields[ $name ] ) ) {
716 $default_fields[ $name ] = $field_from_db;
717 }
718 }
719 }
720 $default_fields = apply_filters( 'latepoint_default_fields_for_customer', $default_fields );
721
722 return $default_fields;
723 }
724
725 public static function remove_setting_by_name( $name ) {
726 $settings_model = new OsSettingsModel();
727 $settings_model = $settings_model->delete_where( array( 'name' => $name ) );
728 self::reset_loaded_value( $name );
729 }
730
731 public static function save_setting_by_name( $name, $value ) {
732 $settings_model = new OsSettingsModel();
733 $settings_model = $settings_model->where( array( 'name' => $name ) )->set_limit( 1 )->get_results_as_models();
734 if ( $settings_model ) {
735 $settings_model->value = self::prepare_value( $name, $value );
736 } else {
737 $settings_model = new OsSettingsModel();
738 $settings_model->name = $name;
739 $settings_model->value = self::prepare_value( $name, $value );
740 }
741 self::reset_loaded_value( $name );
742
743 return $settings_model->save();
744 }
745
746 public static function reset_loaded_value( $name ) {
747 unset( self::$loaded_values[ $name ] );
748 }
749
750 public static function prepare_value( $name, $value ) {
751 if ( in_array( $name, self::get_encrypted_settings() ) ) {
752 $value = OsEncryptHelper::encrypt_value( $value );
753 }
754 if ( is_array( $value ) ) {
755 $value = maybe_serialize( $value );
756 }
757
758 return $value;
759 }
760
761 public static function get_settings_value( $name, $default = false ) {
762 if ( isset( self::$loaded_values[ $name ] ) ) {
763 return self::$loaded_values[ $name ];
764 }
765 $settings_model = new OsSettingsModel();
766 $settings_model = $settings_model->where( array( 'name' => $name ) )->set_limit( 1 )->get_results_as_models();
767 if ( $settings_model ) {
768 if ( in_array( $name, self::get_encrypted_settings() ) ) {
769 $value = OsEncryptHelper::decrypt_value( $settings_model->value );
770 } else {
771 $value = maybe_unserialize( $settings_model->value );
772 }
773 } else {
774 $value = $default;
775 }
776 self::$loaded_values[ $name ] = $value;
777
778 return self::$loaded_values[ $name ];
779 }
780
781
782 public static function get_any_agent_order() {
783 return self::get_settings_value( 'any_agent_order', LATEPOINT_ANY_AGENT_ORDER_RANDOM );
784 }
785
786 public static function get_day_calendar_min_height() {
787 $height = preg_replace( '/\D/', '', self::get_settings_value( 'day_calendar_min_height', 700 ) );
788 if ( ! $height ) {
789 $height = 700;
790 }
791
792 return $height;
793 }
794
795 public static function get_default_timeblock_interval() {
796 $timeblock_interval = self::get_settings_value( 'timeblock_interval', LATEPOINT_DEFAULT_TIMEBLOCK_INTERVAL );
797 if ( empty( $timeblock_interval ) ) {
798 $timeblock_interval = LATEPOINT_DEFAULT_TIMEBLOCK_INTERVAL;
799 }
800
801 return intval( $timeblock_interval );
802 }
803
804 public static function get_customer_dashboard_url( $include_site_url = true ) {
805 $path = self::get_settings_value( 'page_url_customer_dashboard', '/customer-dashboard' );
806
807 return ( $include_site_url ) ? site_url( $path ) : $path;
808 }
809
810 public static function get_customer_login_url( $include_site_url = true ) {
811 $path = self::get_settings_value( 'page_url_customer_login', '/customer-login' );
812
813 return ( $include_site_url ) ? site_url( $path ) : $path;
814 }
815
816
817 // BOOKING STEPS
818
819 public static function steps_show_service_categories() {
820 return ( self::get_settings_value( 'steps_show_service_categories', 'on' ) == 'on' );
821 }
822
823 public static function steps_show_location_categories() {
824 return ( self::get_settings_value( 'steps_show_location_categories', 'on' ) == 'on' );
825 }
826
827 public static function steps_show_agent_bio() {
828 return ( self::get_settings_value( 'steps_show_agent_bio' ) == 'on' );
829 }
830
831 public static function get_booking_form_color_scheme() {
832 return self::get_settings_value( 'color_scheme_for_booking_form', 'blue' );
833 }
834
835 public static function get_booking_form_border_radius() {
836 return self::get_settings_value( 'border_radius', 'flat' );
837 }
838
839 public static function get_default_phone_country() {
840 return OsSettingsHelper::get_settings_value( 'default_phone_country', 'us' );
841 }
842
843 public static function get_included_phone_countries(): array {
844 if ( OsSettingsHelper::get_settings_value( 'list_of_phone_countries', LATEPOINT_ALL ) == LATEPOINT_ALL ) {
845 return [];
846 } else {
847 return array_map( 'trim', explode( ',', OsSettingsHelper::get_settings_value( 'included_phone_countries', '' ) ) );
848 }
849 }
850
851
852 public static function is_on( string $setting, $default = false ): bool {
853 return ( self::get_settings_value( $setting, $default ) == LATEPOINT_VALUE_ON );
854 }
855
856 public static function is_off( string $setting, $default = false ): bool {
857 return ( self::get_settings_value( $setting, $default ) != LATEPOINT_VALUE_ON );
858 }
859
860 public static function generate_default_form_fields( array $default_fields ) {
861 ?>
862 <div class="os-default-fields" data-route="<?php echo esc_attr( OsRouterHelper::build_route_name( 'form_fields', 'update_default_fields' ) ); ?>">
863 <form>
864 <?php foreach ( $default_fields as $name => $default_field ) {
865 $atts = [];
866 ?>
867 <div class="os-default-field <?php echo $default_field['active'] ? '' : 'is-disabled'; ?>">
868 <?php
869 $active = $default_field['active'] ? 'on' : 'off';
870 $field_name = 'default_fields[' . $name . '][active]';
871 echo '<div class="os-toggler ' . esc_attr( $active ) . '" data-for="' . esc_attr( OsFormHelper::name_to_id( $field_name ) ) . '"><div class="toggler-rail"><div class="toggler-pill"></div></div></div>';
872 echo OsFormHelper::hidden_field( $field_name, $default_field['active'] );
873 ?>
874 <div class="os-field-name"><?php echo esc_html( $default_field['label'] ); ?></div>
875 <div class="os-field-setting">
876 <?php echo OsFormHelper::checkbox_field( 'default_fields[' . $name . '][required]', __( 'Required?', 'latepoint' ), 'on', $default_field['required'], $atts ); ?>
877 </div>
878 <div class="os-field-setting">
879 <?php echo OsFormHelper::select_field(
880 'default_fields[' . $name . '][width]',
881 false,
882 array(
883 'os-col-12' => __( 'Full Width', 'latepoint' ),
884 'os-col-6' => __( 'Half Width', 'latepoint' ),
885 ),
886 $default_field['width']
887 ); ?>
888 </div>
889 </div>
890 <?php } ?>
891 </form>
892 </div>
893 <?php
894 }
895
896
897 /**
898 * Get value set for start of week in WordPress, can be from 1-7 (representing Monday-Sunday)
899 * @return mixed
900 */
901 public static function get_start_of_week(): int {
902 $wp_value = intval( get_option( 'start_of_week', 0 ) );
903
904 return $wp_value ? $wp_value : 7; // in WordPress they start count on sunday and it's 0, we are using PHP Date library that starts week on Monday and it's 1-7
905 }
906
907 public static function get_number_of_records_per_page(): int {
908 return intval( OsSettingsHelper::get_settings_value( 'number_of_records_per_page', 20 ) );
909 }
910
911 public static function can_download_records_as_csv(): bool {
912 return OsAuthHelper::is_admin_logged_in() || self::is_on( 'allow_non_admins_download_csv' );
913 }
914
915 public static function get_link_attributes_for_premium_features(): string {
916 return ' data-os-action="' . OsRouterHelper::build_route_name( 'settings', 'premium_modal' ) . '" data-os-lightbox-classes="width-600" data-os-output-target="lightbox" ';
917 }
918
919 public static function generate_instant_booking_page_url( array $url_settings ): string {
920 $url_settings['latepoint'] = 'instant';
921 return OsUtilHelper::get_site_url() . '/?' . http_build_query( $url_settings );
922 }
923
924 public static function instant_page_background_patterns(): array {
925 return [
926 'pattern_1' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(45deg, #ff9a9e 0%, #fad0c4 99%, #fad0c4 100%);',
927 'pattern_2' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(to top, #a18cd1 0%, #fbc2eb 100%);',
928 'pattern_3' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(to top, #fbc2eb 0%, #a6c1ee 100%);',
929 'pattern_4' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(120deg, #f6d365 0%, #fda085 100%);',
930 'pattern_5' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(180deg, #2af598 0%, #009efd 100%);',
931 'pattern_6' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(to right, #6a11cb 0%, #2575fc 100%);',
932 'pattern_7' => 'background-color: transparent; background-size: auto; background-color: #DCD9D4; background-image: linear-gradient(to bottom, rgba(255,255,255,0.50) 0%, rgba(0,0,0,0.50) 100%), radial-gradient(at 50% 0%, rgba(255,255,255,0.10) 0%, rgba(0,0,0,0.50) 50%); background-blend-mode: soft-light,screen;',
933 'pattern_8' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(to top, #f77062 0%, #fe5196 100%);',
934 'pattern_9' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(to top, #00c6fb 0%, #005bea 100%);',
935 'pattern_10' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(to top, #09203f 0%, #537895 100%);',
936 'pattern_11' => 'background-color: transparent; background-size: auto; background-image: radial-gradient(73% 147%, #EADFDF 59%, #ECE2DF 100%), radial-gradient(91% 146%, rgba(255,255,255,0.50) 47%, rgba(0,0,0,0.50) 100%); background-blend-mode: screen;',
937 'pattern_12' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(to top, lightgrey 0%, lightgrey 1%, #e0e0e0 26%, #efefef 48%, #d9d9d9 75%, #bcbcbc 100%);',
938 'pattern_13' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(to top, #fddb92 0%, #d1fdff 100%);',
939 'pattern_14' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(to top, #dfe9f3 0%, white 100%);',
940 'pattern_15' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(-225deg, #5271C4 0%, #B19FFF 48%, #ECA1FE 100%);',
941 'pattern_16' => 'background-color: transparent; background-size: auto; background: linear-gradient(to bottom, rgba(255,255,255,0.15) 0%, rgba(0,0,0,0.15) 100%), radial-gradient(at top center, rgba(255,255,255,0.40) 0%, rgba(0,0,0,0.40) 120%) #989898;background-blend-mode: multiply,multiply;',
942 'pattern_17' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(to top, #f43b47 0%, #453a94 100%);',
943 'pattern_18' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(to right, #222 0%, #111 100%);',
944 'pattern_19' => 'background-color: transparent; background-size: auto; background-image: linear-gradient(-20deg, #d558c8 0%, #24d292 100%);',
945 ];
946 }
947
948 public static function get_default_wp_role_for_new_customers(): string {
949 if ( ! wp_roles()->is_role( LATEPOINT_WP_CUSTOMER_ROLE ) ) {
950 OsRolesHelper::register_customer_role();
951 }
952 return OsSettingsHelper::get_settings_value( 'default_wp_role_for_customer', LATEPOINT_WP_CUSTOMER_ROLE );
953 }
954 }
955
956 ?>