function test_increase_and_decrease_sold()
 {
     $t = EE_Ticket::new_instance(array('TKT_start_date' => current_time('timestamp') - 100, 'TKT_end_date' => current_time('timestamp') + 100, 'TKT_qty' => 10, 'TKT_sold' => 0));
     $this->assertEquals(0, $t->sold());
     $t->increase_sold();
     $this->assertEquals(1, $t->sold());
     $t->increase_sold(2);
     $this->assertEquals(3, $t->sold());
     //now try decreasing
     $t->decrease_sold();
     $this->assertEquals(2, $t->sold());
     $t->decrease_sold(2);
     $this->assertEquals(0, $t->sold());
 }
 function test_finalize()
 {
     $t = EE_Transaction::new_instance(array('STS_ID' => EEM_Transaction::complete_status_code));
     $t->save();
     $e = EE_Event::new_instance();
     $e->save();
     $tkt = EE_Ticket::new_instance();
     $tkt->save();
     $d = EE_Datetime::new_instance(array('EVT_ID' => $e->ID()));
     $d->save();
     $tkt->_add_relation_to($d, 'Datetime');
     /** @type EE_Registration_Processor $registration_processor */
     $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
     $reg_url = $registration_processor->generate_reg_url_link(1, EE_Line_Item::new_instance(array('LIN_name' => $tkt->name(), 'LIN_desc' => $tkt->description(), 'LIN_unit_price' => $tkt->price(), 'LIN_quantity' => 1, 'LIN_is_taxable' => $tkt->taxable(), 'LIN_order' => 0, 'LIN_total' => $tkt->price(), 'LIN_type' => EEM_Line_Item::type_line_item, 'OBJ_ID' => $tkt->ID(), 'OBJ_type' => 'Ticket')));
     $r = EE_REgistration::new_instance(array('EVT_ID' => $e->ID(), 'TXN_ID' => $t->ID(), 'TKT_ID' => $tkt->ID(), 'STS_ID' => EEM_Registration::status_id_pending_payment, 'REG_url_link' => $reg_url));
     $r->set_reg_code($registration_processor->generate_reg_code($r));
     $registration_processor->update_registration_after_checkout_or_payment($r);
     $this->assertNotNull($r->reg_code());
     $this->assertEquals(EEM_Registration::status_id_approved, $r->status_ID());
 }
 function test_active_status()
 {
     /** @type EE_Event $e */
     $e = EE_Event::new_instance(array('status' => 'publish'));
     $e->save();
     //echo "\n\n create Ticket";
     $t = EE_Ticket::new_instance(array('TKT_start_date' => time() - 100, 'TKT_end_date' => time() + 50, 'TKT_qty' => 100, 'TKT_sold' => 0));
     $t->save();
     $d_now = EE_Datetime::new_instance(array('EVT_ID' => $e->ID(), 'DTT_EVT_start' => time() - 100, 'DTT_EVT_end' => time() + 50));
     $d_now->_add_relation_to($t, 'Ticket');
     $d_now->save();
     $d_exp = EE_Datetime::new_instance(array('EVT_ID' => $e->ID(), 'DTT_EVT_start' => time() - 10, 'DTT_EVT_end' => time() - 5));
     $d_exp->_add_relation_to($t, 'Ticket');
     $d_exp->save();
     $d_upcoming = EE_Datetime::new_instance(array('EVT_ID' => $e->ID(), 'DTT_EVT_start' => time() + 10, 'DTT_EVT_end' => time() + 15));
     $d_upcoming->_add_relation_to($t, 'Ticket');
     $d_upcoming->save();
     $this->assertEquals(EE_Datetime::active, $e->get_active_status(TRUE));
     $e->_remove_relation_to($d_now, 'Datetime');
     $this->assertEquals(EE_Datetime::upcoming, $e->get_active_status(TRUE));
     $e->_remove_relation_to($d_upcoming, 'Datetime');
     $this->assertEquals(EE_Datetime::expired, $e->get_active_status(TRUE));
 }
 /**
  * update tickets
  * @param  EE_Event         $evtobj     Event object being updated
  * @param  EE_Datetime[]    $saved_dtts an array of datetime ids being updated
  * @param  array            $data       incoming request data
  * @return EE_Ticket[]
  */
 protected function _update_tkts($evtobj, $saved_dtts, $data)
 {
     $new_tkt = null;
     $new_default = null;
     //stripslashes because WP filtered the $_POST ($data) array to add slashes
     $data = stripslashes_deep($data);
     $timezone = isset($data['timezone_string']) ? $data['timezone_string'] : NULL;
     $saved_tickets = $dtts_on_existing = array();
     $old_tickets = isset($data['ticket_IDs']) ? explode(',', $data['ticket_IDs']) : array();
     //load money helper
     EE_Registry::instance()->load_helper('Money');
     foreach ($data['edit_tickets'] as $row => $tkt) {
         $update_prices = $create_new_TKT = FALSE;
         //figure out what dtts were added to the ticket and what dtts were removed from the ticket in the session.
         $starting_tkt_dtt_rows = explode(',', $data['starting_ticket_datetime_rows'][$row]);
         $tkt_dtt_rows = explode(',', $data['ticket_datetime_rows'][$row]);
         $dtts_added = array_diff($tkt_dtt_rows, $starting_tkt_dtt_rows);
         $dtts_removed = array_diff($starting_tkt_dtt_rows, $tkt_dtt_rows);
         // trim inputs to ensure any excess whitespace is removed.
         $tkt = array_map(function ($ticket_data) {
             return is_array($ticket_data) ? $ticket_data : trim($ticket_data);
         }, $tkt);
         //note we are doing conversions to floats here instead of allowing EE_Money_Field to handle because we're doing calcs prior to using the models.
         //note incoming ['TKT_price'] value is already in standard notation (via js).
         $ticket_price = isset($tkt['TKT_price']) ? round((double) $tkt['TKT_price'], 3) : 0;
         //note incoming base price needs converted from localized value.
         $base_price = isset($tkt['TKT_base_price']) ? EEH_Money::convert_to_float_from_localized_money($tkt['TKT_base_price']) : 0;
         //if ticket price == 0 and $base_price != 0 then ticket price == base_price
         $ticket_price = $ticket_price === 0 && $base_price !== 0 ? $base_price : $ticket_price;
         $base_price_id = isset($tkt['TKT_base_price_ID']) ? $tkt['TKT_base_price_ID'] : 0;
         $price_rows = is_array($data['edit_prices']) && isset($data['edit_prices'][$row]) ? $data['edit_prices'][$row] : array();
         $now = null;
         if (empty($tkt['TKT_start_date'])) {
             //lets' use now in the set timezone.
             $now = new DateTime('now', new DateTimeZone($evtobj->get_timezone()));
             $tkt['TKT_start_date'] = $now->format($this->_date_format_strings['date'] . ' ' . $this->_date_format_strings['time']);
         }
         if (empty($tkt['TKT_end_date'])) {
             /**
              * set the TKT_end_date to the first datetime attached to the ticket.
              */
             $first_dtt = $saved_dtts[reset($tkt_dtt_rows)];
             $tkt['TKT_end_date'] = $first_dtt->start_date_and_time($this->_date_format_strings['date'] . ' ' . $this->_date_format_string['time']);
         }
         $TKT_values = array('TKT_ID' => !empty($tkt['TKT_ID']) ? $tkt['TKT_ID'] : NULL, 'TTM_ID' => !empty($tkt['TTM_ID']) ? $tkt['TTM_ID'] : 0, 'TKT_name' => !empty($tkt['TKT_name']) ? $tkt['TKT_name'] : '', 'TKT_description' => !empty($tkt['TKT_description']) && $tkt['TKT_description'] != __('You can modify this description', 'event_espresso') ? $tkt['TKT_description'] : '', 'TKT_start_date' => $tkt['TKT_start_date'], 'TKT_end_date' => $tkt['TKT_end_date'], 'TKT_qty' => !isset($tkt['TKT_qty']) || $tkt['TKT_qty'] === '' ? EE_INF : $tkt['TKT_qty'], 'TKT_uses' => !isset($tkt['TKT_uses']) || $tkt['TKT_uses'] === '' ? EE_INF : $tkt['TKT_uses'], 'TKT_min' => empty($tkt['TKT_min']) ? 0 : $tkt['TKT_min'], 'TKT_max' => empty($tkt['TKT_max']) ? EE_INF : $tkt['TKT_max'], 'TKT_row' => $row, 'TKT_order' => isset($tkt['TKT_order']) ? $tkt['TKT_order'] : 0, 'TKT_taxable' => !empty($tkt['TKT_taxable']) ? 1 : 0, 'TKT_required' => !empty($tkt['TKT_required']) ? 1 : 0, 'TKT_price' => $ticket_price);
         //if this is a default TKT, then we need to set the TKT_ID to 0 and update accordingly, which means in turn that the prices will become new prices as well.
         if (isset($tkt['TKT_is_default']) && $tkt['TKT_is_default']) {
             $TKT_values['TKT_ID'] = 0;
             $TKT_values['TKT_is_default'] = 0;
             $update_prices = TRUE;
         }
         // if we have a TKT_ID then we need to get that existing TKT_obj and update it
         // we actually do our saves ahead of doing any add_relations to
         // because its entirely possible that this ticket wasn't removed or added to any datetime in the session
         // but DID have it's items modified.
         // keep in mind that if the TKT has been sold (and we have changed pricing information),
         // then we won't be updating the tkt but instead a new tkt will be created and the old one archived.
         if (absint($TKT_values['TKT_ID'])) {
             $TKT = EE_Registry::instance()->load_model('Ticket', array($timezone))->get_one_by_ID($tkt['TKT_ID']);
             if ($TKT instanceof EE_Ticket) {
                 $TKT = $this->_update_ticket_datetimes($TKT, $saved_dtts, $dtts_added, $dtts_removed);
                 // are there any registrations using this ticket ?
                 $tickets_sold = $TKT->count_related('Registration', array(array('STS_ID' => array('NOT IN', array(EEM_Registration::status_id_incomplete)))));
                 //set ticket formats
                 $TKT->set_date_format($this->_date_format_strings['date']);
                 $TKT->set_time_format($this->_date_format_strings['time']);
                 // let's just check the total price for the existing ticket
                 // and determine if it matches the new total price.
                 // if they are different then we create a new ticket (if tkts sold)
                 // if they aren't different then we go ahead and modify existing ticket.
                 $create_new_TKT = $tickets_sold > 0 && $ticket_price != $TKT->price() && !$TKT->deleted() ? TRUE : FALSE;
                 //set new values
                 foreach ($TKT_values as $field => $value) {
                     if ($field === 'TKT_qty') {
                         $TKT->set_qty($value);
                     } else {
                         $TKT->set($field, $value);
                     }
                 }
                 //if $create_new_TKT is false then we can safely update the existing ticket.  Otherwise we have to create a new ticket.
                 if ($create_new_TKT) {
                     $new_tkt = $this->_duplicate_ticket($TKT, $price_rows, $ticket_price, $base_price, $base_price_id);
                 }
             }
         } else {
             // no TKT_id so a new TKT
             $TKT = EE_Ticket::new_instance($TKT_values, $timezone, array($this->_date_format_strings['date'], $this->_date_format_strings['time']));
             if ($TKT instanceof EE_Ticket) {
                 // make sure ticket has an ID of setting relations won't work
                 $TKT->save();
                 $TKT = $this->_update_ticket_datetimes($TKT, $saved_dtts, $dtts_added, $dtts_removed);
                 $update_prices = TRUE;
             }
         }
         //make sure any current values have been saved.
         //$TKT->save();
         //before going any further make sure our dates are setup correctly so that the end date is always equal or greater than the start date.
         if ($TKT->get_raw('TKT_start_date') > $TKT->get_raw('TKT_end_date')) {
             $TKT->set('TKT_end_date', $TKT->get('TKT_start_date'));
             EE_Registry::instance()->load_helper('DTT_Helper');
             $TKT = EEH_DTT_Helper::date_time_add($TKT, 'TKT_end_date', 'days');
         }
         //let's make sure the base price is handled
         $TKT = !$create_new_TKT ? $this->_add_prices_to_ticket(array(), $TKT, $update_prices, $base_price, $base_price_id) : $TKT;
         //add/update price_modifiers
         $TKT = !$create_new_TKT ? $this->_add_prices_to_ticket($price_rows, $TKT, $update_prices) : $TKT;
         //need to make sue that the TKT_price is accurate after saving the prices.
         $TKT->ensure_TKT_Price_correct();
         //handle CREATING a default tkt from the incoming tkt but ONLY if this isn't an autosave.
         if (!defined('DOING_AUTOSAVE')) {
             if (!empty($tkt['TKT_is_default_selector'])) {
                 $update_prices = TRUE;
                 $new_default = clone $TKT;
                 $new_default->set('TKT_ID', 0);
                 $new_default->set('TKT_is_default', 1);
                 $new_default->set('TKT_row', 1);
                 $new_default->set('TKT_price', $ticket_price);
                 //remove any dtt relations cause we DON'T want dtt relations attached (note this is just removing the cached relations in the object)
                 $new_default->_remove_relations('Datetime');
                 //todo we need to add the current attached prices as new prices to the new default ticket.
                 $new_default = $this->_add_prices_to_ticket($price_rows, $new_default, $update_prices);
                 //don't forget the base price!
                 $new_default = $this->_add_prices_to_ticket(array(), $new_default, $update_prices, $base_price, $base_price_id);
                 $new_default->save();
                 do_action('AHEE__espresso_events_Pricing_Hooks___update_tkts_new_default_ticket', $new_default, $row, $TKT, $data);
             }
         }
         //DO ALL dtt relationships for both current tickets and any archived tickets for the given dtt that are related to the current ticket. TODO... not sure exactly how we're going to do this considering we don't know what current ticket the archived tickets are related to (and TKT_parent is used for autosaves so that's not a field we can reliably use).
         //let's assign any tickets that have been setup to the saved_tickets tracker
         //save existing TKT
         $TKT->save();
         if ($create_new_TKT && $new_tkt instanceof EE_Ticket) {
             //save new TKT
             $new_tkt->save();
             //add new ticket to array
             $saved_tickets[$new_tkt->ID()] = $new_tkt;
             do_action('AHEE__espresso_events_Pricing_Hooks___update_tkts_new_ticket', $new_tkt, $row, $tkt, $data);
         } else {
             //add tkt to saved tkts
             $saved_tickets[$TKT->ID()] = $TKT;
             do_action('AHEE__espresso_events_Pricing_Hooks___update_tkts_update_ticket', $TKT, $row, $tkt, $data);
         }
     }
     // now we need to handle tickets actually "deleted permanently".
     // There are cases where we'd want this to happen
     // (i.e. autosaves are happening and then in between autosaves the user trashes a ticket).
     // Or a draft event was saved and in the process of editing a ticket is trashed.
     // No sense in keeping all the related data in the db!
     $old_tickets = isset($old_tickets[0]) && $old_tickets[0] == '' ? array() : $old_tickets;
     $tickets_removed = array_diff($old_tickets, array_keys($saved_tickets));
     foreach ($tickets_removed as $id) {
         $id = absint($id);
         //get the ticket for this id
         $tkt_to_remove = EE_Registry::instance()->load_model('Ticket')->get_one_by_ID($id);
         //if this tkt is a default tkt we leave it alone cause it won't be attached to the datetime
         if ($tkt_to_remove->get('TKT_is_default')) {
             continue;
         }
         // if this tkt has any registrations attached so then we just ARCHIVE
         // because we don't actually permanently delete these tickets.
         if ($tkt_to_remove->count_related('Registration') > 0) {
             $tkt_to_remove->delete();
             continue;
         }
         // need to get all the related datetimes on this ticket and remove from every single one of them
         // (remember this process can ONLY kick off if there are NO tkts_sold)
         $dtts = $tkt_to_remove->get_many_related('Datetime');
         foreach ($dtts as $dtt) {
             $tkt_to_remove->_remove_relation_to($dtt, 'Datetime');
         }
         // need to do the same for prices (except these prices can also be deleted because again,
         // tickets can only be trashed if they don't have any TKTs sold (otherwise they are just archived))
         $tkt_to_remove->delete_related_permanently('Price');
         do_action('AHEE__espresso_events_Pricing_Hooks___update_tkts_delete_ticket', $tkt_to_remove);
         // finally let's delete this ticket
         // (which should not be blocked at this point b/c we've removed all our relationships)
         $tkt_to_remove->delete_permanently();
     }
     return $saved_tickets;
 }
 /**
  * used by factory to create ticket object
  *
  * @since 4.3.0
  *
  * @param array $args Incoming field values to set on the new object
  *
  * @return EE_Ticket|false
  */
 public function create_object($args)
 {
     $this->_special_args['PRC_ID'] = isset($args['PRC_ID']) ? $args['PRC_ID'] : 0;
     $this->_special_args['DTT_ID'] = isset($args['DTT_ID']) ? $args['DTT_ID'] : 0;
     //maybe unset PRC_ID
     if (isset($args['PRC_ID'])) {
         unset($args['PRC_ID']);
     }
     //maybe unset DTT_ID
     if (isset($args['DTT_ID'])) {
         unset($args['DTT_ID']);
     }
     //timezone?
     if (isset($args['timezone'])) {
         $timezone = $args['timezone'];
         unset($args['timezone']);
     } else {
         $timezone = null;
     }
     //date_formats?
     if (isset($args['formats']) && is_array($args['formats'])) {
         $formats = $args['formats'];
         unset($args['formats']);
     } else {
         $formats = array();
     }
     $tkt = EE_Ticket::new_instance($args, $timezone, $formats);
     $tktID = $tkt->save();
     $tkt = $this->_maybe_chained($tkt, $args);
     return $tktID ? $tkt : false;
 }