PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.9.14
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.9.14
1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 All 163 releases
woocommerce-pos / includes / Activator.php

Activator.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.9.14, at includes/Activator.php

460 lines 13.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 ) . ' &raquo;';
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 ) . ' &raquo;';
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' ) ) . ' &raquo;';
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 ) . ' &raquo;';
452
453 Admin\Notices::add( $message );
454 }
455 );
456 }
457 }
458 }
459 }
460