2026-03-27 15:53:29 +00:00

1396 lines
43 KiB
PHP

<?php
/**
* Simply Schedule Appointments Upgrade.
*
* @since 0.0.3
* @package Simply_Schedule_Appointments
*/
/**
* Simply Schedule Appointments Upgrade.
*
* @since 0.0.3
*/
class SSA_Upgrade {
protected $last_version_seen;
protected $versions_requiring_upgrade = array(
// '0.0.3', // create /appointments booking page maybe_create_booking_page()
'1.2.3', // fix "Email address" -> "email"
'1.5.1', // fix customer_information vs custom_customer_information capitalization
'2.6.9_12', // flush permalinks
'2.6.9_13', // Whitelist for Disable REST API
'2.7.1', // Notifications
'2.9.2', // SMS phone
'3.1.0', // Appointment.date_timezone -> Appointment.customer_timezone
'3.5.0', // Appointment.AppointmentType.instructions -> instructions
'4.2.2', // Team Member Role
'4.4.5', // Fix staff_capacity=100000 -> staff_capacity=1
'5.4.4', // Run Google Calendar sync
'5.4.6', // Cleanup Calendar events templates
'6.1.5', // Populate the new appt type labels table
'6.4.3', // Maybe fix appt type label id equal to zero
'6.4.5', // Fix missing appointment types
'6.4.9', // Handle upgrade to new class-error-notices logic
'6.5.1', // Add cancelation_note to the admin and customers canceled notifications
'6.5.4', // Enforce encrypting stripe keys and secrets
'6.5.20', // Remove public_edit_url from event_type_group_shared type
'6.6.19', // Handle displaying new booking app banners
'6.6.21', // Remove public_edit_url from event_type_group_admin type
'6.7.13', // Disable old booking app if enabled with resources enabled at the same time
'6.7.45', // Disable old booking app for all users
'6.8.1', // Update Google Calendar Quick Connect mode from saved dev settings
'6.8.6', // Notifications: update notification column schema in appointment types table
);
/**
* Parent plugin class.
*
* @since 0.0.3
*
* @var Simply_Schedule_Appointments
*/
protected $plugin = null;
/**
* Constructor.
*
* @since 0.0.3
*
* @param Simply_Schedule_Appointments $plugin Main plugin object.
*/
public function __construct( $plugin ) {
$this->plugin = $plugin;
$this->hooks();
}
/**
* Initiate our hooks.
*
* @since 0.0.3
*/
public function hooks() {
add_action( 'init', array( $this, 'migrations' ), 20 );
add_action( 'init', array( $this, 'check_version_change' ), 20 );
add_action( 'ssa_upgrade_free_to_paid', array( $this, 'migrate_free_to_paid_customer_info' ), 30 );
add_action( 'ssa_downgrade_paid_to_free', array( $this, 'migrate_paid_to_free_customer_info' ), 30 );
}
public function record_version_change( $current_version, $stored_version ) {
$ssa_version_changes = get_option('ssa_version_changes', []);
if( ! isset( $ssa_version_changes[ $stored_version . '--' . $current_version ] ) ) {
$utc_timezone = new DateTimeZone( 'UTC' );
$utc_datetime = new DateTimeImmutable( 'now', $utc_timezone );
$record = [
'home_url' => home_url(),
'wordpress_version' => get_bloginfo('version'),
'php_version' => phpversion(),
'current_version' => $current_version,
'previous_version' => $stored_version,
'timestamp' => $utc_datetime
];
$ssa_version_changes[ $stored_version . '--' .$current_version ] = $record;
update_option('ssa_version_changes', $ssa_version_changes);
}
}
/**
* Helper function to check version changes - when the user upgrades or downgrades - and to run migration logic when necessary.
*
* @since 4.4.9
*
* @return void
*/
public function check_version_change() {
$current_version = $this->plugin->get_current_version();
$stored_version = get_option( 'ssa_plugin_version' );
$current_version_num = $this->plugin->get_current_edition();
// Reset/Disable beta option
if ( $current_version_num > 1 && ! empty( $stored_version ) ) {
if ( $current_version !== $stored_version ) {
$this->reset_beta_option();
$this->record_version_change( $current_version, $stored_version );
}
}
// If no stored version, might be the either a fresh install or an existing install with an old version of the plugin.
// On this case, we still want to run the logic to make sure that all fields are properly updated.
if ( ! $stored_version ) {
// if no stored version, and current version is "Free", run the logic to update fields to the free version.
if ( 1 === $current_version_num ) {
do_action( 'ssa_downgrade_paid_to_free' );
}
// if no stored version, and current version is "Paid" run the logic to update fields to paid version.
if ( 1 < $current_version_num ) {
do_action( 'ssa_upgrade_free_to_paid' );
}
$this->plugin->store_current_version();
return;
}
// Ok, we do have a version number. On this case, we need to verify if it's an upgrade or downgrade, and run the necessary conversion.
$stored_version_num = mb_substr( $stored_version, 0, 1 );
// if stored version was "Free" and the user upgraded to a paid version, run the logic to update fields again.
if ( '1' === $stored_version_num && $current_version_num > $stored_version_num ) {
do_action( 'ssa_upgrade_free_to_paid' );
}
// if stored version was "Paid" and the user downgraded to the free version, run the logic to update fields again.
if ( '1' < $stored_version_num && 1 === $current_version_num ) {
do_action( 'ssa_downgrade_paid_to_free' );
}
$this->plugin->store_current_version();
}
/**
* Helper logic to migrate customer info from free to paid edition.
*
* @since 4.4.9
*
* @return void
*/
public function migrate_free_to_paid_customer_info() {
$field_type_conversion_map = array(
'Name' => 'single-text',
'Email' => 'single-text',
'Phone' => 'phone',
'Address' => 'single-text',
'City' => 'single-text',
'State' => 'single-text',
'Zip' => 'single-text',
'Notes' => 'multi-text',
);
/* Migrate Appointment Types */
$appointment_types = $this->plugin->appointment_type_model->query(
array(
'number' => -1,
)
);
foreach ( $appointment_types as $appointment_type_key => $appointment_type ) {
// if custom_customer_information is not empty, then we can't populate it again, since it might lose data.
if ( ! empty( $appointment_type['custom_customer_information'] ) ) {
continue;
}
if ( ! empty( $appointment_type['customer_information']['0']['field'] ) ) {
$appointment_types[ $appointment_type_key ]['custom_customer_information'] = array();
foreach ( $appointment_type['customer_information'] as $field_key => $field ) {
$field = $appointment_type['customer_information'][ $field_key ];
if ( ! $field['display'] ) {
continue;
}
$field_array = $field;
// include field type.
$type = isset( $field_type_conversion_map[ $field['field'] ] ) ? $field_type_conversion_map[ $field['field'] ] : 'single-text';
$field_array['type'] = $type;
$field_array['values'] = array();
$appointment_types[ $appointment_type_key ]['custom_customer_information'][] = $field_array;
}
}
// update appointment type.
$this->plugin->appointment_type_model->update( $appointment_types[ $appointment_type_key ]['id'], $appointment_types[ $appointment_type_key ] );
}
/* Migrate Appointments */
$appointments = $this->plugin->appointment_model->query(
array(
'number' => -1,
)
);
// clear appointment types cache.
$this->plugin->appointment_type_model->invalidate_appointment_type_cache();
}
/**
* Helper logic to migrate customer info from paid to free edition.
*
* @since 4.4.9
*
* @return void
*/
public function migrate_paid_to_free_customer_info() {
$field_conversion_map = array(
'Name' => array(
'field' => 'Name',
'display' => true,
'required' => true,
'icon' => 'face',
),
'Email' => array(
'field' => 'Email',
'display' => true,
'required' => true,
'icon' => 'email',
),
'Phone' => array(
'field' => 'Phone',
'display' => true,
'required' => false,
'icon' => 'phone',
),
'Address' => array(
'field' => 'Address',
'display' => false,
'required' => false,
'icon' => 'place',
),
'City' => array(
'field' => 'City',
'display' => false,
'required' => false,
'icon' => 'place',
),
'State' => array(
'field' => 'State',
'display' => false,
'required' => false,
'icon' => 'place',
),
'Zip' => array(
'field' => 'Zip',
'display' => false,
'required' => false,
'icon' => 'place',
),
'Notes' => array(
'field' => 'Notes',
'display' => false,
'required' => false,
'icon' => 'assignment',
),
);
/* Migrate Appointment Types */
$appointment_types = $this->plugin->appointment_type_model->query(
array(
'number' => -1,
)
);
foreach ( $appointment_types as $appointment_type_key => $appointment_type ) {
// if customer_information is not empty, then we can't populate it again, since it might lose data.
if ( ! empty( $appointment_type['customer_information'] ) ) {
continue;
}
$appointment_types[ $appointment_type_key ]['customer_information'] = array();
if ( ! empty( $appointment_type['custom_customer_information']['0']['field'] ) ) {
$schema = $appointment_type['custom_customer_information']['0'];
foreach ( $field_conversion_map as $field_key => $field_value ) {
// check if we have this field set before so we can verify if it's set to display / require.
$existing_field_key = array_search( $field_value, array_column( $appointment_type['custom_customer_information'], 'field' ), true );
if ( false !== $existing_field_key ) {
$field = $appointment_type['custom_customer_information'][ $existing_field_key ];
} else {
$field = $field_conversion_map[ $field_key ];
}
// remove field type.
if ( isset( $field['type'] ) ) {
unset( $field['type'] );
}
$appointment_types[ $appointment_type_key ]['customer_information'][] = $field;
}
}
// update appointment type.
$this->plugin->appointment_type_model->update( $appointment_types[ $appointment_type_key ]['id'], $appointment_types[ $appointment_type_key ] );
}
// clear appointment types cache.
$this->plugin->appointment_type_model->invalidate_appointment_type_cache();
}
public function get_last_version_seen() {
$db_versions = get_option( 'ssa_versions', json_encode( array() ) );
$db_versions = json_decode( $db_versions, true );
$db_version_keys = array_keys( $db_versions );
$last_changed_date = array_pop( $db_version_keys );
if ( $last_changed_date === null ) {
// First time we're seeing SSA installed
$this->last_version_seen = '0.0.0';
} else {
$this->last_version_seen = $db_versions[$last_changed_date];
}
return $this->last_version_seen;
}
public function record_version( $version ) {
$db_versions = get_option( 'ssa_versions', json_encode( array() ) );
$db_versions = json_decode( $db_versions, true );
$db_versions[gmdate('Y-m-d H:i:s')] = $version;
$this->last_version_seen = $version;
return update_option( 'ssa_versions', json_encode($db_versions) );
}
public function migrations() {
$this->last_version_seen = $this->get_last_version_seen();
foreach ( $this->versions_requiring_upgrade as $version ) {
if ( version_compare($this->last_version_seen, $version,'>=' ) ) {
continue;
}
$method_name = 'migrate_to_version_'.str_replace('.', '_', $version);
$this->$method_name( $this->last_version_seen );
}
}
public function migrate_to_version_1_2_3( $from_version ) {
if ( $from_version === '0.0.0' ) {
return; // we don't need to migrate fresh installs
}
$appointment_types = $this->plugin->appointment_type_model->query( array(
'number' => -1,
) );
if ( empty( $appointment_types['0']['id'] ) ) {
$this->record_version( '1.2.3' );
return;
}
foreach ($appointment_types as $appointment_type_key => $appointment_type) {
if ( empty( $appointment_type['custom_customer_information']['0']['field'] ) ) {
continue;
}
foreach ($appointment_type['custom_customer_information'] as $field_key => $field ) {
if ( $field['field'] != 'Email address' ) {
continue;
}
$appointment_types[$appointment_type_key]['custom_customer_information'][$field_key]['field'] = 'Email';
}
$this->plugin->appointment_type_model->update( $appointment_types[$appointment_type_key]['id'], $appointment_types[$appointment_type_key] );
}
$this->record_version( '1.2.3' );
}
public function migrate_to_version_1_5_1( $from_version ) {
if ( $from_version === '0.0.0' ) {
return; // we don't need to migrate fresh installs
}
$field_name_conversion_map = array(
'name' => 'Name',
'email' => 'Email',
'phone_number' => 'Phone',
'address' => 'Address',
'city' => 'City',
'state' => 'State',
'zip' => 'Zip',
'notes' => 'Notes',
);
/* Migrate Appointment Types */
$appointment_types = $this->plugin->appointment_type_model->query( array(
'number' => -1,
) );
foreach ($appointment_types as $appointment_type_key => $appointment_type) {
if ( !empty( $appointment_type['custom_customer_information']['0']['field'] ) ) {
foreach ($appointment_type['custom_customer_information'] as $field_key => $field ) {
if ( empty( $field_name_conversion_map[$field['field']] ) ) {
continue;
}
$appointment_types[$appointment_type_key]['custom_customer_information'][$field_key]['field'] = $field_name_conversion_map[$field['field']];
}
}
if ( !empty( $appointment_type['customer_information']['0']['field'] ) ) {
foreach ($appointment_type['customer_information'] as $field_key => $field ) {
if ( empty( $field_name_conversion_map[$field['field']] ) ) {
continue;
}
$appointment_types[$appointment_type_key]['customer_information'][$field_key]['field'] = $field_name_conversion_map[$field['field']];
}
}
$this->plugin->appointment_type_model->update( $appointment_types[$appointment_type_key]['id'], $appointment_types[$appointment_type_key] );
}
/* Migrate Appointments */
$appointments = $this->plugin->appointment_model->query( array(
'number' => -1,
) );
foreach ($appointments as $appointment_key => $appointment) {
if ( !empty( $appointment['customer_information'] ) ) {
foreach ($appointment['customer_information'] as $field_key => $value ) {
if ( empty( $field_name_conversion_map[$field_key] ) ) {
continue;
}
$appointments[$appointment_key]['customer_information'][$field_name_conversion_map[$field_key]] = $value;
unset( $appointments[$appointment_key]['customer_information'][$field_key] );
}
}
$this->plugin->appointment_model->update( $appointments[$appointment_key]['id'], $appointments[$appointment_key] );
}
$this->record_version( '1.5.1' );
}
public function migrate_to_version_2_6_9_12( $from_version ) {
global $wp_rewrite;
$wp_rewrite->init();
flush_rewrite_rules();
$this->record_version( '2.6.9_12' );
}
public function migrate_to_version_2_6_9_13( $from_version ) {
$DRA_route_whitelist = get_option( 'DRA_route_whitelist', array() );
$ssa_routes_to_whitelist = array(
"/ssa/v1","/ssa/v1/settings",
"/ssa/v1/settings/(?P&lt;id&gt;[a-zA-Z0-9_-]+)",
"/ssa/v1/settings/schema",
"/ssa/v1/notices",
"/ssa/v1/notices/(?P&lt;id&gt;[a-zA-Z0-9_-]+)",
"/ssa/v1/notices/schema",
"/ssa/v1/license",
"/ssa/v1/license/schema",
"/ssa/v1/google_calendars",
"/ssa/v1/google_calendars/disconnect",
"/ssa/v1/google_calendars/authorize_url",
"/ssa/v1/mailchimp",
"/ssa/v1/mailchimp/disconnect",
"/ssa/v1/mailchimp/authorize",
"/ssa/v1/mailchimp/deauthorize",
"/ssa/v1/mailchimp/lists",
"/ssa/v1/mailchimp/subscribe",
"/ssa/v1/support_status",
"/ssa/v1/support_ticket",
"/oembed/1.0",
"/ssa/v1/appointments",
"/ssa/v1/appointments/bulk",
"/ssa/v1/appointments/(?P&lt;id&gt;[\\d]+)",
"/ssa/v1/appointments/(?P&lt;id&gt;[\\d]+)/ics",
"/ssa/v1/appointment_types",
"/ssa/v1/appointment_types/bulk",
"/ssa/v1/appointment_types/(?P&lt;id&gt;[\\d]+)",
"/ssa/v1/appointment_types/(?P&lt;id&gt;[\\d]+)/availability",
"/ssa/v1/availability",
"/ssa/v1/availability/bulk",
"/ssa/v1/availability/(?P&lt;id&gt;[\\d]+)",
"/ssa/v1/async",
"/ssa/v1/payments",
"/ssa/v1/payments/bulk",
"/ssa/v1/payments/(?P&lt;id&gt;[\\d]+)"
);
if ( empty( $DRA_route_whitelist ) ) {
$DRA_route_whitelist = $ssa_routes_to_whitelist;
} else {
foreach ( $ssa_routes_to_whitelist as $key => $route ) {
if ( ! in_array( $route, $DRA_route_whitelist ) ) {
$DRA_route_whitelist[] = $route;
}
}
}
update_option( 'DRA_route_whitelist', $DRA_route_whitelist );
$this->record_version( '2.6.9_13' );
}
public function migrate_to_version_2_7_1( $from_version ) {
$notifications_settings = $this->plugin->notifications_settings->get();
$should_enable_admin_notification_for_all_appointment_types = true;
$should_enable_customer_notification_for_all_appointment_types = true;
$appointment_type_ids_with_admin_notification = array();
$appointment_type_ids_with_customer_notification = array();
$appointment_types = $this->plugin->appointment_type_model->query();
if ( ! empty( $appointment_types ) ) {
foreach ( $appointment_types as $key => $appointment_type ) {
if ( empty( $appointment_type['notifications'] ) ) {
$appointment_type_ids_with_admin_notification[] = $appointment_type['id'];
$appointment_type_ids_with_customer_notification[] = $appointment_type['id'];
continue;
}
foreach ($appointment_type['notifications'] as $notification_key => $notification ) {
if ( empty( $notification['field'] ) ) {
continue;
}
if ( $notification['field'] === 'admin' ) {
if ( empty( $notification['send'] ) ) {
$should_enable_admin_notification_for_all_appointment_types = false;
} else {
$appointment_type_ids_with_admin_notification[] = $appointment_type['id'];
}
} elseif ( $notification['field'] === 'customer' ) {
if ( empty( $notification['send'] ) ) {
$should_enable_customer_notification_for_all_appointment_types = false;
} else {
$appointment_type_ids_with_customer_notification[] = $appointment_type['id'];
}
}
}
}
}
$id = time();
$booked_admin_notification = array(
'appointment_types' => ( $should_enable_admin_notification_for_all_appointment_types ) ? array() : $appointment_type_ids_with_admin_notification,
'id' => $id,
'schema' => '2019-04-02',
'sent_to' => array(
'{{admin_email}}',
),
'title' => 'Email (Admin)',
'subject' => '{{ Appointment.customer_information.Name }} just booked an appointment',
'message' => wpautop( nl2br( $this->plugin->templates->get_template( 'notifications/email/text/booked-staff.php' ) ) ),
'trigger' => 'appointment_booked',
'type' => 'email',
'when' => 'after',
'duration' => 0,
);
$id = time() + 1;
$booked_customer_notification = array(
'appointment_types' => ( $should_enable_customer_notification_for_all_appointment_types ) ? array() : $appointment_type_ids_with_customer_notification,
'id' => $id,
'schema' => '2019-04-02',
'sent_to' => array(
'{{customer_email}}',
),
'subject' => 'Your appointment details',
'message' => wpautop( nl2br( $this->plugin->templates->get_template( 'notifications/email/text/booked-customer.php' ) ) ),
'title' => 'Email (Customer)',
'trigger' => 'appointment_booked',
'type' => 'email',
'when' => 'after',
'duration' => 0,
);
$id = time() + 2;
$canceled_admin_notification = array(
'appointment_types' => ( $should_enable_admin_notification_for_all_appointment_types ) ? array() : $appointment_type_ids_with_admin_notification,
'id' => $id,
'schema' => '2019-04-02',
'sent_to' => array(
'{{admin_email}}',
),
'title' => 'Email (Admin)',
'subject' => '{{ Appointment.customer_information.Name }} just canceled an appointment',
'message' => wpautop( nl2br( $this->plugin->templates->get_template( 'notifications/email/text/canceled-staff.php' ) ) ),
'trigger' => 'appointment_canceled',
'type' => 'email',
'when' => 'after',
'duration' => 0,
);
$id = time() + 3;
$canceled_customer_notification = array(
'appointment_types' => ( $should_enable_customer_notification_for_all_appointment_types ) ? array() : $appointment_type_ids_with_customer_notification,
'id' => $id,
'schema' => '2019-04-02',
'sent_to' => array(
'{{customer_email}}',
),
'subject' => 'Your appointment has been canceled',
'message' => wpautop( nl2br( $this->plugin->templates->get_template( 'notifications/email/text/canceled-customer.php' ) ) ),
'title' => 'Email (Customer)',
'trigger' => 'appointment_canceled',
'type' => 'email',
'when' => 'after',
'duration' => 0,
);
$notifications_settings['notifications'] = array(
$booked_admin_notification,
$booked_customer_notification,
$canceled_admin_notification,
$canceled_customer_notification,
);
$this->plugin->notifications_settings->update( $notifications_settings );
$this->record_version( '2.7.1' );
}
public function migrate_to_version_2_9_2( $from_version ) {
$appointment_types = $this->plugin->appointment_type_model->query( array(
'number' => -1,
) );
if ( empty( $appointment_types['0']['id'] ) ) {
$this->record_version( '2.9.2' );
return;
}
foreach ($appointment_types as $appointment_type_key => $appointment_type) {
if ( empty( $appointment_type['custom_customer_information']['0']['field'] ) ) {
continue;
}
foreach ($appointment_type['custom_customer_information'] as $field_key => $field ) {
if ( false === stripos( $field['field'], 'phone' ) ) {
if ( $field['type'] !== 'single-text' ) {
continue;
}
if ( empty( $field['icon'] ) || ( $field['icon'] !== 'call' ) ) {
continue;
}
}
$appointment_types[$appointment_type_key]['custom_customer_information'][$field_key]['type'] = 'phone';
}
$this->plugin->appointment_type_model->update( $appointment_types[$appointment_type_key]['id'], $appointment_types[$appointment_type_key] );
}
$this->record_version( '2.9.2' );
}
public function migrate_to_version_3_1_0( $from_version ) {
$notifications_settings = $this->plugin->notifications_settings->get();
foreach ($notifications_settings['notifications'] as $key => $notification) {
if ( empty( $notification['sent_to'] ) || ! is_array( $notification['sent_to'] ) ) {
continue;
}
$is_customer_notification = false;
foreach ( $notification['sent_to'] as $recipient ) {
if ( false !== strpos( $recipient, '{{customer' ) ) {
$is_customer_notification = true;
}
}
if ( ! $is_customer_notification ) {
continue;
}
$notifications_settings['notifications'][$key]['message'] = str_replace( 'Appointment.date_timezone', 'Appointment.customer_timezone', $notifications_settings['notifications'][$key]['message'] );
}
$this->plugin->notifications_settings->update( $notifications_settings );
$this->record_version( '3.1.0' );
}
public function migrate_to_version_3_5_0( $from_version ) {
$notifications_settings = $this->plugin->notifications_settings->get();
foreach ($notifications_settings['notifications'] as $key => $notification) {
$notifications_settings['notifications'][$key]['message'] = str_replace( 'Appointment.AppointmentType.instructions', 'instructions', $notifications_settings['notifications'][$key]['message'] );
}
$this->plugin->notifications_settings->update( $notifications_settings );
$this->record_version( '3.5.0' );
}
public function migrate_to_version_4_2_2( $from_version ) {
$this->plugin->capabilities->remove_roles();
$this->plugin->capabilities->add_roles();
$this->record_version( '4.2.2' );
}
public function migrate_to_version_4_4_5( $from_version ) {
$appointment_types = $this->plugin->appointment_type_model->query( array(
'number' => -1,
) );
foreach ($appointment_types as $appointment_type) {
if ( empty( $appointment_type['staff']['required'] ) ) {
continue;
}
if ( $appointment_type['capacity_type'] == 'individual' && $appointment_type['staff_capacity'] == SSA_Constants::CAPACITY_MAX ) {
$this->plugin->appointment_type_model->update( $appointment_type['id'], array(
'staff_capacity' => 1,
) );
}
}
$this->record_version( '4.4.5' );
}
public function migrate_to_version_5_4_4( $from_version ) {
// Get list of booked + upcoming appointments.
$appointments = $this->plugin->appointment_model->query(
array(
'status' => array('booked'),
'start_date_min' => gmdate('Y-m-d H:i:s'),
'number' => -1,
)
);
if (empty($appointments)) {
$this->record_version('5.4.4');
return;
}
$appointments = array_values(
array_filter(
$appointments,
function ($appointment) {
return empty($appointment['google_calendar_event_id']);
}
)
);
// if we have a list of appointments, we need to sync them with Google Calendar.
$this->plugin->google_calendar->bulk_schedule_google_calendar_sync($appointments);
$this->record_version( '5.4.4' );
}
/**
* Migrate to version 5.4.6
*
* @param string $from_version The version we are migrating from.
*/
public function migrate_to_version_5_4_6( $from_version ) {
$settings = $this->plugin->calendar_events_settings->get();
$settings_map = array(
'event_type_customer',
'event_type_individual_shared',
'event_type_group_shared',
'event_type_individual_admin',
'event_type_group_admin',
);
$clear_map = array(
'</p>' => "</p>\r\n",
'<br />' => "\r\n",
'<br>' => "\r\n",
'<br/>' => "\r\n",
);
foreach ( $settings_map as $event_type ) {
if ( ! isset( $settings[ $event_type ] ) ) {
continue;
}
foreach ( $settings[ $event_type ] as $key => $value ) {
if ( ! isset( $settings[ $event_type ][ $key ] ) ) {
continue;
}
// Remove all html tags and turn paragraphs or <br> into line breaks.
// Required to avoid html issues with Google Calendar and other calendar apps.
$value = str_replace( array_keys( $clear_map ), array_values( $clear_map ), $value );
$value = wp_strip_all_tags( $value );
$settings[ $event_type ][ $key ] = $value;
}
}
$this->plugin->calendar_events_settings->update( $settings );
$this->record_version( '5.4.6' );
}
/**
* Reset/Disable the beta option
* Beta optin one time only; Turn off after one beta is downloaded.
*
* @return void
*/
public function reset_beta_option() {
$developer_settings = $this->plugin->developer_settings->get();
if ( ! empty($developer_settings['beta_updates']) ) {
$developer_settings['beta_updates'] = false;
$this->plugin->developer_settings->update($developer_settings);
}
}
/**
* Populate the new appt type labels with the existing registered labels if any
*
* @param string $from_version
* @return void
*/
public function migrate_to_version_6_1_5( $from_version ) {
$this->migrate_appointment_type_labels();
$this->record_version( '6.1.5' );
}
/**
* Helper function to migrate to the new appt type labels setup
* Needed in case to be called when importing; check class-support-status -> import_data()
*
* @return void
*/
public function migrate_appointment_type_labels(){
if( ! empty( $GLOBALS["_COOKIE"]["__cypress_initial"] ) ) {
return;
}
// First let's make sure we have a fresh appt type labels tabel for the IDs to be reset
$this->plugin->appointment_type_label_model->truncate();
$this->plugin->appointment_type_label_model->create_table();
// Query all appointment types that are defined
$appointment_types = $this->plugin->appointment_type_model->query( array(
'number' => -1,
) );
if ( empty( $appointment_types ) ) {
// This is either a new install or has no appt types defined -> insert one row in labels table
$default_label['name'] = __('Default', 'simply-schedule-appointments');
$default_label['color'] = 'light-green';
$this->plugin->appointment_type_label_model->raw_insert( $default_label );
} else {
// Get all registered labels for this site
$labels = array();
foreach( $appointment_types as $appointment_type ) {
if ( empty( $appointment_type['label'] ) ){
continue;
}
if ( ! in_array( $appointment_type['label'], $labels)){
$labels[] = $appointment_type['label'];
}
}
// If no labels have been detected insert the Default label into labels table
if( empty( $labels ) ){
$default_label['name'] = __('Default', 'simply-schedule-appointments');
$default_label['color'] = 'light-green';
$this->plugin->appointment_type_label_model->raw_insert( $default_label );
}
elseif( count( $labels ) === 1 ){
// If only one label detected then add this label with its name set to Default
$default_label['name'] = __('Default', 'simply-schedule-appointments');
$default_label['color'] = $labels[0];
$this->plugin->appointment_type_label_model->raw_insert( $default_label );
} else {
// Insert all labels into new labels table
foreach( $labels as $label ) {
$row['name'] = ucwords( str_replace( '-', ' ', $label ) );
$row['color'] = $label;
$this->plugin->appointment_type_label_model->raw_insert( $row );
}
}
}
// confirm at this point that we have a label at ID 1
$existing_default_label = $this->plugin->appointment_type_label_model->query( array(
$this->plugin->appointment_type_label_model->primary_key() => 1,
) );
// if not then insert the default label
if ( empty( $existing_default_label ) ) {
$this->plugin->appointment_type_label_model->raw_insert(
array(
// force insert at ID 1
$this->plugin->appointment_type_label_model->primary_key() => 1,
// the name of the label is intentionally different from the default label name above
'name' => __('Default Label', 'simply-schedule-appointments'),
'color' => 'light-green',
)
);
}
if ( ! empty( $appointment_types ) ) {
// Query all labels that were inserted to access their ids
$appointment_type_labels = $this->plugin->appointment_type_label_model->query( array(
'number' => -1,
) );
// Set the new label ids as foreign keys in appt types table
foreach( $appointment_types as $appointment_type ) {
if ( empty( $appointment_type['label'] ) ) {
continue;
}
foreach($appointment_type_labels as $label ) {
if ( $appointment_type['label'] === $label['color'] ) {
$this->plugin->appointment_type_model->update( $appointment_type['id'], array(
'label_id' => $label['id'],
));
}
}
}
}
}
/**
* Since an index ID of 0 in Microsoft SQL Server is allowed
* Fix Default label id not being found
*
* @param string $from_version
* @return void
*/
public function migrate_to_version_6_4_3( $from_version ) {
$this->maybe_fix_appointment_type_label_id_equal_to_zero();
$this->record_version( '6.4.3' );
}
/**
* Since an index ID of 0 in Microsoft SQL Server is allowed
* Fix Default label id not being found
*
* @param string $from_version
* @return void
*/
public function migrate_to_version_6_4_5( $from_version ) {
$this->maybe_fix_deleted_appointment_types();
$this->record_version( '6.4.5' );
}
public function maybe_fix_deleted_appointment_types() {
static $has_already_run_once_during_this_php_execution = false;
if ( $has_already_run_once_during_this_php_execution ) {
return; // this prevents an infinite loop since this function queries, which calls prepare_for_response(), which in turn may call this function again
// There is still a problem if we end up here, but this prevents a fatal error
}
$has_already_run_once_during_this_php_execution = true;
$orphaned_appointments = $this->plugin->appointment_model->query( array(
'number' => -1,
'append_where_sql' => " AND `appointment_type_id` NOT IN( SELECT `id` FROM " . $this->plugin->appointment_type_model->get_table_name() . " )",
) );
$labels = $this->plugin->appointment_type_label_model->query( array(
'number' => 1,
) );
if ( empty( $orphaned_appointments ) ) {
return;
}
$map_missing_appointment_type_ids_to_replacement_ids = array();
foreach( $orphaned_appointments as $orphaned_appointment ) {
$missing_appointment_type_id = $orphaned_appointment['appointment_type_id'];
if ( empty( $map_missing_appointment_type_ids_to_replacement_ids[$missing_appointment_type_id] ) ) {
// Create a new appointment type ID to replace the missing one
$replacement_id = $this->plugin->appointment_type_model->insert( array(
'title' => __( 'Previously Deleted Appointment Type #', 'simply-schedule-appointments' ) . $missing_appointment_type_id,
'label_id' => $labels[0]['id'],
'status' => 'delete',
'custom_customer_information' => array (
array (
'field' => 'Name',
'display' => true,
'required' => true,
'type' => 'single-text',
'icon' => 'face',
'values' => '',
),
array (
'field' => 'Email',
'display' => true,
'required' => true,
'type' => 'single-text',
'icon' => 'email',
'values' => '',
),
),
'customer_information' => array (
array (
'field' => 'Name',
'display' => true,
'required' => true,
'type' => 'single-text',
'icon' => 'face',
'values' => '',
),
array (
'field' => 'Email',
'display' => true,
'required' => true,
'type' => 'single-text',
'icon' => 'email',
'values' => '',
),
),
) );
$map_missing_appointment_type_ids_to_replacement_ids[$missing_appointment_type_id] = $replacement_id;
}
$this->plugin->appointment_model->db_update( $orphaned_appointment['id'], array(
'appointment_type_id' => $map_missing_appointment_type_ids_to_replacement_ids[$missing_appointment_type_id],
) );
}
}
public function maybe_fix_appointment_type_label_id_equal_to_zero() {
// Query all labels that were inserted to access their ids
$appointment_type_labels = $this->plugin->appointment_type_label_model->query( array(
'number' => -1,
'append_where_sql' => " AND `id` IN( 0 ) ",
) );
foreach ( $appointment_type_labels as $label ) {
$raw_id = $this->plugin->appointment_type_label_model->raw_insert( array(
'name' => $label['name'],
'color' => $label['color']
));
$this->plugin->appointment_type_label_model->db_delete( $label['id'], true );
// Update appointment types to use new label id
$appointment_types = $this->plugin->appointment_type_model->query( array(
'number' => -1,
'label_id' => $label['id'],
));
if( empty( $appointment_types ) ) {
continue;
}
foreach( $appointment_types as $type ) {
$this->plugin->appointment_type_model->update( $type['id'], array(
'label_id' => $raw_id,
));
}
}
}
/**
* Clear ssa_error_notices array since the old format of the stored errors has changed from indexed array to associative array
*
* @param string $from_version
* @return void
*/
public function migrate_to_version_6_4_9( $from_version ) {
$errors = get_option( 'ssa_error_notices', array() );
if( ! empty( $errors ) ) {
update_option( 'ssa_error_notices', array() );
}
$this->record_version( '6.4.9' );
}
/**
* Add {{ cancelation_note }} to the customer and admin canceled notifications
*
* @param string $from_version
* @return void
*/
public function migrate_to_version_6_5_1( $from_version ) {
$notifications_settings = $this->plugin->notifications_settings->get();
if ( empty( $notifications_settings['notifications'] ) ) {
$this->record_version( '6.5.1' );
return;
}
foreach( $notifications_settings['notifications'] as $key => $notification ) {
if( $notification['trigger'] === 'appointment_canceled' && $notification['type'] === 'email' ) {
$message = $notification['message'];
if ( empty( $message ) ) {
continue;
}
$pattern = '/has been canceled\s*\<\/p>/i';
$is_matching = preg_match($pattern, $message);
if ( $is_matching ) {
$message = preg_replace_callback( $pattern, function( $matches ) {
return $matches[0] .
'{% if cancelation_note %}' .
'<p>' .
'{% if canceled_by_name %} {{ canceled_by_name }} ' . __( 'left a note', 'simply-schedule-appointments' ) . ': {% else %}' . __( 'Note', 'simply-schedule-appointments' ) . ': {% endif %}' .
'{{ cancelation_note }} </p>' .
'{% endif %}';
}, $message );
} else {
$message .=
'{% if cancelation_note %}' .
'<p>' .
'{% if canceled_by_name %} {{ canceled_by_name }} ' . __( 'left a note', 'simply-schedule-appointments' ) . ': {% else %}' . __( 'Note', 'simply-schedule-appointments' ) . ': {% endif %}' .
'{{ cancelation_note }} </p>' .
'{% endif %}';
}
$notifications_settings['notifications'][$key]['message'] = $message;
}
}
$this->plugin->notifications_settings->update( $notifications_settings );
$this->record_version( '6.5.1' );
}
/**
* Get and save stripe settings to enforce stripe settings encryption
*
* @param string $from_version
* @return void
*/
public function migrate_to_version_6_5_4( $from_version ) {
if ( class_exists( 'SSA_Stripe_Settings' ) ) {
$stripe_settings = $this->plugin->stripe_settings->get();
$this->plugin->stripe_settings->update( $stripe_settings );
}
$this->record_version( '6.5.4' );
}
/**
* Remove public_edit_url from calendar shared group event
*
* @param string $from_version
* @return void
*/
public function migrate_to_version_6_5_20( $from_version ) {
$settings = $this->plugin->calendar_events_settings->get();
if ( ! isset( $settings[ 'event_type_group_shared' ] ) ) {
$this->record_version( '6.5.20' );
return;
}
$settings[ 'event_type_group_shared' ] = str_replace('Need to make changes to this event?', '',$settings[ 'event_type_group_shared' ]);
$settings[ 'event_type_group_shared' ] = str_replace('{{ Appointment.public_edit_url }}', '',$settings[ 'event_type_group_shared' ]);
$this->plugin->calendar_events_settings->update( $settings );
$this->record_version( '6.5.20' );
}
/**
* Migrates settings after specifically checking for the presence of a custom CSS file.
*
* @param string $from_version The version from which the migration is being performed.
*
* @return void
*/
public function migrate_to_version_6_6_19( $from_version ) {
$settings = $this->plugin->settings->get();
if ( $settings ) {
$theme_directory = get_stylesheet_directory();
// Build the path to the custom CSS directory
$custom_css_dir = $theme_directory . '/ssa/booking-app/';
// List all files in the custom CSS directory
if (is_dir($custom_css_dir)) {
// List all files in the custom CSS directory
$files = scandir($custom_css_dir);
// Check if there's at least one CSS file
foreach ($files as $file) {
if (pathinfo($file, PATHINFO_EXTENSION) === 'css') {
$settings['global'][ 'should_display_new_booking_app_banner' ] = 'notice';
$this->plugin->settings->update( $settings );
}
}
}
$is_custom_css_existing = $this->plugin->styles_settings->get()['css'];
if ($is_custom_css_existing !== '') {
$settings['global'][ 'should_display_new_booking_app_banner' ] = 'notice';
}
$this->plugin->settings->update( $settings );
}
$this->record_version( '6.6.19' );
}
/*
* Remove public_edit_url from calendar admin group event
*
* @param string $from_version
* @return void
*/
public function migrate_to_version_6_6_21( $from_version ) {
$settings = $this->plugin->calendar_events_settings->get();
if ( ! isset( $settings[ 'event_type_group_admin' ] ) ) {
$this->record_version( '6.6.21' );
return;
}
$settings[ 'event_type_group_admin' ] = str_replace('Need to make changes to this event?', '',$settings[ 'event_type_group_admin' ]);
$settings[ 'event_type_group_admin' ] = str_replace('{{ Appointment.public_edit_url }}', '',$settings[ 'event_type_group_admin' ]);
$this->plugin->calendar_events_settings->update( $settings );
$this->record_version( '6.6.21' );
}
/**
* Disable old booking app if enabled with resources enabled at the same time
*
* @param string $from_version
* @return void
*/
public function migrate_to_version_6_7_13( $from_version ) {
$ssa_settings = $this->plugin->settings->get();
// if resources are enabled
if( !empty( $ssa_settings['resources']['enabled'] ) ) {
// disable the old booking app
$developer_settings = $this->plugin->developer_settings->get();
if ( !empty( $developer_settings['old_booking_app'] ) ) {
$developer_settings['old_booking_app'] = false;
$this->plugin->developer_settings->update( $developer_settings );
}
}
$this->record_version( '6.7.13' );
}
/**
* Disable old booking app for all users
*
*/
public function migrate_to_version_6_7_45() {
$developer_settings = $this->plugin->developer_settings->get();
if ( !empty( $developer_settings['old_booking_app'] ) ) {
$developer_settings['old_booking_app'] = false;
$this->plugin->developer_settings->update( $developer_settings );
}
$this->record_version( '6.7.45' );
}
/**
* Update Google Calendar Quick Connect mode from saved dev settings
*
* @param string $from_version
* @return void
*/
public function migrate_to_version_6_8_1() {
// global variable skip token invalidation
global $ssa_skip_token_invalidation;
$ssa_skip_token_invalidation = true;
$developer_settings = $this->plugin->developer_settings->get();
// if quick connect was on - keep it on
if ( ! empty( $developer_settings['quick_connect_gcal_mode'] ) ) {
$this->plugin->google_calendar_settings->update( array(
'quick_connect_gcal_mode' => true,
) );
} else {
// if quick connect was off
$settings = $this->plugin->settings->get();
if ( ! empty( $settings[ 'google_calendar' ]['access_token'] ) ) {
// and gcal is connected - do nothing
} else {
// but if gcal is not connected - switch to quick connect mode
$this->plugin->google_calendar_settings->update( array(
'quick_connect_gcal_mode' => true,
) );
}
}
$this->record_version( '6.8.1' );
}
public function migrate_to_version_6_8_6() {
$appointment_types = $this->plugin->appointment_type_model->query();
$old_schema = array (
0 =>
array (
'field' => 'admin',
'send' => true,
),
1 =>
array (
'field' => 'customer',
'send' => true,
),
);
if ( ! empty( $appointment_types ) ) {
foreach ( $appointment_types as $key => $appointment_type ) {
if( ! empty( $appointment_type['notifications']['notifications_opt_in'] ) ) {
continue;
}
if ( ! empty( $appointment_type['notifications'] ) && is_array( $appointment_type['notifications'] ) && ! empty( $appointment_type['notifications'][0]['field'] ) ) {
$old_schema = $appointment_type['notifications'];
}
$updated_notifications = [
"fields" => $old_schema,
"notifications_opt_in" => [
"enabled" => false,
"label" => "Receive notifications",
"description" => "Check this box to receive appointment notifications."
]
];
$this->plugin->appointment_type_model->update( $appointment_type['id'], array(
'notifications' => $updated_notifications,
));
}
}
$this->record_version( '6.8.6' );
}
}