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 / NonLoggedInUsers / Wholesale / Settings / FormFieldsOption.php

FormFieldsOption.php in Tiered Pricing Table for WooCommerce 8.0.2, at src/Addons/NonLoggedInUsers/Wholesale/Settings/FormFieldsOption.php

254 lines 12.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php namespace TierPricingTable\Addons\NonLoggedInUsers\Wholesale\Settings;
2
3 /**
4 * The WooCommerce settings field that edits the application form: one row per field with label, type,
5 * required and, for a dropdown, its options. Rows can be added, removed and reordered. Saved as JSON.
6 */
7 class FormFieldsOption {
8
9 const FIELD_TYPE = 'tpt_wholesale_form_fields';
10
11 public function __construct() {
12 add_action( 'woocommerce_admin_field_' . self::FIELD_TYPE, array( $this, 'render' ) );
13
14 add_filter( 'woocommerce_admin_settings_sanitize_option', function ( $value, $option, $rawValue ) {
15 if ( self::FIELD_TYPE !== ( $option['type'] ?? '' ) ) {
16 return $value;
17 }
18
19 $rows = is_array( $rawValue ) ? wp_unslash( $rawValue ) : array();
20
21 return FormFields::toJson( FormFields::sanitizeList( $rows ) );
22 }, 10, 3 );
23 }
24
25 public function render( $value ) {
26 // the description is plugin-defined text (settings arrays), read once for output
27 $description = (string) ( $value['desc'] ?? '' );
28 $id = (string) ( $value['id'] ?? '' );
29 $fields = FormFields::fromJson( \WC_Admin_Settings::get_option( $id, '' ) );
30 $types = FormFields::getTypes();
31 ?>
32 <tr valign="top">
33 <th scope="row" class="titledesc">
34 <label><?php echo esc_html( $value['title'] ?? '' ); ?></label>
35 </th>
36 <td class="forminp">
37 <div class="tpt-form-builder" data-name="<?php echo esc_attr( $id ); ?>">
38 <input type="hidden" name="<?php echo esc_attr( $id ); ?>[__none__][label]" value="">
39 <table class="widefat tpt-form-builder__table">
40 <colgroup>
41 <col class="tpt-form-builder__col-move">
42 <col>
43 <col class="tpt-form-builder__col-type">
44 <col class="tpt-form-builder__col-required">
45 <col class="tpt-form-builder__col-remove">
46 </colgroup>
47 <thead>
48 <tr>
49 <th class="tpt-form-builder__col-move"></th>
50 <th><?php esc_html_e( 'Label', 'tier-pricing-table' ); ?></th>
51 <th class="tpt-form-builder__col-type"><?php esc_html_e( 'Type', 'tier-pricing-table' ); ?></th>
52 <th class="tpt-form-builder__col-required"><?php esc_html_e( 'Required', 'tier-pricing-table' ); ?></th>
53 <th class="tpt-form-builder__col-remove"></th>
54 </tr>
55 </thead>
56 <tbody>
57 <?php foreach ( $fields as $index => $field ) : ?>
58 <?php $this->renderRow( $id, (string) $index, $field, $types ); ?>
59 <?php endforeach; ?>
60 </tbody>
61 </table>
62
63 <p class="tpt-form-builder__actions">
64 <button type="button" class="button tpt-form-builder__add"><?php esc_html_e( '+ Add field', 'tier-pricing-table' ); ?></button>
65 <select class="tpt-form-builder__preset">
66 <option value=""><?php esc_html_e( 'Or add a common field…', 'tier-pricing-table' ); ?></option>
67 <?php foreach ( FormFields::getPresets() as $key => $preset ) : ?>
68 <option value="<?php echo esc_attr( $key ); ?>" data-label="<?php echo esc_attr( $preset['label'] ); ?>" data-type="<?php echo esc_attr( $preset['type'] ); ?>"><?php echo esc_html( $preset['label'] ); ?></option>
69 <?php endforeach; ?>
70 </select>
71 </p>
72
73 <?php if ( ! empty( $value['desc'] ) ) : ?>
74 <p class="description"><?php echo wp_kses_post( $description ); // nosemgrep ?></p>
75 <?php endif; ?>
76
77 <template class="tpt-form-builder__template">
78 <?php $this->renderRow( $id, '__index__', FormFields::normalize( array( 'label' => ' ' ) ), $types ); ?>
79 </template>
80 </div>
81 <?php $this->renderAssets(); ?>
82 </td>
83 </tr>
84 <?php
85 }
86
87 protected function renderRow( string $name, string $index, array $field, array $types ) {
88 $prefix = $name . '[' . $index . ']';
89 ?>
90 <tr class="tpt-form-builder__row" data-known="<?php echo FormFields::isKnownKey( $field['key'] ) ? '1' : '0'; ?>">
91 <td class="tpt-form-builder__col-move">
92 <button type="button" class="tpt-form-builder__move" data-dir="up" title="<?php esc_attr_e( 'Move up', 'tier-pricing-table' ); ?>">â–²</button>
93 <button type="button" class="tpt-form-builder__move" data-dir="down" title="<?php esc_attr_e( 'Move down', 'tier-pricing-table' ); ?>">â–¼</button>
94 </td>
95 <td>
96 <input type="hidden" name="<?php echo esc_attr( $prefix ); ?>[key]" value="<?php echo esc_attr( $field['key'] ); ?>">
97 <input type="text" class="tpt-form-builder__label" name="<?php echo esc_attr( $prefix ); ?>[label]" value="<?php echo esc_attr( trim( $field['label'] ) ); ?>" placeholder="<?php esc_attr_e( 'Field label', 'tier-pricing-table' ); ?>">
98 <textarea class="tpt-form-builder__options" name="<?php echo esc_attr( $prefix ); ?>[options]" rows="3" placeholder="<?php esc_attr_e( 'One option per line', 'tier-pricing-table' ); ?>" <?php echo 'select' === $field['type'] ? '' : 'hidden'; ?>><?php echo esc_textarea( implode( "\n", $field['options'] ) ); ?></textarea>
99 <?php if ( FormFields::isKnownKey( $field['key'] ) ) : ?>
100 <span class="tpt-form-builder__hint"><?php echo esc_html( $this->knownKeyHint( $field['key'] ) ); ?></span>
101 <?php endif; ?>
102 </td>
103 <td class="tpt-form-builder__col-type">
104 <select name="<?php echo esc_attr( $prefix ); ?>[type]" class="tpt-form-builder__type">
105 <?php foreach ( $types as $type => $label ) : ?>
106 <option value="<?php echo esc_attr( $type ); ?>" <?php selected( $field['type'], $type ); ?>><?php echo esc_html( $label ); ?></option>
107 <?php endforeach; ?>
108 </select>
109 </td>
110 <td class="tpt-form-builder__col-required">
111 <input type="checkbox" name="<?php echo esc_attr( $prefix ); ?>[required]" value="1" <?php checked( $field['required'] ); ?>>
112 </td>
113 <td class="tpt-form-builder__col-remove">
114 <button type="button" class="tpt-form-builder__remove" title="<?php esc_attr_e( 'Remove field', 'tier-pricing-table' ); ?>">&times;</button>
115 </td>
116 </tr>
117 <?php
118 }
119
120 protected function knownKeyHint( string $key ): string {
121 switch ( $key ) {
122 case 'company':
123 return __( 'Saved as the billing company.', 'tier-pricing-table' );
124 case 'phone':
125 return __( 'Saved as the billing phone.', 'tier-pricing-table' );
126 case 'website':
127 return __( 'Saved as the account website.', 'tier-pricing-table' );
128 default:
129 return '';
130 }
131 }
132
133 protected function renderAssets() {
134 static $printed = false;
135
136 if ( $printed ) {
137 return;
138 }
139
140 $printed = true;
141 ?>
142 <style>
143 /* the builder sits inside WooCommerce's settings table, whose th/td/select rules would otherwise size these cells */
144 .woocommerce table.form-table .tpt-form-builder { max-width: 760px; }
145 .woocommerce table.form-table .tpt-form-builder__table { table-layout: fixed; width: 100%; margin: 0 0 10px; border: 1px solid #c3c4c7; border-radius: 4px; box-shadow: none; border-collapse: separate; border-spacing: 0; }
146 .woocommerce table.form-table .tpt-form-builder__table th,
147 .woocommerce table.form-table .tpt-form-builder__table td { width: auto; padding: 8px; vertical-align: top; line-height: 1.4; font-size: 13px; }
148 .woocommerce table.form-table .tpt-form-builder__table thead th { padding: 7px 8px; font-weight: 600; color: #1d2327; border-bottom: 1px solid #c3c4c7; }
149 .woocommerce table.form-table .tpt-form-builder__table tbody tr + tr td { border-top: 1px solid #f0f0f1; }
150 .woocommerce table.form-table col.tpt-form-builder__col-move { width: 30px; }
151 .woocommerce table.form-table col.tpt-form-builder__col-type { width: 150px; }
152 .woocommerce table.form-table col.tpt-form-builder__col-required { width: 76px; }
153 .woocommerce table.form-table col.tpt-form-builder__col-remove { width: 34px; }
154 /* the controls are centred on the input's line (WooCommerce inputs are 40px tall); the hint hangs under the label input */
155 .woocommerce table.form-table .tpt-form-builder__table td.tpt-form-builder__col-move { padding: 19px 0 8px 6px; text-align: center; }
156 .woocommerce table.form-table .tpt-form-builder__table th.tpt-form-builder__col-required,
157 .woocommerce table.form-table .tpt-form-builder__table td.tpt-form-builder__col-required { text-align: center; }
158 .woocommerce table.form-table .tpt-form-builder__table td.tpt-form-builder__col-required { padding-top: 17px; }
159 .woocommerce table.form-table .tpt-form-builder__table td.tpt-form-builder__col-remove { padding: 19px 6px 8px 0; text-align: center; }
160 .woocommerce table.form-table .tpt-form-builder__label,
161 .woocommerce table.form-table .tpt-form-builder__type,
162 .woocommerce table.form-table .tpt-form-builder__options { width: 100% !important; max-width: none; min-width: 0; margin: 0; box-sizing: border-box; }
163 .woocommerce table.form-table .tpt-form-builder__options { display: block; margin-top: 6px; }
164 .woocommerce table.form-table .tpt-form-builder__options[hidden] { display: none; }
165 .woocommerce table.form-table .tpt-form-builder__table input[type="checkbox"] { margin: 0; }
166 .tpt-form-builder__move { display: block; border: 0; background: none; color: #a7aaad; cursor: pointer; padding: 0 4px; line-height: 1; font-size: 9px; }
167 .tpt-form-builder__move:hover { color: #2271b1; }
168 .tpt-form-builder__hint { display: block; margin-top: 3px; color: #646970; font-size: 12px; }
169 .tpt-form-builder__remove { border: 0; background: none; color: #b32d2e; font-size: 18px; cursor: pointer; line-height: 1; padding: 0; }
170 .tpt-form-builder__remove:hover { color: #8a1f28; }
171 .tpt-form-builder__actions { display: flex; gap: 10px; align-items: center; margin: 0 0 8px; }
172 .woocommerce table.form-table .tpt-form-builder__preset { width: 240px; }
173 .woocommerce table.form-table .tpt-form-builder .description { margin: 0; }
174 .tpt-form-builder__row--flash td { background: #fcf9e8; }
175 </style>
176 <script>
177 ( function () {
178 document.querySelectorAll( '.tpt-form-builder' ).forEach( function ( builder ) {
179 var tbody = builder.querySelector( 'tbody' );
180 var template = builder.querySelector( '.tpt-form-builder__template' );
181 var counter = 0;
182
183 function addRow( label, type ) {
184 var html = template.innerHTML.replace( /__index__/g, 'new_' + ( ++counter ) + '_' + Date.now() );
185 var wrap = document.createElement( 'tbody' );
186 wrap.innerHTML = html;
187 var row = wrap.querySelector( 'tr' );
188 row.querySelector( '.tpt-form-builder__label' ).value = label || '';
189 row.querySelector( '.tpt-form-builder__type' ).value = type || 'text';
190 tbody.appendChild( row );
191 syncRow( row );
192 row.classList.add( 'tpt-form-builder__row--flash' );
193 setTimeout( function () { row.classList.remove( 'tpt-form-builder__row--flash' ); }, 900 );
194 row.querySelector( '.tpt-form-builder__label' ).focus();
195 markChanged( row );
196 }
197
198 function syncRow( row ) {
199 var isSelect = row.querySelector( '.tpt-form-builder__type' ).value === 'select';
200 row.querySelector( '.tpt-form-builder__options' ).hidden = ! isSelect;
201 }
202
203 function markChanged( el ) {
204 // WooCommerce enables "Save changes" on input/change events
205 el.dispatchEvent( new Event( 'change', { bubbles: true } ) );
206 }
207
208 builder.querySelector( '.tpt-form-builder__add' ).addEventListener( 'click', function () {
209 addRow( '', 'text' );
210 } );
211
212 builder.querySelector( '.tpt-form-builder__preset' ).addEventListener( 'change', function () {
213 var option = this.options[ this.selectedIndex ];
214 if ( ! option.value ) { return; }
215 // the same common field can be added again (with another label); only the first copy keeps
216 // the recognised key that maps to the customer record, the others get a key from their label
217 var taken = Array.prototype.some.call( tbody.querySelectorAll( 'input[name$="[key]"]' ), function ( i ) { return i.value === option.value; } );
218 addRow( option.dataset.label, option.dataset.type );
219 if ( ! taken ) {
220 tbody.lastElementChild.querySelector( 'input[name$="[key]"]' ).value = option.value;
221 }
222 this.value = '';
223 } );
224
225 tbody.addEventListener( 'change', function ( event ) {
226 if ( event.target.classList.contains( 'tpt-form-builder__type' ) ) {
227 syncRow( event.target.closest( 'tr' ) );
228 }
229 } );
230
231 tbody.addEventListener( 'click', function ( event ) {
232 var button = event.target.closest( 'button' );
233 if ( ! button ) { return; }
234 var row = button.closest( 'tr' );
235
236 if ( button.classList.contains( 'tpt-form-builder__remove' ) ) {
237 row.remove();
238 markChanged( tbody );
239 } else if ( button.classList.contains( 'tpt-form-builder__move' ) ) {
240 if ( button.dataset.dir === 'up' && row.previousElementSibling ) {
241 tbody.insertBefore( row, row.previousElementSibling );
242 } else if ( button.dataset.dir === 'down' && row.nextElementSibling ) {
243 tbody.insertBefore( row.nextElementSibling, row );
244 }
245 markChanged( tbody );
246 }
247 } );
248 } );
249 } )();
250 </script>
251 <?php
252 }
253 }
254