/**
  * 	load_scripts_styles_reports
  *
  * @access public
  * @return void
  */
 public function load_scripts_styles_reports()
 {
     wp_register_script('ee-txn-reports-js', TXN_CAF_ASSETS_URL . 'ee-transaction-admin-reports.js', array('google-charts'), EVENT_ESPRESSO_VERSION, true);
     wp_enqueue_script('ee-txn-reports-js');
     $this->_transaction_reports_js_setup();
     EE_Registry::$i18n_js_strings['currency_format'] = EEH_Money::get_format_for_google_charts();
 }
 /**
  * 	load_scripts_styles_reports
  *
  * @access public
  * @return void
  */
 public function load_scripts_styles_reports()
 {
     //styles
     wp_enqueue_style('jquery-jqplot-css');
     //scripts
     global $is_IE;
     if ($is_IE) {
         wp_enqueue_script('excanvas');
     }
     EE_Registry::instance()->load_helper('Money');
     EE_Registry::$i18n_js_strings['currency_format'] = EEH_Money::get_format_for_jqplot();
     wp_enqueue_script('jqplot-all');
 }
 /**
  * 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;
 }
 /**
  * possibly toggles TXN status
  *
  * @param EE_Transaction $transaction
  * @param 	boolean $update_txn  	whether to save the TXN
  * @return 	boolean 	 	whether the TXN was saved
  * @throws \EE_Error
  */
 public function update_transaction_status_based_on_total_paid(EE_Transaction $transaction, $update_txn = TRUE)
 {
     // verify transaction
     if (!$transaction instanceof EE_Transaction) {
         EE_Error::add_error(__('Please provide a valid EE_Transaction object.', 'event_espresso'), __FILE__, __FUNCTION__, __LINE__);
         return FALSE;
     }
     // set incoming TXN_Status
     $this->set_old_txn_status($transaction->status_ID());
     // set transaction status based on comparison of TXN_paid vs TXN_total
     if (EEH_Money::compare_floats($transaction->paid(), $transaction->total(), '>')) {
         $new_txn_status = EEM_Transaction::overpaid_status_code;
     } else {
         if (EEH_Money::compare_floats($transaction->paid(), $transaction->total())) {
             $new_txn_status = EEM_Transaction::complete_status_code;
         } else {
             if (EEH_Money::compare_floats($transaction->paid(), $transaction->total(), '<')) {
                 $new_txn_status = EEM_Transaction::incomplete_status_code;
             } else {
                 EE_Error::add_error(__('The total paid calculation for this transaction is inaccurate.', 'event_espresso'), __FILE__, __FUNCTION__, __LINE__);
                 return FALSE;
             }
         }
     }
     if ($new_txn_status !== $transaction->status_ID()) {
         // set incoming TXN_Status
         $this->set_new_txn_status($new_txn_status);
         $transaction->set_status($new_txn_status);
         if ($update_txn) {
             return $transaction->save() ? TRUE : FALSE;
         }
     }
     return FALSE;
 }
 /**
  * @param float $payment_amount
  */
 public function set_payment_amount($payment_amount)
 {
     EE_Registry::instance()->load_helper('Money');
     $this->_payment_amount = EEH_Money::convert_to_float_from_localized_money($payment_amount);
 }
 /**
  * @param float $payment_amount
  */
 public function set_payment_amount($payment_amount)
 {
     $this->_payment_amount = EEH_Money::convert_to_float_from_localized_money($payment_amount);
 }
 /**
  * Returns TRUE or FALSE for whether or not this transaction cost any money
  *
  * @return boolean
  * @throws \EE_Error
  */
 public function is_free()
 {
     return EEH_Money::compare_floats($this->get('TXN_total'), 0, '==');
 }
 /**
  * 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 bool                 		success or fail
  */
 private function _update_tkts($evtobj, $saved_dtts, $data)
 {
     //stripslashes because WP filtered the $_POST ($data) array to add slashes
     $data = stripslashes_deep($data);
     $timezone = isset($data['timezone_string']) ? $data['timezone_string'] : NULL;
     $success = TRUE;
     $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 = $ticket_sold = FALSE;
         $new_default = $new_tkt = NULL;
         //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);
         //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();
         $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' => isset($tkt['TKT_start_date']) ? $tkt['TKT_start_date'] : current_time('mysql'), 'TKT_end_date' => isset($tkt['TKT_end_date']) ? $tkt['TKT_end_date'] : current_time('mysql'), 'TKT_qty' => empty($tkt['TKT_qty']) ? INF : $tkt['TKT_qty'], 'TKT_uses' => empty($tkt['TKT_uses']) ? INF : $tkt['TKT_uses'], 'TKT_min' => empty($tkt['TKT_min']) ? 0 : $tkt['TKT_min'], 'TKT_max' => empty($tkt['TKT_max']) ? 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 didn'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 (!empty($TKT_values['TKT_ID'])) {
             $TKT = EE_Registry::instance()->load_model('Ticket', array($timezone))->get_one_by_ID($tkt['TKT_ID']);
             $ticket_sold = $TKT->count_related('Registration', array(array('STS_ID' => array('NOT IN', array(EEM_Registration::status_id_incomplete))))) > 0 ? true : false;
             //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.
             $orig_price = $TKT->price();
             $create_new_TKT = $ticket_sold && $ticket_price != $orig_price && !$TKT->get('TKT_deleted') ? TRUE : FALSE;
             //set new values
             foreach ($TKT_values as $field => $value) {
                 $TKT->set($field, $value);
             }
             //make sure price is set if it hasn't been already
             $TKT->set('TKT_price', $ticket_price);
             //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) {
                 //@TODO need to move this logic into its own method that just receives the ticket object (and other necessary info)
                 $new_tkt = clone $TKT;
                 //we also need to make sure this new ticket gets the same datetime attachments as the archived ticket
                 $dtts_on_existing = $TKT->get_many_related('Datetime');
                 //TKT will get archived later b/c we are NOT adding it to the saved_tickets array.
                 //if existing $TKT has sold amount, then we need to adjust the qty for the new TKT to = the remaining available.
                 if ($TKT->get('TKT_sold') > 0) {
                     $new_qty = $TKT->get('TKT_qty') - $TKT->get('TKT_sold');
                     $new_tkt->set_qty($new_qty);
                 }
                 //create new ticket that's a copy of the existing except a new id of course (and not archived) AND has the new TKT_price associated with it.
                 $new_tkt->set('TKT_ID', 0);
                 $new_tkt->set('TKT_deleted', 0);
                 $new_tkt->set('TKT_price', $ticket_price);
                 $new_tkt->set('TKT_sold', 0);
                 //now we update the prices just for this ticket
                 $new_tkt = $this->_add_prices_to_ticket($price_rows, $new_tkt, TRUE);
                 //and we update the base price
                 $new_tkt = $this->_add_prices_to_ticket(array(), $new_tkt, TRUE, $base_price, $base_price_id);
                 foreach ($dtts_on_existing as $adddtt) {
                     $new_tkt->_add_relation_to($adddtt, 'Datetime');
                 }
             }
         } else {
             //no TKT_id so a new TKT
             $TKT_values['TKT_price'] = $ticket_price;
             $TKT = EE_Registry::instance()->load_class('Ticket', array($TKT_values, $timezone), FALSE, FALSE);
             $update_prices = TRUE;
         }
         $TKT->save();
         //make sure any current values have been saved.
         //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');
                 $new_default->save();
                 //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);
             }
         }
         //now we just have to add the ticket to all the datetimes its supposed to be with and removing the ticket from datetimes it got removed from.
         //first let's do the add_relation_to()
         $dtts_added = empty($dtts_added) || is_array($dtts_added) && (isset($dtts_added[0]) && $dtts_added[0] == '') ? array() : $dtts_added;
         foreach ($dtts_added as $dttrow) {
             $dttrow = (int) $dttrow;
             $TKT->_add_relation_to($saved_dtts[$dttrow], 'Datetime');
             //now wait a minute.  Does this tkt have any sold?  Cause if it does then we need to add that to the DTT sold because this DTT is getting added.
             if ($TKT->get('TKT_sold') > 0) {
                 $saved_dtts[$dttrow]->increase_sold($TKT->get('TKT_sold'));
                 $saved_dtts[$dttrow]->save();
             }
             //if we have a new_tkt... let's add to it as well
             if (!empty($new_tkt)) {
                 $new_tkt->_add_relation_to($saved_dtts[$dttrow], 'Datetime');
             }
         }
         $dtts_removed = empty($dtts_removed) || is_array($dtts_removed) && isset($dtts_removed[0]) && $dtts_removed[0] == '' ? array() : $dtts_removed;
         //now let's do the remove_relation_to()
         foreach ($dtts_removed as $dttrow) {
             $dttrow = (int) $dttrow;
             //its entirely possible that a datetime got deleted (instead of just removed from relationship.  So make sure we skip over this if the dtt isn't in the saved_dtts array)
             if (empty($saved_dtts[$dttrow]) || !$saved_dtts[$dttrow] instanceof EE_Datetime) {
                 continue;
             }
             $TKT->_remove_relation_to($saved_dtts[$dttrow], 'Datetime');
             //now wait a minute.  Does this tkt have any sold? Cause if it does then we need to remove it's sold from the DTT_sold.
             if ($TKT->get('TKT_sold') > 0) {
                 $saved_dtts[$dttrow]->decrease_sold($TKT->get('TKT_sold'));
                 $saved_dtts[$dttrow]->save();
             }
             if (!empty($new_tkt)) {
                 $new_tkt->_remove_relation_to($saved_dtts[$dttrow], 'Datetime');
             }
         }
         //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) {
             //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
             //save existing TKT
             $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));
     /*var_dump($old_tickets);
     		var_dump($saved_tickets);
     		var_dump($tickets_removed);*/
     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();
     }
 }
 /**
  * @param EEI_Payment $payment      the payment to process
  * @param array       $billing_info but should be empty for this gateway
  * @param string      $return_url   URL to send the user to after payment on the payment provider's website
  * @param string      $notify_url   URL to send the instant payment notification
  * @param string      $cancel_url   URL to send the user to after a cancelled payment attempt
  *                                  on the payment provider's website
  * @return EEI_Payment
  * @throws \EE_Error
  */
 public function set_redirection_info($payment, $billing_info = array(), $return_url = null, $notify_url = null, $cancel_url = null)
 {
     $redirect_args = array();
     $transaction = $payment->transaction();
     $primary_registrant = $transaction->primary_registration();
     $item_num = 1;
     /** @type EE_Line_Item $total_line_item */
     $total_line_item = $transaction->total_line_item();
     $total_discounts_to_cart_total = $transaction->paid();
     //only itemize the order if we're paying for the rest of the order's amount
     if (EEH_Money::compare_floats($payment->amount(), $transaction->total(), '==')) {
         $payment->update_extra_meta(EEG_Paypal_Standard::itemized_payment_option_name, true);
         //this payment is for the remaining transaction amount,
         //keep track of exactly how much the itemized order amount equals
         $itemized_sum = 0;
         $shipping_previously_added = 0;
         //so let's show all the line items
         foreach ($total_line_item->get_items() as $line_item) {
             if ($line_item instanceof EE_Line_Item) {
                 //it's some kind of discount
                 if ($line_item->total() < 0) {
                     $total_discounts_to_cart_total += abs($line_item->total());
                     $itemized_sum += $line_item->total();
                     continue;
                 }
                 //dont include shipping again.
                 if (strpos($line_item->code(), 'paypal_shipping_') === 0) {
                     $shipping_previously_added = $line_item->total();
                     continue;
                 }
                 $redirect_args['item_name_' . $item_num] = substr(sprintf(_x('%1$s for %2$s', 'Ticket for Event', 'event_espresso'), $line_item->name(), $line_item->ticket_event_name()), 0, 127);
                 $redirect_args['amount_' . $item_num] = $line_item->unit_price();
                 $redirect_args['quantity_' . $item_num] = $line_item->quantity();
                 //if we're not letting PayPal calculate shipping, tell them its 0
                 if (!$this->_paypal_shipping) {
                     $redirect_args['shipping_' . $item_num] = '0';
                     $redirect_args['shipping2_' . $item_num] = '0';
                 }
                 $item_num++;
                 $itemized_sum += $line_item->total();
             }
         }
         $taxes_li = $this->_line_item->get_taxes_subtotal($total_line_item);
         //ideally itemized sum equals the transaction total. but if not (which is weird)
         //and the itemized sum is LESS than the transaction total
         //add another line item
         //if the itemized sum is MORE than the transaction total,
         //add the difference it to the discounts
         $itemized_sum_diff_from_txn_total = round($transaction->total() - $itemized_sum - $taxes_li->total() - $shipping_previously_added, 2);
         if ($itemized_sum_diff_from_txn_total < 0) {
             //itemized sum is too big
             $total_discounts_to_cart_total += abs($itemized_sum_diff_from_txn_total);
         } elseif ($itemized_sum_diff_from_txn_total > 0) {
             $redirect_args['item_name_' . $item_num] = substr(__('Other charges', 'event_espresso'), 0, 127);
             $redirect_args['amount_' . $item_num] = $this->format_currency($itemized_sum_diff_from_txn_total);
             $redirect_args['quantity_' . $item_num] = 1;
             $item_num++;
         }
         if ($total_discounts_to_cart_total > 0) {
             $redirect_args['discount_amount_cart'] = $this->format_currency($total_discounts_to_cart_total);
         }
         //add our taxes to the order if we're NOT using PayPal's
         if (!$this->_paypal_taxes) {
             $redirect_args['tax_cart'] = $total_line_item->get_total_tax();
         }
     } else {
         $payment->update_extra_meta(EEG_Paypal_Standard::itemized_payment_option_name, false);
         //partial payment that's not for the remaining amount, so we can't send an itemized list
         $redirect_args['item_name_' . $item_num] = substr(sprintf(__('Payment of %1$s for %2$s', "event_espresso"), $payment->amount(), $primary_registrant->reg_code()), 0, 127);
         $redirect_args['amount_' . $item_num] = $payment->amount();
         $redirect_args['shipping_' . $item_num] = '0';
         $redirect_args['shipping2_' . $item_num] = '0';
         $redirect_args['tax_cart'] = '0';
         $item_num++;
     }
     if ($this->_debug_mode) {
         $redirect_args['item_name_' . $item_num] = 'DEBUG INFO (this item only added in sandbox mode';
         $redirect_args['amount_' . $item_num] = 0;
         $redirect_args['on0_' . $item_num] = 'NOTIFY URL';
         $redirect_args['os0_' . $item_num] = $notify_url;
         $redirect_args['on1_' . $item_num] = 'RETURN URL';
         $redirect_args['os1_' . $item_num] = $return_url;
         //			$redirect_args['option_index_' . $item_num] = 1; // <-- dunno if this is needed ?
         $redirect_args['shipping_' . $item_num] = '0';
         $redirect_args['shipping2_' . $item_num] = '0';
     }
     $redirect_args['business'] = $this->_paypal_id;
     $redirect_args['return'] = $return_url;
     $redirect_args['cancel_return'] = $cancel_url;
     $redirect_args['notify_url'] = $notify_url;
     $redirect_args['cmd'] = '_cart';
     $redirect_args['upload'] = 1;
     $redirect_args['currency_code'] = $payment->currency_code();
     $redirect_args['rm'] = 2;
     //makes the user return with method=POST
     if ($this->_image_url) {
         $redirect_args['image_url'] = $this->_image_url;
     }
     $redirect_args['no_shipping'] = $this->_shipping_details;
     $redirect_args['bn'] = 'EventEspresso_SP';
     //EE will blow up if you change this
     $redirect_args = apply_filters("FHEE__EEG_Paypal_Standard__set_redirection_info__arguments", $redirect_args, $this);
     $payment->set_redirect_url($this->_gateway_url);
     $payment->set_redirect_args($redirect_args);
     // log the results
     $this->log(array('message' => sprintf(__('PayPal payment request initiated.', 'event_espresso')), 'transaction' => $transaction->model_field_array()), $payment);
     return $payment;
 }
 /**
  *    column_TXN_paid
  *
  * @param \EE_Transaction $item
  * @return mixed|string
  * @throws \EE_Error
  */
 public function column_TXN_paid(EE_Transaction $item)
 {
     $transaction_total = $item->get('TXN_total');
     $transaction_paid = $item->get('TXN_paid');
     if (\EEH_Money::compare_floats($transaction_total, 0, '>')) {
         // monies owing
         $span_class = 'txn-overview-part-payment-spn';
         if (\EEH_Money::compare_floats($transaction_paid, $transaction_total, '>=')) {
             // paid in full
             $span_class = 'txn-overview-full-payment-spn';
         } elseif (\EEH_Money::compare_floats($transaction_paid, 0, '==')) {
             // no payments made
             $span_class = 'txn-overview-no-payment-spn';
         }
     } else {
         $span_class = 'txn-overview-free-event-spn';
         $transaction_paid = 0;
     }
     $payment_method = $item->payment_method();
     $payment_method_name = $payment_method instanceof EE_Payment_Method ? $payment_method->admin_name() : __('Unknown', 'event_espresso');
     $content = '<span class="' . $span_class . ' txn-pad-rght">' . $transaction_paid !== 0 ? $item->get_pretty('TXN_paid') : $transaction_paid . '</span>';
     if ($transaction_paid > 0) {
         $content .= '<br><span class="ee-status-text-small">' . sprintf(__('...via %s', 'event_espresso'), $payment_method_name) . '</span>';
     }
     return $content;
 }