| @@ -120,8 +120,46 @@ | ||
| 120 | 120 | return self::DB_PREFIX . $this->id(); |
| 121 | 121 | } |
| 122 | 122 | |
| 123 | 123 | /** |
| 124 | + * The wp_options key the upgrade-time autoload flip must target. | |
| 125 | + * | |
| 126 | + * A public accessor rather than a public option_name(): Pro's License | |
| 127 | + * section overrides option_name() at protected visibility (its key is | |
| 128 | + * Pro-prefixed), and a child cannot narrow a public parent method — making | |
| 129 | + * option_name() public fatalled every Pro site (#1846/#1849). Deriving the | |
| 130 | + * key from id() instead flipped the wrong row for that section and seeded a | |
| 131 | + * stray `woocommerce_pos_settings_license` (measured 2026-09-03 on dev-pro: | |
| 132 | + * one query per page for the license row). | |
| 133 | + * | |
| 134 | + * @return string | |
| 135 | + */ | |
| 136 | + public function autoload_option_name(): string { | |
| 137 | + return $this->option_name(); | |
| 138 | + } | |
| 139 | + | |
| 140 | + /** | |
| 141 | + * Whether this section's option rides in alloptions. | |
| 142 | + * | |
| 143 | + * Off by default: byte-compatible with the legacy save path, and most | |
| 144 | + * sections are only read on POS/admin requests. A section that is read on | |
| 145 | + * EVERY request (General, via the Settings service during init) overrides | |
| 146 | + * this, because without an object cache a non-autoloaded option costs one | |
| 147 | + * query per page load (measured 2026-09-03 on dev-next). Declaring it here | |
| 148 | + * is the whole contract: write() honours it for new writes, and | |
| 149 | + * Activator::autoload_request_latches() flips existing rows on upgrade and | |
| 150 | + * reactivation for every registered section that returns true — core's | |
| 151 | + * update_option() never flips autoload on an unchanged value, so the | |
| 152 | + * writer alone cannot repair an existing row. Keep it off for sections that | |
| 153 | + * can hold unbounded lists (Visibility's product ids). | |
| 154 | + * | |
| 155 | + * @return bool | |
| 156 | + */ | |
| 157 | + public function autoload(): bool { | |
| 158 | + return false; | |
| 159 | + } | |
| 160 | + | |
| 161 | + /** | |
| 124 | 162 | * Read the raw option value, coerced to array. |
| 125 | 163 | * |
| 126 | 164 | * @return array |
| 127 | 165 | */ |
| @@ -168,9 +206,9 @@ | ||
| 168 | 206 | * Persist a full settings array. |
| 169 | 207 | * |
| 170 | 208 | * Behaviour is byte-compatible with the legacy |
| 171 | 209 | * Services\Settings::save_settings(): sanitize, stamp date_modified_gmt, |
| 172 | - * apply the pre-save filter, update_option (autoload off), detect | |
| 210 | + * apply the pre-save filter, update_option (autoload per {@see autoload()}), detect | |
| 173 | 211 | * unchanged-value no-ops, fire the saved action, return the post-save |
| 174 | 212 | * read. |
| 175 | 213 | * |
| 176 | 214 | * @param array $settings The full settings array to persist. |
| @@ -198,9 +236,9 @@ | ||
| 198 | 236 | $settings = apply_filters( "woocommerce_pos_pre_save_{$this->id()}_settings", $settings, $this->id() ); |
| 199 | 237 | |
| 200 | 238 | $option_name = $this->option_name(); |
| 201 | 239 | $previous_value = get_option( $option_name, null ); |
| 202 | - $success = update_option( $option_name, $settings, false ); | |
| 240 | + $success = update_option( $option_name, $settings, $this->autoload() ); | |
| 203 | 241 | |
| 204 | 242 | if ( ! $success ) { |
| 205 | 243 | // update_option() returns false both when the value is unchanged (no DB |
| 206 | 244 | // write) and on actual failure. Use the value read *before* the write |