| 1 |
<?php |
| 2 |
/** |
| 3 |
* Activation checks and set up. |
| 4 |
* |
| 5 |
* @author Paul Kilmurray <paul@kilbot.com> |
| 6 |
* |
| 7 |
* @see http://wcpos.com |
| 8 |
* @package WCPOS\WooCommercePOS |
| 9 |
*/ |
| 10 |
|
| 11 |
namespace WCPOS\WooCommercePOS; |
| 12 |
|
| 13 |
use WCPOS\WooCommercePOS\Admin\Consent; |
| 14 |
use const DOING_AJAX; |
| 15 |
|
| 16 |
/** |
| 17 |
* Activator class. |
| 18 |
*/ |
| 19 |
class Activator { |
| 20 |
/** |
| 21 |
* Lock name used by WP_Upgrader::create_lock(). |
| 22 |
*/ |
| 23 |
private const DB_UPGRADE_LOCK_NAME = 'woocommerce_pos_db_upgrade_lock'; |
| 24 |
|
| 25 |
/** |
| 26 |
* Lock TTL in seconds. |
| 27 |
*/ |
| 28 |
private const DB_UPGRADE_LOCK_TTL = 600; |
| 29 |
|
| 30 |
/** |
| 31 |
* Constructor. |
| 32 |
*/ |
| 33 |
public function __construct() { |
| 34 |
register_activation_hook( PLUGIN_FILE, array( $this, 'activate' ) ); |
| 35 |
add_action( 'wpmu_new_blog', array( $this, 'activate_new_site' ) ); |
| 36 |
add_action( 'plugins_loaded', array( $this, 'init' ) ); |
| 37 |
} |
| 38 |
|
| 39 |
/** |
| 40 |
* Checks for valid install and begins execution of the plugin. |
| 41 |
*/ |
| 42 |
public function init(): void { |
| 43 |
// Check for min requirements to run. |
| 44 |
if ( $this->php_check() && $this->woocommerce_check() ) { |
| 45 |
// Defer permalink check to admin_init so __() calls happen after |
| 46 |
// after_setup_theme (WordPress 6.7+ triggers a notice otherwise). |
| 47 |
if ( is_admin() && ( ! \defined( '\DOING_AJAX' ) || ! DOING_AJAX ) ) { // @phpstan-ignore-line |
| 48 |
add_action( |
| 49 |
'admin_init', |
| 50 |
function () { |
| 51 |
$this->permalink_check(); |
| 52 |
} |
| 53 |
); |
| 54 |
} |
| 55 |
|
| 56 |
// Init update script if required. |
| 57 |
$this->version_check(); |
| 58 |
$this->pro_version_check(); |
| 59 |
|
| 60 |
// resolve plugin plugins. |
| 61 |
$this->plugin_check(); |
| 62 |
|
| 63 |
new Init(); |
| 64 |
} |
| 65 |
} |
| 66 |
|
| 67 |
/** |
| 68 |
* Fired when the plugin is activated. |
| 69 |
* |
| 70 |
* @param bool $network_wide Whether to activate network-wide. |
| 71 |
*/ |
| 72 |
public function activate( $network_wide ): void { |
| 73 |
if ( \function_exists( 'is_multisite' ) && is_multisite() ) { |
| 74 |
if ( $network_wide ) { |
| 75 |
// Get all blog ids. |
| 76 |
$blog_ids = $this->get_blog_ids(); |
| 77 |
|
| 78 |
foreach ( $blog_ids as $blog_id ) { |
| 79 |
switch_to_blog( $blog_id ); |
| 80 |
$this->single_activate(); |
| 81 |
|
| 82 |
restore_current_blog(); |
| 83 |
} |
| 84 |
} else { |
| 85 |
self::single_activate(); |
| 86 |
} |
| 87 |
} else { |
| 88 |
self::single_activate(); |
| 89 |
} |
| 90 |
} |
| 91 |
|
| 92 |
/** |
| 93 |
* Fired when the plugin is activated. |
| 94 |
*/ |
| 95 |
public function single_activate(): void { |
| 96 |
// create POS specific roles. |
| 97 |
$this->create_pos_roles(); |
| 98 |
|
| 99 |
// add pos capabilities to non POS roles. |
| 100 |
$this->add_pos_capability( |
| 101 |
array( |
| 102 |
'administrator' => array( |
| 103 |
'manage_woocommerce_pos', |
| 104 |
'access_woocommerce_pos', |
| 105 |
'edit_wcpos_store', |
| 106 |
'read_wcpos_store', |
| 107 |
'delete_wcpos_store', |
| 108 |
'edit_wcpos_stores', |
| 109 |
'edit_others_wcpos_stores', |
| 110 |
'publish_wcpos_stores', |
| 111 |
'read_private_wcpos_stores', |
| 112 |
'delete_wcpos_stores', |
| 113 |
'delete_private_wcpos_stores', |
| 114 |
'delete_published_wcpos_stores', |
| 115 |
'delete_others_wcpos_stores', |
| 116 |
'edit_private_wcpos_stores', |
| 117 |
'edit_published_wcpos_stores', |
| 118 |
), |
| 119 |
'shop_manager' => array( 'manage_woocommerce_pos', 'access_woocommerce_pos' ), |
| 120 |
) |
| 121 |
); |
| 122 |
|
| 123 |
// Flag the consent pop-up for the next admin page load. Done here |
| 124 |
// because the `activated_plugin` action in Admin\Consent fires |
| 125 |
// inside the activation request, at which point our plugin's |
| 126 |
// `plugins_loaded` callback hasn't yet instantiated Init on a |
| 127 |
// fresh install. |
| 128 |
// |
| 129 |
// Read the option directly — woocommerce_pos_get_settings() lives in |
| 130 |
// wcpos-functions.php which Init loads on `plugins_loaded`, but |
| 131 |
// plugins_loaded has already fired by the time activation runs. |
| 132 |
$general_settings = get_option( 'woocommerce_pos_settings_general', array() ); |
| 133 |
$tracking_consent = is_array( $general_settings ) && isset( $general_settings['tracking_consent'] ) |
| 134 |
? $general_settings['tracking_consent'] |
| 135 |
: 'undecided'; |
| 136 |
if ( 'undecided' === $tracking_consent ) { |
| 137 |
set_transient( Consent::MODAL_TRANSIENT, 1, Consent::MODAL_TRANSIENT_TTL ); |
| 138 |
} |
| 139 |
} |
| 140 |
|
| 141 |
/** |
| 142 |
* Fired when a new site is activated with a WPMU environment. |
| 143 |
* |
| 144 |
* @param int $blog_id Blog ID. |
| 145 |
*/ |
| 146 |
public function activate_new_site( $blog_id ): void { |
| 147 |
if ( 1 !== did_action( 'wpmu_new_blog' ) ) { |
| 148 |
return; |
| 149 |
} |
| 150 |
|
| 151 |
switch_to_blog( $blog_id ); |
| 152 |
$this->single_activate(); |
| 153 |
restore_current_blog(); |
| 154 |
} |
| 155 |
|
| 156 |
/** |
| 157 |
* Check min version of PHP. |
| 158 |
*/ |
| 159 |
private function php_check() { |
| 160 |
$php_version = PHP_VERSION; |
| 161 |
if ( version_compare( $php_version, PHP_MIN_VERSION, '>' ) ) { |
| 162 |
return true; |
| 163 |
} |
| 164 |
|
| 165 |
// Defer __() call to avoid "too early" warning in WordPress 6.7+. |
| 166 |
add_action( |
| 167 |
'admin_init', |
| 168 |
function () { |
| 169 |
$message = \sprintf( |
| 170 |
// translators: 1: Minimum PHP version, 2: Update URL. |
| 171 |
__( '<strong>WCPOS</strong> requires PHP %1$s or higher. Read more information about <a href="%2$s">how you can update</a>', 'woocommerce-pos' ), |
| 172 |
PHP_MIN_VERSION, |
| 173 |
'http://www.wpupdatephp.com/update/' |
| 174 |
) . ' »'; |
| 175 |
|
| 176 |
Admin\Notices::add( $message ); |
| 177 |
} |
| 178 |
); |
| 179 |
} |
| 180 |
|
| 181 |
/** |
| 182 |
* Check min version of WooCommerce installed. |
| 183 |
*/ |
| 184 |
private function woocommerce_check() { |
| 185 |
if ( class_exists( '\WooCommerce' ) && version_compare( WC()->version, WC_MIN_VERSION, '>=' ) ) { |
| 186 |
return true; |
| 187 |
} |
| 188 |
|
| 189 |
// Defer __() call to avoid "too early" warning in WordPress 6.7+. |
| 190 |
add_action( |
| 191 |
'admin_init', |
| 192 |
function () { |
| 193 |
$message = \sprintf( |
| 194 |
// translators: 1: WooCommerce URL, 2: Minimum WC version, 3: Plugins URL. |
| 195 |
__( '<strong>WCPOS</strong> requires <a href="%1$s">WooCommerce %2$s or higher</a>. Please <a href="%3$s">install and activate WooCommerce</a>', 'woocommerce-pos' ), |
| 196 |
'http://wordpress.org/plugins/woocommerce/', |
| 197 |
WC_MIN_VERSION, |
| 198 |
admin_url( 'plugins.php' ) |
| 199 |
) . ' »'; |
| 200 |
|
| 201 |
Admin\Notices::add( $message ); |
| 202 |
} |
| 203 |
); |
| 204 |
} |
| 205 |
|
| 206 |
/** |
| 207 |
* POS Frontend will give 404 if pretty permalinks not active. |
| 208 |
*/ |
| 209 |
private function permalink_check(): void { |
| 210 |
$permalinks = get_option( 'permalink_structure' ); |
| 211 |
|
| 212 |
// early return. |
| 213 |
if ( $permalinks ) { |
| 214 |
return; |
| 215 |
} |
| 216 |
|
| 217 |
$message = /* translators: Plugin activation notice label. */ __( '<strong>WooCommerce REST API</strong> requires <em>pretty</em> permalinks to work correctly', 'woocommerce-pos' ) . '. '; |
| 218 |
$message .= \sprintf( '<a href="%s">%s</a>', admin_url( 'options-permalink.php' ), /* translators: Plugin activation notice label. */ __( 'Enable permalinks', 'woocommerce-pos' ) ) . ' »'; |
| 219 |
|
| 220 |
Admin\Notices::add( $message ); |
| 221 |
} |
| 222 |
|
| 223 |
/** |
| 224 |
* Check version number, runs every admin page load. |
| 225 |
*/ |
| 226 |
private function version_check(): void { |
| 227 |
$old = (string) Services\Settings::get_db_version(); |
| 228 |
if ( ! version_compare( $old, VERSION, '<' ) ) { |
| 229 |
return; |
| 230 |
} |
| 231 |
|
| 232 |
if ( ! $this->acquire_db_upgrade_lock() ) { |
| 233 |
return; |
| 234 |
} |
| 235 |
|
| 236 |
$locked_old = (string) Services\Settings::get_db_version(); |
| 237 |
if ( ! version_compare( $locked_old, VERSION, '<' ) ) { |
| 238 |
$this->release_db_upgrade_lock(); |
| 239 |
return; |
| 240 |
} |
| 241 |
|
| 242 |
Services\Settings::bump_versions(); |
| 243 |
|
| 244 |
// Re-run activation to sync role capabilities. add_role() and add_cap() |
| 245 |
// are both idempotent, so this is safe. Without this, capabilities added |
| 246 |
// in newer versions would never reach existing installs because add_role() |
| 247 |
// is a no-op when the role already exists. |
| 248 |
// Deferred to 'init' because create_pos_roles() calls __() which |
| 249 |
// requires translations to be loaded (WordPress 6.7+). |
| 250 |
add_action( |
| 251 |
'init', |
| 252 |
function () { |
| 253 |
$this->single_activate(); |
| 254 |
} |
| 255 |
); |
| 256 |
|
| 257 |
$lock_released = false; |
| 258 |
$release_lock = function () use ( &$lock_released ): void { |
| 259 |
if ( $lock_released ) { |
| 260 |
return; |
| 261 |
} |
| 262 |
|
| 263 |
$lock_released = true; |
| 264 |
$this->release_db_upgrade_lock(); |
| 265 |
}; |
| 266 |
|
| 267 |
// Safety net in case woocommerce_init does not fire for this request. |
| 268 |
add_action( 'shutdown', $release_lock ); |
| 269 |
|
| 270 |
// Defer db_upgrade to woocommerce_init when WC is fully loaded. |
| 271 |
// This prevents conflicts with plugins like WC Subscriptions that hook |
| 272 |
// into before_delete_post and assume WC()->order_factory is available. |
| 273 |
add_action( |
| 274 |
'woocommerce_init', |
| 275 |
function () use ( $locked_old, $release_lock ) { |
| 276 |
try { |
| 277 |
$this->db_upgrade( $locked_old, VERSION ); |
| 278 |
} finally { |
| 279 |
$release_lock(); |
| 280 |
remove_action( 'shutdown', $release_lock ); |
| 281 |
} |
| 282 |
} |
| 283 |
); |
| 284 |
} |
| 285 |
|
| 286 |
/** |
| 287 |
* Acquire the DB upgrade lock. |
| 288 |
* |
| 289 |
* @return bool True when this request owns the lock. |
| 290 |
*/ |
| 291 |
private function acquire_db_upgrade_lock(): bool { |
| 292 |
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; |
| 293 |
|
| 294 |
return \WP_Upgrader::create_lock( self::DB_UPGRADE_LOCK_NAME, self::DB_UPGRADE_LOCK_TTL ); |
| 295 |
} |
| 296 |
|
| 297 |
/** |
| 298 |
* Release the DB upgrade lock. |
| 299 |
*/ |
| 300 |
private function release_db_upgrade_lock(): void { |
| 301 |
if ( ! class_exists( '\WP_Upgrader', false ) ) { |
| 302 |
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; |
| 303 |
} |
| 304 |
|
| 305 |
\WP_Upgrader::release_lock( self::DB_UPGRADE_LOCK_NAME ); |
| 306 |
} |
| 307 |
|
| 308 |
/** |
| 309 |
* Plugin conflicts. |
| 310 |
* |
| 311 |
* - NextGEN Gallery is a terrible plugin. It buffers all content on 'init' action, priority -1 and inserts junk code. |
| 312 |
*/ |
| 313 |
private function plugin_check(): void { |
| 314 |
// disable NextGEN Gallery resource manager |
| 315 |
// if ( ! \defined( 'NGG_DISABLE_RESOURCE_MANAGER' ) ) { |
| 316 |
// \define( 'NGG_DISABLE_RESOURCE_MANAGER', true ); |
| 317 |
// }. |
| 318 |
} |
| 319 |
|
| 320 |
/** |
| 321 |
* Get all blog ids of blogs in the current network that are: |
| 322 |
* - not archived |
| 323 |
* - not spam |
| 324 |
* - not deleted. |
| 325 |
*/ |
| 326 |
private function get_blog_ids() { |
| 327 |
global $wpdb; |
| 328 |
|
| 329 |
// get an array of blog ids. |
| 330 |
$sql = "SELECT blog_id FROM $wpdb->blogs |
| 331 |
WHERE archived = '0' AND spam = '0' |
| 332 |
AND deleted = '0'"; |
| 333 |
|
| 334 |
return $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Static query, no user input |
| 335 |
} |
| 336 |
|
| 337 |
/** |
| 338 |
* Add POS specific roles. |
| 339 |
*/ |
| 340 |
private function create_pos_roles(): void { |
| 341 |
// WC 9.9 replaced promote_users with create_customers for customer creation. |
| 342 |
$customer_create_cap = \defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '9.9', '>=' ) // @phpstan-ignore-line |
| 343 |
? 'create_customers' |
| 344 |
: 'promote_users'; |
| 345 |
|
| 346 |
// Cashier role. |
| 347 |
$cashier_capabilities = array( |
| 348 |
'read' => true, |
| 349 |
'read_private_products' => true, |
| 350 |
'read_private_shop_orders' => true, |
| 351 |
'publish_shop_orders' => true, |
| 352 |
'edit_shop_orders' => true, |
| 353 |
'edit_others_shop_orders' => true, |
| 354 |
'list_users' => true, |
| 355 |
$customer_create_cap => true, |
| 356 |
'edit_users' => true, |
| 357 |
'read_private_shop_coupons' => true, |
| 358 |
'manage_product_terms' => true, |
| 359 |
); |
| 360 |
|
| 361 |
add_role( |
| 362 |
'cashier', |
| 363 |
/* translators: Plugin activation notice label. */ |
| 364 |
__( 'Cashier', 'woocommerce-pos' ), |
| 365 |
$cashier_capabilities |
| 366 |
); |
| 367 |
|
| 368 |
// Sync all capabilities to the existing role. add_role() is a no-op when |
| 369 |
// the role already exists, so capabilities added in newer versions would |
| 370 |
// never reach existing installs without this. |
| 371 |
$this->add_pos_capability( |
| 372 |
array( |
| 373 |
'cashier' => array_merge( |
| 374 |
array( 'access_woocommerce_pos' ), |
| 375 |
array_keys( $cashier_capabilities ) |
| 376 |
), |
| 377 |
) |
| 378 |
); |
| 379 |
} |
| 380 |
|
| 381 |
/** |
| 382 |
* Add default pos capabilities to administrator and shop_manager roles. |
| 383 |
* |
| 384 |
* @param array $roles An array of arrays representing the roles and their POS capabilities. |
| 385 |
*/ |
| 386 |
private function add_pos_capability( $roles ): void { |
| 387 |
foreach ( $roles as $slug => $caps ) { |
| 388 |
$role = get_role( $slug ); |
| 389 |
if ( $role ) { |
| 390 |
foreach ( $caps as $cap ) { |
| 391 |
$role->add_cap( $cap ); |
| 392 |
} |
| 393 |
} |
| 394 |
} |
| 395 |
} |
| 396 |
|
| 397 |
/** |
| 398 |
* Upgrade database. |
| 399 |
* |
| 400 |
* @param string $old Old version. |
| 401 |
* @param string $current Current version. |
| 402 |
*/ |
| 403 |
private function db_upgrade( $old, $current ): void { |
| 404 |
$db_updates = array( |
| 405 |
'0.4' => 'updates/update-0.4.php', |
| 406 |
'0.4.6' => 'updates/update-0.4.6.php', |
| 407 |
'1.0.0-beta.1' => 'updates/update-1.0.0-beta.1.php', |
| 408 |
'1.6.1' => 'updates/update-1.6.1.php', |
| 409 |
'1.8.0' => 'updates/update-1.8.0.php', |
| 410 |
'1.8.7' => 'updates/update-1.8.7.php', |
| 411 |
'1.8.12' => 'updates/update-1.8.12.php', |
| 412 |
'1.8.13' => 'updates/update-1.8.13.php', |
| 413 |
'1.9.0' => 'updates/update-1.9.0.php', |
| 414 |
); |
| 415 |
foreach ( $db_updates as $version => $updater ) { |
| 416 |
if ( version_compare( $version, $old, '>' ) && |
| 417 |
version_compare( $version, $current, '<=' ) ) { |
| 418 |
include $updater; |
| 419 |
} |
| 420 |
} |
| 421 |
} |
| 422 |
|
| 423 |
/** |
| 424 |
* If \WCPOS\WooCommercePOSPro\ is installed, check the version is above MIN_PRO_VERSION. |
| 425 |
*/ |
| 426 |
private function pro_version_check(): void { |
| 427 |
if ( class_exists( '\WCPOS\WooCommercePOSPro\Activator' ) ) { |
| 428 |
if ( version_compare( \WCPOS\WooCommercePOSPro\VERSION, MIN_PRO_VERSION, '<' ) ) { // @phpstan-ignore-line |
| 429 |
|
| 430 |
/* |
| 431 |
* NOTE: the deactivate_plugins function is not available in the frontend or ajax |
| 432 |
* This is an extreme situation where the Pro plugin could crash the site, so we need to deactivate it |
| 433 |
*/ |
| 434 |
if ( ! \function_exists( 'deactivate_plugins' ) ) { |
| 435 |
require_once ABSPATH . '/wp-admin/includes/plugin.php'; |
| 436 |
} |
| 437 |
|
| 438 |
// WCPOS Pro is activated, but the version is too low - use the constant for dynamic folder name. |
| 439 |
deactivate_plugins( \WCPOS\WooCommercePOSPro\PLUGIN_FILE ); // @phpstan-ignore-line |
| 440 |
|
| 441 |
// Defer __() call to avoid "too early" warning in WordPress 6.7+. |
| 442 |
add_action( |
| 443 |
'admin_init', |
| 444 |
function () { |
| 445 |
$message = \sprintf( |
| 446 |
// translators: 1: WCPOS Pro URL, 2: Minimum Pro version, 3: Plugins URL. |
| 447 |
__( '<strong>WCPOS</strong> requires <a href="%1$s">WCPOS Pro %2$s or higher</a>. Please <a href="%3$s">install and activate WCPOS Pro</a>', 'woocommerce-pos' ), |
| 448 |
'https://wcpos.com/my-account', |
| 449 |
MIN_PRO_VERSION, |
| 450 |
admin_url( 'plugins.php' ) |
| 451 |
) . ' »'; |
| 452 |
|
| 453 |
Admin\Notices::add( $message ); |
| 454 |
} |
| 455 |
); |
| 456 |
} |
| 457 |
} |
| 458 |
} |
| 459 |
} |
| 460 |
|