PluginProbe
Tiered Pricing Table for WooCommerce / trunk
Tiered Pricing Table for WooCommerce vtrunk
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 2.3.4 All 90 releases
tier-pricing-table / src / Integrations / Plugins / SeoIntegrationAbstract.php

SeoIntegrationAbstract.php in Tiered Pricing Table for WooCommerce trunk, at src/Integrations/Plugins/SeoIntegrationAbstract.php

242 lines 6.8 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 use TierPricingTable\Managers\FormatPriceManager;
4 use TierPricingTable\PriceManager;
5 use TierPricingTable\PricingRule;
6 use TierPricingTable\TierPricingTablePlugin;
7 use WC_Product;
8
9 /**
10 * Shared behaviour for SEO plugin integrations (Rank Math, Yoast, SEOPress).
11 *
12 * Each concrete integration only wires up its plugin-specific hooks in run() and delegates the schema
13 * building and price-variable formatting to the methods below, so the tiered-pricing schema stays
14 * identical across every SEO plugin.
15 */
16 abstract class SeoIntegrationAbstract extends PluginIntegrationAbstract {
17
18 /**
19 * Memoized product for the current request.
20 *
21 * @var WC_Product|null
22 */
23 protected $product = null;
24
25 /**
26 * Settings key prefix for this integration (e.g. "rank_math", "yoast", "seopress"). Used to build
27 * the "{prefix}_enable_variables" and "{prefix}_enhance_schema" option keys.
28 *
29 * @return string
30 */
31 abstract protected function getSettingsPrefix(): string;
32
33 /**
34 * Slug used in the "tiered_pricing_table/integrations/{slug}/variable_products_supported" filter.
35 *
36 * @return string
37 */
38 abstract protected function getSchemaFilterSlug(): string;
39
40 public function getIntegrationCategory(): string {
41 return 'seo';
42 }
43
44 /**
45 * Add tiered pricing offers to a product's schema data.
46 *
47 * For variable products only the low price is adjusted. For simple products with rules the offer
48 * block becomes an AggregateOffer holding a default base-price offer plus one offer per tier, so
49 * the emitted offer count matches offerCount.
50 *
51 * @param array $data Schema data; the offer block lives under $data['offers'].
52 * @param WC_Product $product
53 *
54 * @return array
55 */
56 protected function enhanceProductSchema( array $data, WC_Product $product ): array {
57
58 // Update only the low price for variable products.
59 if ( TierPricingTablePlugin::isVariableProductSupported( $product ) ) {
60
61 $variableProductsSupported = apply_filters(
62 'tiered_pricing_table/integrations/' . $this->getSchemaFilterSlug() . '/variable_products_supported',
63 true, $product );
64
65 if ( ! $variableProductsSupported ) {
66 return $data;
67 }
68
69 $data['offers']['lowPrice'] = FormatPriceManager::getFormattedPrice( $product, array(
70 'for_display' => true,
71 'with_suffix' => false,
72 'with_default_price' => true,
73 'with_lowest_prefix' => false,
74 'html' => false,
75 'display_type' => 'lowest_price',
76 'use_cache' => false,
77 ) );
78
79 return $data;
80 }
81
82 if ( ! TierPricingTablePlugin::isSimpleProductSupported( $product ) ) {
83 return $data;
84 }
85
86 $pricingRule = PriceManager::getPricingRule( $product->get_id() );
87
88 if ( empty( $pricingRule->getRules() ) ) {
89 return $data;
90 }
91
92 $data['offers']['@type'] = 'AggregateOffer';
93 $data['offers']['offerCount'] = count( $pricingRule->getRules() ) + 1;
94 $data['offers']['lowPrice'] = FormatPriceManager::getFormattedPrice( $product, array(
95 'for_display' => true,
96 'with_suffix' => false,
97 'with_default_price' => true,
98 'with_lowest_prefix' => false,
99 'html' => false,
100 'display_type' => 'lowest_price',
101 ) );
102 $data['offers']['highPrice'] = $product->get_price();
103 $data['offers']['offers'] = $this->buildOffers( $product, $pricingRule );
104
105 return $data;
106 }
107
108 /**
109 * Build the list of schema offers: a default base-price offer followed by one offer per tier.
110 *
111 * @param WC_Product $product
112 * @param PricingRule $pricingRule
113 *
114 * @return array[]
115 */
116 protected function buildOffers( WC_Product $product, PricingRule $pricingRule ): array {
117
118 $rules = $pricingRule->getRules();
119
120 // Default offer for the base price: applies from the product minimum up to the first tier.
121 $offers = array(
122 $this->buildOffer( $product, $product->get_price(), $pricingRule->getMinimum(),
123 array_keys( $rules )[0] - 1 ),
124 );
125
126 $iterator = new \ArrayIterator( $rules );
127
128 while ( $iterator->valid() ) {
129 $quantity = $iterator->key();
130 $iterator->next();
131
132 $maxValue = $iterator->valid() ? $iterator->key() - 1 : null;
133
134 $offers[] = $this->buildOffer( $product, $pricingRule->getTierPrice( $quantity ), $quantity, $maxValue );
135 }
136
137 return $offers;
138 }
139
140 /**
141 * Build a single schema.org Offer.
142 *
143 * @param WC_Product $product
144 * @param mixed $price
145 * @param mixed $minValue
146 * @param int|null $maxValue Omitted from the output when null (open-ended top tier).
147 *
148 * @return array
149 */
150 protected function buildOffer( WC_Product $product, $price, $minValue, $maxValue ): array {
151
152 $eligibleQuantity = array(
153 '@type' => 'QuantitativeValue',
154 'minValue' => $minValue,
155 );
156
157 if ( ! is_null( $maxValue ) ) {
158 $eligibleQuantity['maxValue'] = $maxValue;
159 }
160
161 $offer = array(
162 '@type' => 'Offer',
163 'price' => $price,
164 'seller' => array(
165 '@type' => 'Organization',
166 'name' => get_bloginfo( 'name', 'display' ),
167 ),
168 'eligibleQuantity' => $eligibleQuantity,
169 'availability' => $product->is_in_stock() ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock',
170 );
171
172 if ( $product->get_sku() ) {
173 $offer['sku'] = $product->get_sku();
174 }
175
176 return $offer;
177 }
178
179 /**
180 * Formatted tiered price for use in an SEO title/meta variable.
181 *
182 * @param string $displayType e.g. "lowest_price" or "range".
183 * @param bool $html
184 *
185 * @return string
186 */
187 protected function getFormattedProductPrice( string $displayType, bool $html = true ): string {
188
189 $product = $this->get_product();
190
191 if ( ! $product ) {
192 return '';
193 }
194
195 $price = FormatPriceManager::getFormattedPrice( $product, array(
196 'for_display' => true,
197 'with_suffix' => false,
198 'with_default_price' => true,
199 'with_lowest_prefix' => false,
200 'html' => $html,
201 'display_type' => $displayType,
202 ) );
203
204 return $price ? $price : '';
205 }
206
207 /**
208 * Resolve the product ID for the current request. Overridable for plugins that expose the ID
209 * differently (e.g. Rank Math's admin preview).
210 *
211 * @return int
212 */
213 protected function resolveProductId() {
214 return get_queried_object_id();
215 }
216
217 public function get_product() {
218
219 if ( ! is_null( $this->product ) ) {
220 return $this->product;
221 }
222
223 $productId = $this->resolveProductId();
224
225 $this->product = ( ! function_exists( 'wc_get_product' ) || ! $productId || ( ! is_admin() && ! is_singular( 'product' ) ) )
226 ? null
227 : wc_get_product( $productId );
228
229 return $this->product;
230 }
231
232 public function isVariablesEnabled(): bool {
233 return $this->getContainer()->getSettings()->get( $this->getSettingsPrefix() . '_enable_variables',
234 'yes' ) === 'yes';
235 }
236
237 public function isEnhancedSchemaEnabled(): bool {
238 return $this->getContainer()->getSettings()->get( $this->getSettingsPrefix() . '_enhance_schema',
239 'no' ) === 'yes';
240 }
241 }
242