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