Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/stylish-cost-calculator/admin/controllers/PageControllers/class-page-edit-calculator.php
+++ b/stylish-cost-calculator/admin/controllers/PageControllers/class-page-edit-calculator.php
@@ -3,8 +3,6 @@
exit;
}
-use StripeTerminalLocation;
-
require_once dirname( __FILE__ ) . '/class-pages-breadcrumbs.php';
/**
* *This loads one calculator, settings, translation and preview
@@ -14,8 +12,16 @@
class PageEditTabs extends PagesBreadcrumbs {
public function __construct() {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_die( esc_html__( 'You do not have permission to edit calculators.', 'scc' ) );
+ }
+
require dirname( __DIR__, 1 ) . '/formController.php';
$formC = new formController();
+ $form_id = 0;
+ if ( isset( $_GET['id_form'] ) && ! is_array( $_GET['id_form'] ) ) {
+ $form_id = absint( wp_unslash( $_GET['id_form'] ) );
+ }
wp_enqueue_style( 'wp-color-picker' );
wp_enqueue_script( 'wp-color-picker' );
@@ -64,21 +70,21 @@
wp_register_style( 'scc-modal', SCC_URL . 'assets/css/modals/_modal.css', [], scc_get_file_version( SCC_DIR . '/assets/css/modals/_toast.css' ) );
wp_enqueue_style( 'scc-modal' );
- $currencies_array = 'window["scc_currencies"] = ' . json_encode(
- require_once( SCC_DIR . '/lib/currency_data.php' )
- );
+ $scc_json_encode_flags = JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT;
+ $scc_currencies = require SCC_DIR . '/lib/currency_data.php';
+ $currencies_array = 'window["scc_currencies"] = ' . wp_json_encode( $scc_currencies, $scc_json_encode_flags );
wp_add_inline_script( 'scc-frontend', $currencies_array );
add_thickbox();
- $f1 = $formC->readWithRelations( $_GET['id_form'] );
+ $f1 = $form_id > 0 ? $formC->readWithRelations( $form_id ) : null;
$isActivated = get_option( 'df_scc_licensed', 0 ) ? true : false;
parent::__construct();
wp_enqueue_media();
if ( ! $f1 ) {
- return $this->show_eror();
+ return $this->show_eror( $form_id );
}
$this->updateCalculatorUsageStat();
@@ -95,7 +101,7 @@
require dirname( __DIR__, 2 ) . '/views/adminFooter.php';
}
- function show_eror() {
+ function show_eror( $form_id = 0 ) {
?>
<script>
jQuery(document).ready(function() {
@@ -104,7 +110,7 @@
allowOutsideClick: false,
title: 'Oops...',
confirmButtonText: '<i class="fa fa-thumbs-up"></i> Great!',
- text: "Calculator with ID <?php echo intval( $_GET['id_form'] ); ?> doesn't exist, or has been deleted. Please recheck your shortcode ",
+ text: "Calculator with ID <?php echo intval( $form_id ); ?> doesn't exist, or has been deleted. Please recheck your shortcode ",
}).then((result) => {
if (result.isConfirmed) {
location.href = '<?php echo admin_url( 'admin.php?page=scc-list-all-calculator-forms' ); ?>'
--- a/stylish-cost-calculator/admin/controllers/dealer.php
+++ b/stylish-cost-calculator/admin/controllers/dealer.php
@@ -21,8 +21,12 @@
case 'add_new_form2':
require dirname( __FILE__ ) . '/PageControllers/class-page-new-calcualtor.php';
break;
- case 'scc_edit_items' && isset( $_GET['id_form'] ):
- require dirname( __FILE__ ) . '/PageControllers/class-page-edit-calculator.php';
+ case 'scc_edit_items':
+ if ( isset( $_GET['id_form'] ) ) {
+ require dirname( __FILE__ ) . '/PageControllers/class-page-edit-calculator.php';
+ } else {
+ require dirname( __FILE__ ) . '/PageControllers/class-page-new-calcualtor.php';
+ }
break;
case 'scc-list-all-calculator-forms':
require dirname( __FILE__ ) . '/PageControllers/class-page-all-forms.php';
--- a/stylish-cost-calculator/admin/controllers/formController.php
+++ b/stylish-cost-calculator/admin/controllers/formController.php
@@ -14,8 +14,8 @@
class formController {
protected $db;
- private const RELATIONS_CACHE_GROUP = 'scc_forms';
- private const RELATIONS_CACHE_TTL = 15 * MINUTE_IN_SECONDS;
+ const RELATIONS_CACHE_GROUP = 'scc_forms';
+ const RELATIONS_CACHE_TTL = 15 * MINUTE_IN_SECONDS;
private function normalize_json_text_field( $value ) {
if ( is_array( $value ) || is_object( $value ) ) {
@@ -43,6 +43,14 @@
return 'scc_form_rel_' . $form_id;
}
+ private static function get_frontend_cache_key( int $form_id ) {
+ return 'frontend_form:' . $form_id;
+ }
+
+ private static function get_frontend_transient_key( int $form_id ) {
+ return 'scc_form_front_' . $form_id;
+ }
+
private static function get_cached_relations( int $form_id ) {
$cache_key = self::get_relations_cache_key( $form_id );
$cached = wp_cache_get( $cache_key, self::RELATIONS_CACHE_GROUP );
@@ -54,7 +62,11 @@
$cached = get_transient( self::get_relations_transient_key( $form_id ) );
if ( false !== $cached ) {
- delete_transient( self::get_relations_transient_key( $form_id ) );
+ // Re-prime the per-request object cache but keep the transient in place
+ // so it keeps serving subsequent requests for its full TTL. On installs
+ // without a persistent object cache, deleting it here made the cache
+ // single-use. The transient is invalidated on writes via the
+ // flush_relations_cache() / flush_by_* helpers.
wp_cache_set( $cache_key, $cached, self::RELATIONS_CACHE_GROUP, self::RELATIONS_CACHE_TTL );
return $cached;
}
@@ -67,9 +79,224 @@
set_transient( self::get_relations_transient_key( $form_id ), $data, self::RELATIONS_CACHE_TTL );
}
+ /**
+ * Cached loader for the public calculator shortcode.
+ *
+ * Returns the exact structure the frontend has always rendered (the forced
+ * turnoff flags and empty-price-to-zero coercion that the old inline get()
+ * helper produced). The expensive sectioned walk is cached under its own
+ * keys and invalidated through flush_relations_cache(), so it is shared
+ * across page views instead of running the N+1 query path on every render.
+ * A deep copy is returned because the renderer mutates the object (e.g.
+ * $item->opt_default, $form->formFieldsArray) and those per-render mutations
+ * must not leak into the shared cache.
+ *
+ * @param integer $id calculator id
+ * @return object|null form with relations, or null when the form is missing
+ */
+ public function getFrontendForm( int $id ) {
+ $object_key = self::get_frontend_cache_key( $id );
+ $cached = wp_cache_get( $object_key, self::RELATIONS_CACHE_GROUP );
+
+ if ( false === $cached ) {
+ $cached = get_transient( self::get_frontend_transient_key( $id ) );
+
+ if ( false !== $cached ) {
+ wp_cache_set( $object_key, $cached, self::RELATIONS_CACHE_GROUP, self::RELATIONS_CACHE_TTL );
+ }
+ }
+
+ if ( false === $cached ) {
+ $cached = $this->build_frontend_form( $id );
+
+ if ( null === $cached ) {
+ return null;
+ }
+
+ wp_cache_set( $object_key, $cached, self::RELATIONS_CACHE_GROUP, self::RELATIONS_CACHE_TTL );
+ set_transient( self::get_frontend_transient_key( $id ), $cached, self::RELATIONS_CACHE_TTL );
+ }
+
+ // Hand back an isolated copy so per-render mutations never touch the cache.
+ return json_decode( wp_json_encode( $cached ) );
+ }
+
+ private function normalize_id_list( array $ids ) {
+ $normalized = [];
+
+ foreach ( $ids as $id ) {
+ $id = absint( $id );
+ if ( $id > 0 ) {
+ $normalized[ $id ] = $id;
+ }
+ }
+
+ return array_values( $normalized );
+ }
+
+ private function get_results_by_ids( string $query, array $ids ) {
+ $ids = $this->normalize_id_list( $ids );
+
+ if ( empty( $ids ) ) {
+ return [];
+ }
+
+ $placeholders = implode( ', ', array_fill( 0, count( $ids ), '%d' ) );
+ $query = str_replace( '%IDS%', $placeholders, $query );
+
+ return $this->db->get_results( $this->db->prepare( $query, ...$ids ) );
+ }
+
+ private function build_condition_elementitem_map( array $elementitem_ids, bool $for_frontend = false ) {
+ $elementitems = $this->get_results_by_ids( "SELECT `id`,`name`,`uniqueId` FROM {$this->db->prefix}df_scc_elementitems WHERE id IN (%IDS%);", $elementitem_ids );
+ $map = [];
+
+ foreach ( $elementitems as $elementitem ) {
+ $map[ $elementitem->id ] = $for_frontend
+ ? (object) [ 'name' => $elementitem->name ]
+ : (object) [ 'name' => $elementitem->name, 'uniqueId' => $elementitem->uniqueId ];
+ }
+
+ return $map;
+ }
+
+ private function build_condition_element_map( array $element_ids, bool $for_frontend = false ) {
+ $elements = $this->get_results_by_ids( "SELECT `id`,`titleElement`,`type`,`uniqueId` FROM {$this->db->prefix}df_scc_elements WHERE id IN (%IDS%);", $element_ids );
+ $map = [];
+
+ foreach ( $elements as $element ) {
+ $map[ $element->id ] = $for_frontend
+ ? (object) [ 'titleElement' => $element->titleElement, 'type' => $element->type ]
+ : (object) [ 'titleElement' => $element->titleElement, 'type' => $element->type, 'uniqueId' => $element->uniqueId ];
+ }
+
+ return $map;
+ }
+
+ /**
+ * Builds a calculator with sections, subsections, elements, conditions, and
+ * element items using batched relation queries instead of nested N+1 loops.
+ */
+ private function build_form_with_relations( int $id, bool $for_frontend = false ) {
+ $scc_form = $this->db->get_row( $this->db->prepare( "SELECT * FROM {$this->db->prefix}df_scc_forms WHERE id =%d ;", $id ) );
+
+ if ( ! $scc_form ) {
+ return null;
+ }
+
+ if ( $for_frontend ) {
+ $scc_form->turnoffemailquote = true;
+ $scc_form->turnoffcoupon = true;
+ }
+
+ $sections = $this->db->get_results( $this->db->prepare( "SELECT * FROM {$this->db->prefix}df_scc_sections WHERE form_id =%d ORDER By `order`;", $scc_form->id ) );
+ $scc_form->sections = $sections;
+
+ if ( empty( $sections ) ) {
+ return $scc_form;
+ }
+
+ $section_ids = [];
+ $sections_by_id = [];
+
+ foreach ( $sections as $section ) {
+ $section->subsection = [];
+ $section_ids[] = $section->id;
+ $sections_by_id[ $section->id ] = $section;
+ }
+
+ $subsections = $this->get_results_by_ids( "SELECT * FROM {$this->db->prefix}df_scc_subsections WHERE section_id IN (%IDS%) ORDER BY id;", $section_ids );
+ $subsection_ids = [];
+ $subsections_by_id = [];
+
+ foreach ( $subsections as $subsection ) {
+ $subsection->element = [];
+ $subsection_ids[] = $subsection->id;
+ $subsections_by_id[ $subsection->id ] = $subsection;
+
+ if ( isset( $sections_by_id[ $subsection->section_id ] ) ) {
+ $sections_by_id[ $subsection->section_id ]->subsection[] = $subsection;
+ }
+ }
+
+ $elements = $this->get_results_by_ids( "SELECT * FROM {$this->db->prefix}df_scc_elements WHERE subsection_id IN (%IDS%) ORDER By subsection_id, orden +0;", $subsection_ids );
+ $element_ids = [];
+ $elements_by_id = [];
+
+ foreach ( $elements as $element ) {
+ $element->conditions = [];
+ $element->elementitems = [];
+ $element_ids[] = $element->id;
+ $elements_by_id[ $element->id ] = $element;
+
+ if ( isset( $subsections_by_id[ $element->subsection_id ] ) ) {
+ $subsections_by_id[ $element->subsection_id ]->element[] = $element;
+ }
+ }
+
+ $conditions = $this->get_results_by_ids( "SELECT * FROM {$this->db->prefix}df_scc_conditions WHERE element_id IN (%IDS%) ORDER BY id;", $element_ids );
+ $condition_item_ids = [];
+ $condition_element_ids = [];
+
+ foreach ( $conditions as $condition ) {
+ if ( isset( $elements_by_id[ $condition->element_id ] ) ) {
+ $elements_by_id[ $condition->element_id ]->conditions[] = $condition;
+ }
+
+ if ( ! empty( $condition->elementitem_id ) ) {
+ $condition_item_ids[] = $condition->elementitem_id;
+ }
+
+ if ( ! empty( $condition->condition_element_id ) ) {
+ $condition_element_ids[] = $condition->condition_element_id;
+ }
+ }
+
+ $condition_item_map = $this->build_condition_elementitem_map( $condition_item_ids, $for_frontend );
+ $condition_element_map = $this->build_condition_element_map( $condition_element_ids, $for_frontend );
+
+ foreach ( $conditions as $condition ) {
+ if ( ! empty( $condition->elementitem_id ) && isset( $condition_item_map[ $condition->elementitem_id ] ) ) {
+ $condition->elementitem_name = $condition_item_map[ $condition->elementitem_id ];
+ }
+
+ if ( ! empty( $condition->condition_element_id ) && isset( $condition_element_map[ $condition->condition_element_id ] ) ) {
+ $condition->element_condition = $condition_element_map[ $condition->condition_element_id ];
+ }
+ }
+
+ $elementitems = $this->get_results_by_ids( "SELECT * FROM {$this->db->prefix}df_scc_elementitems WHERE element_id IN (%IDS%) ORDER BY id;", $element_ids );
+
+ foreach ( $elementitems as $elementitem ) {
+ if ( $for_frontend && $elementitem->price == '' ) {
+ $elementitem->price = 0;
+ }
+
+ if ( isset( $elements_by_id[ $elementitem->element_id ] ) ) {
+ $elements_by_id[ $elementitem->element_id ]->elementitems[] = $elementitem;
+ }
+ }
+
+ return $scc_form;
+ }
+
+ /**
+ * Builds the frontend shortcode payload. Kept equivalent to the loader the
+ * shortcode used inline so the rendered markup is unchanged.
+ */
+ private function build_frontend_form( int $id ) {
+ return $this->build_form_with_relations( $id, true );
+ }
+
public static function flush_relations_cache( int $form_id ) {
wp_cache_delete( self::get_relations_cache_key( $form_id ), self::RELATIONS_CACHE_GROUP );
delete_transient( self::get_relations_transient_key( $form_id ) );
+ // Keep the frontend shortcode payload in lock-step with the relations
+ // cache. Every edit path (section/element/condition CRUD and form-level
+ // update/delete) funnels through here, so the cached calculator render
+ // never goes stale after an edit.
+ wp_cache_delete( self::get_frontend_cache_key( $form_id ), self::RELATIONS_CACHE_GROUP );
+ delete_transient( self::get_frontend_transient_key( $form_id ) );
}
public static function flush_by_section( int $section_id ) {
@@ -277,41 +504,8 @@
return $cached;
}
- $scc_form = $this->db->get_row( $this->db->prepare( "SELECT * FROM {$this->db->prefix}df_scc_forms WHERE id =%d ;", $id ) );
+ $scc_form = $this->build_form_with_relations( $id );
if ( $scc_form ) {
- $form_id = $scc_form->id;
- $sections = $this->db->get_results( $this->db->prepare( "SELECT * FROM {$this->db->prefix}df_scc_sections WHERE form_id =%d ORDER By `order`;", $form_id ) );
- $scc_form->sections = $sections;
- foreach ( $sections as $section ) {
- $section_id = $section->id;
- $subsection = $this->db->get_results( $this->db->prepare( "SELECT * FROM {$this->db->prefix}df_scc_subsections WHERE section_id =%d ;", $section_id ) );
- $section->subsection = $subsection;
- foreach ( $section->subsection as $sub ) {
- $sub_id = $sub->id;
- $elements = $this->db->get_results( $this->db->prepare( "SELECT * FROM {$this->db->prefix}df_scc_elements WHERE subsection_id =%d ORDER By orden +0; ", $sub_id ) );
- $sub->element = $elements;
- foreach ( $sub->element as $el2 ) {
- $elem_id = $el2->id;
- $condition = $this->db->get_results( $this->db->prepare( "SELECT * FROM {$this->db->prefix}df_scc_conditions WHERE element_id =%d ;", $elem_id ) );
- $el2->conditions = $condition;
- foreach ( $el2->conditions as $c ) {
- if ( $c->elementitem_id ) {
- $element = $this->db->get_row( $this->db->prepare( "SELECT `name`,`uniqueId` FROM {$this->db->prefix}df_scc_elementitems WHERE id =%d ;", $c->elementitem_id ) );
- $c->elementitem_name = $element;
- }
- if ( $c->condition_element_id ) {
- $element = $this->db->get_row( $this->db->prepare( "SELECT `titleElement`,`type`,`uniqueId` FROM {$this->db->prefix}df_scc_elements WHERE id =%d ;", $c->condition_element_id ) );
- $c->element_condition = $element;
- }
- }
- }
- foreach ( $sub->element as $el ) {
- $elem_id = $el->id;
- $elements = $this->db->get_results( $this->db->prepare( "SELECT * FROM {$this->db->prefix}df_scc_elementitems WHERE element_id =%d ;", $elem_id ) );
- $el->elementitems = $elements;
- }
- }
- }
self::set_cached_relations( $id, $scc_form );
--- a/stylish-cost-calculator/admin/controllers/migrateController.php
+++ b/stylish-cost-calculator/admin/controllers/migrateController.php
@@ -177,9 +177,6 @@
if ( $options->scc_sendername ) {
update_option( 'df_scc_sendername', $options->scc_sendername );
}
- if ( $options->scc_stripe_keys ) {
- update_option( 'df_scc_stripe_keys', $options->scc_stripe_keys );
- }
if ( $options->df_scc_captcha_enablement_status ) {
update_option( 'df_scc-captcha-enablement-status', $options->df_scc_captcha_enablement_status );
}
--- a/stylish-cost-calculator/admin/models/editElementModel.php
+++ b/stylish-cost-calculator/admin/models/editElementModel.php
@@ -13,10 +13,62 @@
$this->calc_id = $calc_id;
$this->is_from_ajax = $is_from_ajax;
$this->df_scc_form_currency = get_option( 'df_scc_currency', 'USD' );
- $this->is_woocommerce_enabled = false;
+ $this->is_woocommerce_enabled = (bool) $is_woocommerce_enabled;
+ if ( $this->is_woocommerce_enabled ) {
+ $this->woo_commerce_products = [];
+ }
$this->scc_icons = require SCC_DIR . '/assets/scc_icons/icon_rsrc.php';
}
+ private function format_woocommerce_product_label( $product ) {
+ if ( ! $product || ! is_object( $product ) || ! method_exists( $product, 'get_name' ) ) {
+ return '';
+ }
+
+ $label = $product->get_name();
+
+ if ( method_exists( $product, 'is_type' ) && $product->is_type( 'variation' ) ) {
+ $parent = function_exists( 'wc_get_product' ) ? wc_get_product( $product->get_parent_id() ) : null;
+ if ( $parent ) {
+ $label = $parent->get_name();
+ }
+
+ if ( function_exists( 'wc_get_formatted_variation' ) ) {
+ $variation_label = wp_strip_all_tags( wc_get_formatted_variation( $product, true, false, true ) );
+ if ( '' !== $variation_label ) {
+ $label .= ': ' . $variation_label;
+ }
+ }
+ }
+
+ if ( method_exists( $product, 'get_price' ) && '' !== $product->get_price() ) {
+ $label .= ' | Price: ' . get_woocommerce_currency_symbol() . $product->get_price();
+ }
+
+ return $label;
+ }
+
+ public function render_woocommerce_product_options( $selected_product_id = 0 ) {
+ $selected_product_id = absint( $selected_product_id );
+
+ if ( $selected_product_id <= 0 || ! function_exists( 'wc_get_product' ) ) {
+ return '';
+ }
+
+ $product = wc_get_product( $selected_product_id );
+ $label = $this->format_woocommerce_product_label( $product );
+
+ if ( '' === $label ) {
+ return '';
+ }
+
+ return sprintf(
+ '<option value="%1$d" selected data-scc-selected-product="1">%2$s</option>',
+ $selected_product_id,
+ esc_html( $label )
+ );
+ }
+
public function renderAdvancedOptions( $el ) {
$defaults = [
'orden' => 0,
@@ -440,12 +492,12 @@
if ( $el->type != 'checkbox' ) {
?>
- <div class="text-scc-col d-flex" style="font-size:13px;">
- <div class="col-md-12 input-field use-premium-tooltip">
- <input onchange="changeTooltipText(this)" onkeyup="changeTooltipText(this)" id="<?php echo esc_attr( 'scc_tooltip_input-' . $el->id ); ?>" class="scc_title_column_mobl" name="scc_title_column_mobl" type="text" value="<?php echo ( isset( $el->tooltiptext ) ) ? esc_attr( $el->tooltiptext ) : ''; ?>" disabled>
- <label class="form-label fw-bold use-tooltip <?php echo ( isset( $el->tooltiptext ) && strlen( $el->tooltiptext ) > 0 ) ? 'active' : ''; ?> " for="<?php echo esc_attr( 'scc_tooltip_input-' . $el->id ); ?>" title="On the frontend, display a tooltip icon and information next to element titles. Explain what this item is about while keeping the calculator form organized.">Tooltip</label>
+ <div class="text-scc-col d-flex scc-tooltip-option-field" style="font-size:13px;">
+ <div class="col-md-12 input-field use-premium-tooltip">
+ <input onchange="changeTooltipText(this)" onkeyup="changeTooltipText(this)" id="<?php echo esc_attr( 'scc_tooltip_input-' . $el->id ); ?>" class="scc_title_column_mobl scc-tooltip-input" name="scc_title_column_mobl" type="text" value="<?php echo ( isset( $el->tooltiptext ) ) ? esc_attr( $el->tooltiptext ) : ''; ?>" disabled>
+ <label class="active form-label fw-bold use-tooltip" for="<?php echo esc_attr( 'scc_tooltip_input-' . $el->id ); ?>" title="On the frontend, display a tooltip icon and information next to element titles. Explain what this item is about while keeping the calculator form organized.">Tooltip</label>
+ </div>
</div>
- </div>
<?php
}
?>
@@ -1612,7 +1664,7 @@
<div class="col-md-12 p-0">
<div class="row">
<div class="col-md-8">
- <input type="text" onkeyup="changeNameElementItem(this, true)" class="form-control scc-input" value="<?php echo stripslashes( wp_kses( $elit->name, SCC_ALLOWTAGS ) ); ?>" placeholder="Product or service name">
+ <input type="text" onkeyup="changeNameElementItem(this, true)" class="form-control scc-input" value="<?php echo esc_attr( wp_kses( wp_unslash( $elit->name ), SCC_ALLOWTAGS ) ); ?>" placeholder="Product or service name">
</div>
<div class="col-md-4 d-inline-flex scc-input-icon">
<span class="input-group-text" style="height: 45px;border-radius: 6px 0px 0px 6px"><?php echo df_scc_get_currency_symbol_by_currency_code( $this->df_scc_form_currency ); ?></span>
@@ -1621,7 +1673,7 @@
</div>
<div class="row">
<div class="col-md-12 col-xs-6 pb-2">
- <input type="text" onkeyup="changeDescriptionElementItem(this, true)" class="input_pad scc_inputoption_desc" placeholder="Description" value="<?php echo stripslashes( wp_kses( $elit->description, SCC_ALLOWTAGS ) ); ?>">
+ <input type="text" onkeyup="changeDescriptionElementItem(this, true)" class="input_pad scc_inputoption_desc" placeholder="Description" value="<?php echo esc_attr( wp_kses( wp_unslash( $elit->description ), SCC_ALLOWTAGS ) ); ?>">
</div>
</div>
</div>
@@ -2188,7 +2240,13 @@
],
'conditions' => [ 1 => [] ],
];
- $el = (object) meks_wp_parse_args( $el, $defaults );
+ $el = (object) meks_wp_parse_args( $el, $defaults );
+ $quantity_box_price = '';
+ if ( isset( $el->value2 ) && $el->value2 !== '' ) {
+ $quantity_box_price = $el->value2;
+ } elseif ( isset( $el->price ) ) {
+ $quantity_box_price = $el->price;
+ }
ob_start();
?>
<div class="scc-element-content" data-element-setup-type="<?php echo esc_attr( $el->type ); ?>" value="selectoption" style="
@@ -2223,7 +2281,7 @@
</div> -->
<div class="col-md-4 d-flex scc-input-icon scc-input">
<span class="input-group-text"><?php echo df_scc_get_currency_symbol_by_currency_code( $this->df_scc_form_currency ); ?></span>
- <input type="number" onchange="changeValue2(this)" onkeyup="changeValue2(this)" class="ssc-margin-0 input_pad inputoption_2" style="width:100%;text-align:center;height:35px;" placeholder="Price" value="<?php echo isset($el->price)?floatval( $el->price ): ''; ?>">
+ <input type="number" onchange="changeValue2(this)" onkeyup="changeValue2(this)" class="ssc-margin-0 input_pad inputoption_2" style="width:100%;text-align:center;height:35px;" placeholder="Price" value="<?php echo esc_attr( $quantity_box_price ); ?>">
</div>
</div>
</div>
@@ -3328,7 +3386,7 @@
<div class="col-md-10">
<div class="row">
<div class="col-md-8">
- <input type="text" class="form-control scc-input" onkeyup="changeNameElementItem(this, true)" value="<?php echo stripslashes( wp_kses( $elit->name, SCC_ALLOWTAGS ) ); ?>">
+ <input type="text" class="form-control scc-input" onkeyup="changeNameElementItem(this, true)" value="<?php echo esc_attr( wp_kses( wp_unslash( $elit->name ), SCC_ALLOWTAGS ) ); ?>">
</div>
<div class="col-md-4 d-inline-flex scc-input-icon">
<span class="input-group-text" style="height: 45px;border-radius: 6px 0px 0px 6px"><?php echo df_scc_get_currency_symbol_by_currency_code( $this->df_scc_form_currency ); ?></span>
@@ -3337,40 +3395,19 @@
</div>
<div class="row">
<div class="col-md-12 col-xs-6 pb-2">
- <input type="text" onkeyup="changeDescriptionElementItem(this, true)" class="input_pad scc_inputoption_desc" placeholder="Description" value="<?php echo stripslashes( wp_kses( $elit->description, SCC_ALLOWTAGS ) ); ?>">
+ <input type="text" onkeyup="changeDescriptionElementItem(this, true)" class="input_pad scc_inputoption_desc" placeholder="Description" value="<?php echo esc_attr( wp_kses( wp_unslash( $elit->description ), SCC_ALLOWTAGS ) ); ?>">
</div>
</div>
</div>
</div>
- <?php if ( ! empty( $this->woo_commerce_products ) ) { ?>
+ <?php if ( isset( $this->woo_commerce_products ) ) { ?>
<div class="col-12 mb-3 edit-field" data-edit-field-type="wc_choices" style="width: 100%;padding-left:12px;">
<label class="form-label fw-bold">
<img class="scc-woo-logo" src="<?php echo esc_url_raw( SCC_ASSETS_URL . '/images/logo-woocommerce.svg' ); ?>" title="Pick an item from your WooCommerce products to link to.">
</label>
- <select class="form-select w-100" data-target="elements_added" onchange="attachProductId(this, <?php echo intval( $elit->id ); ?>)">
+ <select class="form-select w-100 scc_woo_commerce_product_id" data-target="elements_added" onchange="attachProductId(this, <?php echo intval( $elit->id ); ?>)">
<option style="font-size: 10px" value=0>Select a product..</option>
- <?php
- foreach ( $this->woo_commerce_products as $product ) {
- if ( $product->is_type( 'variable' ) ) {
- $available_variations = $product->get_available_variations();
-
- foreach ( $available_variations as $product_variable ) {
- $attributes = [];
-
- foreach ( $product_variable['attributes'] as $key => $value ) {
- $attributes[] = $product->get_name() . ': ' . $value;
- }
- ?>
- <option value=<?php echo esc_html( $product_variable['variation_id'] ); ?> <?php echo selected( $product->get_id() == intval( $elit->woocomerce_product_id ) ); ?>><?php echo esc_html( implode( ' | ', $attributes ) ) . ' | Price: ' . get_woocommerce_currency_symbol() . '' . esc_html( $product_variable['display_regular_price'] ); ?></option>
- <?php
- }
- } else {
- ?>
- <option value=<?php echo esc_html( $product->get_id() ); ?> <?php echo selected( $product->get_id() == intval( $elit->woocomerce_product_id ) ); ?>><?php echo esc_html( $product->get_name() ) . ' | Price: ' . get_woocommerce_currency_symbol() . '' . esc_html( $product->get_price() ); ?></option>
- <?php
- }
- }
- ?>
+ <?php echo $this->render_woocommerce_product_options( $elit->woocomerce_product_id ); ?>
</select>
</div>
<?php } ?>
--- a/stylish-cost-calculator/admin/views/adminHeader.php
+++ b/stylish-cost-calculator/admin/views/adminHeader.php
@@ -249,7 +249,7 @@
height: 100%;
width: 0;
position: fixed;
- z-index: 10;
+ z-index: 1000;
top: 0;
left: 0;
cursor: wait;
--- a/stylish-cost-calculator/admin/views/calculatorQuotes.php
+++ b/stylish-cost-calculator/admin/views/calculatorQuotes.php
@@ -496,18 +496,44 @@
if ( array_key_exists( 'notes_count', $columns ) ) {
$data_args['notes_count'] = true;
}
- $data = $wpdb->get_results( $wpdb->prepare(
- "SELECT * FROM {$wpdb->prefix}df_scc_quote_submissions WHERE {$wpdb->prefix}df_scc_quote_submissions.calc_id=%d LIMIT %d OFFSET %d;",
- $data_args['form_id'],
- $data_args['page-max'],
- $data_args['offset']
+ // Build the WHERE clause from the collected filter args so the starred /
+ // unread / status tabs actually affect results. The column names and sort
+ // direction are whitelisted before interpolation; all values are bound.
+ $where = "{$wpdb->prefix}df_scc_quote_submissions.calc_id = %d";
+ $params = array( $data_args['form_id'] );
+ if ( isset( $data_args['starred'] ) ) {
+ $where .= ' AND starred = %d';
+ $params[] = 1;
+ }
+ if ( isset( $data_args['viewed'] ) ) {
+ // "unread" maps to the `opened` column; there is no `viewed` column.
+ $where .= ' AND opened = %d';
+ $params[] = 0;
+ }
+ if ( isset( $data_args['status'] ) ) {
+ $where .= ' AND status = %s';
+ $params[] = $data_args['status'];
+ }
+ $orderby_whitelist = array(
+ 'id' => 'id',
+ 'status' => 'status',
+ 'starred' => 'starred',
+ 'date_created' => 'created_at',
+ 'created_at' => 'created_at',
+ 'updated_at' => 'updated_at',
+ );
+ $orderby_col = ( isset( $data_args['orderby'] ) && isset( $orderby_whitelist[ $data_args['orderby'] ] ) ) ? $orderby_whitelist[ $data_args['orderby'] ] : 'id';
+ $order_dir = ( isset( $data_args['order'] ) && strtoupper( $data_args['order'] ) === 'ASC' ) ? 'ASC' : 'DESC';
+
+ $data_params = array_merge( $params, array( $data_args['page-max'], $data_args['offset'] ) );
+ $data = $wpdb->get_results( $wpdb->prepare(
+ "SELECT * FROM {$wpdb->prefix}df_scc_quote_submissions WHERE {$where} ORDER BY `{$orderby_col}` {$order_dir} LIMIT %d OFFSET %d;",
+ $data_params
) );
- $total_items = $wpdb->get_results( $wpdb->prepare(
- "SELECT COUNT(*) total FROM {$wpdb->prefix}df_scc_quote_submissions WHERE {$wpdb->prefix}df_scc_quote_submissions.calc_id=%d;",
- $data_args['form_id']
+ $total_items = (int) $wpdb->get_var( $wpdb->prepare(
+ "SELECT COUNT(*) FROM {$wpdb->prefix}df_scc_quote_submissions WHERE {$where};",
+ $params
) );
-
- $total_items = empty( $total_items ) ? 0 : $total_items[0]->total;
// add data
$this->items = $data;
$this->total_items = $total_items;
--- a/stylish-cost-calculator/admin/views/editCalcualtor.php
+++ b/stylish-cost-calculator/admin/views/editCalcualtor.php
@@ -5,22 +5,14 @@
$defaultFields = json_decode( '[{"name":{"name":"Your Name","description":"Type in your name","type":"text","isMandatory":null,"trnKey":"Your Name","deletable":false}},{"email":{"name":"Your Email","description":"Type in your email","type":"email","isMandatory":true,"trnKey":"Your Email","deletable":false}},{"phone":{"name":"Your Phone","description":"phone","type":"phone","isMandatory":null,"trnKey":"Your Phone (Optional)","deletable":false}}]', true );
$formFieldsArray = empty( $f1->formFieldsArray ) ? $defaultFields : json_decode( $f1->formFieldsArray, 1 );
$paypalConfig = json_decode( $f1->paypalConfigArray, true );
-$stripeConfig = ( get_option( 'df_scc_stripe_keys' ) == '' ) ? [
- 'pubKey' => null,
- 'privKey' => null,
-] : get_option( 'df_scc_stripe_keys' );
-$stripeConfig['enabled'] = $f1->isStripeEnabled && ( $f1->isStripeEnabled !== 'false' ) ? true : false;
$stripeData = '';
-$isStripeSetupDone = $stripeConfig['pubKey'] && $stripeConfig['privKey'];
+$isStripeSetupDone = false;
$isPayPalEnabled = $paypalConfig && $paypalConfig['paypal_checked'] == 'true' ? true : false;
-$isStripeEnabled = $stripeConfig && $stripeConfig['enabled'] == 'true' ? true : false;
+$isStripeEnabled = false;
$isWoocommerceCheckoutEnabled = $f1->isWoocommerceCheckoutEnabled == 'true' ? true : false;
$isForceQuoteFormEnabled = $f1->preCheckoutQuoteForm == 'true' ? true : false;
$ShowFormBuilderOnDetails = ( $f1->ShowFormBuilderOnDetails == 'false' || ! $f1->ShowFormBuilderOnDetails ) ? false : true;
-if ( $isStripeSetupDone ) {
- $stripeDataAttr = 'data-pub-key=' . $stripeConfig['pubKey'] . ' ' . 'data-priv-key=' . $stripeConfig['privKey'];
-}
$df_scc_form_currency = get_option( 'df_scc_currency', 'USD' );
$isWoocommerceActive = false;
@@ -29,50 +21,154 @@
}
$isWoocommerceCheckoutEnabled = $f1->isWoocommerceCheckoutEnabled == 'true' ? true : false;
-if ( $isWoocommerceActive && get_option( 'df_scc_licensed' ) == 1 && $isWoocommerceCheckoutEnabled ) {
- $woo_commerce_products = [];
- $args = [
- 'post_type' => 'product',
- 'posts_per_page' => -1,
- ];
- $loop = new WP_Query( $args );
-
- while ( $loop->have_posts() ) {
- $loop->the_post();
- global $product;
- array_push( $woo_commerce_products, $product );
- }
- wp_reset_query();
-}
-// preparing an array for use in dropdown choices in the elements added via ajax
+// WooCommerce product choices are loaded lazily from AJAX when a product picker is used.
$woocommerce_products_array = [];
$icon_trash = $scc_icons['trash-2'] ?? $scc_icons['trash'] ?? '';
-
-if ( $isWoocommerceCheckoutEnabled && $isWoocommerceActive ) {
- foreach ( $woo_commerce_products as $product ) {
- if ( $product->is_type( 'variable' ) ) {
- $available_variations = $product->get_available_variations();
-
- foreach ( $available_variations as $product_variable ) {
- $attributes = [];
-
- foreach ( $product_variable['attributes'] as $key => $value ) {
- $attributes[] = $product->get_name() . ': ' . $value;
- }
- array_push( $woocommerce_products_array, [ esc_html( $product_variable['variation_id'] ) => esc_html( implode( ' | ', $attributes ) ) . ' | Price: ' . get_woocommerce_currency_symbol() . '' . esc_html( $product_variable['display_regular_price'] ) ] );
- }
- } else {
- array_push( $woocommerce_products_array, [ esc_html( $product->get_id() ) => esc_html( $product->get_name() ) . ' | Price: ' . get_woocommerce_currency_symbol() . '' . esc_html( $product->get_price() ) ] );
- }
- }
-}
wp_localize_script( 'scc-backend', 'pageEditCalculator', [ 'nonce' => wp_create_nonce( 'edit-calculator-page' ) ] );
-$edit_page_func = new Stylish_Cost_Calculator_Edit_Page();
+$edit_page_func = new Stylish_Cost_Calculator_Edit_Page( false, false, $isWoocommerceCheckoutEnabled && $isWoocommerceActive );
+$scc_json_encode_flags = JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT;
?>
-<script id="scc-data-schema" type="text/json"><?php echo json_encode( $f1->sections ); ?></script>
+<script id="scc-data-schema" type="text/json"><?php echo wp_json_encode( $f1->sections, $scc_json_encode_flags ); ?></script>
<script>
- window["woocommerceProducts"] = <?php echo json_encode( $woocommerce_products_array ); ?>;
+ window["woocommerceProducts"] = <?php echo wp_json_encode( $woocommerce_products_array, $scc_json_encode_flags ); ?>;
+ window["woocommerceProductsLoaded"] = false;
+ window["woocommerceProductsLoading"] = false;
+ function sccEscapeHtml(value) {
+ const safeValue = (value === null || typeof value === 'undefined') ? '' : value;
+ return String(safeValue).replace(/[&<>"']/g, function(char) {
+ return {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ }[char];
+ });
+ }
+ function sccNormalizeWooProductOption(option) {
+ if (option && typeof option === 'object' && option.id) {
+ return {
+ id: option.id,
+ label: option.label || option.name || option.id
+ };
+ }
+ if (option && typeof option === 'object') {
+ const keys = Object.keys(option);
+ if (keys.length) {
+ return {
+ id: keys[0],
+ label: option[keys[0]]
+ };
+ }
+ }
+ return null;
+ }
+ function sccGetWooCommerceProductOptionsMarkup(selectedValue = '') {
+ return (window.woocommerceProducts || []).map(function(option) {
+ const normalized = sccNormalizeWooProductOption(option);
+ if (!normalized) {
+ return '';
+ }
+ const selected = String(normalized.id) === String(selectedValue) ? ' selected' : '';
+ return `<option value="${sccEscapeHtml(normalized.id)}"${selected}>${sccEscapeHtml(normalized.label)}</option>`;
+ }).join('n');
+ }
+ function sccPopulateWooCommerceProductSelect(select) {
+ if (!select) {
+ return;
+ }
+ const selectedValue = select.value || select.getAttribute('data-selected-value') || '';
+ select.querySelectorAll('option[data-scc-lazy-product="1"]').forEach(function(option) {
+ option.remove();
+ });
+ const existingValues = new Set(Array.from(select.options).map(function(option) {
+ return String(option.value);
+ }));
+ const optionsMarkup = (window.woocommerceProducts || []).map(function(option) {
+ const normalized = sccNormalizeWooProductOption(option);
+ if (!normalized || existingValues.has(String(normalized.id))) {
+ return '';
+ }
+ const selected = String(normalized.id) === String(selectedValue) ? ' selected' : '';
+ return `<option data-scc-lazy-product="1" value="${sccEscapeHtml(normalized.id)}"${selected}>${sccEscapeHtml(normalized.label)}</option>`;
+ }).join('n');
+ select.insertAdjacentHTML('beforeend', optionsMarkup);
+ select.value = selectedValue;
+ }
+ function sccLoadWooCommerceProductOptions(select) {
+ if (window.woocommerceProductsLoaded) {
+ sccPopulateWooCommerceProductSelect(select);
+ return;
+ }
+ if (window.woocommerceProductsLoading) {
+ return;
+ }
+ window.woocommerceProductsLoading = true;
+ jQuery.ajax({
+ url: ajaxurl,
+ type: 'POST',
+ data: {
+ action: 'scc_get_woocommerce_products',
+ nonce: pageEditCalculator.nonce
+ },
+ success: function(response) {
+ if (response && response.success && response.data && Array.isArray(response.data.products)) {
+ window.woocommerceProducts = response.data.products;
+ window.woocommerceProductsLoaded = true;
+ document.querySelectorAll('.scc_woo_commerce_product_id, .scc-woo-commerce-product-selector').forEach(sccPopulateWooCommerceProductSelect);
+ }
+ },
+ complete: function() {
+ window.woocommerceProductsLoading = false;
+ }
+ });
+ }
+ function sccInitWooCommerceProductSelect(select) {
+ if (!select) {
+ return;
+ }
+ if (select.tomselect) {
+ return;
+ }
+ if (typeof TomSelect === 'undefined') {
+ sccLoadWooCommerceProductOptions(select);
+ return;
+ }
+ new TomSelect(select, {
+ valueField: 'id',
+ labelField: 'label',
+ searchField: 'label',
+ maxOptions: 100,
+ preload: 'focus',
+ loadThrottle: 300,
+ load: function(query, callback) {
+ jQuery.ajax({
+ url: ajaxurl,
+ type: 'POST',
+ data: {
+ action: 'scc_get_woocommerce_products',
+ nonce: pageEditCalculator.nonce,
+ search: query || '',
+ limit: 100
+ },
+ success: function(response) {
+ if (response && response.success && response.data && Array.isArray(response.data.products)) {
+ callback(response.data.products);
+ return;
+ }
+ callback();
+ },
+ error: function() {
+ callback();
+ }
+ });
+ }
+ });
+ }
+ jQuery(document).on('focus click', '.scc_woo_commerce_product_id, .scc-woo-commerce-product-selector', function() {
+ sccInitWooCommerceProductSelect(this);
+ });
jQuery(document).ready(function() {
var isNewCalculator = "<?php echo ( isset( $_GET['new'] ) ) ? 1 : 0; ?>"
if (isNewCalculator == 1) {
@@ -80,7 +176,7 @@
}
})
let datacal = JSON.parse(String.raw`${document.querySelector('#scc-data-schema').innerText}`)
- cal = getElmtsNCndns(datacal)
+ let cal = getElmtsNCndns(datacal)
//extracts data from json
function getElmtsNCndns(data) {
let o = []
@@ -1061,6 +1157,20 @@
</div>
<div class="modal df-scc-modal fade in" id="webhook-setup-placeholder" style="padding-right: 0px;" role="dialog" data-backdrop="0"></div>
<script type="text/javascript">
+ var sccEditorDebounceTimers = {};
+
+ function sccDebounceEditorSave(debounceKey, callback, wait) {
+ clearTimeout(sccEditorDebounceTimers[debounceKey])
+ sccEditorDebounceTimers[debounceKey] = setTimeout(function() {
+ delete sccEditorDebounceTimers[debounceKey]
+ callback()
+ }, wait)
+ }
+
+ function sccEditorAjaxFailure() {
+ showSweet(false, "There was an error, please try again")
+ }
+
/** preview */
/**
* *Loads the preview with page load
@@ -1366,6 +1476,9 @@
sccBackendUtils.handleTooltipAjaxAddedElements( clonedElement[ 0 ] );
sccFlatpickrInitBackend();
}
+ },
+ error: function() {
+ sccEditorAjaxFailure();
}
})
//reder duplicated element in dom
@@ -1438,9 +1551,14 @@
sccBackendUtils.disableSaveBtnAjax(true, element);
},
success: function(data) {
- sccBackendUtils.disableSaveBtnAjax(false, element);
var datajson = JSON.parse(data)
sccBackendUtils.handleSavingAlert(data, true);
+ },
+ error: function() {
+ sccEditorAjaxFailure();
+ },
+ complete: function() {
+ sccBackendUtils.disableSaveBtnAjax(false, element);
}
})
}
@@ -1644,19 +1762,13 @@
* *On keyup changes title section in db and updates title text in section
* @param Section_id,text
*/
- var timeChangeTitleSection = null
-
function changeTitleSection(element) {
var idSection = jQuery(element).parents(".addedFieldsStyle").find(".id_section_class").val()
var text = jQuery(element).parents(".title_section_no_edit_container").find(".title_section_no_edit")
var value = jQuery(element).val()
text.text(value)
- jQuery(element).focusout(function() {
- timeChangeTitleSection = 0
- })
sccBackendUtils.disableSaveBtnAjax(true);
- clearTimeout(timeChangeTitleSection)
- timeChangeTitleSection = setTimeout(() => {
+ sccDebounceEditorSave('section:' + idSection + ':title', function() {
jQuery.ajax({
url: ajaxurl,
cache: false,
@@ -1667,9 +1779,14 @@
nonce: pageEditCalculator.nonce
},
success: function(data) {
- sccBackendUtils.disableSaveBtnAjax(false);
var datajson = JSON.parse(data)
sccBackendUtils.handleSavingAlert(datajson, true);
+ },
+ error: function() {
+ sccEditorAjaxFailure();
+ },
+ complete: function() {
+ sccBackendUtils.disableSaveBtnAjax(false);
}
})
}, 2000);
@@ -1677,19 +1794,13 @@
/**
* *On keyup changes description section in db and updates description text in section
*/
- var timechangeDescriptionSection = null
-
function changeDescriptionSection(element) {
var idSection = jQuery(element).parents(".addedFieldsStyle").find(".id_section_class").val()
var text = jQuery(element).parents(".description_section_no_edit_container").find(".description_section_no_edit")
var value = jQuery(element).val()
text.text(value)
- jQuery(element).focusout(function() {
- timechangeDescriptionSection = 0
- })
sccBackendUtils.disableSaveBtnAjax(true);
- clearTimeout(timechangeDescriptionSection)
- timechangeDescriptionSection = setTimeout(() => {
+ sccDebounceEditorSave('section:' + idSection + ':description', function() {
jQuery.ajax({
url: ajaxurl,
cache: false,
@@ -1700,9 +1811,14 @@
nonce: pageEditCalculator.nonce
},
success: function(data) {
- sccBackendUtils.disableSaveBtnAjax(false);
var datajson = JSON.parse(data)
sccBackendUtils.handleSavingAlert(datajson, true);
+ },
+ error: function() {
+ sccEditorAjaxFailure();
+ },
+ complete: function() {
+ sccBackendUtils.disableSaveBtnAjax(false);
}
})
}, 2000);
@@ -1802,16 +1918,10 @@
* *Onkeyup changes column DesktopColum of element
* @param element_id
*/
- var timeColumnDesktop = null
-
function changeColumnDesktop(element) {
var element_id = jQuery(element).parentsUntil(".elements_added").parent().find(".input_id_element").val()
var value = jQuery(element).val()
- jQuery(element).focusout(function() {
- timeColumnDesktop = 0
- })
- clearTimeout(timeColumnDesktop)
- timeColumnDesktop = setTimeout(() => {
+ sccDebounceEditorSave('element:' + element_id + ':desktop', function() {
jQuery.ajax({
url: ajaxurl,
cache: false,
@@ -1833,16 +1943,10 @@
* *Onkeyup changes mobileColumn of element
* @param element_id
*/
- var timeColumnMobile = null
-
function changeColumnMobile(element) {
var element_id = jQuery(element).parentsUntil(".elements_added").parent().find(".input_id_element").val()
var value = jQuery(element).val()
- jQuery(element).focusout(function() {
- timeColumnMobile = 0
- })
- clearTimeout(timeColumnMobile)
- timeColumnMobile = setTimeout(() => {
+ sccDebounceEditorSave('element:' + element_id + ':mobile', function() {
jQuery.ajax({
url: ajaxurl,
cache: false,
@@ -1886,9 +1990,14 @@
sccBackendUtils.disableSaveBtnAjax(true, element);
},
success: function(data) {
- sccBackendUtils.disableSaveBtnAjax(false, element);
var datajson = JSON.parse(data)
sccBackendUtils.handleSavingAlert(datajson, false);
+ },
+ error: function() {
+ sccEditorAjaxFailure();
+ },
+ complete: function() {
+ sccBackendUtils.disableSaveBtnAjax(false, element);
}
})
}
@@ -2074,7 +2183,6 @@
nonce: pageEditCalculator.nonce
},
success: function(data) {
- sccBackendUtils.disableSaveBtnAjax(false, element);
var datajson = JSON.parse(data)
if (datajson.passed == true) {
var item = newPriceRangeItem(previousTo, datajson.id_elementitem)
@@ -2084,6 +2192,12 @@
}, 1000);
}
sccBackendUtils.handleSavingAlert(datajson, true);
+ },
+ error: function() {
+ sccEditorAjaxFailure();
+ },
+ complete: function() {
+ sccBackendUtils.disableSaveBtnAjax(false, element);
}
})
@@ -2154,12 +2268,17 @@
sccBackendUtils.disableSaveBtnAjax(true, element);
},
success: function(data) {
- sccBackendUtils.disableSaveBtnAjax(false, element);
var response = JSON.parse(data);
if (response.passed == true) {
elementItem.remove()
}
sccBackendUtils.handleSavingAlert(response, true, true);
+ },
+ error: function() {
+ sccEditorAjaxFailure();
+ },
+ complete: function() {
+ sccBackendUtils.disableSaveBtnAjax(false, element);
}
})
}
@@ -2168,17 +2287,11 @@
* !this value1 is use multiple tipe for more that one element
* @param element_id
*/
- var timeElementitemValue1 = null
-
function changeElementItemValue1(element) {
var elementSetupContainer = jQuery(element).closest('.elements_added')
var id_elementItem = jQuery(element).closest('.price-slider-item').find('input.id_element_slider_item').val()
var value = jQuery(element).val()
- jQuery(element).focusout(function() {
- timeElementitemValue1 = 0
- })
- clearTimeout(timeElementitemValue1)
- timeElementitemValue1 = setTimeout(() => {
+ sccDebounceEditorSave('element-item:' + id_elementItem + ':value1', function() {
jQuery.ajax({
url: ajaxurl,
cache: false,
@@ -2205,16 +2318,10 @@
* !this value2 is use multiple times for more than one element
* @param element_id
*/
- var timeElementitemValue2 = null
-
function changeElementItemValue2(element) {
var id_elementItem = jQuery(element).closest('.price-slider-item').find('input.id_element_slider_item').val()
var value = jQuery(element).val()
- jQuery(element).focusout(function() {
- timeElementitemValue2 = 0
- })
- clearTimeout(timeElementitemValue2)
- timeElementitemValue2 = setTimeout(() => {
+ sccDebounceEditorSave('element-item:' + id_elementItem + ':value2', function() {
jQuery.ajax({
url: ajaxurl,
cache: false,
@@ -2235,16 +2342,10 @@
* *Updates column value3 of element in db
* !this value2 is use multiple times for more than one element
*/
- var timeElementitemValue3 = null
-
function changeElementItemValue3(element) {
var id_elementItem = jQuery(element).closest('.price-slider-item').find('input.id_element_slider_item').val()
var value = jQuery(element).val()
- jQuery(element).focusout(function() {
- timeElementitemValue3 = 0
- })
- clearTimeout(timeElementitemValue3)
- timeElementitemValue3 = setTimeout(() => {
+ sccDebounceEditorSave('element-item:' + id_elementItem + ':value3', function() {
jQuery.ajax({
url: ajaxurl,
cache: false,
@@ -2421,8 +2522,6 @@
* *Updates the column value4 of element in db
* !this value4 is use multiple times for more than one element
*/
- var timeElementValue4 = null
-
function changeValue4(element) {
var id_element = jQuery(element).closest('.elements_added').find(".input_id_element").val()
var value = jQuery(element).val()
@@ -2432,12 +2531,8 @@
value = jQuery(element).prop('checked')
time = 0
}
- jQuery(element).focusout(function() {
- timeElementValue4 = 0
- })
sccBackendUtils.disableSaveBtnAjax(true, element);
- clearTimeout(timeElementValue4)
- timeElementValue4 = setTimeout(() => {
+ sccDebounceEditorSave('element:' + id_element + ':value4', function() {
jQuery.ajax({
url: ajaxurl,
cache: false,
@@ -2448,9 +2543,14 @@
nonce: pageEditCalculator.nonce
},
success: function(data) {
- sccBackendUtils.disableSaveBtnAjax(false, element);
var datajson = JSON.parse(data)
sccBackendUtils.handleSavingAlert(datajson, true);
+ },
+ error: function() {
+ sccEditorAjaxFailure();
+ },
+ complete: function() {
+ sccBackendUtils.disableSaveBtnAjax(false, element);
}
})
}, time);
@@ -2460,18 +2560,12 @@
* !this value3 is use multiple times for more than one element
* @param element_id
*/
- var timeElementValue3
-
function changeValue3(element) {
var el_container = jQuery(element).closest('.elements_added')
- id_element = el_container.find(".input_id_element").val()
+ var id_element = el_container.find(".input_id_element").val()
var value = jQuery(element).val()
- jQuery(element).focusout(function() {
- timeElementValue3 = 0
- })
sccBackendUtils.disableSaveBtnAjax(true, element);
- clearTimeout(timeElementValue3)
- timeElementValue3 = setTimeout(() => {
+ sccDebounceEditorSave('element:' + id_element + ':value3', function() {
jQuery.ajax({
url: ajaxurl,
cache: false,
@@ -2482,9 +2576,14 @@
nonce: pageEditCalculator.nonce
},
success: function(data) {
- sccBackendUtils.disableSaveBtnAjax(false, element);
var datajson = JSON.parse(data)
sccBackendUtils.handleSavingAlert(datajson, false);
+ },
+ error: function() {
+ sccEditorAjaxFailure();
+ },
+ complete: function() {
+ sccBackendUtils.disableSaveBtnAjax(false, element);
}
})
}, 2000);
@@ -2494,18 +2593,12 @@
* !this value2 is use multiple times for more than one element
* @param element_id
*/
- var timeElementValue2 = null
-
function changeValue2(element) {
var id_element = jQuery(element).closest('.elements_added').find(".input_id_element").css("background-color", "red").val()
var value = jQuery(element).val()
- jQuery(element).focusout(function() {
- timeElementValue2 = 0
- })
var tt = jQuery(element).attr('data-type')
sccBackendUtils.disableSaveBtnAjax(true, element);
- clearTimeout(timeElementValue2)
- timeElementValue2 = setTimeout(() => {
+ sccDebounceEditorSave('element:' + id_element + ':value2', function() {
jQuery.ajax({
url: ajaxurl,
cache: false,
@@ -2517,10 +2610,15 @@
tt
},
success: function(data) {
- sccBackendUtils.disableSaveBtnAjax(false, element);
var datajson = JSON.parse(data)
sccBackendUtils.handleSavingAlert(datajson, false);
+ },
+ error: function() {
+ sccEditorAjaxFailure();
+ },
+ complete: function() {
+ sccBackendUtils.disableSaveBtnAjax(false, element);
}
})
}, 2000);
@@ -2651,7 +2749,6 @@
is_image_checkbox: (type == 8)
},
success: function(data) {
- sccBackendUtils.disableSaveBtnAjax(false, element);
var response = JSON.parse(data);
if (response.passed == true) {
// var newElementitem = newElementItemCheckbox(response.id_element, count, type);
@@ -2659,6 +2756,12 @@
toolPrem()
}
sccBackendUtils.handleSavingAlert(response, true, true);
+ },
+ error: function() {
+ sccEditorAjaxFailure();
+ },
+ complete: function() {
+ sccBackendUtils.disableSaveBtnAjax(false, element);
}
})
}
@@ -2741,7 +2844,6 @@
if ( type == 'element' ) {
datajson = JSON.parse( data );
}
- sccBackendUtils.disableSaveBtnAjax( false, formField );
// handle frontend changes after response
const imageIcon = formField.closest( '.scc-icon-picker' ).querySelector( '.scc-image-icon' );
@@ -2753,6 +2855,9 @@
sccBackendUtils.handleSavingAlert( datajson );
},
error( err ) {
+ sccEditorAjaxFailure();
+ },
+ complete() {
sccBackendUtils.disableSaveBtnAjax( false, formField );
},
} );
@@ -2872,9 +2977,14 @@
sccBackendUtils.disableSaveBtnAjax(true, element);
},
success: function(data) {
- sccBackendUtils.disableSaveBtnAjax(false, element);
var datajson = JSON.parse(data)
sccBackendUtils.handleSavingAlert(datajson, false);
+ },
+ error: function() {
+ sccEditorAjaxFailure();
+ },
+ complete: function() {
+ sccBackendUtils.disableSaveBtnAjax(false, element);
}
})
}
@@ -2931,8 +3041,6 @@
* todo: Not all elements have title
* @param element_id
*/
- var timeTitledropdown = null
-
function clickedTitleElement(element) {
var elementSetupContainer = jQuery(element).closest('.elements_added')
var id_element = elementSetupContainer.find(".input_id_element").val()
@@ -2940,13 +3048,10 @@
var text = jQuery(element).val()
var truncatedText = truncateElementTitle(text, 30)
jQuery(title_element_dom).text(truncatedText)
- jQuery(element).focusout(function() {
- timeTitledropdown = 0
- });
sccBackendUtils.disableSaveBtnAjax(true, element);
- clearTimeout(timeTitledropdown)
- timeTitledropdown = setTimeout(() => {
+ sccDebounceEditorSave('element:' + id_element + ':title', function() {
if (text == "") {
+ sccBackendUtils.disableSaveBtnAjax(false, element);
showSweet(false, "the title is empty")
return
}
@@ -2960,9 +3065,14 @@
nonce: pageEditCalculator.nonce
},
success: function(data) {
- sccBackendUtils.disableSaveBtnAjax(false, element);
var datajson = JSON.parse(data)
sccBackendUtils.handleSavingAlert(datajson, false);
+ },
+ error: function() {
+ sccEditorAjaxFailure();
+ },
+ complete: function() {
+ sccBackendUtils.disableSaveBtnAjax(false, element);
}
})
}, 2000);
@@ -2971,24 +3081,18 @@
* *Updates name elementItem of DropdownElement
* @param elementItem_id
*/
- var titmeNameElementItem = null
-
function changeNameElementItem(element, idFromDataAttribute = false) {
var itemContainer = jQuery(element).closest(".scc-item-field-container, .dd-item-field-container");
var id_elemntItem = jQuery(element).closest(".selopt3").find(".swichoptionitem_id").val()
if (itemContainer.length) {
id_elemntItem = itemContainer.data('elementItemId')
}
- jQuery(element).focusout(function() {
- titmeNameElementItem = 0
- });
var name = jQuery(element).val();
if (itemContainer.length) {
jQuery(".display_title_" + itemContainer.data('elementItemId')).text(name);
}
sccBackendUtils.disableSaveBtnAjax(true, element);
- clearTimeout(titmeNameElementItem)
- titmeNameElementItem = setTimeout(() => {
+ sccDebounceEditorSave('element-item:' + id_elemntItem + ':name', function() {
// showLoadingChanges()
jQuery.ajax({
url: ajaxurl,
@@ -3000,8 +3104,13 @@
nonce: pageEditCalculator.nonce
},
success: function(data) {
- sccBackendUtils.disableSaveBtnAjax(false, element);
sccBackendUtils.handleSavingAlert(data, false);
+ },
+ error: function() {
+ sccEditorAjaxFailure();
+ },
+ complete: function() {
+ sccBackendUtils.disableSaveBtnAjax(false, element);
}
})
}, 2000);
@@ -3010,8 +3119,6 @@
* *Updates description elementItem of Dropdown
* @param elementItem_id
*/
- var timeDescriptionElementItem = null
-
function changeDescriptionElementItem(element, idFromDataAttribute = false) {
var itemContainer = jQuery(element).closest(".scc-item-field-container, .dd-item-field-container");
var id_elemntItem = jQuery(element).closest(".selopt3").find(".swichoptionitem_id").val()
@@ -3019,12 +3126,8 @@
id_elemntItem = itemContainer.data('elementItemId')
}
var description = jQuery(element).val();
- jQuery(element).focusout(function() {
- timeDescriptionElementItem = 0
- })
sccBackendUtils.disableSaveBtnAjax(true, element);
- clearTimeout(timeDescriptionElementItem)
- timeDescriptionElementItem = setTimeout(() => {
+ sccDebounceEditorSave('element-item:' + id_elemntItem + ':description', function() {
jQuery.ajax({
url: ajaxurl,
cache: false,
@@ -3035,8 +3138,13 @@
nonce: pageEditCalculator.nonce
},
success: function(data) {
- sccBackendUtils.disableSaveBtnAjax(false, element);
sccBackendUtils.handleSavingAlert(data, false);
+ },
+ error: function() {
+ sccEditorAjaxFailure();
+ },
+ complete: function() {
+ sccBackendUtils.disableSaveBtnAjax(false, element);
}
})
}, 2000);
@@ -3045,8 +3153,6 @@
* *Updates price elementItem of Drop