storeengine_version = get_option( 'storeengine_version' ); } public static function init() { add_action( 'init', [ __CLASS__, 'check_version' ], 5 ); } /** * Check version & run installer if needed. * * @since 1.6.4 * @return void */ public static function check_version() { $current_version = get_option( 'storeengine_version' ); $current_db_version = get_option( 'storeengine_db_version' ); $requires_update = version_compare( $current_version, STOREENGINE_VERSION, '<' ) || version_compare( $current_db_version, STOREENGINE_DB_VERSION, '<' ); if ( ! defined( 'IFRAME_REQUEST' ) && $requires_update ) { define( 'STOREENGINE_UPDATING', true ); // Run the installer. self::install(); /** * Run after StoreEngine has been updated. * * @since 1.6.4 */ do_action( 'storeengine/updated' ); } } public static function install() { if ( ! is_blog_installed() ) { return; } // Check if we are not already running this routine. if ( 'yes' === get_transient( 'storeengine_installing' ) ) { return; } // If we made it till here nothing is running yet, lets set the transient now. set_transient( 'storeengine_installing', 'yes', MINUTE_IN_SECONDS * 10 ); define( 'STOREENGINE_INSTALLING', true ); global $wpdb; $wpdb->hide_errors(); try { Database::init()->wpdb_table_fix(); Notices::remove_all_notices(); Database::create_initial_custom_table(); Settings::save_settings(); self::add_role(); // Copy any rows from legacy affiliate/vendor payout tables into the unified ledger. PayoutsRepository::migrate_legacy_tables(); // Create files. self::create_base_secure_directory(); // Need for flush rewrite rules. PermalinkRewrite::init(); Database::init()->register_product_post_type(); if ( ! self::is_new_install() && 'yes' !== get_option( 'storeengine_has_redirect_to_setup_wizard', 'no' ) ) { // Don't redirect old users to setup-wizard upon update. update_option( 'storeengine_has_redirect_to_setup_wizard', 'yes', false ); } update_option( 'storeengine_version', STOREENGINE_VERSION ); } catch ( \Exception $e ) { // NoOp. Helper::log_error( $e ); } finally { delete_transient( 'storeengine_installing' ); } if ( ! get_option( 'storeengine_first_install_time' ) ) { add_option( 'storeengine_first_install_time', Helper::get_time() ); } // Force a flush of rewrite rules even if the corresponding hook isn't initialized yet. if ( ! has_action( 'storeengine/flush_rewrite_rules' ) ) { flush_rewrite_rules(); } /** * Flush the rewrite rules after install or update. */ do_action( 'storeengine/flush_rewrite_rules' ); /** * Run after StoreEngine has been installed or updated. * * @since 1.6.4 */ do_action( 'storeengine/installed' ); } /** * Is this a brand-new StoreEngine installation? * * A brand-new installation has no version yet. Also treat empty installations as 'new'. * * @return boolean */ public static function is_new_install(): bool { return is_null( get_option( 'storeengine_version', null ) ) || is_null( get_option( 'storeengine_first_install_time', null ) ) || ( ! Helper::get_settings( 'shop_page' ) && 0 === array_sum( (array) wp_count_posts( Helper::PRODUCT_POST_TYPE ) ) ); } public static function add_role() { // customer role Role::add_customer_role(); // shop manager role Role::add_shop_manager_role(); // One-shot back-fill for caps added in v2 (manage_storeengine_settings, edit_storeengine_orders). Role::migrate_caps_v2(); } /** * Create files/directories. */ public static function create_base_secure_directory() { // Install files and folders for uploading files and prevent hotlinking. // Directory is restricted and downloads must be proxied via script. $htaccess = '' . PHP_EOL; $htaccess .= "\tRequire all denied" . PHP_EOL; $htaccess .= '' . PHP_EOL; $htaccess .= '' . PHP_EOL; $htaccess .= "\tDeny from all" . PHP_EOL; $htaccess .= ''; self::create_uploads_upload_directory_files( [ [ 'base' => STOREENGINE_SECURE_UPLOADS_DIR, 'file' => 'index.html', // Empty index, so server does not output auto-generated directory index. 'content' => '', ], [ 'base' => STOREENGINE_SECURE_UPLOADS_DIR, 'file' => '.htaccess', 'content' => $htaccess, ], [ 'base' => STOREENGINE_LOGS_DIR, 'file' => 'index.html', 'content' => '', ], [ 'base' => STOREENGINE_LOGS_DIR, 'file' => '.htaccess', 'content' => $htaccess, ], ] ); // @TODO (In a separate function:private) Create a placeholder image in the media library (STOREENGINE_ASSETS_URI . images/thumbnail-placeholder.png). // Add settings for user to change placeholder image. // @TODO Add image sizes for gallery, preview & thumbnails. // Maybe add settings for user to change image sizes // If image sizes changes or theme switched regenerate thumbnails. } public static function create_uploads_upload_directory_files( array $config ) { $upload_dir = wp_get_upload_dir(); foreach ( $config as $file ) { $file = wp_parse_args( $file, [ 'base' => '', 'file' => '', 'content' => '', ] ); if ( empty( $file['base'] ) ) { continue; } if ( ! str_starts_with( $file['base'], $upload_dir['basedir'] ) ) { // Maybe out-of wp-content/uploads. continue; } $file['file'] = ltrim( rtrim( $file['file'], '/\\' ), '/\\' ); // Check if file not exists first. If the file exists, the directory is already created. if ( ! file_exists( trailingslashit( $file['base'] ) . $file['file'] ) && wp_mkdir_p( $file['base'] ) && $file['file'] ) { $file_handle = @fopen( trailingslashit( $file['base'] ) . $file['file'], 'wb' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_read_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fopen if ( $file_handle ) { fwrite( $file_handle, $file['content'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite, WordPress.WP.AlternativeFunctions.file_system_operations_fwrite fclose( $file_handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose, WordPress.WP.AlternativeFunctions.file_system_operations_fclose } } } } public static function uninstall() { as_unschedule_all_actions( 'storeengine/geolocation/maxmind/db-update' ); Role::remove_shop_manager_role(); Role::remove_customer_role(); flush_rewrite_rules(); } /** * Every Action Scheduler hook owned by StoreEngine core + bundled addons. * Kept in one place so the "Reset Action Scheduler" admin tool and the * uninstall hook stay in sync. * * @return string[] */ public static function get_known_scheduled_action_hooks(): array { return [ // Core. 'storeengine/geolocation/maxmind/db-update', 'storeengine/logs/auto_cleanup', 'storeengine/store_product_lookup', 'storeengine/remove_product_lookup_data', // Webhooks addon. 'storeengine/webhooks/async_delivery', // CSV addon. 'storeengine/csv/clean_tmp_csv', // Invoice addon. 'storeengine/mail/clean_tmp_invoices', // Multi-currency addon. 'storeengine/multi_currency/refresh_rates', // Subscription addon. 'storeengine/subscription/start', 'storeengine/subscription/renewal', 'storeengine/subscription/scheduled_payment', 'storeengine/subscription/schedule_payment_retry', 'storeengine/subscription/schedule_trial_end', 'storeengine/subscription/schedule_expiration', 'storeengine/subscription/schedule_end_of_prepaid_term', // Deprecated subscription hooks (kept for cleanup of legacy installs). 'storeengine_subscription_renewal', 'storeengine_create_renewal_order_schedule', // StoreEngine Pro — license management addon. 'storeengine_pro/license/update_top_product_cache', 'storeengine_pro/license/update_event_cc', ]; } }