597 lines
19 KiB
PHP
597 lines
19 KiB
PHP
<?php
|
|
/**
|
|
* Simply Schedule Appointments Support Status.
|
|
*
|
|
* @since 2.1.6
|
|
* @package Simply_Schedule_Appointments
|
|
*/
|
|
|
|
/**
|
|
* Simply Schedule Appointments Support Status.
|
|
*
|
|
* @since 2.1.6
|
|
*/
|
|
class SSA_Support_Status {
|
|
/**
|
|
* Parent plugin class.
|
|
*
|
|
* @since 2.1.6
|
|
*
|
|
* @var Simply_Schedule_Appointments
|
|
*/
|
|
protected $plugin = null;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @since 2.1.6
|
|
*
|
|
* @param Simply_Schedule_Appointments $plugin Main plugin object.
|
|
*/
|
|
public function __construct( $plugin ) {
|
|
$this->plugin = $plugin;
|
|
$this->hooks();
|
|
}
|
|
|
|
/**
|
|
* Initiate our hooks.
|
|
*
|
|
* @since 2.1.6
|
|
*/
|
|
public function hooks() {
|
|
add_action( 'admin_init', array( $this, 'validate_directory_name' ), 0 );
|
|
|
|
}
|
|
|
|
/**
|
|
* Get file path
|
|
*
|
|
* @param string $filename Filename
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_log_file_path($filename = 'debug')
|
|
{
|
|
$path = SSA_Filesystem::get_uploads_dir_path();
|
|
if (empty($path)) {
|
|
return false;
|
|
}
|
|
|
|
$path .= '/logs';
|
|
if (!wp_mkdir_p($path)) {
|
|
return false;
|
|
}
|
|
|
|
if (!file_exists($path . '/index.html')) {
|
|
$handle = @fopen($path . '/index.html', 'w');
|
|
@fwrite($handle, '');
|
|
@fclose($handle);
|
|
}
|
|
|
|
if ( defined('AUTH_KEY') ) {
|
|
$filename .= '-' . substr(sha1(AUTH_KEY), 0, 10);
|
|
}
|
|
|
|
return $path . '/' . sanitize_title($filename) . '.log';
|
|
}
|
|
|
|
|
|
/**
|
|
* Performs a list o site and plugin status checks and return the results.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_site_status() {
|
|
$site_status = new TD_Health_Check_Site_Status();
|
|
|
|
$status = array(
|
|
'plugin_version' => $site_status->test_ssa_plugin_version(),
|
|
'php_version' => $site_status->test_php_version(),
|
|
'wordpress_version' => $site_status->test_wordpress_version(),
|
|
'sql_server' => $site_status->test_sql_server(),
|
|
'json_extension' => $site_status->test_json_extension(),
|
|
'utf8mb4_support' => $site_status->test_utf8mb4_support(),
|
|
'dotorg_communication' => $site_status->test_dotorg_communication(),
|
|
'https_status' => $site_status->test_https_status(),
|
|
'ssl_support' => $site_status->test_ssl_support(),
|
|
'scheduled_events' => $site_status->test_scheduled_events(),
|
|
'php_timezone' => $site_status->test_php_default_timezone()
|
|
);
|
|
|
|
// If Paid edition, test site license
|
|
if ( $this->plugin->settings_installed->is_installed( 'license' ) ) {
|
|
$status = array_merge( array( 'ssa_license' => $this->test_site_license() ), $status );
|
|
}
|
|
// if google calendar is installed and enabled
|
|
if ( $this->plugin->settings_installed->is_enabled( 'google_calendar' ) ) {
|
|
|
|
$google_calendar_settings = ssa()->google_calendar_settings->get();
|
|
// if google calendar has ssa_quick_connect_gcal_mode set to true
|
|
if( true === $google_calendar_settings[ "quick_connect_gcal_mode" ] ){
|
|
$status = array_merge( $status, array( 'ssa_quick_connect_status'=> $site_status->test_ssa_quick_connect_status() ) );
|
|
}
|
|
}
|
|
|
|
if( current_user_can( 'ssa_manage_site_settings' ) ) {
|
|
$status = array_merge(
|
|
$status,
|
|
array(
|
|
'support_pin' => $this->get_site_support_pin()
|
|
)
|
|
);
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
|
|
/**
|
|
* Receives a JSON formatted string, parses into import data, and runs all the import process.
|
|
*
|
|
* @param array $decoded the JSON import data, decoded into an associative array format.
|
|
* @return boolean|WP_Error true if import process was successful, WP_Error if something bad happens.
|
|
*/
|
|
public function import_data( $decoded ) {
|
|
|
|
// If settings data is available, disable all settings (so we don't trigger hooks for notifications, webhooks, etc).
|
|
// The settings will get overwritten again at the end of this import process.
|
|
if ( isset( $decoded['settings'] ) ) {
|
|
$old_settings = $this->plugin->settings->get();
|
|
foreach ( $old_settings as $key => &$old_setting ) {
|
|
if ( empty( $old_setting ) || ! is_array( $old_setting ) ) {
|
|
continue;
|
|
}
|
|
|
|
$old_setting['enabled'] = false;
|
|
}
|
|
|
|
// disable settings before import.
|
|
$update = $this->plugin->settings->update( $old_settings );
|
|
|
|
// staff.
|
|
$delete = $this->plugin->staff_model->truncate();
|
|
$this->plugin->staff_model->create_table();
|
|
if( !empty( $decoded['staff'] ) && is_array( $decoded['staff'] ) ){
|
|
foreach ( $decoded['staff'] as $staff ) {
|
|
// Remove user IDs from export code since it sometimes assign staff members to the wrong WP users.
|
|
$staff['user_id'] = 0;
|
|
$include = $this->plugin->staff_model->raw_insert( $staff );
|
|
// if any error happens while trying to staff data, return.
|
|
if ( is_wp_error( $include ) ) {
|
|
return $include;
|
|
}
|
|
}
|
|
}
|
|
|
|
// resource group.
|
|
$delete = $this->plugin->resource_group_model->truncate();
|
|
$this->plugin->resource_group_model->create_table();
|
|
if ( isset( $decoded['resource_groups'] ) && ! empty( $decoded['resource_groups'] ) ) {
|
|
foreach ( $decoded['resource_groups'] as $resource_group ) {
|
|
$include = $this->plugin->resource_group_model->raw_insert( $resource_group );
|
|
|
|
// if any error happens while trying to import resource group data, return.
|
|
if ( is_wp_error( $include ) ) {
|
|
return $include;
|
|
}
|
|
}
|
|
}
|
|
|
|
// resources.
|
|
$delete = $this->plugin->resource_model->truncate();
|
|
$this->plugin->resource_model->create_table();
|
|
if ( isset( $decoded['resources'] ) && ! empty( $decoded['resources'] ) ) {
|
|
foreach ( $decoded['resources'] as $resource ) {
|
|
$include = $this->plugin->resource_model->raw_insert( $resource );
|
|
|
|
// if any error happens while trying to import resource data, return.
|
|
if ( is_wp_error( $include ) ) {
|
|
return $include;
|
|
}
|
|
}
|
|
}
|
|
|
|
// resource groups resource relation.
|
|
$delete = $this->plugin->resource_group_resource_model->truncate();
|
|
$this->plugin->resource_group_resource_model->create_table();
|
|
if ( isset( $decoded['resource_group_resources'] ) && ! empty( $decoded['resource_group_resources'] ) ) {
|
|
foreach ( $decoded['resource_group_resources'] as $resource_group_resource ) {
|
|
$include = $this->plugin->resource_group_resource_model->raw_insert( $resource_group_resource );
|
|
|
|
// if any error happens while trying to import resource group/resource data, return.
|
|
if ( is_wp_error( $include ) ) {
|
|
return $include;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if appointment types data is available, update.
|
|
if ( isset( $decoded['appointment_types'] ) ) {
|
|
$delete = $this->plugin->appointment_type_model->truncate();
|
|
$this->plugin->appointment_type_model->create_table();
|
|
|
|
foreach ( $decoded['appointment_types'] as $appointment_type ) {
|
|
$include = $this->plugin->appointment_type_model->raw_insert( $appointment_type );
|
|
|
|
// If any error happens while trying to import appointment type data, return.
|
|
if ( is_wp_error( $include ) ) {
|
|
return $include;
|
|
}
|
|
}
|
|
|
|
$delete = $this->plugin->staff_appointment_type_model->truncate();
|
|
$this->plugin->staff_appointment_type_model->create_table();
|
|
if( !empty( $decoded['staff_appointment_types'] ) && is_array( $decoded['staff_appointment_types'] ) ) {
|
|
foreach ( $decoded['staff_appointment_types'] as $staff_appointment_type ) {
|
|
$include = $this->plugin->staff_appointment_type_model->raw_insert( $staff_appointment_type );
|
|
|
|
// If any error happens while trying to import staff appointment type data, return.
|
|
if ( is_wp_error( $include ) ) {
|
|
return $include;
|
|
}
|
|
}
|
|
}
|
|
|
|
$delete = $this->plugin->resource_group_appointment_type_model->truncate();
|
|
$this->plugin->resource_group_appointment_type_model->create_table();
|
|
if ( isset( $decoded['resource_group_appointment_types'] ) && ! empty( $decoded['resource_group_appointment_types'] ) ) {
|
|
foreach ( $decoded['resource_group_appointment_types'] as $resource_group_appointment_type ) {
|
|
$include = $this->plugin->resource_group_appointment_type_model->raw_insert( $resource_group_appointment_type );
|
|
|
|
// If any error happens while trying to import resource group appointment type data, return.
|
|
if ( is_wp_error( $include ) ) {
|
|
return $include;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If $decoded contains 'appointment_type_labels' -> the exported site has the migration run & all was set for the labels -> just import labels
|
|
if ( isset( $decoded['appointment_type_labels'] ) && ! empty( $decoded['appointment_type_labels'] ) ) {
|
|
|
|
$delete = $this->plugin->appointment_type_label_model->truncate();
|
|
$this->plugin->appointment_type_label_model->create_table();
|
|
|
|
foreach ( $decoded['appointment_type_labels'] as $appointment_type_label ) {
|
|
$include = $this->plugin->appointment_type_label_model->raw_insert( $appointment_type_label );
|
|
|
|
if ( is_wp_error( $include ) ) {
|
|
return $include;
|
|
}
|
|
}
|
|
} else {
|
|
// We need to call the migration for the appt type labels to be set
|
|
$this->plugin->upgrade->migrate_appointment_type_labels();
|
|
$this->plugin->upgrade->maybe_fix_appointment_type_label_id_equal_to_zero();
|
|
}
|
|
|
|
}
|
|
|
|
// If appointments data is available, update.
|
|
if ( isset( $decoded['appointments'] ) ) {
|
|
$delete = $this->plugin->appointment_model->truncate();
|
|
$this->plugin->appointment_model->create_table();
|
|
|
|
foreach ( $decoded['appointments'] as $appointment ) {
|
|
$include = $this->plugin->appointment_model->raw_insert( $appointment );
|
|
|
|
// If any error happens while trying to import appointment data, return.
|
|
if ( is_wp_error( $include ) ) {
|
|
return $include;
|
|
}
|
|
}
|
|
|
|
// staff
|
|
$delete = $this->plugin->staff_appointment_model->truncate();
|
|
$this->plugin->staff_appointment_model->create_table();
|
|
|
|
if ( ! empty( $decoded['staff_appointments'] ) ) {
|
|
foreach ( $decoded['staff_appointments'] as $staff_appointment ) {
|
|
$include = $this->plugin->staff_appointment_model->raw_insert( $staff_appointment );
|
|
|
|
// If any error happens while trying to import staff_appointment data, return.
|
|
if ( is_wp_error( $include ) ) {
|
|
return $include;
|
|
}
|
|
}
|
|
}
|
|
|
|
// resource
|
|
$delete = $this->plugin->resource_appointment_model->truncate();
|
|
$this->plugin->resource_appointment_model->create_table();
|
|
|
|
if ( ! empty( $decoded['resource_appointments'] ) ) {
|
|
foreach ( $decoded['resource_appointments'] as $resource_appointment ) {
|
|
$include = $this->plugin->resource_appointment_model->raw_insert( $resource_appointment );
|
|
|
|
// If any error happens while trying to import resource_appointment data, return.
|
|
if ( is_wp_error( $include ) ) {
|
|
return $include;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If appointments meta data is available, update.
|
|
if ( isset( $decoded['appointment_meta'] ) ) {
|
|
$delete = $this->plugin->appointment_meta_model->truncate();
|
|
|
|
foreach ( $decoded['appointment_meta'] as $appointment_meta ) {
|
|
$include = $this->plugin->appointment_meta_model->raw_insert( $appointment_meta );
|
|
|
|
// If any error happens while trying to import appointment data, return.
|
|
if ( is_wp_error( $include ) ) {
|
|
return $include;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If settings data is available, update.
|
|
if ( isset( $decoded['settings'] ) ) {
|
|
$update = $this->plugin->settings->update( $decoded['settings'] );
|
|
}
|
|
|
|
$delete_revison_meta = $this->plugin->revision_meta_model->truncate();
|
|
$delete_revision = $this->plugin->revision_model->truncate();
|
|
$delete = $this->plugin->availability_model->truncate();
|
|
$this->plugin->availability_cache_invalidation->increment_cache_version();
|
|
$this->plugin->google_calendar->increment_google_cache_version();
|
|
|
|
$this->plugin->upgrade->migrate_free_to_paid_customer_info();
|
|
$this->plugin->upgrade->migrate_paid_to_free_customer_info();
|
|
// Everything was successfully imported.
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Save JSON export backups into the database.
|
|
*
|
|
* @param string $code
|
|
* @return bool|WP_Error true if backup was successfully saved. WP_Error if something wrong happens.
|
|
*/
|
|
public function save_export_backup( $code = null ) {
|
|
if( ! $code ) {
|
|
return false;
|
|
}
|
|
|
|
$date = date('Y-m-d H:i:s');
|
|
$encoded = json_encode($code);
|
|
|
|
$backups = get_option( 'ssa_export_backups' );
|
|
|
|
if( ! $backups ) {
|
|
$backups = array();
|
|
}
|
|
|
|
// if there is already 3 backups, remove the oldest one
|
|
if( count($backups) >= 3 ) {
|
|
array_pop($backups);
|
|
}
|
|
|
|
// insert the newest one at the beginning of the array
|
|
array_unshift(
|
|
$backups,
|
|
array(
|
|
'date' => $date,
|
|
'content' => $encoded
|
|
)
|
|
);
|
|
|
|
$update = update_option( 'ssa_export_backups', $backups, false );
|
|
|
|
if( ! $update ) {
|
|
return new WP_Error( 'ssa-export-backup-not-saved', __( 'An error occurred while trying to save a backup.', 'simply-schedule-appointments' ) );
|
|
}
|
|
|
|
return $update;
|
|
}
|
|
|
|
/**
|
|
* Checks if there is a backup export file stored and, if it does, then decode the JSON into an associative array.
|
|
*
|
|
* @return boolean|array false if we can't find the file, or an associative array if we find it and it has a valid format.
|
|
*/
|
|
public function get_export_backup() {
|
|
$backups = $this->get_export_backup_list();
|
|
|
|
if( is_wp_error($backups) ) {
|
|
return $backups;
|
|
}
|
|
|
|
$json = $backups[0]['content'];
|
|
|
|
// verify if JSON data is valid
|
|
$decoded = json_decode( $json, true );
|
|
|
|
if ( ! is_object( $decoded ) && ! is_array( $decoded ) ) {
|
|
return new WP_Error( 'export-code-invalid-format', __( 'Invalid data format.', 'simply-schedule-appointments'));
|
|
}
|
|
|
|
if ( json_last_error() !== JSON_ERROR_NONE ) {
|
|
return new WP_Error( 'export-code-invalid-format', __( 'Invalid data format.', 'simply-schedule-appointments'));
|
|
}
|
|
|
|
if( $decoded ) {
|
|
return $decoded;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if there is an export backup stored and returns a list if any.
|
|
*
|
|
* @return array|WP_Error An associative array if we find backups. WP_Error if we can't find anything.
|
|
*/
|
|
public function get_export_backup_list() {
|
|
$backups = get_option('ssa_export_backups');
|
|
|
|
if( ! $backups || empty($backups) ) {
|
|
return new WP_Error( 'ssa-export-backups-not-found', __( 'Could not find any export backups.', 'simply-schedule-appointments' ) );
|
|
}
|
|
|
|
return $backups;
|
|
}
|
|
|
|
/**
|
|
* Searches for latest export backup and, if found, recover the data by running the import logic.
|
|
*
|
|
* @return boolean|WP_Error
|
|
*/
|
|
public function restore_settings_backup() {
|
|
$backup = $this->get_export_backup();
|
|
|
|
if( ! $backup || is_wp_error( $backup ) ) {
|
|
return new WP_Error( 'ssa-export-file-not-found', __( 'No backup files were found.', 'simply-schedule-appointments' ) );
|
|
}
|
|
|
|
$import = $this->import_data( $backup );
|
|
|
|
if( is_wp_error( $import ) ) {
|
|
return $import;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks the current status of the plugin license.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function test_site_license() {
|
|
$expiration_date = $this->get_license_expiration_date();
|
|
$settings = $this->plugin->settings->get();
|
|
$license = $settings['license'];
|
|
|
|
|
|
$login_url = 'https://simplyscheduleappointments.com/your-account/';
|
|
$pricing_url = 'https://simplyscheduleappointments.com/pricing/';
|
|
|
|
|
|
if ( ! $this->plugin->settings_installed->is_installed( 'license' ) || 'empty' === $license['license_status'] || 'inactive' === $license['license_status'] ) {
|
|
return array(
|
|
// translators: %s is the URL to the login page.
|
|
'notices' => array( sprintf( __( '<a href="%s" target="_blank">Get your license key</a> and add it to <a href="#/ssa/settings/license">this site\'s settings</a> to enable automatic updates.', 'simply-schedule-appointments' ), $login_url ) ),
|
|
'status' => 'warning',
|
|
'value' => false,
|
|
);
|
|
}
|
|
|
|
if ( 'disabled' === $license['license_status'] ) {
|
|
return array(
|
|
// translators: %s is the URL to the login page.
|
|
'notices' => array( sprintf( __( 'Your license is disabled. <a href="%s" target="_blank">Purchase</a> a new license key to enable automatic updates and support.', 'simply-schedule-appointments' ), $pricing_url ) ),
|
|
'status' => 'warning',
|
|
'value' => false,
|
|
);
|
|
}
|
|
|
|
if ( 'expired' === $license['license_status'] ) {
|
|
return array(
|
|
// translators: %s is the URL to the login page.
|
|
'notices' => array(
|
|
sprintf(
|
|
__( 'Your license expired on %1$s. <a href="%2$s" target="_blank">Renew your license</a> to enable automatic updates, bug fixes and support.', 'simply-schedule-appointments' ),
|
|
$expiration_date,
|
|
$license['license_renewal_link']
|
|
),
|
|
),
|
|
'status' => 'warning',
|
|
'value' => false,
|
|
);
|
|
}
|
|
|
|
if ( 'active' === $license['license_status'] || 'valid' === $license['license_status'] ) {
|
|
if ( 'lifetime' === $license['license_expiration_date'] ) {
|
|
$notice = __( 'Your license is active. You have lifetime access.', 'simply-schedule-appointments' );
|
|
} else {
|
|
$notice = sprintf(
|
|
__( 'Your license is up-to-date. Next renewal is due on %s.', 'simply-schedule-appointments' ),
|
|
$expiration_date
|
|
);
|
|
}
|
|
return array(
|
|
'notices' => array( $notice ),
|
|
'status' => 'good',
|
|
'value' => true,
|
|
);
|
|
}
|
|
|
|
// if there isn't any other information, then we assume that the license is invalid.
|
|
return array(
|
|
// translators: %s is the URL to the login page.
|
|
'notices' => array( sprintf( __( '<a href="%s" target="_blank">Get your license key</a> and add it to <a href="#/ssa/settings/license">this site\'s settings</a> to enable automatic updates.', 'simply-schedule-appointments' ), $login_url ) ),
|
|
'status' => 'warning',
|
|
'value' => false,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* get the expiration date for expired license
|
|
*
|
|
*/
|
|
public function get_license_expiration_date() {
|
|
|
|
$license = $this->plugin->license->check();
|
|
|
|
if ( ! empty( $license['license_expiration_date'] ) && 'lifetime' !== $license['license_expiration_date'] ) {
|
|
|
|
$formatted_date = date_i18n( get_option( 'date_format' ), strtotime( $license['license_expiration_date'] ) );
|
|
|
|
return $formatted_date;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the main directory is named correctly
|
|
*
|
|
* @return void
|
|
*/
|
|
public function validate_directory_name() {
|
|
|
|
global $pagenow;
|
|
if ( 'plugins.php' !== $pagenow ) {
|
|
return;
|
|
}
|
|
|
|
$directory = $this->plugin->dir();
|
|
$pattern = '/(.*)(\/|\\\)simply\-schedule\-appointments(\/|\\\)$/';
|
|
$matching = preg_match( $pattern, $directory );
|
|
|
|
if( ! $matching ) {
|
|
add_action( 'after_plugin_row_' . $this->plugin->basename, array( $this, 'display_wrong_dir_name_error' ), 10 );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display warning message to users in plugins.php screen
|
|
*
|
|
* @return void
|
|
*/
|
|
public function display_wrong_dir_name_error () {
|
|
|
|
echo '<tr class="plugin-update-tr active">';
|
|
echo '<td colspan="4" class="plugin-update colspanchange">';
|
|
echo '<div class="update-message notice inline notice-error notice-alt">';
|
|
echo '<p>' . __( '<strong>Error: Invalid directory name</strong>. Please rename this plugin\'s directory to simply-schedule-appointments to avoid errors when updating.', 'simply-schedule-appointments' ) . '</p>';
|
|
echo '</div>';
|
|
echo '</td>';
|
|
echo '</tr>';
|
|
}
|
|
|
|
public function get_site_support_pin() {
|
|
$status = 'good';
|
|
$value = true;
|
|
$notices = array(
|
|
SSA_Debug::get_site_unique_hash_for_debug_logs()
|
|
);
|
|
|
|
return compact(
|
|
'status',
|
|
'value',
|
|
'notices'
|
|
);
|
|
}
|
|
|
|
}
|