Support

Home Forums Event Espresso Premium Promo code for specific ticket types

Promo code for specific ticket types

Posted: February 26, 2020 at 10:50 am


mlevison

February 26, 2020 at 10:50 am

Like others that have asked similar questions, we have three ticket types for our events and we want a promo code to only apply to the “Regular” ticket.
I have seen the link to this code https://github.com/eventespresso/ee-code-snippet-library/blob/master/addons/eea-promotions/bc_ee_ticket_scope_promotions.php as the most common answer but it relies on tying specific promotion ids to specific ticket ids.
We are frequently adding new events and also creating specific promo codes for specific groups of people that could be used for more than one of the events.

To use the solution that I have linked to above I would have to be frequently modifying the lists of promo code ids and ticket ids to enforce correct promo code usage.
I am wondering if there is a way to automate this at all.
Is there functionality that could get me a list of all upcoming events tickets that have the name “Regular” that I could loop through to get the ids? Would this have to be done in steps by getting upcoming events then getting tickets for each?
Is there functionality to retrieve a list of valid promotions that I could loop through to get the ids?
If I can get both of those I should be able to build the necessary arrays to use in the linked code snippet.
Thanks


Tony

  • Support Staff

February 27, 2020 at 5:24 am

Hi there,

Is there functionality that could get me a list of all upcoming events tickets that have the name “Regular” that I could loop through to get the ids?

Yes, there is.

Our model system was designed to allow you to pull various objects even with complex queries fairly easily.

You can start by checking the docs here:

https://github.com/eventespresso/event-espresso-core/blob/master/docs/G–Model-System/

Would this have to be done in steps by getting upcoming events then getting tickets for each?

You could, but you don’t need to. To give you an example for your use case.

Your looking for tickets and for specifically pulling in tickets you can use the EEM_Ticket model (this is explained in the docs above, which is why I started with those 🙂 ). Tickets relate to datetimes within an event and you want to search
tickets within a specific ticket name (TKT_name) on upcoming datetimes, which would be based on DTT_EVT_end (when the datetime ‘ends’).

So to pull in all of the tickets with the name ‘Regular’ assigned to upcoming datetimes, you can do something like this:

$where = array(
    'TKT_name' => 'Regular',
    'Datetime.DTT_EVT_end' => array( '>=', EEM_Datetime::instance()->current_time_for_query( 'DTT_EVT_end' ) )
);
$tickets = EEM_Ticket::instance()->get_all( array( $where ) );

In English, pull Tickets with the TKT_Name of ‘Regular’ that are assigned to Datetimes with a DTT_EVT_end (Datetime.DTT_EVT_end) greater than now (meaning they are upcoming).

The model system takes care of the joins for you based on the dot notation used and returns an array of EE_Ticket objects which will be numerically indexed using the tickets ID.

Is there functionality to retrieve a list of valid promotions that I could loop through to get the ids?

Again, yes and again, you can use the models to pull those in.

What are you defining at ‘valid promotions’ in this context?

Note I recommend you do not simply add these queries to the function you mentioned above and run these queries on each and every request but rather run these queries when either an event or promotion is saved and cache the response, then simply call that value in the function mentioned.


mlevison

February 28, 2020 at 9:15 am

Hi Tony,
Thanks for pointing me the right direction. Based on your recommendation to not run the query every time, how do I hook into the event and promotion save events? Also, when you suggest caching the response, should I just save the data in a txt file or is there a better method to store the array data for quick recall?


Tony

  • Support Staff

February 28, 2020 at 2:24 pm

There is an an action hook for hooking into promotions:

AHEE__Promotions_Admin_Page___insert_update_promotion__after

For creating/updating events you can use the default ‘save_post‘ wordpress hook.

Also, when you suggest caching the response, should I just save the data in a txt file or is there a better method to store the array data for quick recall?

Another option is to use a transient, save the results of the query to a transient and then rather than run the queries on every request, pull the transient, check it is still valid and use the values, if not run the queries again and save the transient once again.


mlevison

March 2, 2020 at 4:52 pm

Hi Tony,

I have successfully automated the generation of applicable promotion/ticket combinations and storing the value in a transient for use by the code snippet I linked to in my first post. I did not filter the promotions as there are different reasons for a promotion to be invalid and I will just let the checkout process handle that part.
I am sharing it below in case it will help others.

I use:

function update_promo_code_ticket_array( ) {
    $where = array(
        'TKT_name' => 'Regular',
        'Datetime.DTT_EVT_end' => array( '>=', EEM_Datetime::instance()->current_time_for_query( 'DTT_EVT_end' ) )
    );
    $tickets = EEM_Ticket::instance()->get_all( array( $where ) );

    $promotions = EEM_Promotion::instance()->get_all();

    // create array of all ticket ids for tickets named 'Regular'
    $ticket_array = array();
    foreach ($tickets as $ticket) {
        $ticket_array[] = $ticket->ID();
    }

    // create promotion id indexed array of all active promotions and assign the ticket array to each
    $promotion_tickets = array();
    foreach ($promotions as $promotion) {
        $promotion_tickets[$promotion->ID()] = $ticket_array;
    }

    // store the multi-dimensional array of valid promotion/ticket combinations as a transient
    return $promotion_tickets;
}

if ( false === ( $applicable_promotion_tickets = get_transient( 'ticket-promotion-array' ) ) ) {
    // It wasn't there, so regenerate the data and save the transient
    $applicable_promotion_tickets = update_promo_code_ticket_array();
    set_transient( 'ticket-promotion-array', $applicable_promotion_tickets, 3600 );
}

instead of:

// PLEASE ADD DATA TO THE FOLLOWING ARRAY
$applicable_promotion_tickets = array();

I set the transient variable to expire after an hour and the code will regenerate it at that time. I will add the hooks to regenerate the array when either an event or promotion was added/updated at a later time.


Tony

  • Support Staff

March 3, 2020 at 2:37 am

First, thanks for sharing your code, I’m sure it will be useful for some users.

A couple of improvements for you, nothing major but should reduce the amount of processing needed.

// create array of all ticket ids for tickets named 'Regular'
$ticket_array = array();
foreach ($tickets as $ticket) {
    $ticket_array[] = $ticket->ID();
}

$tickets will either be an array indexed by the ticket IDs, or NULL (if no tickets) mean you could do something like this:

foreach ($tickets as $TKT_ID => $ticket) {
    $ticket_array[] = $TKT_ID;
}

However, rather than looping over each element to pull the ID from the ticket itself (or the above), you could just use $ticket_array = array_keys($tickets);

Or even pull only the ID in the query itself:

$ticket_ids = EEM_Ticket::instance()->get_col( array( $where ), 'TKT_ID' );

Which reduces the query processing and as you are constructing the EE_Ticket objects, the amount of memory used.

(Note the above will return the ID’s as strings so you’ll need to cast them to ints)

With this:

$promotions = EEM_Promotion::instance()->get_all();

$promotions is going to get big, possibly very quickly depending on how many promotions you use, even if your only using the ID here, that query has the potential to pull a LOT of data in, most of which may not even apply (depends on how you use the promotions).

I know you mentioned not filtering the promotions, but I’d recommend you do.

You could go through an filter the promotions manually but the next version of the promotions add-on has a getAllActiveCodePromotions() method on the model which does it for you so you may be better just waiting a little for that and then using:

$active_promotions = EEM_Promotion::instance()->getAllActiveCodePromotions();


mlevison

March 4, 2020 at 11:16 am

I seem to be having an issue when the transient variable expires. When the function runs to update it throws this:
PHP Fatal error: Uncaught Error: Class ‘EEM_Datetime’ not found

Any ideas why and how to prevent it?


mlevison

March 4, 2020 at 11:34 am

The only reference to that class is in this section and the error does point to the line number containing the EEM_Datetime class reference:

 $where = array(
        'TKT_name' => 'Regular',
        'Datetime.DTT_EVT_end' => array( '>=', EEM_Datetime::instance()->current_time_for_query( 'DTT_EVT_end' ) )
    );
    $tickets = EEM_Ticket::instance()->get_all( array( $where ) );


Tony

  • Support Staff

March 5, 2020 at 3:03 am

Hmm, where are you adding that code on the site?

You could try:

$datetime_model = EE_Registry::instance()->load_model('Datetime');
$where = array(
    'TKT_name' => 'Regular',
    'Datetime.DTT_EVT_end' => array( '>=', $datetime_model->current_time_for_query( 'DTT_EVT_end' ) )
);
$tickets = EEM_Ticket::instance()->get_all( array( $where ) );

Although if you are getting the above error you may now simply get say the ‘EE_Registry’ class can not be found.


mlevison

March 5, 2020 at 9:41 am

I am using the code in a plugin as we want to keep all of our custom code easily portable to other themes.

Here is the code in its entirety:

<?php
/*
Plugin Name: Agile EE Promo Code Limiter
Description: Ensures that promo codes can only be used on 'Regular' tickets
Version: 1.0
*/

function update_promo_code_ticket_array( ) {
    $where = array(
        'TKT_name' => 'Regular',
        'Datetime.DTT_EVT_end' => array( '>=', EEM_Datetime::instance()->current_time_for_query( 'DTT_EVT_end' ) )
    );
    $tickets = EEM_Ticket::instance()->get_all( array( $where ) );

    $promotions = EEM_Promotion::instance()->get_all();

    // create array of all ticket ids for tickets named 'Regular'
    $ticket_array = array();
    foreach ($tickets as $ticket) {
        $ticket_array[] = $ticket->ID();
    }

    // create promotion id indexed array of all active promotions and assign the ticket array to each
    $promotion_tickets = array();
    foreach ($promotions as $promotion) {
        $promotion_tickets[$promotion->ID()] = $ticket_array;
    }

    // store the multi-dimensional array of valid promotion/ticket combinations as a transient
    return $promotion_tickets;
}

if ( false === ( $applicable_promotion_tickets = get_transient( 'ticket-promotion-array' ) ) ) {
    // It wasn't there, so regenerate the data and save the transient
    $applicable_promotion_tickets = update_promo_code_ticket_array();
    set_transient( 'ticket-promotion-array', $applicable_promotion_tickets, 3600 );
}

// this is an array where keys are promotion IDs
// and values are arrays of ticket IDs that are applicable to that promotion
// in the following format: array( promo_ID => array( ticket_ID, ticket_ID ) );
// ex: array( 2 => array( 30, 35 ) );
// STOP!!! do not edit anything else beyond this
add_filter('FHEE__EED_Promotions__get_applicable_items__applicable_items',
    function ($applicable_items, $promotion) use ($applicable_promotion_tickets) {
        $promotion_IDs = array_keys($applicable_promotion_tickets);
        if (! $promotion instanceof EE_Promotion || ! in_array($promotion->ID(), $promotion_IDs, true) ) {
            return $applicable_items;
        }

        foreach ($applicable_items as $key => $applicable_item) {
            if ($applicable_item instanceof EE_Line_Item && $applicable_item->OBJ_type() === 'Event') {
                $ticket_line_items = EEH_Line_Item::get_ticket_line_items($applicable_item);
                $valid_items = [];
                $invalid_items = [];
                if (is_array($ticket_line_items)) {
                    foreach ($ticket_line_items as $ticket_line_item) {
                        if (! $ticket_line_item instanceof EE_Line_Item) {
                            continue;
                        }
                        foreach ($applicable_promotion_tickets as $promotion_ID => $promotion_tickets) {
                            if ($promotion_ID !== $promotion->ID()) {
                                continue;
                            }
                            if (in_array($ticket_line_item->OBJ_ID(), $promotion_tickets, true)) {
                                // overwrite applicable event line item with ticket line item
                                $applicable_items[ $key ] = $ticket_line_item;
                                // and track the key so it's not removed on subsequent iterations
                                $valid_items[] = $key;
                                add_filter('FHEE__EED_Promotions__add_promotion_line_item__bypass_increment_promotion_scope_uses',
                                    function ($bypass_increment_promotion_scope_uses, $parent_line_item, $bypass_promotion) use ($ticket_line_item, $promotion) {
                                        if ($parent_line_item === $ticket_line_item && $bypass_promotion === $promotion) { $bypass_increment_promotion_scope_uses = true; }
                                        return $bypass_increment_promotion_scope_uses;
                                    }, 10, 4 );
                                add_filter('FHEE__EE_Promotion_Scope__generate_promotion_line_item',
                                    function ($new_line_item_props) use ($promotion_IDs) {
                                        if ($new_line_item_props['OBJ_type'] === 'Promotion' && in_array($new_line_item_props['OBJ_ID'], $promotion_IDs, true)) {
                                            $new_line_item_props['LIN_type'] = EEM_Line_Item::type_sub_line_item;
                                        }
                                        return $new_line_item_props;
                                    });
                            } else {
                                // this ticket is not valid, but don't remove the applicable item just yet
                                $invalid_items[] = $key;
                            }
                        }
                    }
                }
                // remove valid items from list of invalid ones
                $invalid_items = array_diff($invalid_items, $valid_items);
                // then remove invalid items from list of applicable items
                foreach ($invalid_items as $invalid_item) {
                    unset($applicable_items[ $invalid_item ]);
                }
            }
        }
        return $applicable_items;
    }, 10, 3
);


Tony

  • Support Staff

March 5, 2020 at 12:00 pm

Ok, so it could be load order.

Did you try the code I posted? It just forces manually loads the class first.


mlevison

March 5, 2020 at 2:45 pm

Hi Tony,

I did try the code you posted and got the error you predicted after the first time the transient value expired. I too suspected it was load order so I moved the code that checks if the transient has expired and triggers the reload to inside add_filter section. This seems to have fixed the problem as the error didn’t return after the transient would have expired the first time.

I am sharing the entire working code below again in case it might help somebody else:

<?php
/*
Plugin Name: Event Espresso Promo Code Limiter
Description: Ensures that promo codes can only be used on 'Regular' tickets
Version: 1.0
*/

function update_promo_code_ticket_array( ) {
    $datetime_model = EE_Registry::instance()->load_model('Datetime');
    $where = array(
        'TKT_name' => 'Regular',
        'Datetime.DTT_EVT_end' => array( '>=', $datetime_model->current_time_for_query( 'DTT_EVT_end' ) )
    );
    $tickets = EEM_Ticket::instance()->get_all( array( $where ) );

    $promotions = EEM_Promotion::instance()->get_all();

    // create array of all ticket ids for tickets named 'Regular'
    $ticket_array = array();
    foreach ($tickets as $ticket) {
        $ticket_array[] = $ticket->ID();
    }

    // create promotion id indexed array of all active promotions and assign the ticket array to each
    $promotion_tickets = array();
    foreach ($promotions as $promotion) {
        $promotion_tickets[$promotion->ID()] = $ticket_array;
    }

    // store the multi-dimensional array of valid promotion/ticket combinations as a transient
    return $promotion_tickets;
}

// this is an array where keys are promotion IDs
// and values are arrays of ticket IDs that are applicable to that promotion
// in the following format: array( promo_ID => array( ticket_ID, ticket_ID ) );
// ex: array( 2 => array( 30, 35 ) );
// STOP!!! do not edit anything else beyond this
add_filter('FHEE__EED_Promotions__get_applicable_items__applicable_items',
    function ($applicable_items, $promotion) {
        if ( false === ( $applicable_promotion_tickets = get_transient( 'ticket-promotion-array' ) ) ) {
            // It wasn't there, so regenerate the data and save the transient
            $applicable_promotion_tickets = update_promo_code_ticket_array();
            set_transient( 'ticket-promotion-array', $applicable_promotion_tickets, 3600 );
        }
        $promotion_IDs = array_keys($applicable_promotion_tickets);
        if (! $promotion instanceof EE_Promotion || ! in_array($promotion->ID(), $promotion_IDs, true) ) {
            return $applicable_items;
        }

        foreach ($applicable_items as $key => $applicable_item) {
            if ($applicable_item instanceof EE_Line_Item && $applicable_item->OBJ_type() === 'Event') {
                $ticket_line_items = EEH_Line_Item::get_ticket_line_items($applicable_item);
                $valid_items = [];
                $invalid_items = [];
                if (is_array($ticket_line_items)) {
                    foreach ($ticket_line_items as $ticket_line_item) {
                        if (! $ticket_line_item instanceof EE_Line_Item) {
                            continue;
                        }
                        foreach ($applicable_promotion_tickets as $promotion_ID => $promotion_tickets) {
                            if ($promotion_ID !== $promotion->ID()) {
                                continue;
                            }
                            if (in_array($ticket_line_item->OBJ_ID(), $promotion_tickets, true)) {
                                // overwrite applicable event line item with ticket line item
                                $applicable_items[ $key ] = $ticket_line_item;
                                // and track the key so it's not removed on subsequent iterations
                                $valid_items[] = $key;
                                add_filter('FHEE__EED_Promotions__add_promotion_line_item__bypass_increment_promotion_scope_uses',
                                    function ($bypass_increment_promotion_scope_uses, $parent_line_item, $bypass_promotion) use ($ticket_line_item, $promotion) {
                                        if ($parent_line_item === $ticket_line_item && $bypass_promotion === $promotion) { $bypass_increment_promotion_scope_uses = true; }
                                        return $bypass_increment_promotion_scope_uses;
                                    }, 10, 4 );
                                add_filter('FHEE__EE_Promotion_Scope__generate_promotion_line_item',
                                    function ($new_line_item_props) use ($promotion_IDs) {
                                        if ($new_line_item_props['OBJ_type'] === 'Promotion' && in_array($new_line_item_props['OBJ_ID'], $promotion_IDs, true)) {
                                            $new_line_item_props['LIN_type'] = EEM_Line_Item::type_sub_line_item;
                                        }
                                        return $new_line_item_props;
                                    });
                            } else {
                                // this ticket is not valid, but don't remove the applicable item just yet
                                $invalid_items[] = $key;
                            }
                        }
                    }
                }
                // remove valid items from list of invalid ones
                $invalid_items = array_diff($invalid_items, $valid_items);
                // then remove invalid items from list of applicable items
                foreach ($invalid_items as $invalid_item) {
                    unset($applicable_items[ $invalid_item ]);
                }
            }
        }
        return $applicable_items;
    }, 10, 3
);


mlevison

March 5, 2020 at 2:53 pm

This reply has been marked as private.


Tony

  • Support Staff

March 6, 2020 at 3:38 pm

Is there somewhere that I could look to see what other features are being added in the next version?

You can view the repo HERE.

I’m really hoping that promo code discounts applicable per ticket rather than per event is coming soon as this is a feature that we really need.

Whilst I’d love to say yes, this isn’t a feature we’ve start working on yet, so its unliekyl to be released ‘soon’.

The support post ‘Promo code for specific ticket types’ is closed to new replies.

Have a question about this support post? Create a new support post in our support forums and include a link to this existing support post so we can help you.

Event Espresso