spivurno
10/21/2013 - 2:57 PM

OSFPriceMod // Modify Product Base Price by Date & 1st Product Full Price, 2nd+ Discounted

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'
    )
) );