PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.17
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.17
1.10.27 1.10.26 1.10.25 1.10.24 1.10.23 1.10.22 1.10.21 1.10.20 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 All 171 releases
← All changes | includes/Activator.php +102 -1 1.10.2 → 1.10.17 View file →
@@ -102,8 +102,20 @@
102 102 */
103 103 public function single_activate( bool $install_sync_schema = true ): void {
104 104 $role_capabilities = self::role_capability_definition();
105 105
106 + // Reseed the default template terms on the next request: (re)activation
107 + // is the repair a merchant reaches for after deleting a term by hand.
108 + // This also runs once per upgrade (version_check re-activates to sync
109 + // role caps), so one post-upgrade request pays the ~18 seeding queries.
110 + delete_option( Templates::DEFAULT_TERMS_OPTION );
111 +
112 + // Second, merchant-reachable trigger for the autoload repair: db_upgrade()
113 + // only runs when version_check() trips on an admin load that reaches
114 + // woocommerce_init, and a miss there is permanent once bump_versions() ran.
115 + self::autoload_request_latches();
116 + Admin\Permalink::ensure_default();
117 +
106 118 // create POS specific roles.
107 119 $this->create_pos_roles();
108 120
109 121 // add pos capabilities to non POS roles.
@@ -193,9 +205,12 @@
193 205 ) {
194 206 return;
195 207 }
196 208
197 - update_option( Sync_Api::SCHEMA_OPTION, Sync_Api::SCHEMA_VERSION, false );
209 + // Autoloaded: the Init constructor reads this latch on every request.
210 + // This flips an existing row only on WP 6.4+; older rows are flipped by
211 + // autoload_request_latches() on upgrade.
212 + update_option( Sync_Api::SCHEMA_OPTION, Sync_Api::SCHEMA_VERSION, true );
198 213
199 214 if ( null !== $previous_schema && version_compare( (string) $previous_schema, Sync_Api::SCHEMA_VERSION, '<' ) ) {
200 215 global $wpdb;
201 216 $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}wcpos_sync_change_log" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Known legacy table name.
@@ -574,8 +589,94 @@
574 589
575 590 if ( Sync_Api::SCHEMA_VERSION !== get_option( Sync_Api::SCHEMA_OPTION, null ) ) {
576 591 $this->install_sync_schema();
577 592 }
593 +
594 + // Installs that predate 2026-09 wrote the per-request latches with
595 + // autoload off; every upgrade re-asserts autoload so the flip is
596 + // idempotent and needs no versioned update file.
597 + self::autoload_request_latches();
598 + Admin\Permalink::ensure_default();
599 + }
600 +
601 + /**
602 + * Every option row that is read on EVERY request and must therefore ride in
603 + * alloptions: the three sync latches the Init constructor reads, the permalink
604 + * slug Template_Router reads, and each registered settings section that
605 + * declares {@see Services\Settings\Abstract_Section::autoload()} — the
606 + * sections are the extension point, so Pro's and extensions' sections join
607 + * the repair by declaring it, without touching this file.
608 + *
609 + * Needed because core's update_option() returns early on an unchanged value
610 + * WITHOUT touching the autoload column, so a writer alone never repairs a row
611 + * an older release wrote with autoload off. Without a persistent object cache
612 + * each such row cost one `SELECT option_value` per page load (measured
613 + * 2026-09-03 on dev-next and dev-free).
614 + *
615 + * @return string[]
616 + */
617 + private static function request_option_names(): array {
618 + $names = array(
619 + Sync_Api::SCHEMA_OPTION,
620 + \WCPOS\WooCommercePOS\Sync\Visibility_Observer::SEED_VERSION_OPTION,
621 + \WCPOS\WooCommercePOS\Sync\Config_Fingerprint::CLEANUP_VERSION_OPTION,
622 + Admin\Permalink::DB_KEY,
623 + );
624 + foreach ( Services\Settings::instance()->sections()->all() as $section ) {
625 + if ( $section instanceof Services\Settings\Abstract_Section && $section->autoload() ) {
626 + $names[] = $section->autoload_option_name();
627 + }
628 + }
629 + return $names;
630 + }
631 +
632 + /**
633 + * Flip the per-request rows to autoload in place, and seed the settings
634 + * sections that are absent.
635 + *
636 + * One UPDATE on the flag column: never delete-and-recreate, because
637 + * `Sync_Api::SCHEMA_OPTION` gates the sync observers on every request and a
638 + * request landing in that gap would run with journaling off. 'yes' is
639 + * accepted by every core version (6.6+ maps it alongside 'on'). Idempotent:
640 + * already-autoloaded rows match nothing. Latches that were never written
641 + * stay absent (their absence is the signal). An autoloaded settings section
642 + * that was never saved is seeded as an autoloaded row — an absent option is
643 + * queried on every request too. General also persists its migrated consent
644 + * so the legacy row does not remain on the read path; other defaults stay
645 + * dynamic.
646 + */
647 + public static function autoload_request_latches(): void {
648 + global $wpdb;
649 + // The key comes from the section itself (autoload_option_name()): Pro's
650 + // License section stores under a Pro-prefixed key, so deriving it from
651 + // id() flipped nothing for that row and seeded a stray free-prefixed one.
652 + foreach ( Services\Settings::instance()->sections()->all() as $section ) {
653 + if ( ! $section instanceof Services\Settings\Abstract_Section || ! $section->autoload() ) {
654 + continue;
655 + }
656 + $option_name = $section->autoload_option_name();
657 + if ( false === get_option( $option_name ) ) {
658 + $value = $section instanceof Services\Settings\General_Section
659 + ? array( 'tracking_consent' => $section->raw_tracking_consent() )
660 + : array();
661 + add_option( $option_name, $value, '', true );
662 + }
663 + }
664 + $options = self::request_option_names();
665 + $placeholders = implode( ', ', array_fill( 0, \count( $options ), '%s' ) );
666 + $flipped = $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- the flag column is the target; caches are cleared below.
667 + $wpdb->prepare(
668 + "UPDATE {$wpdb->options} SET autoload = 'yes' WHERE option_name IN ({$placeholders}) AND autoload NOT IN ('yes', 'on', 'auto-on')", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- placeholders are generated for the prepared values.
669 + $options
670 + )
671 + );
672 + if ( ! $flipped ) {
673 + return;
674 + }
675 + foreach ( $options as $option ) {
676 + wp_cache_delete( $option, 'options' );
677 + }
678 + wp_cache_delete( 'alloptions', 'options' );
578 679 }
579 680
580 681 /**
581 682 * If \WCPOS\WooCommercePOSPro\ is installed, check the version is above MIN_PRO_VERSION.