PluginProbe
Tiered Pricing Table for WooCommerce / 8.0.2
Tiered Pricing Table for WooCommerce v8.0.2
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
← All changes | src/Settings/Settings.php +383 -430 2.3.3 → 8.0.2 View file →
@@ -1,8 +1,25 @@
1 -<?php namespace TierPricingTable\Settings;
1 +<?php
2 2
3 -use Premmerce\SDK\V2\FileManager\FileManager;
3 +namespace TierPricingTable\Settings;
4 4
5 +use TierPricingTable\Core\ServiceContainerTrait;
6 +use TierPricingTable\Settings\CustomOptions\TPTCheckboxListOption;
7 +use TierPricingTable\Settings\CustomOptions\TPTDisplayType;
8 +use TierPricingTable\Settings\CustomOptions\TPTLinkButton;
9 +use TierPricingTable\Settings\CustomOptions\TPTQuantityMeasurementField;
10 +use TierPricingTable\Settings\CustomOptions\TPTTableColumnsField;
11 +use TierPricingTable\Settings\CustomOptions\TPTFeatureFlagOption;
12 +use TierPricingTable\Settings\CustomOptions\TPTIntegrationOption;
13 +use TierPricingTable\Settings\CustomOptions\TPTSwitchOption;
14 +use TierPricingTable\Settings\CustomOptions\TPTTextTemplate;
15 +use TierPricingTable\Settings\Sections\Advanced\AdvancedSection;
16 +use TierPricingTable\Settings\Sections\GeneralSection\GeneralSection;
17 +use TierPricingTable\Settings\Sections\Integrations\IntegrationSection;
18 +use TierPricingTable\Settings\Sections\Multicurrency\MulticurrencySection;
19 +use TierPricingTable\Settings\Sections\ProductAddons\ProductAddonsSection;
20 +use TierPricingTable\Settings\Sections\SectionAbstract;
21 +use TierPricingTable\TierPricingTablePlugin;
5 22 /**
6 23 * Class Settings
7 24 *
8 25 * @package TierPricingTable\Settings
@@ -7,454 +24,390 @@
7 24 *
8 25 * @package TierPricingTable\Settings
9 26 */
10 27 class Settings {
28 + use ServiceContainerTrait;
29 + const SETTINGS_PREFIX = 'tier_pricing_table_';
11 30
12 - const SETTINGS_PREFIX = 'tier_pricing_table_';
31 + const SETTINGS_PAGE = 'tiered_pricing_table_settings';
13 32
14 - const SETTINGS_PAGE = 'tiered_pricing_table_settings';
33 + const DEFAULT_SECTION = 'general';
15 34
16 - /**
17 - * @var FileManager
18 - */
19 - private $fileManager;
35 + /**
36 + * Settings
37 + *
38 + * @var array
39 + */
40 + private $settings = array();
20 41
21 - /**
22 - * @var array
23 - */
24 - private $settings;
42 + /**
43 + * Settings sections
44 + *
45 + * @var SectionAbstract[]
46 + */
47 + protected $sections = array();
25 48
26 - /**
27 - * Settings constructor.
28 - *
29 - * @param FileManager $fileManager
30 - */
31 - public function __construct( FileManager $fileManager ) {
32 - $this->fileManager = $fileManager;
49 + /**
50 + * Settings constructor.
51 + */
52 + public function __construct() {
53 + $this->initCustomOptions();
54 + $this->hooks();
55 + if ( !tpt_fs()->can_use_premium_code() ) {
56 + new PremiumSettingsManager();
57 + }
58 + if ( !tpt_fs()->can_use_premium_code() ) {
59 + $template = 'upgrade-banner.php';
60 + add_action( 'woocommerce_settings_' . self::SETTINGS_PAGE, function () use($template) {
61 + $this->getContainer()->getFileManager()->includeTemplate( 'admin/banners/' . $template, [
62 + 'upgradeUrl' => tpt_fs_activation_url(),
63 + 'contactUsUrl' => TierPricingTablePlugin::getContactUsURL(),
64 + 'documentationUrl' => TierPricingTablePlugin::getDocumentationURL(),
65 + ] );
66 + } );
67 + }
68 + if ( tpt_fs()->can_use_premium_code() && !tpt_fs()->is_premium() ) {
69 + add_action( 'woocommerce_settings_' . self::SETTINGS_PAGE, function () {
70 + $this->getContainer()->getFileManager()->includeTemplate( 'admin/banners/free-version-used-premium-available.php' );
71 + } );
72 + }
73 + add_action( 'admin_enqueue_scripts', function () {
74 + $assetName = self::SETTINGS_PAGE . '_script';
75 + wp_register_script(
76 + $assetName,
77 + $this->getContainer()->getFileManager()->locateJSAsset( 'admin/settings' ),
78 + array('jquery'),
79 + TierPricingTablePlugin::VERSION,
80 + true
81 + );
82 + wp_enqueue_script( $assetName );
83 + } );
84 + }
33 85
34 - $this->hooks();
35 - }
86 + protected function initCustomOptions() {
87 + $this->getContainer()->add( 'settings.tpt_switch_option', new TPTSwitchOption() );
88 + $this->getContainer()->add( 'settings.tpt_integration_option', new TPTIntegrationOption() );
89 + $this->getContainer()->add( 'settings.tpt_feature_flag_option', new TPTFeatureFlagOption() );
90 + $this->getContainer()->add( 'settings.tpt_text_template', new TPTTextTemplate() );
91 + $this->getContainer()->add( 'settings.tpt_multiple_fields', new TPTTableColumnsField() );
92 + $this->getContainer()->add( 'settings.tpt_display_type', new TPTDisplayType() );
93 + $this->getContainer()->add( 'settings.tpt_checkbox_list', new TPTCheckboxListOption() );
94 + $this->getContainer()->add( 'settings.tpt_link_button', new TPTLinkButton() );
95 + $this->getContainer()->add( 'settings.tpt_quantity_measurement', new TPTQuantityMeasurementField() );
96 + }
36 97
37 - /**
38 - * Handle updating settings
39 - */
40 - public function updateSettings() {
41 - woocommerce_update_options( $this->settings );
42 - }
98 + protected function initSections() {
99 + }
43 100
44 - /**
45 - * Init all settings
46 - */
47 - public function initSettings() {
48 - $this->settings = array(
49 - [
50 - 'title' => __( 'Tiered price table settings', 'tier-pricing-table' ),
51 - 'desc' => __( 'This controls how tiered pricing table will be displayed in your store',
52 - 'tier-pricing-table' ),
53 - 'id' => self::SETTINGS_PREFIX . 'options',
54 - 'type' => 'title',
55 - ],
56 - [
57 - 'title' => __( 'Show tiered price table', 'tier-pricing-table' ),
58 - 'id' => self::SETTINGS_PREFIX . 'display',
59 - 'type' => 'checkbox',
60 - 'default' => 'yes',
61 - 'desc' => __( 'Display a table on frontend? Prices will change even if it is off',
62 - 'tier-pricing-table' ),
63 - 'desc_tip' => true,
64 - ],
65 - [
66 - 'title' => __( 'Display', 'tier-pricing-table' ),
67 - 'id' => self::SETTINGS_PREFIX . 'display_type',
68 - 'type' => 'select',
69 - 'options' => [
70 - 'tooltip' => __( 'Tooltip', 'tier-pricing-table' ),
71 - 'table' => __( 'Table', 'tier-pricing-table' ),
72 - ],
73 - 'desc' => __( 'Type of displaying', 'tier-pricing-table' ),
74 - 'desc_tip' => true,
75 - 'default' => 'table',
76 - ],
101 + /**
102 + * Handle updating settings
103 + */
104 + public function updateSettings() {
105 + woocommerce_update_options( $this->settings );
106 + $this->getContainer()->getCache()->purge();
107 + }
77 108
78 - [
79 - 'title' => __( 'Tooltip icon color', 'tier-pricing-table' ),
80 - 'id' => self::SETTINGS_PREFIX . 'tooltip_color',
81 - 'type' => 'color',
82 - 'default' => '#cc99c2',
83 - 'desc' => __( 'Color of icon', 'tier-pricing-table' ),
84 - 'desc_tip' => true,
85 - ],
86 - [
87 - 'title' => __( 'Tooltip icon size (px)', 'tier-pricing-table' ),
88 - 'id' => self::SETTINGS_PREFIX . 'tooltip_size',
89 - 'type' => 'number',
90 - 'default' => '15',
91 - 'desc' => __( 'Size of icon', 'tier-pricing-table' ),
92 - 'desc_tip' => true,
93 - ],
94 - [
95 - 'title' => __( 'Tooltip border', 'tier-pricing-table' ),
96 - 'id' => self::SETTINGS_PREFIX . 'tooltip_border',
97 - 'type' => 'checkbox',
98 - 'default' => 'yes',
99 - 'desc' => __( 'Enable tooltip border', 'tier-pricing-table' ),
100 - ],
101 - [
102 - 'title' => __( 'Table title', 'tier-pricing-table' ),
103 - 'id' => self::SETTINGS_PREFIX . 'table_title',
104 - 'type' => 'text',
105 - 'default' => '',
106 - 'desc' => __( 'The name is being displayed above the table', 'tier-pricing-table' ),
107 - 'desc_tip' => true,
108 - ],
109 - [
110 - 'title' => __( 'Table position', 'tier-pricing-table' ),
111 - 'id' => self::SETTINGS_PREFIX . 'position_hook',
112 - 'type' => 'select',
113 - 'options' => [
114 - 'woocommerce_before_add_to_cart_button' => __( 'Above buy button', 'tier-pricing-table' ),
115 - 'woocommerce_after_add_to_cart_button' => __( 'Below buy button', 'tier-pricing-table' ),
116 - 'woocommerce_before_add_to_cart_form' => __( 'Above add to cart form', 'tier-pricing-table' ),
117 - 'woocommerce_after_add_to_cart_form' => __( 'Below add to cart form', 'tier-pricing-table' ),
118 - 'woocommerce_single_product_summary' => __( 'Above product title', 'tier-pricing-table' ),
119 - 'woocommerce_before_single_product_summary' => __( 'Before product summary', 'tier-pricing-table' ),
120 - 'woocommerce_after_single_product_summary' => __( 'After product summary', 'tier-pricing-table' ),
121 - ],
122 - 'desc' => __( 'Where to display the table', 'tier-pricing-table' ),
123 - 'desc_tip' => true,
124 - ],
125 - [
126 - 'title' => __( 'Active price background color', 'tier-pricing-table' ),
127 - 'id' => self::SETTINGS_PREFIX . 'selected_quantity_color',
128 - 'type' => 'color',
129 - 'css' => 'width:6em;',
130 - 'default' => '#cc99c2',
131 - 'desc' => __( 'Active tiered price background color', 'tier-pricing-table' ),
132 - 'desc_tip' => true,
133 - ],
134 - [
135 - 'title' => __( 'Quantity text', 'tier-pricing-table' ),
136 - 'id' => self::SETTINGS_PREFIX . 'head_quantity_text',
137 - 'type' => 'text',
138 - 'default' => __( 'Quantity', 'tier-pricing-table' ),
139 - 'desc' => __( 'Name of quantity column. Leave Price text and Quantity text blank to do not show table head',
140 - 'tier-pricing-table' ),
141 - 'desc_tip' => true,
142 - ],
143 - [
144 - 'title' => __( 'Price text', 'tier-pricing-table' ),
145 - 'id' => self::SETTINGS_PREFIX . 'head_price_text',
146 - 'type' => 'text',
147 - 'default' => __( 'Price', 'tier-pricing-table' ),
148 - 'desc' => __( 'Name of price column. Leave Price text and Quantity text blank to do not show table head',
149 - 'tier-pricing-table' ),
150 - 'desc_tip' => true,
151 - ],
152 - [
153 - 'title' => __( 'CSS class', 'tier-pricing-table' ),
154 - 'id' => self::SETTINGS_PREFIX . 'table_css_class',
155 - 'type' => 'text',
156 - 'default' => '',
157 - 'desc' => __( 'Add your own CSS styles for the class. The class would be added to the table.',
158 - 'tier-pricing-table' ),
159 - 'desc_tip' => true,
160 - ],
161 - array(
162 - 'title' => __( 'Summarize all variation during calculation product variation tiered price',
163 - 'tier-pricing-table' ),
164 - 'id' => self::SETTINGS_PREFIX . 'summarize_variations',
165 - 'type' => 'checkbox',
166 - 'default' => 'no',
167 - 'desc' => __( 'Add all variations quantity to calculate actual tiered price for product variation.',
168 - 'tier-pricing-table' ),
169 - 'desc_tip' => true,
170 - ),
171 - [
172 - 'type' => 'sectionend',
173 - 'id' => self::SETTINGS_PREFIX . 'options'
174 - ],
109 + /**
110 + * Order the settings sections deterministically: configuration first, then display, features,
111 + * feature toggles, third-party sections, and maintenance last. Without this the tab order is an
112 + * accident of hook registration, because every addon splices its section in relative to another.
113 + *
114 + * Sections with an unknown slug (third-party) keep their relative order and land before Tools.
115 + *
116 + * @param array $sections
117 + *
118 + * @return array
119 + */
120 + protected function sortSections( array $sections ) : array {
121 + $order = (array) apply_filters( 'tiered_pricing_table/settings/sections_order', array(
122 + 'general' => 10,
123 + 'shop-loop-display' => 30,
124 + 'tier-labels' => 40,
125 + 'request-a-quote' => 50,
126 + 'integrations' => 70,
127 + 'multicurrency' => 80,
128 + 'product-addons' => 90,
129 + 'advanced' => 120,
130 + ) );
131 + // Stable sort: usort() is only guaranteed stable on PHP 8+, so tie-break on the original index.
132 + $indexed = array();
133 + foreach ( array_values( $sections ) as $index => $section ) {
134 + $indexed[] = array($order[$section->getSlug()] ?? 100, $index, $section);
135 + }
136 + usort( $indexed, function ( $a, $b ) {
137 + return ( $a[0] <=> $b[0] ?: $a[1] <=> $b[1] );
138 + } );
139 + return array_column( $indexed, 2 );
140 + }
175 141
176 - // Premium Here
177 - [
178 - 'title' => __( 'Advanced settings', 'tier-pricing-table' ),
179 - 'id' => self::SETTINGS_PREFIX . 'premium_options',
180 - 'type' => 'title',
181 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
182 - ],
183 - [
184 - 'title' => __( 'Show Tiered Price in Cart as the Discount', 'tier-pricing-table' ),
185 - 'id' => self::SETTINGS_PREFIX . 'show_discount_in_cart',
186 - 'desc' => __( 'Example:', 'tier-pricing-table' ) . ' <del>10$</del> 8$',
187 - 'type' => 'checkbox',
188 - 'default' => 'yes',
189 - 'desc_tip' => true,
190 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => 'yes' ]
191 - ],
192 - [
193 - 'title' => __( 'Enable price formatting on the catalog page, widgets, etc',
194 - 'tier-pricing-table' ),
195 - 'id' => self::SETTINGS_PREFIX . 'tiered_price_at_catalog',
196 - 'type' => 'checkbox',
197 - 'default' => 'yes',
198 - 'desc' => __( 'Change price formatting on the whole site (show range or minimal price)',
199 - 'tier-pricing-table' ),
200 - 'desc_tip' => true,
201 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
202 - ],
203 - [
204 - 'title' => __( 'Enable price formatting on product page ', 'tier-pricing-table' ),
205 - 'id' => self::SETTINGS_PREFIX . 'tiered_price_at_product_page',
206 - 'type' => 'checkbox',
207 - 'default' => 'no',
208 - 'desc' => __( 'Change price for product at product page (show range or minimal price)',
209 - 'tier-pricing-table' ),
210 - 'desc_tip' => true,
211 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
212 - ],
213 - [
214 - 'title' => __( 'Enable catalog, widget price formatting for variable products',
215 - 'tier-pricing-table' ),
216 - 'id' => self::SETTINGS_PREFIX . 'tiered_price_at_catalog_for_variable',
217 - 'type' => 'checkbox',
218 - 'default' => 'yes',
219 - 'desc' => __( 'Show tiered price at the catalog for variable products. Uses the lowest and the highest prices from all variations',
220 - 'tier-pricing-table' ),
221 - 'desc_tip' => true,
222 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
223 - ],
224 - [
225 - 'title' => __( 'Display Tiered Price as', 'tier-pricing-table' ),
226 - 'id' => self::SETTINGS_PREFIX . 'tiered_price_at_catalog_type',
227 - 'type' => 'select',
228 - 'options' => [
229 - 'range' => __( 'Range (from lowest to highest)', 'tier-pricing-table' ),
230 - 'lowest' => __( 'Lowest price', 'tier-pricing-table' ),
231 - ],
232 - 'desc' => __( 'How to Display Tiered Price at The Catalog', 'tier-pricing-table' ),
233 - 'desc_tip' => true,
234 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
235 - ],
236 - [
237 - 'title' => __( 'Lowest price prefix', 'tier-pricing-table' ),
238 - 'id' => self::SETTINGS_PREFIX . 'lowest_prefix',
239 - 'type' => 'text',
240 - 'default' => __( 'From', 'tier-pricing-table' ),
241 - 'desc' => __( 'Prefix Before Lowest Tiered Product Price at the Catalog. Example: <b>From 10$</b>',
242 - 'tier-pricing-table' ),
243 - 'desc_tip' => true,
244 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
245 - ],
246 - [
247 - 'title' => __( 'Show discount column for percentage table', 'tier-pricing-table' ),
248 - 'id' => self::SETTINGS_PREFIX . 'show_discount_column',
249 - 'type' => 'checkbox',
250 - 'default' => 'yes',
251 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
252 - ],
253 - [
254 - 'title' => __( 'Discount column text', 'tier-pricing-table' ),
255 - 'id' => self::SETTINGS_PREFIX . 'head_discount_text',
256 - 'type' => 'text',
257 - 'default' => __( 'Discount (%)', 'tier-pricing-table' ),
258 - 'desc' => __( 'Name of discount column', 'tier-pricing-table' ),
259 - 'desc_tip' => true,
260 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
261 - ],
262 - [
263 - 'title' => __( 'Set tiered price on click', 'tier-pricing-table' ),
264 - 'id' => self::SETTINGS_PREFIX . 'clickable_table_rows',
265 - 'type' => 'checkbox',
266 - 'default' => 'no',
267 - 'desc' => __( 'Change product quantity on click at table pricing row',
268 - 'tier-pricing-table' ),
269 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
270 - ],
271 - [
272 - 'title' => __( 'Show total price', 'tier-pricing-table' ),
273 - 'id' => self::SETTINGS_PREFIX . 'show_total_price',
274 - 'type' => 'checkbox',
275 - 'default' => 'no',
276 - 'desc' => __( 'Calculate and show total price at product page',
277 - 'tier-pricing-table' ),
278 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
279 - ],
280 - [
281 - 'type' => 'sectionend',
282 - 'id' => self::SETTINGS_PREFIX . 'premium_options'
283 - ],
284 - // Summary section
285 - array(
286 - 'title' => __( 'Summary block', 'tier-pricing-table' ),
287 - 'id' => self::SETTINGS_PREFIX . 'summary_section',
288 - 'type' => 'title',
289 - 'desc' => __( 'Section controls how summary block at a product page.',
290 - 'tier-pricing-table' ),
291 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
292 - ),
293 - array(
294 - 'title' => __( 'Show summary block',
295 - 'tier-pricing-table' ),
296 - 'id' => self::SETTINGS_PREFIX . 'display_summary',
297 - 'type' => 'checkbox',
298 - 'default' => 'yes',
299 - 'desc' => __( 'Show summary block at a product page. Display information about actual unit price, product quantity and total',
300 - 'tier-pricing-table' ),
301 - 'desc_tip' => true,
302 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
303 - ),
304 - array(
305 - 'title' => __( 'Summary block title', 'tier-pricing-table' ),
306 - 'id' => self::SETTINGS_PREFIX . 'summary_title',
307 - 'type' => 'text',
308 - 'desc' => __( 'The name is displaying above the summary block.', 'tier-pricing-table' ),
309 - 'desc_tip' => true,
310 - 'default' => '',
311 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
312 - ),
313 - array(
314 - 'title' => __( 'Summary block type', 'tier-pricing-table' ),
315 - 'id' => self::SETTINGS_PREFIX . 'summary_type',
316 - 'type' => 'select',
317 - 'options' => array(
318 - 'table' => __( 'Table', 'tier-pricing-table' ),
319 - 'inline' => __( 'Inline', 'tier-pricing-table' ),
320 - ),
321 - 'desc' => __( 'Type of displaying.', 'tier-pricing-table' ),
322 - 'desc_tip' => true,
323 - 'default' => 'table',
324 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
325 - ),
326 - array(
327 - 'title' => __( '"Total" label', 'tier-pricing-table' ),
328 - 'id' => self::SETTINGS_PREFIX . 'summary_total_label',
329 - 'type' => 'text',
330 - 'default' => __( 'Total:', 'tier-pricing-table' ),
331 - 'desc' => __( 'Label for the "total" line.', 'tier-pricing-table' ),
332 - 'desc_tip' => true,
333 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
334 - ),
335 - array(
336 - 'title' => __( '"Each" label', 'tier-pricing-table' ),
337 - 'id' => self::SETTINGS_PREFIX . 'summary_each_label',
338 - 'type' => 'text',
339 - 'default' => __( 'Each: ', 'tier-pricing-table' ),
340 - 'desc' => __( 'Label for the "each" line.', 'tier-pricing-table' ),
341 - 'desc_tip' => true,
342 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
343 - ),
344 - array(
345 - 'title' => __( 'Summary position', 'tier-pricing-table' ),
346 - 'id' => self::SETTINGS_PREFIX . 'summary_position_hook',
347 - 'type' => 'select',
348 - 'options' => array(
349 - 'woocommerce_before_add_to_cart_button' => __( 'Above buy button', 'tier-pricing-table' ),
350 - 'woocommerce_after_add_to_cart_button' => __( 'Below buy button', 'tier-pricing-table' ),
351 - 'woocommerce_before_add_to_cart_form' => __( 'Above add to cart form', 'tier-pricing-table' ),
352 - 'woocommerce_after_add_to_cart_form' => __( 'Below add to cart form', 'tier-pricing-table' ),
353 - 'woocommerce_single_product_summary' => __( 'Above product title', 'tier-pricing-table' ),
354 - 'woocommerce_before_single_product_summary' => __( 'Before product summary', 'tier-pricing-table' ),
355 - 'woocommerce_after_single_product_summary' => __( 'After product summary', 'tier-pricing-table' ),
356 - ),
357 - 'default' => 'woocommerce_after_add_to_cart_button',
358 - 'desc' => __( 'Where to display the summary block.', 'tier-pricing-table' ),
359 - 'desc_tip' => true,
360 - 'custom_attributes' => [ 'data-tiered-pricing-premium-setting' => true ]
361 - ),
362 - array(
363 - 'type' => 'sectionend',
364 - 'id' => self::SETTINGS_PREFIX . 'premium_options'
365 - ),
366 - );
367 - }
142 + /**
143 + * Init all settings
144 + */
145 + public function initSettings() {
146 + $this->sections = apply_filters( 'tiered_pricing_table/settings/sections', array(
147 + new GeneralSection(),
148 + new AdvancedSection(),
149 + new MulticurrencySection(),
150 + new ProductAddonsSection(),
151 + new IntegrationSection()
152 + ) );
153 + $this->sections = $this->sortSections( $this->sections );
154 + // Only on the plugin's own settings tab: other WooCommerce tabs (REST API keys, webhooks, tax,
155 + // shipping…) use the same "section" parameter and must never see it rewritten.
156 + if ( $this->isOwnSettingsTab() ) {
157 + // The "Tools" tab merged into "Advanced" in 7.2.1; old links and bookmarks keep working.
158 + if ( isset( $_GET['section'] ) && 'tools' === $_GET['section'] ) {
159 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
160 + $_GET['section'] = 'advanced';
161 + }
162 + // A removed or unknown section in the URL (old links to the "Calculations" tab, for example)
163 + // opens the default section instead of an empty page. The sections read the request directly.
164 + if ( isset( $_GET['section'] ) ) {
165 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
166 + $known = array_map( function ( $section ) {
167 + return $section->getSlug();
168 + }, $this->sections );
169 + if ( !in_array( sanitize_text_field( wp_unslash( $_GET['section'] ) ), $known, true ) ) {
170 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
171 + unset($_GET['section']);
172 + }
173 + }
174 + }
175 + foreach ( $this->sections as $section ) {
176 + if ( $section->isActive() ) {
177 + $this->settings = $section->getSettings();
178 + break;
179 + }
180 + }
181 + }
368 182
369 - /**
370 - * Register hooks
371 - */
372 - public function hooks() {
373 - add_action( 'init', [ $this, 'initSettings' ] );
183 + /**
184 + * Register hooks
185 + */
186 + public function hooks() {
187 + add_action( 'init', array($this, 'initSettings') );
188 + add_filter( 'woocommerce_settings_tabs_' . self::SETTINGS_PAGE, array($this, 'addTieredPricingTableSettings') );
189 + add_filter( 'woocommerce_settings_tabs_array', array($this, 'addSettingsTab'), 50 );
190 + add_action( 'woocommerce_update_options_' . self::SETTINGS_PAGE, array($this, 'updateSettings') );
191 + add_action( 'woocommerce_settings_' . self::SETTINGS_PAGE, array($this, 'renderSections'), 99 );
192 + }
374 193
375 - add_filter( 'woocommerce_settings_tabs_' . self::SETTINGS_PAGE, [ $this, 'addTieredPricingTableSettings' ] );
376 - add_filter( 'woocommerce_settings_tabs_array', [ $this, 'addSettingsTab' ], 50 );
377 - add_action( 'woocommerce_update_options_' . self::SETTINGS_PAGE, [ $this, 'updateSettings' ] );
378 - }
194 + /**
195 + * Whether the request is for the plugin's own WooCommerce settings tab.
196 + */
197 + protected function isOwnSettingsTab() : bool {
198 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
199 + return is_admin() && isset( $_GET['page'], $_GET['tab'] ) && 'wc-settings' === $_GET['page'] && self::SETTINGS_PAGE === $_GET['tab'];
200 + }
379 201
380 - /**
381 - * Add own settings tab
382 - *
383 - * @param array $settings_tabs
384 - *
385 - * @return mixed
386 - */
387 - public static function addSettingsTab( $settings_tabs ) {
202 + public function renderSections() {
203 + // Opening a section clears its "unread" indicator (store-wide)
204 + foreach ( $this->sections as $section ) {
205 + if ( $section->isActive() ) {
206 + $section->markBadgeSeen();
207 + }
208 + }
209 + ?>
210 + <style>
211 + h2 {
212 + margin-top: 2em;
213 + font-size: 1.45em;
214 + }
215 + </style>
388 216
389 - $settings_tabs[ self::SETTINGS_PAGE ] = __( 'Tiered Pricing', 'tier-pricing-table' );
217 + <ul class="subsubsub" style="font-size: 1.1em; margin-top: 3px">
218 + <?php
219 + $sectionsCount = count( $this->sections );
220 + ?>
221 + <?php
222 + foreach ( $this->sections as $index => $section ) {
223 + ?>
224 + <li>
225 + <?php
226 + if ( !$section->isActive() ) {
227 + ?>
228 + <a href="<?php
229 + echo esc_attr( $section->getURL() );
230 + ?>">
231 + <?php
232 + if ( $section->isIntegration() ) {
233 + ?>
234 + <small style="width: 10px; height: 10px; display: inline-block; margin-right: 10px;">
235 + <span class="dashicons dashicons-admin-plugins"></span>
236 + </small>
237 + <b><?php
238 + echo esc_html( $section->getName() );
239 + ?></b>
240 + <?php
241 + } else {
242 + ?>
243 + <?php
244 + echo esc_html( $section->getName() );
245 + ?>
246 + <?php
247 + if ( $section->isBadgeUnseen() ) {
248 + ?>
249 + <span class="tpt-unread-badge" aria-hidden="true">1</span><span class="screen-reader-text"><?php
250 + esc_html_e( 'New', 'tier-pricing-table' );
251 + ?></span>
252 + <?php
253 + }
254 + ?>
255 + <?php
256 + }
257 + ?>
390 258
391 - return $settings_tabs;
392 - }
259 + </a>
260 + <?php
261 + } else {
262 + ?>
393 263
394 - /**
395 - * Add settings to WooCommerce
396 - */
397 - public function addTieredPricingTableSettings() {
264 + <?php
265 + if ( $section->getSectionCSS() ) {
266 + ?>
267 + <style>
268 + <?php
269 + echo esc_html( $section->getSectionCSS() );
270 + ?>
271 + </style>
272 + <?php
273 + }
274 + ?>
398 275
399 - wp_enqueue_script( 'quantity-table-settings-js', $this->fileManager->locateAsset( 'admin/settings.js' ) );
276 + <a class="current" href="#">
277 + <?php
278 + echo esc_html( $section->getName() );
279 + ?>
280 + </a>
281 + <?php
282 + }
283 + ?>
284 + <?php
285 + if ( $index < $sectionsCount - 1 ) {
286 + ?>
287 + |
288 + <?php
289 + }
290 + ?>
291 + </li>
292 + <?php
293 + }
294 + ?>
295 + </ul>
296 + <br class="clear">
297 + <?php
298 + }
400 299
401 - return woocommerce_admin_fields( $this->settings );
402 - }
300 + /**
301 + * Add own settings tab
302 + *
303 + * @param $settingsTabs
304 + *
305 + * @return array
306 + */
307 + public static function addSettingsTab( $settingsTabs ) : array {
308 + $settingsTabs = ( is_array( $settingsTabs ) ? $settingsTabs : array() );
309 + $settingsTabs[self::SETTINGS_PAGE] = __( 'Tiered Pricing', 'tier-pricing-table' );
310 + return $settingsTabs;
311 + }
403 312
404 - /**
405 - * Get all settings
406 - *
407 - * @return array
408 - */
409 - public function getAll() {
410 - return [
411 - 'display' => $this->get( 'display', 'yes' ),
412 - 'position_hook' => $this->get( 'position_hook',
413 - 'woocommerce_after_add_to_cart_button' ),
414 - 'head_quantity_text' => $this->get( 'head_quantity_text',
415 - __( 'Quantity', 'tier-pricing-table' ) ),
416 - 'head_price_text' => $this->get( 'head_price_text',
417 - __( 'Price', 'tier-pricing-table' ) ),
418 - 'display_type' => $this->get( 'display_type', 'table' ),
419 - 'selected_quantity_color' => $this->get( 'selected_quantity_color', '#cc99c2' ),
420 - 'table_title' => $this->get( 'table_title', '' ),
421 - 'table_css_class' => $this->get( 'table_css_class', '#fff' ),
422 - 'tooltip_size' => $this->get( 'tooltip_size', 15 ),
423 - 'tooltip_border' => $this->get( 'tooltip_border', 'yes' ),
313 + /**
314 + * Add settings to WooCommerce
315 + */
316 + public function addTieredPricingTableSettings() {
317 + woocommerce_admin_fields( $this->settings );
318 + }
424 319
425 - /* Premium */
426 - 'show_discount_in_cart' => $this->get( 'show_discount_in_cart', 'yes' ),
427 - 'tiered_price_at_catalog' => $this->get( 'tiered_price_at_catalog', 'yes' ),
428 - 'tiered_price_at_product_page' => $this->get( 'tiered_price_at_product_page', 'no' ),
429 - 'tiered_price_at_catalog_for_variable' => $this->get( 'tiered_price_at_catalog_for_variable', 'yes' ),
430 - 'tiered_price_at_catalog_type' => $this->get( 'tiered_price_at_catalog_type', 'yes' ),
431 - 'lowest_prefix' => $this->get( 'lowest_prefix', 'yes' ),
432 - 'show_discount_column' => $this->get( 'show_discount_column', 'yes' ),
433 - 'clickable_table_rows' => $this->get( 'clickable_table_rows', 'no' ),
434 - 'show_total_price' => $this->get( 'show_total_price', 'no' ),
435 - 'head_discount_text' => $this->get( 'head_discount_text',
436 - __( 'Discount (%)', 'tier-pricing-table' ) ),
437 - ];
438 - }
320 + /**
321 + * Get setting by name
322 + *
323 + * @param string $optionName
324 + * @param mixed $default
325 + *
326 + * @return mixed
327 + */
328 + public function get( string $optionName, $default = null ) {
329 + return get_option( self::SETTINGS_PREFIX . $optionName, $default );
330 + }
439 331
440 - /**
441 - * Get setting by name
442 - *
443 - * @param string $option_name
444 - * @param mixed $default
445 - *
446 - * @return mixed
447 - */
448 - public function get( $option_name, $default = null ) {
449 - return get_option( self::SETTINGS_PREFIX . $option_name, $default );
450 - }
332 + public static function deleteOptions() {
333 + GeneralSection::deleteOptions();
334 + AdvancedSection::deleteOptions();
335 + IntegrationSection::deleteOptions();
336 + }
451 337
452 - /**
453 - * Get url to settings page
454 - *
455 - * @return string
456 - */
457 - public function getLink() {
458 - return admin_url( 'admin.php?page=wc-settings&tab=tiered_pricing_table_settings' );
459 - }
460 -}
338 + /**
339 + * Get url to settings page
340 + *
341 + * @return string
342 + */
343 + public function getLink() : string {
344 + return admin_url( 'admin.php?page=wc-settings&tab=tiered_pricing_table_settings' );
345 + }
346 +
347 + public static function hex2rgba( $color, $opacity = false ) : string {
348 + $default = 'rgb(0,0,0)';
349 + if ( empty( $color ) ) {
350 + return $default;
351 + }
352 + if ( '#' === $color[0] ) {
353 + $color = substr( $color, 1 );
354 + }
355 + if ( strlen( $color ) == 6 ) {
356 + $hex = array($color[0] . $color[1], $color[2] . $color[3], $color[4] . $color[5]);
357 + } elseif ( strlen( $color ) == 3 ) {
358 + $hex = array($color[0] . $color[0], $color[1] . $color[1], $color[2] . $color[2]);
359 + } else {
360 + return $default;
361 + }
362 + $rgb = array_map( 'hexdec', $hex );
363 + if ( $opacity ) {
364 + if ( abs( $opacity ) > 1 ) {
365 + $opacity = 1.0;
366 + }
367 + $output = 'rgba(' . implode( ',', $rgb ) . ',' . $opacity . ')';
368 + } else {
369 + $output = 'rgb(' . implode( ',', $rgb ) . ')';
370 + }
371 + return $output;
372 + }
373 +
374 + public static function shadeHexWithOpacity( $hex, $opacity, $bgHex = '#FFFFFF' ) {
375 + // Normalize and parse the foreground hex
376 + $hex = ltrim( $hex, '#' );
377 + if ( strlen( $hex ) === 3 ) {
378 + $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
379 + }
380 + if ( strlen( $hex ) !== 6 ) {
381 + return false;
382 + }
383 + // Normalize and parse the background hex
384 + $bgHex = ltrim( $bgHex, '#' );
385 + if ( strlen( $bgHex ) === 3 ) {
386 + $bgHex = $bgHex[0] . $bgHex[0] . $bgHex[1] . $bgHex[1] . $bgHex[2] . $bgHex[2];
387 + }
388 + if ( strlen( $bgHex ) !== 6 ) {
389 + return false;
390 + }
391 + // Clamp opacity
392 + $opacity = max( 0, min( 1, $opacity ) );
393 + // Extract RGB
394 + $r = hexdec( substr( $hex, 0, 2 ) );
395 + $g = hexdec( substr( $hex, 2, 2 ) );
396 + $b = hexdec( substr( $hex, 4, 2 ) );
397 + $bgR = hexdec( substr( $bgHex, 0, 2 ) );
398 + $bgG = hexdec( substr( $bgHex, 2, 2 ) );
399 + $bgB = hexdec( substr( $bgHex, 4, 2 ) );
400 + // Blend with background using alpha compositing
401 + $newR = round( $opacity * $r + (1 - $opacity) * $bgR );
402 + $newG = round( $opacity * $g + (1 - $opacity) * $bgG );
403 + $newB = round( $opacity * $b + (1 - $opacity) * $bgB );
404 + // Return final hex
405 + return sprintf(
406 + '#%02X%02X%02X',
407 + $newR,
408 + $newG,
409 + $newB
410 + );
411 + }
412 +
413 +}