PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.18
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.18
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
← All changes | includes/Templates.php +86 -9 1.10.61.10.18 View file →
@@ -53,8 +53,22 @@
53 53 */
54 54 private static $gallery_preview_data_cache = array();
55 55
56 56 /**
57 + * Bump when the default term set below changes; a lower stored value reseeds.
58 + *
59 + * This class is constructed on EVERY request (Init::init_common), and the
60 + * seeding used to run nine `term_exists()` checks each time — 18 of the 31
61 + * queries the plugin added to every storefront page (measured 2026-09-03
62 + * on dev-next, see .claude/research/2026-09-03-online-store-footprint.md).
63 + * Behind the latch the whole registration costs no queries.
64 + */
65 + public const DEFAULT_TERMS_VERSION = 1;
66 +
67 + /** Autoloaded latch: read on every request, so it must ride in alloptions. */
68 + public const DEFAULT_TERMS_OPTION = 'woocommerce_pos_template_default_terms_version';
69 +
70 + /**
57 71 * Constructor.
58 72 */
59 73 public function __construct() {
60 74 // Register immediately since this is already being called during 'init'.
@@ -59,11 +73,68 @@
59 73 public function __construct() {
60 74 // Register immediately since this is already being called during 'init'.
61 75 $this->register_post_type();
62 76 $this->register_taxonomy();
77 + $this->maybe_seed_default_terms();
63 78 }
64 79
65 80 /**
81 + * Make sure the template post type and taxonomies exist on this request.
82 + *
83 + * A storefront request constructs Templates only on its first order write
84 + * (see Init::ensure_order_services()), but the static readers below are
85 + * also reached from a plain page — the My Account order actions read the
86 + * active receipt template. A tax_query against an unregistered taxonomy
87 + * matches nothing, and get_active_template_id() would then treat the
88 + * merchant's custom template as gone and delete the active-template
89 + * option. Registration itself costs no queries (the default terms are
90 + * behind an autoloaded latch), so every static reader calls this first.
91 + */
92 + public static function ensure_registered(): void {
93 + if ( taxonomy_exists( 'wcpos_template_type' ) && post_type_exists( 'wcpos_template' ) ) {
94 + return;
95 + }
96 + new self();
97 + }
98 +
99 + /**
100 + * Seed the default template types and categories once per DEFAULT_TERMS_VERSION.
101 + *
102 + * The latch is set only once every default term verifiably exists, so a
103 + * failed `wp_insert_term()` (a filter returning WP_Error, a transient DB
104 + * fault) leaves seeding armed for the next request instead of marking it
105 + * done. A term deleted by hand is restored on the next version bump or
106 + * plugin (re)activation, not the next request; the taxonomies are hidden
107 + * from menus and the defaults exist for the receipt UI, so that is the
108 + * right trade for a free page load.
109 + */
110 + private function maybe_seed_default_terms(): void {
111 + if ( (int) get_option( self::DEFAULT_TERMS_OPTION, 0 ) >= self::DEFAULT_TERMS_VERSION ) {
112 + return;
113 + }
114 + $this->register_default_template_types();
115 + $this->register_default_template_categories();
116 + if ( $this->default_terms_present() ) {
117 + update_option( self::DEFAULT_TERMS_OPTION, self::DEFAULT_TERMS_VERSION, true );
118 + }
119 + }
120 +
121 + /** Whether every default type and category term exists. */
122 + private function default_terms_present(): bool {
123 + foreach ( array( 'receipt', 'report' ) as $slug ) {
124 + if ( ! term_exists( $slug, 'wcpos_template_type' ) ) {
125 + return false;
126 + }
127 + }
128 + foreach ( array_keys( self::default_template_categories() ) as $slug ) {
129 + if ( ! term_exists( $slug, 'wcpos_template_category' ) ) {
130 + return false;
131 + }
132 + }
133 + return true;
134 + }
135 +
136 + /**
66 137 * Register the custom post type for templates.
67 138 * Only custom user-created templates are stored in the database.
68 139 *
69 140 * @return void
@@ -182,10 +253,9 @@
182 253 );
183 254
184 255 register_taxonomy( 'wcpos_template_type', array( 'wcpos_template' ), $args );
185 256
186 - // Register default template types.
187 - $this->register_default_template_types();
257 + // Default terms are seeded by maybe_seed_default_terms(), behind a latch.
188 258
189 259 // Register category taxonomy for gallery filtering.
190 260 register_taxonomy(
191 261 'wcpos_template_category',
@@ -209,10 +279,8 @@
209 279 'assign_terms' => 'manage_woocommerce_pos',
210 280 ),
211 281 )
212 282 );
213 -
214 - $this->register_default_template_categories();
215 283 }
216 284
217 285 /**
218 286 * Save raw post content directly to the database, bypassing wp_kses.
@@ -321,8 +389,9 @@
321 389 *
322 390 * @return null|array Template data or null if not found.
323 391 */
324 392 public static function get_template( int $template_id ): ?array {
393 + self::ensure_registered();
325 394 $post = get_post( $template_id );
326 395
327 396 if ( ! $post || 'wcpos_template' !== $post->post_type ) {
328 397 return null;
@@ -893,8 +962,9 @@
893 962 *
894 963 * @return array Array of template data arrays.
895 964 */
896 965 public static function get_enabled_templates( string $type = 'receipt' ): array {
966 + self::ensure_registered();
897 967 $disabled_virtual = self::get_disabled_virtual_templates( $type );
898 968 $order = self::get_template_order( $type );
899 969 $templates = array();
900 970
@@ -1320,14 +1390,14 @@
1320 1390 }
1321 1391 }
1322 1392
1323 1393 /**
1324 - * Register default template categories.
1394 + * The default template categories: slug => label.
1325 1395 *
1326 - * @return void
1396 + * @return array<string, string>
1327 1397 */
1328 - private function register_default_template_categories(): void {
1329 - $categories = array(
1398 + private static function default_template_categories(): array {
1399 + return array(
1330 1400 'receipt' => /* translators: Receipt template post type or template option label. */ __( 'Receipt', 'woocommerce-pos' ),
1331 1401 'invoice' => /* translators: Receipt template post type or template option label. */ __( 'Invoice', 'woocommerce-pos' ),
1332 1402 'gift-receipt' => /* translators: Receipt template post type or template option label. */ __( 'Gift Receipt', 'woocommerce-pos' ),
1333 1403 'credit-note' => /* translators: Receipt template post type or template option label. */ __( 'Credit Note', 'woocommerce-pos' ),
@@ -1334,10 +1404,17 @@
1334 1404 'purchase-order' => /* translators: Receipt template post type or template option label. */ __( 'Purchase Order', 'woocommerce-pos' ),
1335 1405 'kitchen-ticket' => /* translators: Receipt template post type or template option label. */ __( 'Kitchen Ticket', 'woocommerce-pos' ),
1336 1406 'bar-ticket' => /* translators: Receipt template post type or template option label. */ __( 'Bar Ticket', 'woocommerce-pos' ),
1337 1407 );
1408 + }
1338 1409
1339 - foreach ( $categories as $slug => $name ) {
1410 + /**
1411 + * Register default template categories.
1412 + *
1413 + * @return void
1414 + */
1415 + private function register_default_template_categories(): void {
1416 + foreach ( self::default_template_categories() as $slug => $name ) {
1340 1417 if ( ! term_exists( $slug, 'wcpos_template_category' ) ) {
1341 1418 wp_insert_term( $name, 'wcpos_template_category', array( 'slug' => $slug ) );
1342 1419 }
1343 1420 }