| 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 WCPOS\WooCommercePOS\Services\Lifecycle_Events; |
| 15 |
use WCPOS\WooCommercePOS\Sync\Api as Sync_Api; |
| 16 |
use WCPOS\WooCommercePOS\Sync\Health as Sync_Health; |
| 17 |
use WCPOS\WooCommercePOS\Sync\Integrity_Digest; |
| 18 |
use WCPOS\WooCommercePOS\Sync\Mutation_Store; |
| 19 |
use WCPOS\WooCommercePOS\Sync\Sync_Journal; |
| 20 |
use const DOING_AJAX; |
| 21 |
|
| 22 |
/** |
| 23 |
* Activator class. |
| 24 |
*/ |
| 25 |
class Activator { |
| 26 |
/** |
| 27 |
* Lock name used by WP_Upgrader::create_lock(). |
| 28 |
*/ |
| 29 |
private const DB_UPGRADE_LOCK_NAME = 'woocommerce_pos_db_upgrade_lock'; |
| 30 |
|
| 31 |
/** |
| 32 |
* Lock TTL in seconds. |
| 33 |
*/ |
| 34 |
private const DB_UPGRADE_LOCK_TTL = 600; |
| 35 |
|
| 36 |
/** |
| 37 |
* Constructor. |
| 38 |
*/ |
| 39 |
public function __construct() { |
| 40 |
register_activation_hook( PLUGIN_FILE, array( $this, 'activate' ) ); |
| 41 |
add_action( 'wpmu_new_blog', array( $this, 'activate_new_site' ) ); |
| 42 |
add_action( 'plugins_loaded', array( $this, 'init' ) ); |
| 43 |
} |
| 44 |
|
| 45 |
/** |
| 46 |
* Checks for valid install and begins execution of the plugin. |
| 47 |
*/ |
| 48 |
public function init(): void { |
| 49 |
// Check for min requirements to run. |
| 50 |
if ( $this->php_check() && $this->woocommerce_check() ) { |
| 51 |
// Defer permalink check to admin_init so __() calls happen after |
| 52 |
// after_setup_theme (WordPress 6.7+ triggers a notice otherwise). |
| 53 |
if ( is_admin() && ( ! \defined( '\DOING_AJAX' ) || ! DOING_AJAX ) ) { // @phpstan-ignore-line |
| 54 |
add_action( |
| 55 |
'admin_init', |
| 56 |
function () { |
| 57 |
$this->permalink_check(); |
| 58 |
} |
| 59 |
); |
| 60 |
} |
| 61 |
|
| 62 |
// Init update script if required. |
| 63 |
$this->version_check(); |
| 64 |
$this->pro_version_check(); |
| 65 |
|
| 66 |
// resolve plugin plugins. |
| 67 |
$this->plugin_check(); |
| 68 |
|
| 69 |
new Init(); |
| 70 |
} |
| 71 |
} |
| 72 |
|
| 73 |
/** |
| 74 |
* Fired when the plugin is activated. |
| 75 |
* |
| 76 |
* @param bool $network_wide Whether to activate network-wide. |
| 77 |
*/ |
| 78 |
public function activate( $network_wide ): void { |
| 79 |
if ( \function_exists( 'is_multisite' ) && is_multisite() ) { |
| 80 |
if ( $network_wide ) { |
| 81 |
// Get all blog ids. |
| 82 |
$blog_ids = $this->get_blog_ids(); |
| 83 |
|
| 84 |
foreach ( $blog_ids as $blog_id ) { |
| 85 |
switch_to_blog( $blog_id ); |
| 86 |
$this->single_activate(); |
| 87 |
|
| 88 |
restore_current_blog(); |
| 89 |
} |
| 90 |
} else { |
| 91 |
self::single_activate(); |
| 92 |
} |
| 93 |
} else { |
| 94 |
self::single_activate(); |
| 95 |
} |
| 96 |
} |
| 97 |
|
| 98 |
/** |
| 99 |
* Fired when the plugin is activated. |
| 100 |
* |
| 101 |
* @param bool $install_sync_schema Whether to install the sync schema. |
| 102 |
*/ |
| 103 |
public function single_activate( bool $install_sync_schema = true ): void { |
| 104 |
$role_capabilities = self::role_capability_definition(); |
| 105 |
|
| 106 |
// create POS specific roles. |
| 107 |
$this->create_pos_roles(); |
| 108 |
|
| 109 |
// add pos capabilities to non POS roles. |
| 110 |
$this->add_pos_capability( |
| 111 |
array( |
| 112 |
'administrator' => $role_capabilities['administrator'], |
| 113 |
'shop_manager' => $role_capabilities['shop_manager'], |
| 114 |
) |
| 115 |
); |
| 116 |
|
| 117 |
$stored_roles = get_option( wp_roles()->role_key, array() ); |
| 118 |
$roles_are_persisted = is_array( $stored_roles ); |
| 119 |
if ( $roles_are_persisted ) { |
| 120 |
foreach ( $role_capabilities as $slug => $capabilities ) { |
| 121 |
$required_capabilities = 'cashier' === $slug |
| 122 |
? array_merge( array( 'access_woocommerce_pos' ), array_keys( $capabilities ) ) |
| 123 |
: $capabilities; |
| 124 |
foreach ( $required_capabilities as $capability ) { |
| 125 |
if ( empty( $stored_roles[ $slug ]['capabilities'][ $capability ] ) ) { |
| 126 |
$roles_are_persisted = false; |
| 127 |
break 2; |
| 128 |
} |
| 129 |
} |
| 130 |
} |
| 131 |
} |
| 132 |
|
| 133 |
$obsolete_customer_create_cap = isset( $role_capabilities['cashier']['create_customers'] ) ? 'promote_users' : 'create_customers'; |
| 134 |
if ( $roles_are_persisted && empty( $stored_roles['cashier']['capabilities'][ $obsolete_customer_create_cap ] ) ) { |
| 135 |
update_option( 'woocommerce_pos_role_caps_fingerprint', $this->role_caps_fingerprint(), true ); |
| 136 |
} |
| 137 |
|
| 138 |
// Flag the consent pop-up for the next admin page load. Done here |
| 139 |
// because the `activated_plugin` action in Admin\Consent fires |
| 140 |
// inside the activation request, at which point our plugin's |
| 141 |
// `plugins_loaded` callback hasn't yet instantiated Init on a |
| 142 |
// fresh install. |
| 143 |
// |
| 144 |
// Read the option directly — woocommerce_pos_get_settings() lives in |
| 145 |
// wcpos-functions.php which Init loads on `plugins_loaded`, but |
| 146 |
// plugins_loaded has already fired by the time activation runs. |
| 147 |
$general_settings = get_option( 'woocommerce_pos_settings_general', array() ); |
| 148 |
$tracking_consent = is_array( $general_settings ) && isset( $general_settings['tracking_consent'] ) |
| 149 |
? $general_settings['tracking_consent'] |
| 150 |
: 'undecided'; |
| 151 |
if ( 'undecided' === $tracking_consent ) { |
| 152 |
set_transient( Consent::MODAL_TRANSIENT, 1, Consent::MODAL_TRANSIENT_TTL ); |
| 153 |
} |
| 154 |
|
| 155 |
if ( $install_sync_schema ) { |
| 156 |
$this->install_sync_schema(); |
| 157 |
} |
| 158 |
|
| 159 |
// Record the install for analytics. Consent is still `undecided` at this |
| 160 |
// point, so the event is held until the user answers the pop-up flagged |
| 161 |
// above; Lifecycle_Events owns that deferral and reports at most once. |
| 162 |
( new Lifecycle_Events() )->record_install(); |
| 163 |
} |
| 164 |
|
| 165 |
/** |
| 166 |
* Install the sync store and latch its aggregate schema version after verification. |
| 167 |
*/ |
| 168 |
public function install_sync_schema(): void { |
| 169 |
$previous_schema = get_option( Sync_Api::SCHEMA_OPTION, null ); |
| 170 |
|
| 171 |
$journal = new Sync_Journal(); |
| 172 |
$journal->install(); |
| 173 |
( new Integrity_Digest() )->install(); |
| 174 |
( new Mutation_Store() )->install(); |
| 175 |
|
| 176 |
if ( ! Sync_Health::is_healthy() ) { |
| 177 |
if ( Sync_Api::SCHEMA_VERSION === $previous_schema ) { |
| 178 |
delete_option( Sync_Api::SCHEMA_OPTION ); |
| 179 |
} |
| 180 |
return; |
| 181 |
} |
| 182 |
|
| 183 |
// Schema 3 (#1379): the customer space widened from role=customer to ALL users. |
| 184 |
// Upgrading installs carry role-departure tombstones in the persisted stream that |
| 185 |
// would replay against now-live users; compensating updates supersede them (see |
| 186 |
// Sync_Journal::append_customer_updates_for_all_users). The old latch stays until |
| 187 |
// migration succeeds, so retries may append duplicate but harmless superseding |
| 188 |
// updates. Fresh installs (no previous latch) have no stream to repair. |
| 189 |
if ( |
| 190 |
null !== $previous_schema |
| 191 |
&& version_compare( (string) $previous_schema, '3', '<' ) |
| 192 |
&& ! $journal->append_customer_updates_for_all_users() |
| 193 |
) { |
| 194 |
return; |
| 195 |
} |
| 196 |
|
| 197 |
update_option( Sync_Api::SCHEMA_OPTION, Sync_Api::SCHEMA_VERSION, false ); |
| 198 |
|
| 199 |
if ( null !== $previous_schema && version_compare( (string) $previous_schema, Sync_Api::SCHEMA_VERSION, '<' ) ) { |
| 200 |
global $wpdb; |
| 201 |
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}wcpos_sync_change_log" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Known legacy table name. |
| 202 |
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}wcpos_sync_order_index" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Known legacy table name. |
| 203 |
// The legacy Change_Log_Purge class is gone; its recurring cron event |
| 204 |
// would otherwise survive the upgrade and fire a hook with no handler |
| 205 |
// forever. Literal hook name — the constant was removed with the class. |
| 206 |
wp_clear_scheduled_hook( 'wcpos_change_log_purge' ); |
| 207 |
} |
| 208 |
} |
| 209 |
|
| 210 |
/** |
| 211 |
* Fired when a new site is activated with a WPMU environment. |
| 212 |
* |
| 213 |
* @param int $blog_id Blog ID. |
| 214 |
*/ |
| 215 |
public function activate_new_site( $blog_id ): void { |
| 216 |
if ( 1 !== did_action( 'wpmu_new_blog' ) ) { |
| 217 |
return; |
| 218 |
} |
| 219 |
|
| 220 |
switch_to_blog( $blog_id ); |
| 221 |
$this->single_activate(); |
| 222 |
restore_current_blog(); |
| 223 |
} |
| 224 |
|
| 225 |
/** |
| 226 |
* Check min version of PHP. |
| 227 |
*/ |
| 228 |
private function php_check() { |
| 229 |
$php_version = PHP_VERSION; |
| 230 |
if ( version_compare( $php_version, PHP_MIN_VERSION, '>' ) ) { |
| 231 |
return true; |
| 232 |
} |
| 233 |
|
| 234 |
// Defer __() call to avoid "too early" warning in WordPress 6.7+. |
| 235 |
add_action( |
| 236 |
'admin_init', |
| 237 |
function () { |
| 238 |
$message = \sprintf( |
| 239 |
// translators: 1: Minimum PHP version, 2: Update URL. |
| 240 |
__( '<strong>WCPOS</strong> requires PHP %1$s or higher. Read more information about <a href="%2$s">how you can update</a>', 'woocommerce-pos' ), |
| 241 |
PHP_MIN_VERSION, |
| 242 |
'http://www.wpupdatephp.com/update/' |
| 243 |
) . ' »'; |
| 244 |
|
| 245 |
Admin\Notices::add( $message ); |
| 246 |
} |
| 247 |
); |
| 248 |
} |
| 249 |
|
| 250 |
/** |
| 251 |
* Check min version of WooCommerce installed. |
| 252 |
*/ |
| 253 |
private function woocommerce_check() { |
| 254 |
if ( class_exists( '\WooCommerce' ) && version_compare( WC()->version, WC_MIN_VERSION, '>=' ) ) { |
| 255 |
return true; |
| 256 |
} |
| 257 |
|
| 258 |
// Defer __() call to avoid "too early" warning in WordPress 6.7+. |
| 259 |
add_action( |
| 260 |
'admin_init', |
| 261 |
function () { |
| 262 |
$message = \sprintf( |
| 263 |
// translators: 1: WooCommerce URL, 2: Minimum WC version, 3: Plugins URL. |
| 264 |
__( '<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' ), |
| 265 |
'http://wordpress.org/plugins/woocommerce/', |
| 266 |
WC_MIN_VERSION, |
| 267 |
admin_url( 'plugins.php' ) |
| 268 |
) . ' »'; |
| 269 |
|
| 270 |
Admin\Notices::add( $message ); |
| 271 |
} |
| 272 |
); |
| 273 |
} |
| 274 |
|
| 275 |
/** |
| 276 |
* POS Frontend will give 404 if pretty permalinks not active. |
| 277 |
*/ |
| 278 |
private function permalink_check(): void { |
| 279 |
$permalinks = get_option( 'permalink_structure' ); |
| 280 |
|
| 281 |
// early return. |
| 282 |
if ( $permalinks ) { |
| 283 |
return; |
| 284 |
} |
| 285 |
|
| 286 |
$message = /* translators: Plugin activation notice label. */ __( '<strong>WooCommerce REST API</strong> requires <em>pretty</em> permalinks to work correctly', 'woocommerce-pos' ) . '. '; |
| 287 |
$message .= \sprintf( '<a href="%s">%s</a>', admin_url( 'options-permalink.php' ), /* translators: Plugin activation notice label. */ __( 'Enable permalinks', 'woocommerce-pos' ) ) . ' »'; |
| 288 |
|
| 289 |
Admin\Notices::add( $message ); |
| 290 |
} |
| 291 |
|
| 292 |
/** |
| 293 |
* Check version number, runs every admin page load. |
| 294 |
*/ |
| 295 |
private function version_check(): void { |
| 296 |
$old = (string) Services\Settings::get_db_version(); |
| 297 |
$plugin_needs_upgrade = version_compare( $old, VERSION, '<' ); |
| 298 |
$sync_needs_upgrade = Sync_Api::SCHEMA_VERSION !== get_option( Sync_Api::SCHEMA_OPTION, null ); |
| 299 |
|
| 300 |
$role_caps_fingerprint = $this->role_caps_fingerprint(); |
| 301 |
$role_caps_need_sync = get_option( 'woocommerce_pos_role_caps_fingerprint' ) !== $role_caps_fingerprint; |
| 302 |
if ( ! $plugin_needs_upgrade && ! $sync_needs_upgrade && ! $role_caps_need_sync ) { |
| 303 |
return; |
| 304 |
} |
| 305 |
|
| 306 |
if ( ! $this->acquire_db_upgrade_lock() ) { |
| 307 |
return; |
| 308 |
} |
| 309 |
|
| 310 |
$locked_old = (string) Services\Settings::get_db_version(); |
| 311 |
$locked_plugin_needs_upgrade = version_compare( $locked_old, VERSION, '<' ); |
| 312 |
$locked_sync_needs_upgrade = Sync_Api::SCHEMA_VERSION !== get_option( Sync_Api::SCHEMA_OPTION, null ); |
| 313 |
|
| 314 |
$locked_role_caps_fingerprint = $this->role_caps_fingerprint(); |
| 315 |
$locked_role_caps_need_sync = get_option( 'woocommerce_pos_role_caps_fingerprint' ) !== $locked_role_caps_fingerprint; |
| 316 |
if ( ! $locked_plugin_needs_upgrade && ! $locked_sync_needs_upgrade && ! $locked_role_caps_need_sync ) { |
| 317 |
$this->release_db_upgrade_lock(); |
| 318 |
return; |
| 319 |
} |
| 320 |
|
| 321 |
if ( $locked_plugin_needs_upgrade ) { |
| 322 |
Services\Settings::bump_versions(); |
| 323 |
} |
| 324 |
|
| 325 |
if ( $locked_plugin_needs_upgrade || $locked_role_caps_need_sync ) { |
| 326 |
// Re-run activation to sync role capabilities. add_role() and add_cap() |
| 327 |
// are both idempotent, so this is safe. Without this, capabilities added |
| 328 |
// in newer versions would never reach existing installs because add_role() |
| 329 |
// is a no-op when the role already exists. |
| 330 |
// Deferred to 'init' because create_pos_roles() calls __() which |
| 331 |
// requires translations to be loaded (WordPress 6.7+). |
| 332 |
add_action( |
| 333 |
'init', |
| 334 |
function () { |
| 335 |
$this->single_activate( false ); |
| 336 |
} |
| 337 |
); |
| 338 |
} |
| 339 |
|
| 340 |
$lock_released = false; |
| 341 |
$release_lock = function () use ( &$lock_released ): void { |
| 342 |
if ( $lock_released ) { |
| 343 |
return; |
| 344 |
} |
| 345 |
|
| 346 |
$lock_released = true; |
| 347 |
$this->release_db_upgrade_lock(); |
| 348 |
}; |
| 349 |
|
| 350 |
// Safety net in case woocommerce_init does not fire for this request. |
| 351 |
add_action( 'shutdown', $release_lock ); |
| 352 |
|
| 353 |
// Defer db_upgrade to woocommerce_init when WC is fully loaded. |
| 354 |
// This prevents conflicts with plugins like WC Subscriptions that hook |
| 355 |
// into before_delete_post and assume WC()->order_factory is available. |
| 356 |
add_action( |
| 357 |
'woocommerce_init', |
| 358 |
function () use ( $locked_old, $locked_plugin_needs_upgrade, $locked_sync_needs_upgrade, $release_lock ) { |
| 359 |
try { |
| 360 |
$this->db_upgrade( $locked_old, VERSION ); |
| 361 |
|
| 362 |
// Report the upgrade only once the migration has actually |
| 363 |
// completed — queueing it beside bump_versions() would claim a |
| 364 |
// finished upgrade even when db_upgrade() threw or never ran. |
| 365 |
// Still exactly-once: the version was bumped above, so the |
| 366 |
// upgrade is not re-detected on the next request. |
| 367 |
if ( $locked_plugin_needs_upgrade ) { |
| 368 |
( new Lifecycle_Events() )->record_upgrade( $locked_old, VERSION ); |
| 369 |
} |
| 370 |
if ( $locked_sync_needs_upgrade && Sync_Api::SCHEMA_VERSION === get_option( Sync_Api::SCHEMA_OPTION, null ) ) { |
| 371 |
( new Sync_Journal() )->register_hooks(); |
| 372 |
( new Integrity_Digest() )->register_hooks(); |
| 373 |
} |
| 374 |
} finally { |
| 375 |
$release_lock(); |
| 376 |
remove_action( 'shutdown', $release_lock ); |
| 377 |
} |
| 378 |
} |
| 379 |
); |
| 380 |
} |
| 381 |
|
| 382 |
/** |
| 383 |
* Acquire the DB upgrade lock. |
| 384 |
* |
| 385 |
* @return bool True when this request owns the lock. |
| 386 |
*/ |
| 387 |
private function acquire_db_upgrade_lock(): bool { |
| 388 |
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; |
| 389 |
|
| 390 |
return \WP_Upgrader::create_lock( self::DB_UPGRADE_LOCK_NAME, self::DB_UPGRADE_LOCK_TTL ); |
| 391 |
} |
| 392 |
|
| 393 |
/** |
| 394 |
* Release the DB upgrade lock. |
| 395 |
*/ |
| 396 |
private function release_db_upgrade_lock(): void { |
| 397 |
if ( ! class_exists( '\WP_Upgrader', false ) ) { |
| 398 |
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; |
| 399 |
} |
| 400 |
|
| 401 |
\WP_Upgrader::release_lock( self::DB_UPGRADE_LOCK_NAME ); |
| 402 |
} |
| 403 |
|
| 404 |
/** |
| 405 |
* Plugin conflicts. |
| 406 |
* |
| 407 |
* - NextGEN Gallery is a terrible plugin. It buffers all content on 'init' action, priority -1 and inserts junk code. |
| 408 |
*/ |
| 409 |
private function plugin_check(): void { |
| 410 |
// disable NextGEN Gallery resource manager |
| 411 |
// if ( ! \defined( 'NGG_DISABLE_RESOURCE_MANAGER' ) ) { |
| 412 |
// \define( 'NGG_DISABLE_RESOURCE_MANAGER', true ); |
| 413 |
// }. |
| 414 |
} |
| 415 |
|
| 416 |
/** |
| 417 |
* Get all blog ids of blogs in the current network that are: |
| 418 |
* - not archived |
| 419 |
* - not spam |
| 420 |
* - not deleted. |
| 421 |
*/ |
| 422 |
private function get_blog_ids() { |
| 423 |
global $wpdb; |
| 424 |
|
| 425 |
// get an array of blog ids. |
| 426 |
$sql = "SELECT blog_id FROM $wpdb->blogs |
| 427 |
WHERE archived = '0' AND spam = '0' |
| 428 |
AND deleted = '0'"; |
| 429 |
|
| 430 |
return $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Static query, no user input |
| 431 |
} |
| 432 |
|
| 433 |
/** |
| 434 |
* Get the role capability definition. |
| 435 |
* |
| 436 |
* @return array<string, array<string, bool>|array<int, string>> Role capabilities keyed by role. |
| 437 |
*/ |
| 438 |
private static function role_capability_definition(): array { |
| 439 |
// WC 9.9 replaced promote_users with create_customers for customer creation. |
| 440 |
$customer_create_cap = \defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '9.9', '>=' ) // @phpstan-ignore-line |
| 441 |
? 'create_customers' |
| 442 |
: 'promote_users'; |
| 443 |
|
| 444 |
// Cashier role. |
| 445 |
$cashier_capabilities = array( |
| 446 |
'read' => true, |
| 447 |
'read_private_products' => true, |
| 448 |
'publish_products' => true, |
| 449 |
'edit_product' => true, |
| 450 |
'edit_products' => true, |
| 451 |
'edit_published_products' => true, |
| 452 |
'edit_private_products' => true, |
| 453 |
'edit_others_products' => true, |
| 454 |
'read_private_shop_orders' => true, |
| 455 |
'publish_shop_orders' => true, |
| 456 |
'edit_shop_orders' => true, |
| 457 |
'edit_others_shop_orders' => true, |
| 458 |
'list_users' => true, |
| 459 |
$customer_create_cap => true, |
| 460 |
'edit_users' => true, |
| 461 |
'read_private_shop_coupons' => true, |
| 462 |
'publish_shop_coupons' => true, |
| 463 |
'edit_shop_coupons' => true, |
| 464 |
'edit_published_shop_coupons' => true, |
| 465 |
'edit_private_shop_coupons' => true, |
| 466 |
'edit_others_shop_coupons' => true, |
| 467 |
'manage_product_terms' => true, |
| 468 |
); |
| 469 |
|
| 470 |
return array( |
| 471 |
'cashier' => $cashier_capabilities, |
| 472 |
'administrator' => array( |
| 473 |
'manage_woocommerce_pos', |
| 474 |
'access_woocommerce_pos', |
| 475 |
'edit_wcpos_store', |
| 476 |
'read_wcpos_store', |
| 477 |
'delete_wcpos_store', |
| 478 |
'edit_wcpos_stores', |
| 479 |
'edit_others_wcpos_stores', |
| 480 |
'publish_wcpos_stores', |
| 481 |
'read_private_wcpos_stores', |
| 482 |
'delete_wcpos_stores', |
| 483 |
'delete_private_wcpos_stores', |
| 484 |
'delete_published_wcpos_stores', |
| 485 |
'delete_others_wcpos_stores', |
| 486 |
'edit_private_wcpos_stores', |
| 487 |
'edit_published_wcpos_stores', |
| 488 |
), |
| 489 |
'shop_manager' => array( 'manage_woocommerce_pos', 'access_woocommerce_pos' ), |
| 490 |
); |
| 491 |
} |
| 492 |
|
| 493 |
/** |
| 494 |
* Get the role-capabilities definition fingerprint. |
| 495 |
*/ |
| 496 |
private function role_caps_fingerprint(): string { |
| 497 |
return md5( wp_json_encode( self::role_capability_definition() ) ); |
| 498 |
} |
| 499 |
|
| 500 |
/** |
| 501 |
* Add POS specific roles. |
| 502 |
*/ |
| 503 |
private function create_pos_roles(): void { |
| 504 |
$role_capabilities = self::role_capability_definition(); |
| 505 |
$cashier_capabilities = $role_capabilities['cashier']; |
| 506 |
|
| 507 |
add_role( |
| 508 |
'cashier', |
| 509 |
/* translators: Plugin activation notice label. */ |
| 510 |
__( 'Cashier', 'woocommerce-pos' ), |
| 511 |
$cashier_capabilities |
| 512 |
); |
| 513 |
|
| 514 |
$obsolete_customer_create_cap = isset( $cashier_capabilities['create_customers'] ) ? 'promote_users' : 'create_customers'; |
| 515 |
$cashier = get_role( 'cashier' ); |
| 516 |
if ( $cashier ) { |
| 517 |
$cashier->remove_cap( $obsolete_customer_create_cap ); |
| 518 |
} |
| 519 |
|
| 520 |
// Sync all capabilities to the existing role. add_role() is a no-op when |
| 521 |
// the role already exists, so capabilities added in newer versions would |
| 522 |
// never reach existing installs without this. |
| 523 |
$this->add_pos_capability( |
| 524 |
array( |
| 525 |
'cashier' => array_merge( |
| 526 |
array( 'access_woocommerce_pos' ), |
| 527 |
array_keys( $cashier_capabilities ) |
| 528 |
), |
| 529 |
) |
| 530 |
); |
| 531 |
} |
| 532 |
|
| 533 |
/** |
| 534 |
* Add default pos capabilities to administrator and shop_manager roles. |
| 535 |
* |
| 536 |
* @param array $roles An array of arrays representing the roles and their POS capabilities. |
| 537 |
*/ |
| 538 |
private function add_pos_capability( $roles ): void { |
| 539 |
foreach ( $roles as $slug => $caps ) { |
| 540 |
$role = get_role( $slug ); |
| 541 |
if ( $role ) { |
| 542 |
foreach ( $caps as $cap ) { |
| 543 |
$role->add_cap( $cap ); |
| 544 |
} |
| 545 |
} |
| 546 |
} |
| 547 |
} |
| 548 |
|
| 549 |
/** |
| 550 |
* Upgrade database. |
| 551 |
* |
| 552 |
* @param string $old Old version. |
| 553 |
* @param string $current Current version. |
| 554 |
*/ |
| 555 |
private function db_upgrade( $old, $current ): void { |
| 556 |
$db_updates = array( |
| 557 |
'0.4' => 'updates/update-0.4.php', |
| 558 |
'0.4.6' => 'updates/update-0.4.6.php', |
| 559 |
'1.0.0-beta.1' => 'updates/update-1.0.0-beta.1.php', |
| 560 |
'1.6.1' => 'updates/update-1.6.1.php', |
| 561 |
'1.8.0' => 'updates/update-1.8.0.php', |
| 562 |
'1.8.7' => 'updates/update-1.8.7.php', |
| 563 |
'1.8.12' => 'updates/update-1.8.12.php', |
| 564 |
'1.8.13' => 'updates/update-1.8.13.php', |
| 565 |
'1.9.0' => 'updates/update-1.9.0.php', |
| 566 |
'1.10.0' => 'updates/update-1.10.0.php', |
| 567 |
); |
| 568 |
foreach ( $db_updates as $version => $updater ) { |
| 569 |
if ( version_compare( $version, $old, '>' ) && |
| 570 |
version_compare( $version, $current, '<=' ) ) { |
| 571 |
include $updater; |
| 572 |
} |
| 573 |
} |
| 574 |
|
| 575 |
if ( Sync_Api::SCHEMA_VERSION !== get_option( Sync_Api::SCHEMA_OPTION, null ) ) { |
| 576 |
$this->install_sync_schema(); |
| 577 |
} |
| 578 |
} |
| 579 |
|
| 580 |
/** |
| 581 |
* If \WCPOS\WooCommercePOSPro\ is installed, check the version is above MIN_PRO_VERSION. |
| 582 |
*/ |
| 583 |
private function pro_version_check(): void { |
| 584 |
if ( class_exists( '\WCPOS\WooCommercePOSPro\Activator' ) ) { |
| 585 |
if ( version_compare( \WCPOS\WooCommercePOSPro\VERSION, MIN_PRO_VERSION, '<' ) ) { // @phpstan-ignore-line |
| 586 |
|
| 587 |
/* |
| 588 |
* NOTE: the deactivate_plugins function is not available in the frontend or ajax |
| 589 |
* This is an extreme situation where the Pro plugin could crash the site, so we need to deactivate it |
| 590 |
*/ |
| 591 |
if ( ! \function_exists( 'deactivate_plugins' ) ) { |
| 592 |
require_once ABSPATH . '/wp-admin/includes/plugin.php'; |
| 593 |
} |
| 594 |
|
| 595 |
// WCPOS Pro is activated, but the version is too low - use the constant for dynamic folder name. |
| 596 |
deactivate_plugins( \WCPOS\WooCommercePOSPro\PLUGIN_FILE ); // @phpstan-ignore-line |
| 597 |
|
| 598 |
// Defer __() call to avoid "too early" warning in WordPress 6.7+. |
| 599 |
add_action( |
| 600 |
'admin_init', |
| 601 |
function () { |
| 602 |
$message = \sprintf( |
| 603 |
// translators: 1: WCPOS Pro URL, 2: Minimum Pro version, 3: Plugins URL. |
| 604 |
__( '<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' ), |
| 605 |
'https://wcpos.com/my-account', |
| 606 |
MIN_PRO_VERSION, |
| 607 |
admin_url( 'plugins.php' ) |
| 608 |
) . ' »'; |
| 609 |
|
| 610 |
Admin\Notices::add( $message ); |
| 611 |
} |
| 612 |
); |
| 613 |
} |
| 614 |
} |
| 615 |
} |
| 616 |
} |
| 617 |
|