OSFPriceMod // Modify Product Base Price by Date & 1st Product Full Price, 2nd+ Discounted
<?php
/**
* OSFPriceMod (Ounce: Scott Foster)
* Modify Product Base Price by Date & 1st Product Full Price, 2nd+ Discounted
*
*/
class OSFPriceMod {
var $slug = 'gwosf';
static $_output_script = false;
function __construct( $args ) {
$this->_args = $args;
extract( $args ); // $form_id, $field_ids, $date_field_id, $base_price
add_filter( 'gform_pre_render', array( $this, 'pre_render' ), 9 );
add_action( 'gform_register_init_scripts', array( $this, 'register_init_script' ) );
add_filter( 'gpcp_has_pricing_logic', array( $this, 'has_pricing_logic' ), 10, 2 );
add_action( 'gform_pre_validation', array( $this, 'add_custom_pricing_logic' ), 8 );
add_action( 'gform_validation', array( $this, 'validate_submission' ), 11 );
add_action( 'gform_pre_submission_filter', array( $this, 'add_custom_pricing_logic' ) );
add_action( 'init', array( $this, 'reprioritize_hooks' ), 11 );
}
function reprioritize_hooks() {
remove_filter( 'gform_validation', array( "GFAuthorizeNet", "authorizenet_validation" ), 10, 4 );
add_filter( 'gform_validation', array( "GFAuthorizeNet", "authorizenet_validation" ), 12, 4 );
}
function pre_render( $form ) {
if( $this->_args['form_id'] != $form['id'] )
return $form;
$form = $this->add_custom_pricing_logic( $form );
if( ! self::$_output_script ) {
$this->output_script();
self::$_output_script = true;
}
return $form;
}
function register_init_script( $form ) {
if( $this->_args['form_id'] != $form['id'] )
return;
$args = array(
'fieldIds' => $this->_args['field_ids']
);
$script = 'new gwosf( ' . json_encode( $args ) . ' );';
$script_slug = $this->slug . '_' . $form['id'] . implode( '_', $this->_args['field_ids'] );
GFFormDisplay::add_init_script( $form['id'], $script_slug, GFFormDisplay::ON_PAGE_RENDER, $script );
}
function output_script() {
?>
<script type="text/javascript">
var gwosf;
(function($){
window.gwosf = function( args ) {
if( typeof gwosf.productGroups == 'undefined' )
gwosf.productGroups = [];
if( typeof gwosf.currency == 'undefined' )
gwosf.currency = new Currency( gf_global['gf_currency_config'] );
this.init = function( args ) {
gwosf.productGroups.push( {
fieldIds: args.fieldIds,
disperseTotal: 0,
singleDiscPrice: 0,
lastProductId: false,
pricingIteration: false,
noProductSelected: [], // field IDs where no product is selected
} );
// only add our modifyPrice filter once
if( typeof gform.hooks.filter.gpcp_price != 'undefined' ) {
for( var i = 0; i < gform.hooks.filter.gpcp_price.length; i++ ) {
if( gform.hooks.filter.gpcp_price[i].callable == gwosf.modifyPrice )
return;
}
}
gform.addFilter( 'gpcp_price', gwosf.modifyPrice );
}
gwosf.modifyPrice = function( price, opts ) {
var gpcp = opts.gwcp,
productGroup = false;
for( var i = 0; i < gwosf.productGroups.length; i++ ) {
if( jQuery.inArray( parseInt( opts.productId ), gwosf.productGroups[i].fieldIds ) != -1 )
productGroup = gwosf.productGroups[i];
}
if( ! productGroup || ! price )
return price;
var isSameIteration = productGroup.pricingIteration == gpcp.pricingIteration;
// if is not the same iteration, calculate 'totalQty'
if( ! isSameIteration ) {
productGroup.pricingIteration = gpcp.pricingIteration;
productGroup.noProductSelected = [];
productGroup.totalQty = 0;
productGroup.disperseTotal = false; // tricky tricky
for( var i = 0; i < productGroup.fieldIds.length; i++ ) {
var productId = productGroup.fieldIds[i],
qty = GWConditionalPricing.getProductQuantity( productId, gpcp._formId ),
productInput = GWConditionalPricing.getProductInput( productId, gpcp._formId ),
isSelect = productInput.prop('tagName').toLowerCase() == 'option';
var parent = productInput.parent();
var val = parent.val();
if( isSelect && productInput.parent().val() == '|' )
productGroup.noProductSelected.push( productId );
// if you have an 'empty product' in a product select, override default quantity from '1' to '0'
if( $.inArray( productId, productGroup.noProductSelected ) != -1 )
qty = 0;
productGroup.totalQty += qty;
}
//console.log( [ productGroup.pricingIteration, productGroup.noProductSelected, productGroup.totalQty ] );
}
if( productGroup.totalQty <= 1 || $.inArray( parseInt( opts.productId ), productGroup.noProductSelected ) != -1 )
return price;
//console.log( parseInt( opts.productId ), isSameIteration, productGroup );
// if not same iteration, calculate 'disperseTotal' and 'singleDiscPrice'
if( productGroup.disperseTotal === false ) {
var singleTotal = gwosf.currency.toNumber( price ),
groupTotal = productGroup.totalQty * singleTotal;
productGroup.disperseTotal = ( ( ( groupTotal - singleTotal ) / 2 ) + singleTotal );
productGroup.singleDiscPrice = Math.round( ( productGroup.disperseTotal / productGroup.totalQty ) * 100 ) / 100;
//console.log( [ productGroup.disperseTotal, productGroup.singleDiscPrice ] );
}
price = productGroup.singleDiscPrice;
// for each new product, deduct the price from the disperseTotal
if( parseInt( productGroup.lastProductId ) != parseInt( opts.productId ) )
productGroup.disperseTotal -= price;
// added parseInt comparision for situations where singleDiscPrice (which becomes price here) was rounded up and would be
// greater than the remaining disperseTotal prematurely
if( price > productGroup.disperseTotal && parseInt( price ) != parseInt( productGroup.disperseTotal ) )
price = Math.round( ( price + productGroup.disperseTotal ) * 100 ) / 100;
// set the current product id as the last product id for the next loop
productGroup.lastProductId = opts.productId;
return price;
}
this.init( args );
}
})(jQuery);
</script>
<?php
}
function add_custom_pricing_logic( $form ) {
if( $this->_args['form_id'] != $form['id'] )
return $form;
if( ! isset( $form['gw_pricing_logic'] ) )
$form['gw_pricing_logic'] = array();
foreach( $this->_args['field_ids'] as $field_id ) {
$product_pricing_logic = array();
foreach( $this->_args['base_price'] as $date => $base_price ) {
$product_pricing_logic[] = array(
'price' => $base_price,
'conditionalLogic' => array(
'actionType' => 'show',
'logicType' => 'any',
'rules' => array(
array(
'fieldId' => $this->_args['date_field_id'],
'operator' => 'is',
'value' => $date
),
array(
'fieldId' => $this->_args['date_field_id'],
'operator' => '>',
'value' => $date
)
)
)
);
}
$form['gw_pricing_logic'][$field_id] = $product_pricing_logic;
}
return $form;
}
function has_pricing_logic( $has_pricing_logic, $form ) {
if( $form['id'] == $this->_args['form_id'] )
$has_pricing_logic = true;
return $has_pricing_logic;
}
function validate_submission( $validation_result ) {
$form = $validation_result['form'];
if( $this->_args['form_id'] != $form['id'] )
return $validation_result;
$lead = GWPerk::create_lead_object( $form );
$product_info = GFCommon::get_product_fields( $form, $lead );
$pricing_logic = GWConditionalPricing::get_pricing_logic( $form );
$pricing_field_ids = array_keys( $pricing_logic );
// get_product_fields() sets meta value, remove it so future checks pull from current info not static cache
gform_delete_meta( $lead['id'], 'gform_product_info' );
foreach( $form['fields'] as &$field ) {
if( $field['type'] != 'product' || !in_array( $field['id'], $pricing_field_ids ) )
continue;
// validate any product without a quantity as it won't be included in the order anyways
if( GWConditionalPricing::get_product_quantity( $field ) <= 0 ) {
$field['failed_validation'] = false;
continue;
}
foreach( $product_info['products'] as $field_id => $product ) {
if( $field_id != $field['id'] )
continue;
$match_found = false;
$matched_pricing_level = false;
//$pricing_logic = $this->add_custom_qty_field_support( $pricing_logic );
foreach( $pricing_logic[$field_id] as $pricing_level ) {
if( ! GWConditionalPricing::is_match( $form, $pricing_level, $lead ) )
continue;
$matched_pricing_level = $pricing_level;
// we only want the first match per product, otherwise, subsequent matches will overwrite the price
break;
}
$adjust_base_price = ! $matched_pricing_level ? false : $matched_pricing_level['price'];
// allow default validation to fail...
if( $this->is_valid_discount_price( $field['id'], $adjust_base_price, $product_info, $form ) === false )
continue;
$field['failed_validation'] = false;
break;
}
}
$validation_result['is_valid'] = GWPerk::is_form_valid( $form );
$validation_result['form'] = $form;
return $validation_result;
}
function is_valid_discount_price( $current_product_id, $adjust_base_price, $product_info, $form ) {
$products = $product_info['products'];
$current_product = $product_info['products'][$current_product_id];
$product_group = $this->_args['field_ids'];
$total_qty = 0;
foreach( $product_group as $product_id ) {
$qty = $product_info['products'][$product_id]['quantity'];
$total_qty += $qty;
}
// if total qty for product group is less than or equal to 1, let's not fuss with this product
if( $total_qty <= 1 )
return null;
// get our base price, relies on all products having same price in a pricing group
$base_prices = GWConditionalPricing::get_base_prices( $form );
$single_total = $adjust_base_price;
if( ! $single_total ) {
foreach( $base_prices as $input_id => $base_price ) {
if( intval( $input_id ) == $current_product_id && $base_price > $single_total )
$single_total = $base_price;
}
}
$single_total = GFCommon::to_number( $single_total );
$group_total = $total_qty * $single_total;
$disperse_total = ( ( $group_total - $single_total ) / 2 ) + $single_total;
$single_disc_price = round( $disperse_total / $total_qty, 2 );
$range = 0.5;
if( $current_product['price'] == $single_disc_price || $current_product['price'] - $single_disc_price < $range )
return true;
return false;
}
}
new OSFPriceMod ( array(
'form_id' => 12,
'field_ids' => array( 2, 3, 4 ),
'date_field_id' => 1,
'base_price' => array(
'11/01/2013' => '40.00',
'10/31/2013' => '50.00',
'10/20/2013' => '60.00'
)
) );
new OSFPriceMod ( array(
'form_id' => 12,
'field_ids' => array( 6, 7 ),
'date_field_id' => 1,
'base_price' => array(
'10/31/2013' => '50.00',
'10/20/2013' => '60.00'
)
) );