| 1 |
<?php |
| 2 |
|
| 3 |
namespace StoreEngine; |
| 4 |
|
| 5 |
if ( ! defined( 'ABSPATH' ) ) { |
| 6 |
exit; // Exit if accessed directly. |
| 7 |
} |
| 8 |
|
| 9 |
use StoreEngine\Admin\Notices; |
| 10 |
use StoreEngine\Admin\Settings; |
| 11 |
use StoreEngine\Classes\Role; |
| 12 |
use StoreEngine\Utils\Helper; |
| 13 |
|
| 14 |
class Installer { |
| 15 |
protected $storeengine_version; |
| 16 |
|
| 17 |
public function __construct() { |
| 18 |
$this->storeengine_version = get_option( 'storeengine_version' ); |
| 19 |
} |
| 20 |
|
| 21 |
public static function init() { |
| 22 |
add_action( 'init', [ __CLASS__, 'check_version' ], 5 ); |
| 23 |
} |
| 24 |
|
| 25 |
/** |
| 26 |
* Check version & run installer if needed. |
| 27 |
* |
| 28 |
* @since 1.6.4 |
| 29 |
* @return void |
| 30 |
*/ |
| 31 |
public static function check_version() { |
| 32 |
$requires_update = version_compare( get_option( 'storeengine_version' ), STOREENGINE_VERSION, '<' ); |
| 33 |
if ( ! defined( 'IFRAME_REQUEST' ) && $requires_update ) { |
| 34 |
define( 'STOREENGINE_UPDATING', true ); |
| 35 |
// Run the installer. |
| 36 |
self::install(); |
| 37 |
|
| 38 |
/** |
| 39 |
* Run after StoreEngine has been updated. |
| 40 |
* |
| 41 |
* @since 1.6.4 |
| 42 |
*/ |
| 43 |
do_action( 'storeengine/updated' ); |
| 44 |
} |
| 45 |
} |
| 46 |
|
| 47 |
public static function install() { |
| 48 |
if ( ! is_blog_installed() ) { |
| 49 |
return; |
| 50 |
} |
| 51 |
|
| 52 |
// Check if we are not already running this routine. |
| 53 |
if ( 'yes' === get_transient( 'storeengine_installing' ) ) { |
| 54 |
return; |
| 55 |
} |
| 56 |
|
| 57 |
// If we made it till here nothing is running yet, lets set the transient now. |
| 58 |
set_transient( 'storeengine_installing', 'yes', MINUTE_IN_SECONDS * 10 ); |
| 59 |
|
| 60 |
define( 'STOREENGINE_INSTALLING', true ); |
| 61 |
|
| 62 |
global $wpdb; |
| 63 |
$wpdb->hide_errors(); |
| 64 |
|
| 65 |
try { |
| 66 |
Database::init()->wpdb_table_fix(); |
| 67 |
Notices::remove_all_notices(); |
| 68 |
Database::create_initial_custom_table(); |
| 69 |
Settings::save_settings(); |
| 70 |
self::add_role(); |
| 71 |
// Create files. |
| 72 |
self::create_base_secure_directory(); |
| 73 |
// Need for flush rewrite rules. |
| 74 |
PermalinkRewrite::init(); |
| 75 |
Database::init()->register_product_post_type(); |
| 76 |
|
| 77 |
if ( ! self::is_new_install() && 'yes' !== get_option( 'storeengine_has_redirect_to_setup_wizard', 'no' ) ) { |
| 78 |
// Don't redirect old users to setup-wizard upon update. |
| 79 |
update_option( 'storeengine_has_redirect_to_setup_wizard', 'yes', false ); |
| 80 |
} |
| 81 |
|
| 82 |
update_option( 'storeengine_version', STOREENGINE_VERSION ); |
| 83 |
} catch ( \Exception $e ) { |
| 84 |
// NoOp. |
| 85 |
Helper::log_error( $e ); |
| 86 |
} finally { |
| 87 |
delete_transient( 'storeengine_installing' ); |
| 88 |
} |
| 89 |
|
| 90 |
if ( ! get_option( 'storeengine_first_install_time' ) ) { |
| 91 |
add_option( 'storeengine_first_install_time', Helper::get_time() ); |
| 92 |
} |
| 93 |
|
| 94 |
// Force a flush of rewrite rules even if the corresponding hook isn't initialized yet. |
| 95 |
if ( ! has_action( 'storeengine/flush_rewrite_rules' ) ) { |
| 96 |
flush_rewrite_rules(); |
| 97 |
} |
| 98 |
|
| 99 |
/** |
| 100 |
* Flush the rewrite rules after install or update. |
| 101 |
*/ |
| 102 |
do_action( 'storeengine/flush_rewrite_rules' ); |
| 103 |
|
| 104 |
/** |
| 105 |
* Run after StoreEngine has been installed or updated. |
| 106 |
* |
| 107 |
* @since 1.6.4 |
| 108 |
*/ |
| 109 |
do_action( 'storeengine/installed' ); |
| 110 |
} |
| 111 |
|
| 112 |
/** |
| 113 |
* Is this a brand-new StoreEngine installation? |
| 114 |
* |
| 115 |
* A brand-new installation has no version yet. Also treat empty installations as 'new'. |
| 116 |
* |
| 117 |
* @return boolean |
| 118 |
*/ |
| 119 |
public static function is_new_install(): bool { |
| 120 |
return |
| 121 |
is_null( get_option( 'storeengine_version', null ) ) || |
| 122 |
is_null( get_option( 'storeengine_first_install_time', null ) ) || |
| 123 |
( |
| 124 |
! Helper::get_settings( 'shop_page' ) && |
| 125 |
0 === array_sum( (array) wp_count_posts( Helper::PRODUCT_POST_TYPE ) ) |
| 126 |
); |
| 127 |
} |
| 128 |
|
| 129 |
public static function add_role() { |
| 130 |
// customer role |
| 131 |
Role::add_customer_role(); |
| 132 |
// shop manager role |
| 133 |
Role::add_shop_manager_role(); |
| 134 |
} |
| 135 |
|
| 136 |
/** |
| 137 |
* Create files/directories. |
| 138 |
*/ |
| 139 |
public static function create_base_secure_directory() { |
| 140 |
// Install files and folders for uploading files and prevent hotlinking. |
| 141 |
// Directory is restricted and downloads must be proxied via script. |
| 142 |
$htaccess = '<IfModule mod_authz_core.c>' . PHP_EOL; |
| 143 |
$htaccess .= "\tRequire all denied" . PHP_EOL; |
| 144 |
$htaccess .= '</IfModule>' . PHP_EOL; |
| 145 |
$htaccess .= '<IfModule !mod_authz_core.c>' . PHP_EOL; |
| 146 |
$htaccess .= "\tDeny from all" . PHP_EOL; |
| 147 |
$htaccess .= '</IfModule>'; |
| 148 |
|
| 149 |
self::create_uploads_upload_directory_files( [ |
| 150 |
[ |
| 151 |
'base' => STOREENGINE_SECURE_UPLOADS_DIR, |
| 152 |
'file' => 'index.html', // Empty index, so server does not output auto-generated directory index. |
| 153 |
'content' => '', |
| 154 |
], |
| 155 |
[ |
| 156 |
'base' => STOREENGINE_SECURE_UPLOADS_DIR, |
| 157 |
'file' => '.htaccess', |
| 158 |
'content' => $htaccess, |
| 159 |
], |
| 160 |
[ |
| 161 |
'base' => STOREENGINE_LOGS_DIR, |
| 162 |
'file' => 'index.html', |
| 163 |
'content' => '', |
| 164 |
], |
| 165 |
[ |
| 166 |
'base' => STOREENGINE_LOGS_DIR, |
| 167 |
'file' => '.htaccess', |
| 168 |
'content' => $htaccess, |
| 169 |
], |
| 170 |
] ); |
| 171 |
|
| 172 |
// @TODO (In a separate function:private) Create a placeholder image in the media library (STOREENGINE_ASSETS_URI . images/thumbnail-placeholder.png). |
| 173 |
// Add settings for user to change placeholder image. |
| 174 |
// @TODO Add image sizes for gallery, preview & thumbnails. |
| 175 |
// Maybe add settings for user to change image sizes |
| 176 |
// If image sizes changes or theme switched regenerate thumbnails. |
| 177 |
} |
| 178 |
|
| 179 |
public static function create_uploads_upload_directory_files( array $config ) { |
| 180 |
$upload_dir = wp_get_upload_dir(); |
| 181 |
|
| 182 |
foreach ( $config as $file ) { |
| 183 |
$file = wp_parse_args( $file, [ |
| 184 |
'base' => '', |
| 185 |
'file' => '', |
| 186 |
'content' => '', |
| 187 |
] ); |
| 188 |
|
| 189 |
if ( empty( $file['base'] ) ) { |
| 190 |
continue; |
| 191 |
} |
| 192 |
|
| 193 |
if ( ! str_starts_with( $file['base'], $upload_dir['basedir'] ) ) { |
| 194 |
// Maybe out-of wp-content/uploads. |
| 195 |
continue; |
| 196 |
} |
| 197 |
|
| 198 |
$file['file'] = ltrim( rtrim( $file['file'], '/\\' ), '/\\' ); |
| 199 |
|
| 200 |
// Check if file not exists first. If the file exists, the directory is already created. |
| 201 |
if ( ! file_exists( trailingslashit( $file['base'] ) . $file['file'] ) && wp_mkdir_p( $file['base'] ) && $file['file'] ) { |
| 202 |
$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 |
| 203 |
|
| 204 |
if ( $file_handle ) { |
| 205 |
fwrite( $file_handle, $file['content'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite, WordPress.WP.AlternativeFunctions.file_system_operations_fwrite |
| 206 |
fclose( $file_handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose, WordPress.WP.AlternativeFunctions.file_system_operations_fclose |
| 207 |
} |
| 208 |
} |
| 209 |
} |
| 210 |
} |
| 211 |
|
| 212 |
public static function uninstall() { |
| 213 |
// Cancel every action this plugin (and its addons) may have scheduled. |
| 214 |
// Leaving them in the queue causes orphaned actions to fire forever |
| 215 |
// against handlers that no longer exist. |
| 216 |
foreach ( self::get_known_scheduled_action_hooks() as $hook ) { |
| 217 |
as_unschedule_all_actions( $hook ); |
| 218 |
} |
| 219 |
|
| 220 |
flush_rewrite_rules(); |
| 221 |
} |
| 222 |
|
| 223 |
/** |
| 224 |
* Every Action Scheduler hook owned by StoreEngine core + bundled addons. |
| 225 |
* Kept in one place so the "Reset Action Scheduler" admin tool and the |
| 226 |
* uninstall hook stay in sync. |
| 227 |
* |
| 228 |
* @return string[] |
| 229 |
*/ |
| 230 |
public static function get_known_scheduled_action_hooks(): array { |
| 231 |
return [ |
| 232 |
// Core. |
| 233 |
'storeengine/geolocation/maxmind/db-update', |
| 234 |
'storeengine/logs/auto_cleanup', |
| 235 |
'storeengine/store_product_lookup', |
| 236 |
'storeengine/remove_product_lookup_data', |
| 237 |
|
| 238 |
// Webhooks addon. |
| 239 |
'storeengine/webhooks/async_delivery', |
| 240 |
|
| 241 |
// CSV addon. |
| 242 |
'storeengine/csv/clean_tmp_csv', |
| 243 |
|
| 244 |
// Invoice addon. |
| 245 |
'storeengine/mail/clean_tmp_invoices', |
| 246 |
|
| 247 |
// Multi-currency addon. |
| 248 |
'storeengine/multi_currency/refresh_rates', |
| 249 |
|
| 250 |
// Subscription addon. |
| 251 |
'storeengine/subscription/start', |
| 252 |
'storeengine/subscription/renewal', |
| 253 |
'storeengine/subscription/scheduled_payment', |
| 254 |
'storeengine/subscription/schedule_payment_retry', |
| 255 |
'storeengine/subscription/schedule_trial_end', |
| 256 |
'storeengine/subscription/schedule_expiration', |
| 257 |
'storeengine/subscription/schedule_end_of_prepaid_term', |
| 258 |
|
| 259 |
// Deprecated subscription hooks (kept for cleanup of legacy installs). |
| 260 |
'storeengine_subscription_renewal', |
| 261 |
'storeengine_create_renewal_order_schedule', |
| 262 |
|
| 263 |
// StoreEngine Pro — license management addon. |
| 264 |
'storeengine_pro/license/update_top_product_cache', |
| 265 |
'storeengine_pro/license/update_event_cc', |
| 266 |
]; |
| 267 |
} |
| 268 |
} |
| 269 |
|