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( __( 'Get your license key and add it to this site\'s settings 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. Purchase 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. Renew your license 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( __( 'Get your license key and add it to this site\'s settings 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 '