PluginProbe
Tiered Pricing Table for WooCommerce / 7.1.4
Tiered Pricing Table for WooCommerce v7.1.4
8.0.2 7.1.7 7.1.5 7.1.4 7.1.1 6.5.0 6.4.0 6.1.0 trunk 1.0 1.1 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2 2.2.3 2.3.0 2.3.1 2.3.2 2.3.3 All 91 releases
tier-pricing-table / src / Integrations / Plugins / WPMLMultilingual.php

WPMLMultilingual.php in Tiered Pricing Table for WooCommerce 7.1.4, at src/Integrations/Plugins/WPMLMultilingual.php

154 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php namespace TierPricingTable\Integrations\Plugins;
2
3 class WPMLMultilingual extends PluginIntegrationAbstract {
4
5 /**
6 * Suffixes of product meta keys whose full name embeds a role slug or user ID and therefore cannot
7 * be declared statically in wpml-config.xml (e.g. "_wholesale_fixed_price_rules",
8 * "_user_42_tiered_price_discount"). Matched against every meta key stored on the product.
9 *
10 * The base (role-less) equivalents — "_fixed_price_rules" etc. — are handled by wpml-config.xml and
11 * are intentionally excluded by the pattern in getDynamicPricingMetaKeys().
12 */
13 const DYNAMIC_KEY_SUFFIXES = array(
14 'fixed_price_rules',
15 'percentage_price_rules',
16 'tiered_price_rules_type',
17 'tiered_price_minimum_qty',
18 'tiered_price_regular_price',
19 'tiered_price_sale_price',
20 'tiered_price_discount',
21 'tiered_price_discount_type',
22 'tiered_price_pricing_type',
23 'tiered_price_tax_status',
24 'tiered_price_tax_class',
25 );
26
27 /**
28 * Re-entrancy guard for the WPML sync path.
29 *
30 * @var bool
31 */
32 protected $syncing = false;
33
34 public function run() {
35
36 // WPML: push the original's dynamic pricing meta to its translations whenever a product or one
37 // of its translations is saved, or a translation job completes.
38 if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
39 add_action( 'wpml_after_save_post', array( $this, 'syncDynamicPricingMeta' ), 20 );
40 add_action( 'wpml_pro_translation_completed', array( $this, 'syncDynamicPricingMeta' ), 20 );
41 }
42
43 // Polylang: declare the dynamic keys as "copy" fields so they travel to translations. Polylang
44 // already reads wpml-config.xml for the static keys, but has no wildcard support for these.
45 if ( defined( 'POLYLANG_VERSION' ) || function_exists( 'pll_get_post_translations' ) ) {
46 add_filter( 'pll_copy_post_metas', array( $this, 'addPolylangCopyKeys' ), 10, 4 );
47 }
48 }
49
50 /**
51 * Copy role-based and user-based tiered pricing meta from the original product to its translations.
52 *
53 * Without this, role/user tier prices configured on the original product never reach its
54 * translations, so a customer viewing a translated product sees no role/user pricing.
55 *
56 * @param int $postId A product ID in any language (the original or one of its translations).
57 *
58 * @return void
59 */
60 public function syncDynamicPricingMeta( $postId ) {
61
62 if ( $this->syncing || 'product' !== get_post_type( $postId ) ) {
63 return;
64 }
65
66 // Always sync from the original (source of truth), regardless of which language triggered the save.
67 $originalId = apply_filters( 'wpml_original_element_id', null, $postId, 'post_product' );
68
69 if ( ! $originalId ) {
70 $originalId = $postId;
71 }
72
73 $metaKeys = $this->getDynamicPricingMetaKeys( (int) $originalId );
74
75 if ( empty( $metaKeys ) ) {
76 return;
77 }
78
79 // sync_to_translations() writes meta on the translations; guard against re-entering this handler.
80 $this->syncing = true;
81
82 foreach ( $metaKeys as $metaKey ) {
83 // WPML resolves the translations of $originalId and copies the value to each of them.
84 do_action( 'wpml_sync_custom_field', (int) $originalId, $metaKey );
85 }
86
87 $this->syncing = false;
88 }
89
90 /**
91 * Append the source product's dynamic pricing keys to Polylang's list of meta to copy.
92 *
93 * @param string[] $keys Meta keys Polylang will copy to the translation.
94 * @param bool $sync Whether this is an ongoing sync (vs. an initial copy).
95 * @param int $from Source (original) post ID.
96 * @param int $to Target (translation) post ID.
97 *
98 * @return string[]
99 */
100 public function addPolylangCopyKeys( $keys, $sync, $from, $to ) {
101
102 if ( ! $from || 'product' !== get_post_type( $from ) ) {
103 return $keys;
104 }
105
106 return array_values( array_unique( array_merge( (array) $keys,
107 $this->getDynamicPricingMetaKeys( (int) $from ) ) ) );
108 }
109
110 /**
111 * All role-based / user-based tiered pricing meta keys actually present on a product.
112 *
113 * @param int $productId
114 *
115 * @return string[]
116 */
117 protected function getDynamicPricingMetaKeys( int $productId ): array {
118
119 $allKeys = array_keys( get_post_meta( $productId ) );
120
121 // _{role}_<suffix> (role may contain underscores, e.g. shop_manager) or _user_{id}_<suffix>.
122 $pattern = '/^_(?:user_\d+|[a-z0-9\-]+(?:_[a-z0-9\-]+)*)_(?:' . implode( '|',
123 self::DYNAMIC_KEY_SUFFIXES ) . ')$/';
124
125 return array_values( array_filter( $allKeys, function ( $key ) use ( $pattern ) {
126 return (bool) preg_match( $pattern, $key );
127 } ) );
128 }
129
130 public function getTitle(): string {
131 return 'WPML / Polylang Multilingual';
132 }
133
134 public function getIconURL(): string {
135 return $this->getContainer()->getFileManager()->locateAsset( 'admin/integrations/wpml-multicurrency-icon.png' );
136 }
137
138 public function getAuthorURL(): string {
139 return 'https://wpml.org/documentation/related-projects/woocommerce-multilingual/';
140 }
141
142 public function getDescription(): string {
143 return __( 'Copy role-based and user-based tiered pricing to product translations when using WPML or Polylang.', 'tier-pricing-table' );
144 }
145
146 public function getSlug(): string {
147 return 'wpml-multilingual';
148 }
149
150 public function getIntegrationCategory(): string {
151 return 'other';
152 }
153 }
154