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
tier-pricing-table / src / Addons / RequestAQuote / Frontend / QuoteFormDisplay.php

QuoteFormDisplay.php in Tiered Pricing Table for WooCommerce 8.0.2, at src/Addons/RequestAQuote/Frontend/QuoteFormDisplay.php

312 lines 11.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php namespace TierPricingTable\Addons\RequestAQuote\Frontend;
2
3 use TierPricingTable\Addons\RequestAQuote\Models\RequestQuoteForm;
4 use TierPricingTable\Core\ServiceContainer;
5 use TierPricingTable\PriceManager;
6 use TierPricingTable\TierPricingTablePlugin;
7
8 class QuoteFormDisplay {
9
10 public static array $renderedModals = array();
11
12 public static function addModalToRender( $form, $productId ) {
13 self::$renderedModals[ $productId ] = $form;
14 }
15
16 /**
17 * The product a modal is keyed by: variations share their parent's modal.
18 *
19 * @param int $productId
20 *
21 * @return int
22 */
23 public static function getModalProductId( $productId ): int {
24 $product = wc_get_product( $productId );
25
26 if ( $product && $product->get_parent_id() ) {
27 return (int) $product->get_parent_id();
28 }
29
30 return (int) $productId;
31 }
32
33 /**
34 * Make sure a variable product's modal is printed in wp_footer even if no quote button rendered
35 * during the page request: with more variations than the AJAX threshold, every variation's
36 * pricing table (and its button) is fetched by AJAX later, and no default variation may be
37 * selected at all. Fires only in the main page render, never in the AJAX handler.
38 *
39 * @param \WC_Product|mixed $parentProduct
40 * @param mixed $variationId
41 * @param array $settings
42 */
43 public function registerVariableProductModal( $parentProduct, $variationId = null, $settings = array() ) {
44
45 if ( ! ( $parentProduct instanceof \WC_Product ) || ! TierPricingTablePlugin::isVariableProductSupported( $parentProduct ) ) {
46 return;
47 }
48
49 // Only the product page switches variations (and loads their tables by AJAX). Tables in shop
50 // loops, related products etc. never do, so they don't need a modal registered up front.
51 if ( ( $settings['display_context'] ?? 'product-page' ) !== 'product-page' ) {
52 return;
53 }
54
55 $parentId = $parentProduct->get_id();
56
57 if ( isset( self::$renderedModals[ $parentId ] ) ) {
58 return;
59 }
60
61 $formId = PriceManager::getPricingRule( $parentId )->data['tier_pricing_table_quote_form_id'] ?? null;
62 $form = $formId ? RequestQuoteForm::get( (string) $formId ) : null;
63
64 if ( $form ) {
65 self::addModalToRender( $form, $parentId );
66 }
67 }
68
69 public function __construct() {
70 // Initialize and register hooks for Layout Adapters
71 ( new Adapters\TableAdapter() )->registerHooks();
72 ( new Adapters\HorizontalTableAdapter() )->registerHooks();
73 ( new Adapters\BlocksAdapter() )->registerHooks();
74 ( new Adapters\OptionsAdapter() )->registerHooks();
75 ( new Adapters\DropdownAdapter() )->registerHooks();
76 ( new Adapters\PlainTextAdapter() )->registerHooks();
77 ( new Adapters\AddToCartAdapter() )->registerHooks();
78
79 add_action( 'tiered_pricing_table/before_rendering_tiered_pricing', array( $this, 'registerVariableProductModal' ), 10, 3 );
80 add_action( 'wp_footer', array( $this, 'renderFormModal' ) );
81 add_action( 'wp_enqueue_scripts', array( $this, 'enqueueAssets' ) );
82 add_action( 'template_redirect', array( $this, 'handleSuccessNotice' ) );
83 }
84
85 public function handleSuccessNotice() {
86 if ( isset( $_GET['tier_pricing_table_quote_success'] ) && ! empty( $_GET['form_id'] ) ) {
87 $formId = sanitize_text_field( $_GET['form_id'] );
88 $form = RequestQuoteForm::get( $formId );
89 $message = __( 'Your quote request has been submitted successfully!', 'tier-pricing-table' );
90
91 if ( $form ) {
92 $message = $form->getSuccessMessage();
93 }
94
95 if ( function_exists( 'wc_add_notice' ) ) {
96 if ( function_exists( 'WC' ) && isset( WC()->session ) && ! WC()->session->has_session() ) {
97 WC()->session->set_customer_session_cookie( true );
98 }
99 wc_add_notice( $message, 'success' );
100 wp_safe_redirect( remove_query_arg( array( 'tier_pricing_table_quote_success', 'form_id' ) ) );
101 exit;
102 }
103 }
104 }
105
106 public function renderFormModal() {
107 if ( empty( self::$renderedModals ) ) {
108 return;
109 }
110
111 foreach ( self::$renderedModals as $productId => $form ) :
112 $product = wc_get_product( $productId );
113 if ( ! $product ) {
114 continue;
115 }
116 ServiceContainer::getInstance()->getFileManager()->includeTemplate( 'frontend/quote-modal.php', array(
117 'form' => $form,
118 'productId' => $productId,
119 'product' => $product,
120 'formDisplay' => $this,
121 ), plugin_dir_path( dirname( __FILE__ ) ) . 'views/' );
122 endforeach;
123 }
124
125 /**
126 * Render the form fields natively in PHP.
127 *
128 * @param RequestQuoteForm $form
129 * @param int $productId
130 */
131 public function renderFormFields( $form, $productId = 0 ) {
132 $fields = $form->getFields();
133 if ( empty( $fields ) ) {
134 return;
135 }
136
137 $currentUser = wp_get_current_user();
138 $defaultName = $currentUser->exists() ? $currentUser->display_name : '';
139 $defaultEmail = $currentUser->exists() ? $currentUser->user_email : '';
140
141 foreach ( $fields as $field ) {
142 $type = $field['type'] ?? 'text';
143 $name = $field['name'] ?? '';
144 $label = $field['label'] ?? '';
145 $placeholder = $field['placeholder'] ?? '';
146 $description = $field['description'] ?? '';
147 $required = ! empty( $field['required'] ) ? 'required' : '';
148 $optionsText = $field['options'] ?? '';
149
150 $options = array();
151 if ( $optionsText ) {
152 $options = array_filter( array_map( 'trim', explode( "\n", $optionsText ) ) );
153 }
154
155 $value = '';
156 if ( $name === 'name' ) {
157 $value = $defaultName;
158 } elseif ( $name === 'email' ) {
159 $value = $defaultEmail;
160 }
161
162 ?>
163 <div class="tpt-quote-field-wrapper">
164 <?php if ( $type === 'heading' ) : ?>
165 <h3 style="margin: 0 0 10px 0; color: inherit;"><?php echo esc_html( $label ); ?></h3>
166 <?php elseif ( $type === 'paragraph' ) : ?>
167 <p style="margin: 0 0 10px 0; color: inherit;"><?php echo wp_kses_post( $label ); ?></p>
168 <?php else : ?>
169 <?php
170 $titleFormat = $field['titleFormat'] ?? '';
171 if ( $titleFormat !== 'hidden' ) :
172 ?>
173 <label for="tpt_field_<?php echo esc_attr( $name ); ?>">
174 <?php echo esc_html( $label ); ?>
175 <?php if ( $required ) : ?>
176 <span class="required" style="color: #d63638; font-weight: normal; margin-left: 4px;"
177 title="required">*</span>
178 <?php endif; ?>
179 </label>
180 <?php endif; ?>
181
182 <?php if ( $type === 'select' ) : ?>
183 <select id="tpt_field_<?php echo esc_attr( $name ); ?>"
184 name="<?php echo esc_attr( $name ); ?>" <?php echo $required ? 'required' : ''; ?>>
185 <?php if ( ! empty( $field['hasPlaceholder'] ) && $placeholder ) : ?>
186 <option value="" disabled selected
187 hidden><?php echo esc_html( $placeholder ); ?></option>
188 <?php endif; ?>
189 <?php foreach ( $options as $opt ) : ?>
190 <option value="<?php echo esc_attr( $opt ); ?>"><?php echo esc_html( $opt ); ?></option>
191 <?php endforeach; ?>
192 </select>
193
194 <?php elseif ( $type === 'radio' ) : ?>
195 <div class="tpt-radio-group">
196 <?php foreach ( $options as $opt ) : ?>
197 <label>
198 <input type="radio" name="<?php echo esc_attr( $name ); ?>"
199 value="<?php echo esc_attr( $opt ); ?>" <?php echo $required ? 'required' : ''; ?>>
200 <?php echo esc_html( $opt ); ?>
201 </label>
202 <?php endforeach; ?>
203 </div>
204
205 <?php elseif ( $type === 'checkbox' ) : ?>
206 <div class="tpt-radio-group">
207 <?php
208 $isMultiple = count( $options ) > 1;
209 $chkName = $isMultiple ? $name . '[]' : $name;
210 $chkReq = $isMultiple ? '' : ( $required ? 'required' : '' );
211 foreach ( $options as $opt ) :
212 ?>
213 <label>
214 <input type="checkbox" name="<?php echo esc_attr( $chkName ); ?>"
215 value="<?php echo esc_attr( $opt ); ?>" <?php echo esc_attr( $chkReq ); ?>>
216 <?php echo esc_html( $opt ); ?>
217 </label>
218 <?php endforeach; ?>
219 </div>
220
221 <?php elseif ( $type === 'textarea' ) : ?>
222 <textarea id="tpt_field_<?php echo esc_attr( $name ); ?>"
223 name="<?php echo esc_attr( $name ); ?>"
224 maxlength="1500" <?php echo $required ? 'required' : ''; ?> <?php echo ( ! empty( $field['hasPlaceholder'] ) && $placeholder ) ? 'placeholder="' . esc_attr( $placeholder ) . '"' : ''; ?>><?php echo esc_textarea( $value ); ?></textarea>
225
226 <?php elseif ( $type === 'file' ) : ?>
227 <?php
228 $allowedTypes = ! empty( $field['allowedTypes'] ) ? esc_attr( '.' . str_replace( ',', ',.',
229 str_replace( ' ', '', $field['allowedTypes'] ) ) ) : '';
230 ?>
231 <input type="file" id="tpt_field_<?php echo esc_attr( $name ); ?>"
232 name="<?php echo esc_attr( $name ); ?>"
233 <?php if ( $allowedTypes ) : ?>accept="<?php echo esc_attr( $allowedTypes ); ?>"<?php endif; ?>
234 <?php echo $required ? 'required' : ''; ?>>
235
236 <?php else : ?>
237 <?php
238 $syncClass = ( $type === 'number' && ! empty( $field['syncWithQuantity'] ) ) ? 'tpt-quote-sync-quantity' : '';
239 $maxLength = in_array( $type, array( 'text', 'email', 'tel', 'url' ), true ) ? 255 : 0;
240
241 $min = 1;
242 if ( $type === 'number' && ! empty( $field['syncWithQuantity'] ) && $productId ) {
243 $pricingRule = PriceManager::getPricingRule( $productId );
244 $ruleMin = $pricingRule->getMinimum();
245 if ( $ruleMin > 0 ) {
246 $min = $ruleMin;
247 }
248 }
249
250 $minValue = ( $type === 'number' ) ? (string) $min : '';
251
252 if ( $type === 'date' && ! empty( $field['disablePastDates'] ) ) {
253 $minValue = wp_date( 'Y-m-d' );
254 }
255 ?>
256 <input type="<?php echo esc_attr( $type ); ?>" id="tpt_field_<?php echo esc_attr( $name ); ?>"
257 name="<?php echo esc_attr( $name ); ?>"
258 class="<?php echo esc_attr( $syncClass ); ?>"
259 value="<?php echo esc_attr( $value ); ?>" <?php if ( $maxLength ) : ?>maxlength="<?php echo esc_attr( $maxLength ); ?>"<?php endif; ?> <?php if ( '' !== $minValue ) : ?>min="<?php echo esc_attr( $minValue ); ?>"<?php endif; ?> <?php echo $required ? 'required' : ''; ?> <?php echo ( ! empty( $field['hasPlaceholder'] ) && $placeholder ) ? 'placeholder="' . esc_attr( $placeholder ) . '"' : ''; ?>>
260 <?php endif; ?>
261
262 <?php if ( ! empty( $field['hasDescription'] ) && $description ) : ?>
263 <p class="tpt-field-description"
264 style="font-size: 12px; opacity: 0.8; margin: 4px 0 0 0;"><?php echo esc_html( $description ); ?></p>
265 <?php endif; ?>
266
267 <?php endif; ?>
268 </div>
269 <?php
270 }
271 }
272
273 function enqueueAssets() {
274 // Get forms
275 $formObjects = RequestQuoteForm::getAll();
276 $forms = array_map( function ( $form ) {
277 $data = $form->toArray();
278 unset( $data['fields'] );
279
280 return $data;
281 }, $formObjects );
282
283 // Check for reCAPTCHA
284 $globalSettings = get_option( 'tier_pricing_table_quote_global_settings', array() );
285 $siteKey = $globalSettings['recaptcha_site_key'] ?? null;
286
287 if ( ! empty( $siteKey ) && ! empty( $globalSettings['recaptcha_secret_key'] ) ) {
288 wp_enqueue_script( 'google-recaptcha-v3',
289 'https://www.google.com/recaptcha/api.js?render=' . esc_attr( $siteKey ), array(), null, true );
290 }
291
292 // Enqueue CSS
293 wp_enqueue_style( 'tpt-quote-form', plugins_url( '../assets/css/quote-form.css', __FILE__ ), array(),
294 TierPricingTablePlugin::VERSION );
295
296 // Enqueue JS
297 wp_enqueue_script( 'tpt-quote-form', plugins_url( '../assets/js/quote-form.js', __FILE__ ), array( 'jquery' ),
298 TierPricingTablePlugin::VERSION, true );
299
300 wp_localize_script( 'tpt-quote-form', 'tptQuoteFormConfig', array(
301 'forms' => $forms,
302 'restUrl' => esc_url_raw( rest_url( 'tier-pricing-table/v1/quote-request' ) ),
303 'nonce' => wp_create_nonce( 'wp_rest' ),
304 'i18n' => array(
305 'defaultModalTitle' => __( 'Request a Quote', 'tier-pricing-table' ),
306 'defaultSubmitText' => __( 'Submit Request', 'tier-pricing-table' ),
307 'defaultSubmittingText' => __( 'Submitting...', 'tier-pricing-table' ),
308 ),
309 ) );
310 }
311 }
312