File: /home/cpt/public_html/wp-content/plugins/events-manager/classes/em-booking.php
<?php
/**
* gets a booking in a more db-friendly manner, allows hooking into booking object right after instantiation
* @param mixed $id
* @param mixed $search_by
* @return EM_Booking
*/
function em_get_booking($id = false) {
global $EM_Booking;
//check if it's not already global so we don't instantiate again
if( $EM_Booking instanceof EM_Booking ){
if( is_object($id) && $EM_Booking->booking_id == $id->booking_id ){
return apply_filters('em_get_booking', $EM_Booking);
}else{
if( is_numeric($id) && $EM_Booking->booking_id == $id ){
return apply_filters('em_get_booking', $EM_Booking);
}elseif( is_array($id) && !empty($id['booking_id']) && $EM_Booking->booking_id == $id['booking_id'] ){
return apply_filters('em_get_booking', $EM_Booking);
}elseif( is_string($id) && strlen($id) == 32 && $EM_Booking->booking_uuid === $id ){
return apply_filters('em_get_booking', $EM_Booking);
}
}
}
if( $id instanceof EM_Booking ){
return apply_filters('em_get_booking', $id);
}else{
return apply_filters('em_get_booking', new EM_Booking($id));
}
}
/**
* Contains all information and relevant functions surrounding a single booking made with Events Manager
* @property int|false $booking_status
* @property string $language
* @property EM_Person $person
*/
class EM_Booking extends EM_Object{
//DB Fields
var $booking_id;
var $booking_uuid;
var $event_id;
var $person_id;
var $booking_price = null;
var $booking_spaces;
var $booking_comment;
public $booking_status = false;
public $booking_rsvp_status = null;
var $booking_tax_rate = null;
var $booking_taxes = null;
var $booking_meta = array();
var $fields = array(
'booking_id' => array('name'=>'id','type'=>'%d'),
'booking_uuid' => array('name'=>'uuid','type'=>'%s'),
'event_id' => array('name'=>'event_id','type'=>'%d'),
'person_id' => array('name'=>'person_id','type'=>'%d'),
'booking_price' => array('name'=>'price','type'=>'%f'),
'booking_spaces' => array('name'=>'spaces','type'=>'%d'),
'booking_comment' => array('name'=>'comment','type'=>'%s'),
'booking_status' => array('name'=>'status','type'=>'%d'),
'booking_rsvp_status' => array('name'=>'rsvp_status','type'=>'%d','null'=>1),
'booking_tax_rate' => array('name'=>'tax_rate','type'=>'%f','null'=>1),
'booking_taxes' => array('name'=>'taxes','type'=>'%f','null'=>1),
'booking_meta' => array('name'=>'meta','type'=>'%s')
);
public static $field_shortcuts = array(
'id' => 'booking_id',
'uuid' => 'booking_uuid',
'price' => 'booking_price',
'spaces' => 'booking_spaces',
'comment' => 'booking_comment',
'status' => 'booking_status',
'rsvp_status' => 'booking_rsvp_status',
'tax_rate' => 'booking_tax_rate',
'taxes' => 'booking_taxes',
'meta' => 'booking_meta'
);
//Other Vars
/**
* array of notes by admins on this booking. loaded from em_meta table in construct
* @var array
*/
var $notes;
/**
* Deprecated as of 5.8.2, previously used to store timestamp of booking date. Use EM_Booking->date()->getTimestamp() instead.
* @var int
*/
private $timestamp;
/**
* The date of the booking, in UTC time, represented as a DATETIME mysql value.
* @var string
*/
protected $booking_date;
/**
* Contains the booking date in EM_DateTime object form.
* @var EM_DateTime
*/
protected $date;
/**
* @var EM_Person
*/
protected $person;
var $required_fields = array('booking_id', 'event_id', 'person_id', 'booking_spaces');
var $feedback_message = "";
var $errors = array();
/**
* when using EM_Booking::email_send(), this number is updated with sent emails
* @var int
*/
var $mails_sent = 0;
/**
* Contains an array of custom fields for a booking. This is loaded from em_meta, where the booking_custom name contains arrays of data.
* @var array
*/
var $custom = array();
/**
* If saved in this instance, you can see what previous approval status was.
* @var int
*/
var $previous_status = false;
var $previous_rsvp_status;
/**
* The booking approval status number corresponds to a state in this array.
* @var array
*/
var $status_array = array();
/**
* @var EM_Tickets
*/
var $tickets;
/**
* @var EM_Event
*/
var $event;
/**
* @var EM_Tickets_Bookings
*/
var $tickets_bookings;
/**
* If set to true, this booking can be managed by any logged in user.
* @var EM_Tickets_Bookings
*/
var $manage_override;
static $rsvp_statuses = array();
/**
* Creates booking object and retrieves booking data (default is a blank booking object). Accepts either array of booking data (from db) or a booking id.
* @param mixed $booking_data
* @return null
*/
function __construct( $booking_data = false ){
//Get the person for this booking
global $wpdb;
if( $booking_data !== false ){
//Load booking data
$booking = array();
if( is_array($booking_data) ){
$booking = $booking_data;
}elseif( is_numeric($booking_data) ){
//Retrieving from the database
$sql = $wpdb->prepare("SELECT * FROM ". EM_BOOKINGS_TABLE ." WHERE booking_id =%d", $booking_data);
$booking = $wpdb->get_row($sql, ARRAY_A);
} elseif( is_string($booking_data) && preg_match('/^[a-zA-Z0-9]{32}$/', $booking_data) ){
$sql = $wpdb->prepare("SELECT * FROM " . EM_BOOKINGS_TABLE . " WHERE booking_uuid=%s", $booking_data);
$booking = $wpdb->get_row($sql, ARRAY_A);
}
//booking meta
$booking['booking_meta'] = array(); // we don't use booking meta from the table anymore
if( !empty($booking['booking_id']) ) {
$sql = $wpdb->prepare("SELECT meta_key, meta_value FROM " . EM_BOOKINGS_META_TABLE . " WHERE booking_id=%d", $booking['booking_id']);
$booking_meta_results = $wpdb->get_results($sql, ARRAY_A);
$booking['booking_meta'] = $this->process_meta($booking_meta_results);
}
//Save into the object
$this->to_object($booking);
$this->booking_status = absint($this->booking_status);
$this->previous_status = $this->booking_status;
$this->booking_date = !empty($booking['booking_date']) ? $booking['booking_date']:false;
if( empty($this->booking_uuid) ) {
if( !empty($this->booking_id) ){
$this->booking_uuid = md5($this->ticket_booking_id); // fallback, create a consistent but unique MD5 hash in case it's not saved for some reason.
} else {
$this->booking_uuid = $this->generate_uuid();
}
}
// format status of rsvp into an int
$this->booking_rsvp_status = $this->booking_rsvp_status === '' || $this->booking_rsvp_status === null ? null : absint($this->booking_rsvp_status);
}else{
$this->booking_uuid = $this->generate_uuid();
}
//Do it here so things appear in the po file.
$this->status_array = array(
0 => __('Pending','events-manager'),
1 => __('Approved','events-manager'),
2 => __('Rejected','events-manager'),
3 => __('Cancelled','events-manager'),
4 => __('Awaiting Online Payment','events-manager'),
5 => __('Awaiting Payment','events-manager'),
6 => __('Waitlist','events-manager'),
7 => __('Waitlist Approved','events-manager'),
8 => __('Waitlist Expired','events-manager'),
);
$this->compat_keys(); //deprecating in 6.0
//do some legacy checking here for bookings made prior to 5.4, due to how taxes are calculated
$this->get_tax_rate();
if( !empty($this->legacy_tax_rate) ){
//reset booking_price, it'll be recalculated later (if you're using this property directly, don't use $this->get_price())
$this->booking_price = $this->booking_taxes = null;
}
// allow others to intervene
do_action('em_booking', $this, $booking_data);
}
function __get( $var ){
//get the modified or created date from the DB only if requested, and save to object
if( $var == 'timestamp' ){
if( $this->date() === false ) return 0;
return $this->date()->getTimestampWithOffset();
}elseif( $var == 'language' ){
if( !empty($this->booking_meta['lang']) ){
return $this->booking_meta['lang'];
}
}elseif( $var == 'person' ){
return $this->get_person();
}elseif( $var == 'date' ){
return $this->date();
}elseif( $var == 'uuid' ){
return $this->booking_uuid;
}
return null;
}
public function __set( $prop, $val ){
if( $prop == 'timestamp' ){
if( $this->date() !== false ) $this->date()->setTimestamp($val);
}elseif( $prop == 'language' ){
$this->booking_meta['lang'] = $val;
}elseif( $prop == 'person' ){
// prevent non EM_Person objects from being set
if( $val instanceof EM_Person ) {
$this->person_id = $val->ID;
$this->person = $val;
} else {
$this->person = null;
}
}
parent::__set( $prop, $val );
}
public function __isset( $prop ){
if( $prop == 'timestamp' ) return $this->date()->getTimestamp() > 0;
if( $prop == 'language' ) return !empty($this->booking_meta['lang']);
return parent::__isset( $prop );
}
/**
* Return relevant fields that will be used for storage, excluding things such as event and ticket objects that should get reloaded
* @return string[]
*/
public function __sleep(){
$array = array('booking_id','booking_uuid','event_id','person_id','booking_price','booking_spaces','booking_comment','booking_status','booking_tax_rate','booking_taxes','booking_meta','notes','booking_date','person','feedback_message','errors','mails_sent','custom','previous_status','status_array','manage_override','tickets_bookings');
if( !empty($this->bookings) ) $array[] = 'bookings'; // EM Pro backwards compatibility
return apply_filters('em_booking_sleep', $array, $this);
}
/**
* Repopulate the ticket bookings with this object and its event reference.
*/
public function __wakeup(){
// we need to do this here because the __wakeup function bubbles up from the innermost class
$this->get_tickets_bookings()->booking = $this;
foreach( $this->get_tickets_bookings() as $EM_Ticket_Bookings ){
$EM_Ticket_Bookings->booking = $this;
foreach( $EM_Ticket_Bookings as $EM_Ticket_Booking ){
$EM_Ticket_Booking->booking = $this;
}
}
}
function get_notes(){
global $wpdb;
if( !is_array($this->notes) && !empty($this->booking_id) ){
$notes = $wpdb->get_results("SELECT * FROM ". EM_META_TABLE ." WHERE meta_key='booking-note' AND object_id ='{$this->booking_id}'", ARRAY_A);
$this->notes = array();
foreach($notes as $note){
$this->notes[] = unserialize($note['meta_value']);
}
}elseif( empty($this->booking_id) ){
$this->notes = array();
}
return $this->notes;
}
/**
* Saves the booking into the database, whether a new or existing booking
* @param bool $mail whether or not to email the user and contact people
* @return boolean
*/
function save($mail = true){
global $wpdb;
$table = EM_BOOKINGS_TABLE;
do_action('em_booking_save_pre',$this); // last chance to circumvent
if( empty($this->errors) && $this->can_manage() ){
//update prices, spaces, person_id
$this->get_spaces(true);
$this->calculate_price();
$this->person_id = (empty($this->person_id)) ? $this->get_person()->ID : $this->person_id;
//Step 1. Save the booking
$data = $this->to_array();
$data['booking_meta'] = serialize($data['booking_meta']);
//update or save
if($this->booking_id != ''){
$update = true;
$where = array( 'booking_id' => $this->booking_id );
$result = $wpdb->update($table, $data, $where, $this->get_types($data));
$result = ($result !== false);
$this->feedback_message = __('Changes saved','events-manager');
}else{
$update = false;
$data_types = $this->get_types($data);
$data['booking_date'] = $this->booking_date = gmdate('Y-m-d H:i:s');
$data_types[] = '%s';
// first check that the uuid is unique, if not change it and repeat until unique
while( $wpdb->get_var( $wpdb->prepare("SELECT booking_uuid FROM $table WHERE booking_uuid=%s", $this->booking_uuid) ) ){
$this->booking_uuid = $data['booking_uuid'] = $this->generate_uuid();
}
// now insert
$result = $wpdb->insert($table, $data, $data_types);
$this->booking_id = $wpdb->insert_id;
$this->feedback_message = __('Your booking has been recorded','events-manager');
}
//Step 2. Insert meta and ticket bookings for this booking id if no errors so far
if( $result === false ){
$this->feedback_message = __('There was a problem saving the booking.', 'events-manager');
$this->errors[] = __('There was a problem saving the booking.', 'events-manager');
}else{
//Step 2a - Save booking meta
$wpdb->delete(EM_BOOKINGS_META_TABLE, array('booking_id' => $this->booking_id));
$meta_insert = array();
foreach( $this->booking_meta as $meta_key => $meta_value ){
if( is_array($meta_value) ){
$associative = array_keys($meta_value) !== range(0, count($meta_value) - 1);
// we go down one level of array
foreach( $meta_value as $kk => $vv ){
if( is_array($vv) ) $vv = serialize($vv);
if( $associative ) {
$meta_insert[] = $wpdb->prepare('(%d, %s, %s)', $this->booking_id, '_'.$meta_key.'|'.$kk, $vv);
}else{
$meta_insert[] = $wpdb->prepare('(%d, %s, %s)', $this->booking_id, '_'.$meta_key.'|', $vv);
}
}
}else{
$meta_insert[] = $wpdb->prepare('(%d, %s, %s)', $this->booking_id, $meta_key, $meta_value);
}
}
if( !empty($meta_insert) ){
$wpdb->query('INSERT INTO '. EM_BOOKINGS_META_TABLE .' (booking_id, meta_key, meta_value) VALUES '. implode(',', $meta_insert));
}
// Step 2b - Save Ticket Bookings
$tickets_bookings_result = $this->get_tickets_bookings()->save();
if( !$tickets_bookings_result ){
if( !$update ){
//delete the booking and tickets, instead of a transaction
$this->delete();
}
$this->errors[] = __('There was a problem saving the booking.', 'events-manager');
$this->add_error( $this->get_tickets_bookings()->get_errors() );
} else {
// save the ticket data back into the booking meta
}
}
// Step 3. Run filter for return value before sending emails
$this->compat_keys();
$return = apply_filters('em_booking_save', ( count($this->errors) == 0 ), $this, $update);
//Final Step: email if necessary after all the saving has been done
if ( $return && $mail ) {
$this->email();
}
if( $return && empty($update) ){
/**
* When a booking has been added to an event
*
* @param EM_Booking $this The EM_Booking object just added
* @param boolean $mail If a mail would have been sent (if applicable)
*/
do_action('em_booking_added', $this, $mail);
}
return $return;
}else{
$this->feedback_message = __('There was a problem saving the booking.', 'events-manager');
if( !$this->can_manage() ){
$this->add_error(sprintf(__('You cannot manage this %s.', 'events-manager'),__('Booking','events-manager')));
}
}
return apply_filters('em_booking_save', false, $this, false);
}
/**
* Gets the user meta for this booking, which may reside withih the booking context or in the user meta context.
*
* Returns null if not defined.
*
* @param $meta_key
*
* @return mixed
*/
public function get_user_meta( $meta_key ) {
if( $this->person_id == 0 ) {
$meta_value = $this->booking_meta['registration'][ $meta_key ] ?? null;
} else {
$meta_value = get_user_meta( $this->person_id, $meta_key, true );
if( $meta_value === '' ) $meta_value = null;
}
return apply_filters('em_booking_get_user_meta', $meta_value, $meta_key, $this);
}
/**
* Gets meta stored in booking meta, useful especially for data that may require interception such as at a multiple booking level.
*
* @param $meta_key
*
* @return mixed|null
*/
public function get_meta( $meta_key ) {
$meta_value = $this->booking_meta[$meta_key] ?? null;
return apply_filters('em_booking_get_meta', $meta_value, $meta_key, $this);
}
public function update_user_meta( $meta_key, $meta_value ) {
global $wpdb;
// set the meta in booking object first
$this->booking_meta['registration'][$meta_key] = $meta_key;
// check if this is in no-user mode, if so we save directly to the DB, otherwise we save it directly to the usermeta table
if( $this->person_id === 0 ) {
$wpdb->update( EM_BOOKINGS_META_TABLE, array('meta_key' => '_registration|'.$meta_key, 'meta_value' => $meta_value), array('booking_id' => $this->booking_id) );
} else {
update_user_meta( $this->person_id, $meta_key, $meta_value );
}
}
/**
* Update a specific key value in the booking meta data, or create one if it doesn't exist. If set to null it'll remove that value
* @param $meta_key
* @param $meta_value
* @return bool
* @since 5.9.11
*/
public function update_meta( $meta_key, $meta_value, $subkey = null ) {
global $wpdb;
if( !$this->booking_id ) return false;
if( $subkey !== null ) {
// Update a specific subkey without affecting other records.
if( !isset($this->booking_meta[$meta_key]) || !is_array($this->booking_meta[$meta_key]) )
$this->booking_meta[$meta_key] = array();
if( $meta_value === null ) {
unset($this->booking_meta[$meta_key][$subkey]);
} else {
$this->booking_meta[$meta_key][$subkey] = $meta_value;
}
$booking_meta = serialize($this->booking_meta);
$wpdb->update( EM_BOOKINGS_TABLE, array('booking_meta' => $booking_meta), array('booking_id' => $this->booking_id) );
$meta_table_key = '_' . $meta_key . '|' . $subkey;
if( $meta_value === null ) {
$result = $wpdb->delete( EM_BOOKINGS_META_TABLE, array('booking_id' => $this->booking_id, 'meta_key' => $meta_table_key) );
} else {
$existing = $wpdb->get_var( $wpdb->prepare("SELECT COUNT(*) FROM " . EM_BOOKINGS_META_TABLE . " WHERE booking_id=%d AND meta_key=%s", $this->booking_id, $meta_table_key) );
if( $existing ) {
$result = $wpdb->update( EM_BOOKINGS_META_TABLE, array('meta_value' => $meta_value), array('booking_id' => $this->booking_id, 'meta_key' => $meta_table_key) );
} else {
$result = $wpdb->insert( EM_BOOKINGS_META_TABLE, array('booking_id' => $this->booking_id, 'meta_key' => $meta_table_key, 'meta_value' => $meta_value) );
}
}
} else {
if( $meta_value === null ) {
unset($this->booking_meta[$meta_key]);
}else{
$this->booking_meta[$meta_key] = $meta_value;
}
// add to new meta table if not exists
if( is_array($meta_value) ){
// associative arrays are deleted by prefix key
$result = $wpdb->query( $wpdb->prepare('DELETE FROM '. EM_BOOKINGS_META_TABLE .' WHERE booking_id=%d AND meta_key LIKE %s', $this->booking_id, '_' . $meta_key . '|%') );
}else{
$result = $wpdb->delete( EM_BOOKINGS_META_TABLE, array('booking_id' => $this->booking_id, 'meta_key' => $meta_key) );
}
// if null, then we already deleted it and skip this
if( $meta_value !== null ) {
if( is_array($meta_value) ){
$associative = array_keys($meta_value) !== range(0, count($meta_value) - 1);
// we go down one level of array
foreach( $meta_value as $kk => $vv ){
if( is_array($vv) ) $vv = serialize($vv);
if( $associative ) {
$meta_insert[] = $wpdb->prepare('(%d, %s, %s)', $this->booking_id, '_'.$meta_key.'|'.$kk, $vv);
}else{
$meta_insert[] = $wpdb->prepare('(%d, %s, %s)', $this->booking_id, '_'.$meta_key.'|', $vv);
}
}
$result = $wpdb->query('INSERT INTO '. EM_BOOKINGS_META_TABLE .' (booking_id, meta_key, meta_value) VALUES '. implode(',', $meta_insert));
}else{
$result = $wpdb->insert( EM_BOOKINGS_META_TABLE, array('booking_id' => $this->booking_id, 'meta_key' => $meta_key, 'meta_value' => $meta_value));
}
}
}
$booking_meta = serialize($this->booking_meta);
$wpdb->update( EM_BOOKINGS_TABLE, array('booking_meta' => $booking_meta), array('booking_id' => $this->booking_id) );
// fire filter
return apply_filters('em_booking_update_meta', $result !== false, $meta_key, $meta_value, $this);
}
/**
* Load a record into this object by passing an associative array of table criteria to search for.
* Returns boolean depending on whether a record is found or not.
* @param $search
* @return boolean
*/
function get($search) {
global $wpdb;
$conds = array();
foreach($search as $key => $value) {
if( array_key_exists($key, $this->fields) ){
$value = esc_sql($value);
$conds[] = "`$key`='$value'";
}
}
$sql = "SELECT * FROM ". EM_BOOKINGS_TABLE ." WHERE " . implode(' AND ', $conds) ;
$result = $wpdb->get_row($sql, ARRAY_A);
if($result){
$this->to_object($result);
$this->person = new EM_Person($this->person_id);
return true;
}else{
return false;
}
}
/**
* Get posted data and save it into the object (not db)
* @return boolean
*/
function get_post( $override_availability = false ){
if( EM_Bookings::$disable_restrictions ) $override_availability = true;
$this->tickets_bookings = new EM_Tickets_Bookings($this);
do_action('em_booking_get_post_pre',$this);
$result = array();
$this->event_id = absint($_REQUEST['event_id']);
if ( $this->get_event()->event_status != 1 || $this->get_event()->event_active_status != 1 ) {
$this->add_error( __('This event is not available or has been cancelled', 'events-manager') ); // uncommon, not needed for custom error.
}
if( isset($_REQUEST['em_tickets']) && is_array($_REQUEST['em_tickets']) && ($_REQUEST['em_tickets'] || $override_availability) ){
if( !$this->get_tickets_bookings()->get_post( $override_availability ) ){
$this->add_error($this->tickets_bookings->get_errors());
}
$this->booking_comment = (!empty($_REQUEST['booking_comment'])) ? wp_kses_data(wp_unslash($_REQUEST['booking_comment'])):'';
//allow editing of tax rate
if( !empty($this->booking_id) && $this->can_manage() ){
$this->booking_tax_rate = (!empty($_REQUEST['booking_tax_rate']) && is_numeric($_REQUEST['booking_tax_rate'])) ? $_REQUEST['booking_tax_rate']:$this->booking_tax_rate;
}
//recalculate spaces/price
$this->get_spaces(true);
$this->calculate_price();
//get person
$this->get_person();
//re-run compatiblity keys function
$this->compat_keys(); //depracating in 6.0
}
/*
if( !$this->booking_id && !empty($_REQUEST['booking_intent']) && preg_match('/^[a-zA-Z0-9]{32}/', $_REQUEST['booking_intent']) ){
$this->booking_uuid = sanitize_key($_REQUEST['booking_intent']);
}
*/
return apply_filters('em_booking_get_post', empty($this->errors), $this);
}
function validate( $override_availability = false ){
do_action( 'em_booking_validate_pre', $this, $override_availability );
if( EM_Bookings::$disable_restrictions ) $override_availability = true;
//step 1, basic info
$basic = (empty($this->event_id) || is_numeric($this->event_id)) && (empty($this->person_id) || is_numeric($this->person_id));
if( !$basic ){
$this->add_error('Incomplete booking information provided.');
}
//give some errors in step 1
if( !is_numeric($this->get_spaces()) || $this->booking_spaces == 0 ){
$this->add_error(get_option('dbem_booking_feedback_min_space'));
}
//step 2, tickets bookings info
if( !$this->get_tickets_bookings()->validate( $override_availability ) ){
$this->errors = array_merge( $this->errors, $this->get_tickets_bookings()->get_errors() );
}
if( !$override_availability ){
// are bookings even available due to event and ticket cut-offs/restrictions? This is checked earlier in booking processes, but is relevant in checkout/cart situations where a previously-made booking is validated just before checkout
if( $this->get_event()->rsvp_end()->getTimestamp() < time() ){
$result = false;
$this->add_error(get_option('dbem_bookings_form_msg_closed'));
}
//is there enough space overall?
if( $this->get_event()->get_bookings()->get_available_spaces() < $this->get_spaces() ){
$result = false;
$this->add_error(get_option('dbem_booking_feedback_full'));
}
}
//can we book this amount of spaces at once?
if( $this->get_event()->event_rsvp_spaces > 0 && $this->get_spaces() > $this->get_event()->event_rsvp_spaces ){
$result = false;
$this->add_error( sprintf(get_option('dbem_booking_feedback_spaces_limit'), $this->get_event()->event_rsvp_spaces));
}
do_action( 'em_booking_validate_after', $this, $override_availability );
return apply_filters('em_booking_validate', empty($this->errors), $this);
}
/**
* Get the total number of spaces booked in THIS booking. Setting $force_refresh to true will recheck spaces, even if previously done so.
* @param boolean $force_refresh
* @return int
*/
function get_spaces( $force_refresh=false ){
if($this->booking_spaces == 0 || $force_refresh == true ){
$this->booking_spaces = $this->get_tickets_bookings()->get_spaces($force_refresh);
}
return apply_filters('em_booking_get_spaces',$this->booking_spaces,$this);
}
/* Price Calculations */
/**
* Gets the total price for this whole booking, including any discounts, taxes, and any other additional items. In other words, what the person has to pay or has supposedly paid.
* This price shouldn't change once established, unless there's any alteration to the booking itself that'd affect the price, such as a change in ticket numbers, discount, etc.
* @param boolean $format
* @return double|string
*/
function get_price( $format = false, $format_depricated = null ){
if( $format_depricated !== null ) $format = $format_depricated; //support for old parameters, will be depricated soon
//recalculate price here only if price is not actually set
if( $this->booking_price === null ){
$this->calculate_price();
/* Deprecated filter - Equivalent of em_booking_calculate_price, please use that instead */
$this->booking_price = apply_filters('em_booking_get_price', $this->booking_price, $this);
}
//return booking_price, formatted or not
if($format){
return $this->format_price($this->booking_price);
}
return round($this->booking_price,2);
}
/**
* Total of tickets without taxes, discounts or any other modification.
* @param boolean $format
* @return double|string
*/
function get_price_base( $format = false ){
$price = apply_filters('em_booking_get_price_base', $this->get_tickets_bookings()->get_price(), $this);
if($format){
return $this->format_price($price);
}
return $price;
}
/**
* Get total price of booking before taxes are applied, this includes discounts and surcharges.
* @param $format
* @param $include_adjustments
*
* @return int|string
*/
function get_price_pre_taxes( $format = false, $include_adjustments = true ){
$price = $base_price = $this->get_price_base();
//apply pre-tax discounts
if( $include_adjustments ){
$price -= $this->get_price_adjustments_amount('discounts', 'pre', $base_price);
$price += $this->get_price_adjustments_amount('surcharges', 'pre', $base_price);
}
$price = apply_filters('em_booking_get_price_pre_taxes', $price, $base_price, $this, $include_adjustments);
if( $price < 0 ){ $price = 0; } //no negative prices
//return amount of taxes applied, formatted or not
if( $format ) return $this->format_price($price);
return $price;
}
/**
* Gets price AFTER taxes and (optionally) post-tax discounts and surcharges have also been added.
* @param boolean $format
* @param boolean $include_adjustments If set to true discounts and surcharges won't be applied to the overall price.
* @return double|string
*/
function get_price_post_taxes( $format = false, $include_adjustments = true ){
//get price before taxes
$price = $this->get_price_pre_taxes( false, $include_adjustments );
//add taxes to price
if( $this->get_tax_rate() > 0 ){
$this->booking_taxes = $price * ($this->get_tax_rate()/100); //calculate and save tax amount
$price += $this->booking_taxes; //add taxes
$this->taxes_applied = true;
}
//apply post-tax discounts
$price_after_taxes = $price;
if( $include_adjustments ){
$price -= $this->get_price_adjustments_amount('discounts', 'post', $price_after_taxes);
$price += $this->get_price_adjustments_amount('surcharges', 'post', $price_after_taxes);
}
$price = apply_filters('em_booking_get_price_post_taxes', $price, $price_after_taxes, $this, $include_adjustments);
if( $price < 0 ){ $price = 0; } //no negative prices
//return amount of taxes applied, formatted or not
if( $format ) return $this->format_price($price);
return $price;
}
/**
* Get amount of taxes applied to this booking price.
* @param boolean $format
* @return double|string
*/
function get_price_taxes( $format=false ){
if( $this->booking_taxes !== null ){
$this->booking_taxes; //taxes already calculated
}else{
$this->calculate_price(); //recalculate price and taxes
}
//return amount of taxes applied, formatted or not
if( $format ){
return $this->format_price($this->booking_taxes);
}
return $this->booking_taxes;
}
/**
* Calculates (or recalculates) the price of this booking including taxes, discounts etc., saves it to the booking_price property and writes to relevant properties booking_meta variables
* @return double
*/
function calculate_price(){
//any programatic price adjustments should be added here, otherwise you need to run this function again
do_action('em_booking_pre_calculate_price', $this);
//reset price and taxes calculations
$this->booking_price = $this->booking_taxes = null;
//get post-tax price and save it to booking_price
$this->booking_price = apply_filters('em_booking_calculate_price', $this->get_price_post_taxes(), $this);
return $this->booking_price;
}
/*
* Gets tax rate of booking
* @see EM_Object::get_tax_rate()
*/
function get_tax_rate( $decimal = false ){
if( $this->booking_tax_rate === null ){
//booking not saved or tax never defined
if( !empty($this->booking_id) && get_option('dbem_legacy_bookings_tax', 'x') !== 'x'){ //even if 0 if defined as tax rate we still use it, delete the option entirely to stop
//no tax applied yet to an existing booking, or tax possibly applied (but handled separately in EM_Tickets_Bookings but in legacy < v5.4
//sort out MultiSite nuances
if( EM_MS_GLOBAL && $this->get_event()->blog_id != get_current_blog_id() ){
//MultiSite AND Global tables enabled AND this event belongs to another blog - get settings for blog that published the event
$this->booking_tax_rate = get_blog_option($this->get_event()->blog_id, 'dbem_legacy_bookings_tax');
}else{
//get booking from current site, whether or not we're in MultiSite
$this->booking_tax_rate = get_option('dbem_legacy_bookings_tax');
}
$this->legacy_tax_rate = true;
}else{
//first time we're applying tax rate
$this->booking_tax_rate = $this->get_event()->get_tax_rate();
}
}
$this->booking_tax_rate = $this->booking_tax_rate > 0 ? $this->booking_tax_rate : 0;
$this->booking_tax_rate = apply_filters('em_booking_get_tax_rate', $this->booking_tax_rate, $this);
if( $this->booking_tax_rate > 0 && $decimal ){
return $this->booking_tax_rate / 100;
}else{
return $this->booking_tax_rate;
}
}
/* START Price Adjustment Functions */
//now we can use one function for both discounts and surcharges, the three functions below are now deprecated.
/**
* DEPRECATED. Use $this->get_price_adjustments('discounts'); instead.
*/
function get_price_discounts(){
return apply_filters('em_booking_get_price_discounts', $this->get_price_adjustments('discounts'), $this);
}
/**
* DEPRECATED - Use $this->get_price_adjustments_amount('discounts', $pre_or_post, $price); instead.
*/
function get_price_discounts_amount( $pre_or_post = 'pre', $price = false ){
return $this->get_price_adjustments_amount( 'discounts', $pre_or_post, $price );
}
/**
* DEPRECATED - Use get_price_discounts_summary('discounts', $pre_or_post, $price); instead.
*/
function get_price_discounts_summary( $pre_or_post = 'pre', $price = false ){
return $this->get_price_adjustments_summary( 'discounts', $pre_or_post, $price );
}
/**
* Returns an array of discounts to be applied to a booking. Here is an example of an array item that is expected:
* array('name' => 'Name of Discount', 'type'=>'% or #', 'amount'=> 0.00, 'desc' => 'Comments about discount', 'tax'=>'pre/post', 'data' => 'any info for hooks to use' );
* About the array keys:
* type - # means a fixed amount of discount, % means a percentage off the base price
* amount - if type is a percentage, it is written as a number from 0-100, e.g. 10 = 10%
* tax - 'pre' means discount is applied before tax, 'post' means after tax
* data - any data to be stored that can be used by actions/filters
* @param string $type The type of adjustment you would like to retrieve. This would normally be 'discounts' or 'surcharges'.
* @return array
*/
function get_price_adjustments( $type ){
$adjustments = array();
if( !empty($this->booking_meta[$type]) && is_array($this->booking_meta[$type]) ){
$adjustments = $this->booking_meta[$type];
}
//run this filter to be backwards compatible, e.g. em_booking_get_price_discount
if( $type == 'discounts' ){
$adjustments = apply_filters('em_booking_get_price_discounts', $adjustments, $this);
}
return apply_filters('em_booking_get_price_adjustments', $adjustments, $type, $this);
}
/**
* Returns a numerical amount to adjust the price by, in the context of a certain type and before or after taxes.
* This will be a positive number whether or not this is to be added or subtracted from the price.
* @param string $type The type of adjustment to get, which would normally be 'discounts' or 'surcharges'
* @param string $pre_or_post Adjustments limited to 'pre' (before), 'post' (after) taxes or 'both'
* @param float $price Price relative to be adjusted.
* @return float
*/
function get_price_adjustments_amount( $type, $pre_or_post = 'both', $price = false ){
$adjustments = $this->get_price_adjustments_summary($type, $pre_or_post, $price);
$adjustment_amount = 0;
foreach($adjustments as $adjustment){
$adjustment_amount += $adjustment['amount_adjusted'];
}
return $adjustment_amount;
}
/**
* Provides an array summary of adjustments to make to the price, in the context of a certain type and before or after taxes.
* @param string $type The type of adjustment to get, which would normally be 'discounts' or 'surcharges'
* @param string $pre_or_post Adjustments limited to 'pre' (before), 'post' (after) taxes or 'both'
* @param float $price Price to calculate relative to adjustments. If not supplied or if $pre_or_post is 'both', price is automatically obtained from booking instance according to pre/post taxes requirement.
* @return array
*/
function get_price_adjustments_summary( $type, $pre_or_post = 'both', $price = false ){
if( $pre_or_post == 'both' ){
$adjustment_summary_pre = $this->get_price_adjustments_summary($type, 'pre');
$adjustment_summary_post = $this->get_price_adjustments_summary($type, 'post');
return $adjustment_summary = array_merge($adjustment_summary_pre, $adjustment_summary_post);
}
$adjustments = $this->get_price_adjustments($type);
$adjustment_summary = array();
if( $price === false ){
if( $pre_or_post == 'post' ){
$price = $this->get_price_pre_taxes() + $this->get_price_taxes();
}else{
$price = $this->get_price_base();
}
}
foreach($adjustments as $adjustment){
$adjustment_amount = 0;
if( !empty($adjustment['amount']) ){
if( !empty($adjustment['tax']) && $adjustment['tax'] == $pre_or_post ){
if( !empty($adjustment['type']) ){
$desc = !empty($adjustment['desc']) ? $adjustment['desc'] : '';
$adjustment_summary_item = array('name' => $adjustment['name'], 'desc' => $desc, 'adjustment'=>'0', 'amount_adjusted'=>0, 'tax'=>$pre_or_post);
if( $adjustment['type'] == '%' ){ //adjustment by percentage
$adjustment_summary_item['amount_adjusted'] = round($price * ($adjustment['amount']/100),2);
$adjustment_summary_item['amount'] = $this->format_price($adjustment_summary_item['amount_adjusted']);
$adjustment_summary_item['adjustment'] = number_format($adjustment['amount'],2).'%';
$adjustment_summary[] = $adjustment_summary_item;
}elseif( $adjustment['type'] == '#' ){ //adjustment by amount
$adjustment_summary_item['amount_adjusted'] = round($adjustment['amount'],2);
$adjustment_summary_item['amount'] = $this->format_price($adjustment_summary_item['amount_adjusted']);
$adjustment_summary_item['adjustment'] = $this->format_price($adjustment['amount']);
$adjustment_summary[] = $adjustment_summary_item;
}
}
}
}
}
return $adjustment_summary;
}
/* END Price Adjustment Functions */
/**
* When generating totals at the bottom of a booking, this creates a useful array for displaying the summary in a meaningful way.
*/
function get_price_summary_array(){
$summary = array();
//get base price of bookings
$summary['total_base'] = $this->get_price_base();
//apply pre-tax discounts
$summary['discounts_pre_tax'] = $this->get_price_adjustments_summary('discounts', 'pre');
$summary['surcharges_pre_tax'] = $this->get_price_adjustments_summary('surcharges', 'pre');
//add taxes to price
$summary['taxes'] = array('rate'=> 0, 'amount'=> 0);
if( $this->get_price_taxes() > 0 ){
$summary['taxes'] = array('rate'=> number_format($this->get_tax_rate(),2, get_option('dbem_bookings_currency_decimal_point'), get_option('dbem_bookings_currency_thousands_sep')).'%', 'amount'=> $this->get_price_taxes(true));
}
//apply post-tax discounts
$summary['discounts_post_tax'] = $this->get_price_adjustments_summary('discounts', 'post');
$summary['surcharges_post_tax'] = $this->get_price_adjustments_summary('surcharges', 'post');
//final price
$summary['total'] = $this->get_price(true);
return $summary;
}
/**
* Returns the amount paid for this booking. By default, a booking is considered either paid in full or not at all depending on whether the booking is confirmed or not.
* @param boolean $format If set to true a currency-formatted string value is returned
* @return string|float
*/
function get_total_paid( $format = false ){
$status = ($this->booking_status == 0 && !get_option('dbem_bookings_approval') ) ? 1:$this->booking_status;
$total = $status ? $this->get_price() : 0;
$total = apply_filters('em_booking_get_total_paid', $total, $this);
if( $format ){
return $this->format_price($total);
}
return $total;
}
/**
* Returns the 3-character ISO-4217 currency code of this booking.
* NOTE!
* This is an in-progress feature and not recommended overriding as this will cause unexpected results. For now, it will always return the general currency setting from EM.
* You can, however, use this function to reference the current currency of this booking and expect that in the future if the currency varies it will be reflected here.
*
* @return string
*/
function get_currency(){
$currency = get_option('dbem_bookings_currency','USD');
return apply_filters('em_booking_get_currency', $currency, $this);
}
/* Get Objects linked to booking */
/**
* Gets the event this booking belongs to and saves a reference in the event property
* @return EM_Event
*/
function get_event(){
global $EM_Event;
if( is_object($this->event) && get_class($this->event)=='EM_Event' && ($this->event->event_id == $this->event_id || (EM_ML::$is_ml && $this->event->event_parent == $this->event_id)) ){
return $this->event;
}elseif( is_object($EM_Event) && $EM_Event->event_id == $this->event_id ){
$this->event = $EM_Event;
}else{
$this->event = em_get_event($this->event_id, 'event_id');
}
return apply_filters('em_booking_get_event', $this->event, $this);
}
/**
* Gets the ticket object this booking belongs to, saves a reference in ticket property
* @return EM_Tickets
*/
function get_tickets(){
if( is_object($this->tickets) && get_class($this->tickets)=='EM_Tickets' ){
return apply_filters('em_booking_get_tickets', $this->tickets, $this);
}else{
$this->tickets = new EM_Tickets($this);
}
return apply_filters('em_booking_get_tickets', $this->tickets, $this);
}
/**
* Gets the ticket object this booking belongs to, saves a reference in ticket property
* @return EM_Tickets_Bookings EM_Tickets_Bookings
*/
function get_tickets_bookings(){
global $wpdb;
if( !is_object($this->tickets_bookings) || get_class($this->tickets_bookings) != 'EM_Tickets_Bookings'){
$this->tickets_bookings = new EM_Tickets_Bookings($this);
}
return apply_filters('em_booking_get_tickets_bookings', $this->tickets_bookings, $this);
}
/**
* @return EM_Person
*/
function get_person(){
global $EM_Person;
if( is_object($this->person) && get_class($this->person)=='EM_Person' && ($this->person->ID == $this->person_id || empty($this->person_id) ) ){
//This person is already included, so don't do anything
}elseif( is_object($EM_Person) && ($EM_Person->ID === $this->person_id || $this->booking_id == '') ){
$this->person = $EM_Person;
$this->person_id = $this->person->ID;
}elseif( is_numeric($this->person_id) ){
$this->person = new EM_Person($this->person_id);
}else{
$this->person = new EM_Person(0);
$this->person_id = $this->person->ID;
}
//if this user is the parent user of disabled registrations, replace user details here:
if( $this->person->ID === 0 && (empty($this->person->loaded_no_user) || $this->person->loaded_no_user != $this->booking_id) ){
//override any registration data into the person objet
if( !empty($this->booking_meta['registration']) ){
foreach($this->booking_meta['registration'] as $key => $value){
$this->person->$key = $value;
}
}
$this->person->user_email = ( !empty($this->booking_meta['registration']['user_email']) ) ? $this->booking_meta['registration']['user_email']:$this->person->user_email;
//if a full name is given, overwrite the first/last name values IF they are also not defined
if( !empty($this->booking_meta['registration']['user_name']) ){
if( is_array($this->booking_meta['registration']['user_name']) ){
$this->booking_meta['registration']['user_name'] = reset($this->booking_meta['registration']['user_name']); // prevent fatal errors further down, this is still a problem though
}
if( !empty($this->booking_meta['registration']['first_name']) ){
//if last name isn't defined, provide the rest of the name minus the first name we just removed
if( empty($this->booking_meta['registration']['last_name']) ){
//first name is defined, so we remove it from full name in case we need the rest for surname
$last_name = trim(str_replace($this->booking_meta['registration']['first_name'], '', $this->booking_meta['registration']['user_name']));
$this->booking_meta['registration']['last_name'] = $last_name;
}
}else{
//no first name defined, check for last name and act accordingly
if( !empty($this->booking_meta['registration']['last_name']) ){
//we do opposite of above, remove last name from full name and use the rest as first name
$first_name = trim(str_replace($this->booking_meta['registration']['last_name'], '', $this->booking_meta['registration']['user_name']));
$this->booking_meta['registration']['first_name'] = $first_name;
}else{
//no defined first or last name, so we use the name and take first string for first name, second part for surname
$name_string = explode(' ',$this->booking_meta['registration']['user_name']);
$this->booking_meta['registration']['first_name'] = array_shift($name_string);
$this->booking_meta['registration']['last_name'] = implode(' ', $name_string);
}
}
}
$this->person->user_firstname = ( !empty($this->booking_meta['registration']['first_name']) ) ? $this->booking_meta['registration']['first_name']:__('Guest User','events-manager');
$this->person->first_name = $this->person->user_firstname;
$this->person->user_lastname = ( !empty($this->booking_meta['registration']['last_name']) ) ? $this->booking_meta['registration']['last_name']:'';
$this->person->last_name = $this->person->user_lastname;
$this->person->phone = ( !empty($this->booking_meta['registration']['dbem_phone']) ) ? $this->booking_meta['registration']['dbem_phone']:__('Not Supplied','events-manager');
//build display name
$full_name = $this->person->user_firstname . " " . $this->person->user_lastname ;
$full_name = trim($full_name);
$display_name = ( empty($full_name) ) ? __('Guest User','events-manager'):$full_name;
$this->person->display_name = $display_name;
$this->person->loaded_no_user = $this->booking_id;
}
return apply_filters('em_booking_get_person', $this->person, $this);
}
/**
* Gets personal information from the $_REQUEST array and saves it to the $EM_Booking->booking_meta['registration'] array
* @return boolean
*/
function get_person_post(){
$user_data = array();
$registration = true;
if( empty($this->booking_meta['registration']) ) $this->booking_meta['registration'] = array();
// Check the e-mail address
$user_email = trim(wp_unslash($_REQUEST['user_email'])); //apostrophes will not be allowed otherwise
if ( $user_email == '' ) {
$registration = false;
$this->add_error(__( '<strong>ERROR</strong>: Please type your e-mail address.', 'events-manager') );
} elseif ( !is_email( $user_email ) ) {
$registration = false;
$this->add_error( __( '<strong>ERROR</strong>: The email address isn’t correct.', 'events-manager') );
}elseif(email_exists( $user_email ) && !get_option('dbem_bookings_registration_disable_user_emails') ){
$registration = false;
$this->add_error( get_option('dbem_booking_feedback_email_exists') );
}else{
$user_data['user_email'] = $user_email;
}
//Check the user name
if( !empty($_REQUEST['user_name']) ){
//split full name up and save full, first and last names
$user_data['user_name'] = wp_kses(wp_unslash($_REQUEST['user_name']), array());
$name_string = explode(' ',$user_data['user_name']);
$user_data['first_name'] = array_shift($name_string);
$user_data['last_name'] = implode(' ', $name_string);
}else{
//Check the first/last name
$name_string = array();
if( !empty($_REQUEST['first_name']) ){
$user_data['first_name'] = $name_string[] = wp_kses(wp_unslash($_REQUEST['first_name']), array());
}
if( !empty($_REQUEST['last_name']) ){
$user_data['last_name'] = $name_string[] = wp_kses(wp_unslash($_REQUEST['last_name']), array());
}
if( !empty($name_string) ) $user_data['user_name'] = implode(' ', $name_string);
}
//Check the phone
if( !empty($_REQUEST['dbem_phone']) ){
$user_data['dbem_phone'] = wp_kses(wp_unslash($_REQUEST['dbem_phone']), array());
}
//Add booking meta
if( $registration ){
$this->booking_meta['registration'] = array_merge($this->booking_meta['registration'], $user_data); //in case someone else added stuff
}
$registration = apply_filters('em_booking_get_person_post', $registration, $this, $user_data);
if( $registration ){
$this->feedback_message = __('Personal details have successfully been modified.', 'events-manager');
}
return $registration;
}
/**
* Displays a form containing user fields, used in no-user booking mode for editing guest users within a booking
* @return string
*/
function get_person_editor(){
ob_start();
$name = $this->get_person()->get_name();
$email = $this->get_person()->user_email;
$phone = ($this->get_person()->phone != __('Not Supplied','events-manager')) ? $this->get_person()->phone:'';
if( !empty($_REQUEST['action']) && $_REQUEST['action'] == 'booking_modify_person' ){
$name = !empty($_REQUEST['user_name']) ? sanitize_text_field($_REQUEST['user_name']):$name;
$email = !empty($_REQUEST['user_email']) ? sanitize_email($_REQUEST['user_email']):$email;
$phone = !empty($_REQUEST['dbem_phone']) ? sanitize_text_field($_REQUEST['dbem_phone']):$phone;
}
?>
<table class="em-form-fields">
<tr><th><?php _e('Name','events-manager'); ?> : </th><td><input type="text" name="user_name" value="<?php echo esc_attr($name); ?>" ></td></tr>
<tr><th><?php _e('Email','events-manager'); ?> : </th><td><input type="text" name="user_email" value="<?php echo esc_attr($email); ?>" ></td></tr>
<tr><th><?php _e('Phone','events-manager'); ?> : </th><td><input type="tel" name="dbem_phone" value="<?php echo esc_attr($phone); ?>" ></td></tr>
<?php do_action('em_booking_get_person_editor_bottom', $this ); ?>
</table>
<?php
return apply_filters('em_booking_get_person_editor', ob_get_clean(), $this );
}
/**
* Returns a string representation of the booking's status
* @return string
*/
function get_status(){
if( $this->booking_status === false && isset($this->previous_status) ) {
$status_text = __('Deleted', 'events-manager');
}else{
$status_text = $this->status_array[ $this->booking_status ];
}
return apply_filters('em_booking_get_status', $status_text, $this);
}
/**
* I wonder what this does....
* @return boolean
*/
function delete(){
global $wpdb;
$result = false;
if( $this->can_manage('manage_bookings','manage_others_bookings') ){
$this->tickets_bookings = null; // reload tickets
$this->get_tickets_bookings(); // get this before bookings deleted from DB
$sql = $wpdb->prepare("DELETE FROM ". EM_BOOKINGS_TABLE . " WHERE booking_id=%d", $this->booking_id);
$result = $wpdb->query( $sql );
if( $result !== false ){
//delete the tickets too
$this->get_tickets_bookings()->delete();
$this->previous_status = $this->booking_meta['previous_status'] = $this->booking_status;
$this->booking_status = false;
$this->feedback_message = sprintf(__('%s deleted', 'events-manager'), __('Booking','events-manager'));
$wpdb->delete( EM_META_TABLE, array('meta_key'=>'booking-note', 'object_id' => $this->booking_id), array('%s','%d'));
$wpdb->delete( EM_BOOKINGS_META_TABLE, array('booking_id'=> $this->booking_id), array('%d'));
$this->deleted = true;
do_action('em_booking_deleted', $this);
}else{
$this->add_error(sprintf(__('%s could not be deleted', 'events-manager'), __('Booking','events-manager')));
}
}
do_action('em_bookings_deleted', $result, array($this->booking_id), array($this->event_id));
return apply_filters('em_booking_delete',( $result !== false ), $this);
}
/**
* Cancel a booking
* @param $email
* param array $email_args Overloaded
* @return bool
*/
function cancel( $email = true ){
$func_args = func_get_args();
$email_args = !empty($func_args[1]) ? $func_args[1] : array();
if( $this->get_person()->ID == get_current_user_id() ){
$this->manage_override = true; //normally, users can't manage a booking, only event owners, so we allow them to mod their booking status in this case only.
}
return $this->set_status(3, $email, false, $email_args);
}
/**
* Approve a booking.
* @param $email
* param array $email_args Overloaded
* @return bool
*/
function approve($email = true, $ignore_spaces = false ){
$func_args = func_get_args();
$email_args = !empty($func_args[2]) ? $func_args[2] : array();
return $this->set_status(1, $email, $ignore_spaces, $email_args);
}
/**
* Reject a booking and save
* @param $email
* param array $email_args Overloaded
* @return bool
*/
function reject($email = true ){
$func_args = func_get_args();
$email_args = !empty($func_args[1]) ? $func_args[1] : array();
return $this->set_status(2, $email, false, $email_args);
}
/**
* Unapprove a booking.
* @param $email
* param array $email_args Overloaded
* @return bool
*/
function unapprove( $email = true ){
$func_args = func_get_args();
$email_args = !empty($func_args[1]) ? $func_args[1] : array();
return $this->set_status(0, $email, false, $email_args);
}
function uncancel( $email = true, $email_args = array() ) {
if ( $this->can_uncancel() ) {
// get status to uncancel to
if ( isset($this->booking_meta['previous_status']) ) {
$status = $this->booking_meta['previous_status'];
if( $status == 2 ){ // we shouldn't reject an uncancelled booking!
$status = 0;
}
} elseif ( defined('EM_BOOKINGS_UNCANCEL_STATUS') ) {
$status = EM_BOOKINGS_UNCANCEL_STATUS;
} else {
$status = 0;
}
return $this->set_status( $status, $email, false, $email_args );
} else {
return false;
}
}
/**
* Change the status of the booking. This will save to the Database too.
* @param int $status
* @param bool $email
* @param bool $ignore_spaces
* param array $email_args Overloaded
* @return boolean
*/
function set_status($status, $email = true, $ignore_spaces = false ){
global $wpdb;
$action_string = strtolower($this->status_array[$status]);
//if we're approving we can't approve a booking if spaces are full, so check before it's approved.
if(!$ignore_spaces && $status == 1){
if( !$this->is_reserved() && $this->get_event()->get_bookings()->get_available_spaces() < $this->get_spaces() && !get_option('dbem_bookings_approval_overbooking') ){
$this->feedback_message = sprintf(__('Not approved, spaces full.','events-manager'), $action_string);
$this->add_error($this->feedback_message);
return apply_filters('em_booking_set_status', false, $this);
}
}
$this->previous_status = $this->booking_status;
$this->booking_status = absint($status);
$result = $wpdb->query($wpdb->prepare('UPDATE '.EM_BOOKINGS_TABLE.' SET booking_status=%d WHERE booking_id=%d', array($status, $this->booking_id)));
if($result !== false){
$this->update_meta('previous_status', $this->previous_status);
$this->feedback_message = sprintf(__('Booking %s.','events-manager'), $action_string);
$result = apply_filters('em_booking_set_status', $result, $this); // run the filter before emails go out, in case others need to hook in first
if( $result && $this->previous_status != $this->booking_status ){ //email if status has changed
do_action('em_booking_status_changed', $this, array('status' => $status, 'email' => $email, 'ignore_spaces' => $ignore_spaces)); // method params passed as array
if( $email ){
$func_args = func_get_args();
$email_args = !empty($func_args[3]) ? $func_args[3] : array();
$email_args = array_merge( array('email_admin'=> true, 'force_resend' => false, 'email_attendee' => true), $email_args );
if( $this->email( !empty($email_args['email_admin']), !empty($email_args['force_resend']), !empty($email_args['email_attendee'])) ){
if( $this->mails_sent > 0 ){
$this->feedback_message .= " ".__('Email Sent.','events-manager');
}
}else{
//extra errors may be logged by email() in EM_Object
$this->feedback_message .= ' <span style="color:red">'.__('ERROR : Email Not Sent.','events-manager').'</span>';
$this->add_error(__('ERROR : Email Not Sent.','events-manager'));
}
}
}
}else{
//errors should be logged by save()
$this->feedback_message = sprintf(__('Booking could not be %s.','events-manager'), $action_string);
$this->add_error(sprintf(__('Booking could not be %s.','events-manager'), $action_string));
$result = apply_filters('em_booking_set_status', false, $this);
}
return $result;
}
public function can_cancel(){
if( get_option('dbem_bookings_user_cancellation') && !in_array($this->booking_status, array(2,3)) ){
$cancellation_time = get_option('dbem_bookings_user_cancellation_time');
$can_cancel = $this->get_event()->start()->getTimestamp() > time(); // previously default was rsvp end
if( !empty($cancellation_time) && $cancellation_time > 0 ){
$EM_DateTime = $this->get_event()->start()->copy()->sub('PT'.$cancellation_time.'H');
$can_cancel = time() < $EM_DateTime->getTimestamp();
}elseif( static::is_dateinterval_string($cancellation_time) && $cancellation_time[0] !== '-' ){
$EM_DateTime = $this->get_event()->start()->copy()->sub($cancellation_time);
$can_cancel = time() < $EM_DateTime->getTimestamp();
}
}else{
$can_cancel = false;
}
return apply_filters('em_booking_can_cancel', $can_cancel, $this);
}
/*
* Bookings that are made since
*/
public function can_uncancel() {
$has_previous_status = isset( $this->booking_meta['previous_status'] ) || defined('EM_BOOKINGS_UNCANCEL_STATUS');
$can_uncancel = get_option('dbem_bookings_user_uncancellation') && $this->validate() && $has_previous_status;
return apply_filters('em_booking_can_uncancel', $can_uncancel, $this);
}
public static function is_dateinterval_string( $string ){
return preg_match('/^\-?P(([0-9]+[YMDW])+)?(T(([0-9]+[HMS])+))?$/', $string);
}
/**
* Returns true if booking is reserving a space at this event, whether confirmed or not
*/
function is_reserved(){
$result = false;
if( $this->booking_status == 0 && get_option('dbem_bookings_approval_reserved') ){
$result = true;
}elseif( $this->booking_status == 0 && !get_option('dbem_bookings_approval') ){
$result = true;
}elseif( $this->booking_status == 1 ){
$result = true;
}
return apply_filters('em_booking_is_reserved', $result, $this);
}
/**
* Returns true if booking is associated with a non-registered user, i.e. booked as a guest 'no user mode'.
* @return mixed
*/
function is_no_user(){
return apply_filters('em_booking_is_no_user', $this->get_person()->ID === 0, $this);
}
/**
* Returns true if booking is either pending but not confirmed (which is assumed pending).
* Pending bookings do not mean they are necessarily reserved spaces, check is_reserved() for that.
*/
function is_pending(){
$result = ($this->is_reserved() || $this->booking_status == 0) && $this->booking_status != 1;
return apply_filters('em_booking_is_pending', $result, $this);
}
/**
* Returns true if booking is approved, i.e. confirmed. Takes into account that pending bookings may be auto-approved.
* @return bool
*/
function is_approved() {
$result = $this->booking_status == 1 || ($this->booking_status == 0 && !get_option('dbem_bookings_approval'));
return apply_filters('em_booking_is_approved', $result, $this);
}
/**
* Set RSVP status to given number. Null sets booking to unconfirmed.
*
* @param null|int $status
* @param array $args
*
* @return bool
*/
function set_rsvp_status( $status, $args = array() ) {
global $wpdb;
// get status strings
$action_string = static::get_rsvp_statuses( $status )->label;
//if we're approving we can't approve a booking if spaces are full, so check before it's approved.
$this->previous_rsvp_status = $this->booking_rsvp_status;
$this->booking_rsvp_status = ( $status !== null && $status <= 2 && $status >= 0 ) ? absint($status) : null;
if ( $this->booking_rsvp_status === null ) {
$result = $wpdb->query($wpdb->prepare('UPDATE '.EM_BOOKINGS_TABLE.' SET booking_rsvp_status=NULL WHERE booking_id=%d', array($this->booking_id)));
} else {
$result = $wpdb->query($wpdb->prepare('UPDATE '.EM_BOOKINGS_TABLE.' SET booking_rsvp_status=%d WHERE booking_id=%d', array($this->booking_rsvp_status, $this->booking_id)));
}
if ( $result !== false ) {
$this->feedback_message = esc_html__( sprintf(__("Booking RSVP status set to '%s'.",'events-manager'), $action_string) );
$result = apply_filters('em_booking_set_rsvp_status', true, $this);
if( $result && $this->previous_rsvp_status != $this->booking_rsvp_status ){ // act on booking status if there's a change in rsvp
do_action('em_booking_rsvp_status_changed', $this, $status, $args); // method params passed as array
if( $this->booking_rsvp_status === 0 && get_option('dbem_bookings_rsvp_sync_cancel') ) {
$this->cancel();
} elseif ( $this->booking_rsvp_status === 1 && get_option('dbem_bookings_rsvp_sync_confirm') ) {
$this->set_status(1);
} elseif( $this->previous_rsvp_status === 0 && $this->can_uncancel() ) {
$this->uncancel();
}
}
$this->feedback_message = static::get_rsvp_statuses($status)->confirmation;
} else {
//errors should be logged by save()
$this->feedback_message = sprintf(__('Booking could not be %s.','events-manager'), $action_string);
$this->add_error(sprintf(__('Booking could not be %s.','events-manager'), $action_string));
$result = apply_filters('em_booking_set_rsvp_status', false, $this, $args);
}
return $result;
}
/**
* Get RSVP Status equivalents
* @param $text
* @param $status
*
* @return int|string|null
*/
function get_rsvp_status( $text = false ) {
if( $text ) {
$status = static::get_rsvp_statuses( $this->booking_rsvp_status );
return apply_filters('em_booking_get_rsvp_status_text', $status->label, $this, array('text' => $text, 'status' => $status));
}
return apply_filters('em_booking_get_rsvp_status', $this->booking_rsvp_status, $this);
}
/**
* Sets the RSVP status of a booking to 'Maybe' (status 2), not to be confused with the actual status of the booking.
* @param $args
*
* @return bool
*/
public function can_change_rsvp() {
$can_change = false;
$changeable_statuses = apply_filters( 'em_booking_statuses_rsvp_changeable', array(0,1,3), $this );
if ( get_option('dbem_bookings_rsvp_can_change') && in_array( $this->booking_status, $changeable_statuses) ) {
if ( $this->booking_status == 3 && $this->can_uncancel() ) {
$can_change = true;
} else {
$can_change = true;
}
}
return apply_filters( 'can_change_rsvp', $can_change, $this );
}
/**
* Returns true or false if user can RSVP a certain status, null if the current status is already the one requested.
* @param int|null $status
*
* @return mixed|null
*/
public function can_rsvp( $status ) {
$result = false;
if( $this->is_approved() ) {
if( get_option( 'dbem_bookings_rsvp' ) ) {
// check if we're changing the RSVP or doing anew with a specific status
if ( $this->booking_rsvp_status !== null && $this->can_change_rsvp() ) {
$can_rsvp = true;
} else {
$rsvpable_booking_statuses = apply_filters( 'em_booking_rsvpable_booking_statuses', array( 0, 1 ) );
if ( $this->booking_status === 3 && get_option( 'dbem_bookings_rsvp_sync_cancel' ) && $this->can_uncancel() ) {
$rsvpable_booking_statuses[] = 3;
}
$can_rsvp = in_array( $this->booking_status, $rsvpable_booking_statuses );
}
// general RSVP possible, now go deeper
if ( $can_rsvp ) {
if( $status !== null ) $status = absint( $status );
if ( $status === null ) { // unconfirm
$result = $this->can_manage(); // we cannot unconfirm unless an admin
} elseif ( $status === 0 ) { // cancel
if ( get_option( 'dbem_bookings_rsvp_sync_cancel' ) && $this->booking_rsvp_status !== $status ) {
$result = $this->can_cancel();
} else {
$result = true;
}
} elseif ( $status === 1 ) { // confirm
$result = true;
} elseif ( $status === 2 ) { // maybe
if ( get_option( 'dbem_bookings_rsvp_maybe' ) ) {
$result = true;
}
}
if( $result ){
$result = $this->booking_rsvp_status === $status ? null : true;
}
}
}
}
return apply_filters('em_booking_can_rsvp', $result, $this, $status );
}
public static function get_rsvp_statuses( $status = false ) {
if( empty(static::$rsvp_statuses) ) {
$statuses = array(
null => array(
'label' => __('Unconfirmed', 'events-manager'),
'label_action' => __('Unconfirm', 'events-manager'),
'action' => 'unconfirm',
'confirmation' => __('Your booking is now unconfirmed', 'events-manager'),
),
0 => array(
'label' => __('Not Attending', 'events-manager'),
'label_action' => sprintf( __('RSVP - %s', 'events-manager'), __('No') ),
'label_answer' => __('No'),
'confirmation' => __('You have declined your attendance.', 'events-manager'),
'action' => 'decline',
),
1 => array(
'label' => __('Attending', 'events-manager'),
'label_action' => sprintf( __('RSVP - %s', 'events-manager'), __('Yes') ),
'label_answer' => __('Yes'),
'action' => 'confirm',
'confirmation' => __('You have confirmed your attendance.', 'events-manager'),
),
);
if( get_option('dbem_bookings_rsvp_sync_cancel') ) {
$statuses[0] = array_merge( $statuses[0], array(
'confirmation' => __('You have declined your attendance, your booking is now cancelled.', 'events-manager'),
));
}
if( get_option('dbem_bookings_rsvp_maybe') ) {
$statuses[2] = array(
'label' => __('Maybe Attending', 'events-manager'),
'label_action' => sprintf( __('RSVP - %s', 'events-manager'), __('Maybe', 'events-manager') ),
'label_answer' => __('Maybe', 'events-manager'),
'action' => 'maybe',
'confirmation' => __('You have not definitively confrimed your attendance.', 'events-manager'),
);
}
$statuses = apply_filters( 'em_booking_get_rsvp_statuses', $statuses );
foreach( $statuses as $k => $s ) $statuses[$k] = (object) $s;
static::$rsvp_statuses = $statuses;
}
if ( $status !== false ) {
return !empty(static::$rsvp_statuses[$status]) ? static::$rsvp_statuses[$status] : static::$rsvp_statuses[null];
}
return static::$rsvp_statuses;
}
/**
* Add a booking note to this booking. returns wpdb result or false if use can't manage this event.
* @param string $note
* @return mixed
*/
function add_note( $note_text ){
global $wpdb;
if( $this->can_manage() ){
$this->get_notes();
$note = array('author'=>get_current_user_id(),'note'=>wp_kses_data($note_text),'timestamp'=>time());
$this->notes[] = $note;
$this->feedback_message = __('Booking note successfully added.','events-manager');
return $wpdb->insert(EM_META_TABLE, array('object_id'=>$this->booking_id, 'meta_key'=>'booking-note', 'meta_value'=> serialize($note)),array('%d','%s','%s'));
}
return false;
}
function get_admin_url(){
if( get_option('dbem_edit_bookings_page') && (!is_admin() || !empty($_REQUEST['is_public'])) ){
$my_bookings_page = get_permalink(get_option('dbem_edit_bookings_page'));
$bookings_link = em_add_get_params($my_bookings_page, array('event_id'=>$this->event_id, 'booking_id'=>$this->booking_id), false);
}else{
if( $this->get_event()->blog_id != get_current_blog_id() ){
$bookings_link = get_admin_url($this->get_event()->blog_id, 'edit.php?post_type='.EM_POST_TYPE_EVENT."&page=events-manager-bookings&event_id=".$this->event_id."&booking_id=".$this->booking_id);
}else{
$bookings_link = EM_ADMIN_URL. "&page=events-manager-bookings&event_id=".$this->event_id."&booking_id=".$this->booking_id;
}
}
return apply_filters('em_booking_get_bookings_url', $bookings_link, $this);
}
function output($format, $target="html") {
do_action('em_booking_output_pre', $this, $format, $target);
$output_string = $format;
for ($i = 0 ; $i < EM_CONDITIONAL_RECURSIONS; $i++){
preg_match_all('/\{([a-zA-Z0-9_\-,]+)\}(.+?)\{\/\1\}/s', $output_string, $conditionals);
if( count($conditionals[0]) > 0 ){
//Check if the language we want exists, if not we take the first language there
foreach ($conditionals[1] as $key => $condition) {
$show_condition = apply_filters('em_booking_output_show_condition', false, array('format' => $format, 'target' => $target, 'condition' => $condition, 'conditionals' => $conditionals, 'key' => $key), $this );
if ($condition == 'has_rsvp_reply') { //check if there's an rsvp
$show_condition = $this->booking_rsvp_status !== null;
} elseif ( $condition == 'no_rsvp_reply' ) { //check if there's no rsvp
$show_condition = $this->booking_rsvp_status === null;
} elseif ( $condition == 'is_rsvp_reply_no' ) { //check if there's no rsvp
$show_condition = $this->booking_rsvp_status === 0;
} elseif ( $condition == 'is_rsvp_reply_yes' ) { //check if there's no rsvp
$show_condition = $this->booking_rsvp_status === 1;
} elseif ( $condition == 'is_rsvp_reply_maybe' ) { //check if there's no rsvp
$show_condition = $this->booking_rsvp_status === 2;
} elseif ( preg_match('/^is_rsvp_reply_([0-9]+)$/', $condition, $matches ) ) { //check if there's no rsvp
$show_condition = $this->booking_rsvp_status == $matches[1];
}
if( $show_condition ){
//calculate lengths to delete placeholders
$placeholder_length = strlen($condition)+2;
$replacement = substr($conditionals[0][$key], $placeholder_length, strlen($conditionals[0][$key])-($placeholder_length *2 +1));
$output_string = str_replace($conditionals[0][$key], apply_filters('em_booking_output_condition', $replacement, $condition, $conditionals[0][$key], $this), $output_string);
}
}
}
}
preg_match_all("/(#@?_?[A-Za-z0-9_]+)({([^}]+)})?/", $output_string, $placeholders);
foreach( $this->get_tickets() as $EM_Ticket){ /* @var $EM_Ticket EM_Ticket */ break; } //Get first ticket for single ticket placeholders
$replaces = array();
foreach($placeholders[1] as $key => $result) {
$replace = '';
$full_result = $placeholders[0][$key];
$placeholder_atts = array($result);
if( !empty($placeholders[3][$key]) ) $placeholder_atts[] = $placeholders[3][$key];
switch( $result ){
case '#_BOOKINGID':
$replace = $this->booking_id;
break;
case '#_BOOKING_UUID':
$replace = $this->booking_uuid;
break;
case '#_RESPNAME' : //deprecated
case '#_BOOKINGNAME':
$replace = $this->get_person()->get_name();
break;
case '#_RESPEMAIL' : //deprecated
case '#_BOOKINGEMAIL':
$replace = $this->get_person()->user_email;
break;
case '#_RESPPHONE' : //deprecated
case '#_BOOKINGPHONE':
$replace = $this->get_person()->phone;
break;
case '#_BOOKINGSPACES':
$replace = $this->get_spaces();
break;
case '#_BOOKINGDATE':
$replace = ( $this->date() !== false ) ? $this->date()->i18n( em_get_date_format() ):'n/a';
break;
case '#_BOOKINGTIME':
$replace = ( $this->date() !== false ) ? $this->date()->i18n( em_get_hour_format() ):'n/a';
break;
case '#_BOOKINGDATETIME':
$replace = ( $this->date() !== false ) ? $this->date()->i18n( em_get_date_format().' '.em_get_hour_format()):'n/a';
break;
case '#_BOOKINGLISTURL':
$replace = em_get_my_bookings_url();
break;
case '#_COMMENT' : //deprecated
case '#_BOOKINGCOMMENT':
$replace = $this->booking_comment;
break;
case '#_BOOKINGPRICEWITHOUTTAX':
$replace = $this->format_price($this->get_price() - $this->get_price_taxes());
break;
case '#_BOOKINGPRICETAX':
$replace = $this->get_price_taxes(true);
break;
case '#_BOOKINGPRICEWITHTAX':
case '#_BOOKINGPRICE':
$replace = $this->get_price(true);
break;
case '#_BOOKINGTICKETNAME':
$replace = $EM_Ticket->name;
break;
case '#_BOOKINGTICKETDESCRIPTION':
$replace = $EM_Ticket->description;
break;
case '#_BOOKINGTICKETPRICEWITHTAX':
$replace = $this->format_price( $EM_Ticket->get_price_without_tax() * (1+$this->get_tax_rate()/100) );
break;
case '#_BOOKINGTICKETPRICEWITHOUTTAX':
$replace = $EM_Ticket->get_price_without_tax(true);
break;
case '#_BOOKINGTICKETTAX':
$replace = $this->format_price( $EM_Ticket->get_price_without_tax() * ($this->get_tax_rate()/100) );
break;
case '#_BOOKINGTICKETPRICE':
$replace = $EM_Ticket->get_price(true);
break;
case '#_BOOKINGTICKETS':
ob_start();
em_locate_template('emails/bookingtickets.php', true, array('EM_Booking'=>$this));
$replace = ob_get_clean();
break;
case '#_BOOKINGSUMMARY':
ob_start();
em_locate_template('emails/bookingsummary.php', true, array('EM_Booking'=>$this));
$replace = ob_get_clean();
break;
case '#_BOOKINGADMINURL':
case '#_BOOKINGADMINLINK':
$bookings_link = esc_url( add_query_arg('booking_id', $this->booking_id, $this->get_event()->get_bookings_url()) );
if($result == '#_BOOKINGADMINLINK'){
$replace = '<a href="'.$bookings_link.'">'.esc_html__('Edit Booking', 'events-manager'). '</a>';
}else{
$replace = $bookings_link;
}
break;
case '#_BOOKINGSTATUS':
case '#_BOOKING_STATUS':
$replace = $this->get_status();
break;
case '#_BOOKINGRSVPSTATUS':
case '#_BOOKING_RSVP_STATUS':
$replace = $this->get_rsvp_status( true );
break;
default:
$replace = $this->output_placeholder( $full_result, $placeholder_atts, $format, $target );
break;
}
$replaces[$full_result] = apply_filters('em_booking_output_placeholder', $replace, $this, $full_result, $target, $placeholder_atts);
}
//sort out replacements so that during replacements shorter placeholders don't overwrite longer varieties.
krsort($replaces);
foreach($replaces as $full_result => $replacement){
$output_string = str_replace($full_result, $replacement , $output_string );
}
//run event output too, since this is never run from within events and will not infinitely loop
$EM_Event = apply_filters('em_booking_output_event', $this->get_event(), $this); //allows us to override the booking event info if it belongs to a parent or translation
$output_string = $EM_Event->output($output_string, $target);
return apply_filters('em_booking_output', $output_string, $this, $format, $target);
}
/**
* Function mainly aimed for overriding by extending classes, avoiding the need to use a filter instead.
* @param string $full_result
* @param array $placeholder_atts
* @param string $format
* @param string $target
* @return string
*/
public function output_placeholder( $full_result, $placeholder_atts, $format, $target ){
// $placeholder = $placeholder_atts[0]; // this is the placeholder, no atts
return $full_result;
}
public function output_intent_html(){
$input = '<input type="hidden" name="booking_intent" value="' . esc_attr($this->booking_uuid) .'" class="em-booking-intent" id="em-booking-intent-'. esc_attr($this->event_id) .'"';
foreach( $this->get_intent_data() as $key => $value ){
$input .= ' data-'.$key.'="'. esc_attr($value) .'"';
}
$input .= '>';
return apply_filters('em_booking_output_intent_html', $input, $this);
}
public function get_intent_data(){
return array(
'uuid' => $this->booking_uuid,
'event_id' => $this->event_id,
'spaces' => $this->get_spaces(),
'amount' => $this->get_price(),
'amount_formatted' => $this->get_price( true ),
'amount_base' => $this->get_price_base(),
'taxes' => $this->get_price_taxes(),
'currency' => $this->get_currency(),
);
}
/**
* @param boolean $email_admin
* @param boolean $force_resend
* @param boolean $email_attendee
* @return boolean
*/
function email( $email_admin = true, $force_resend = false, $email_attendee = true ){
$result = true;
$this->mails_sent = 0;
//Make sure event matches booking, and that booking used to be approved.
if( $this->booking_status !== $this->previous_status || $force_resend ){
// before we format dates or any other language-specific placeholders, make sure we're translating the site language, not the user profile language in the admin area (e.g. if an admin is sending a booking confirmation email), assuming this isn't a ML-enabled site.
if( !EM_ML::$is_ml && is_admin() && EM_ML::$wplang != get_user_locale() ) EM_ML::switch_locale(EM_ML::$wplang);
do_action('em_booking_email_before_send', $this);
//get event info and refresh all bookings
$EM_Event = $this->get_event(); //We NEED event details here.
$EM_Event->get_bookings(true); //refresh all bookings
//messages can be overridden just before being sent
$msg = $this->email_messages();
$filter_args = array('email_admin'=> true, 'force_resend' => $force_resend, 'email_attendee' => $email_attendee, 'msg' => $msg );
//Send user (booker) emails
if( !empty($msg['user']['subject']) && $email_attendee ){
$result = $this->email_attendee( $msg, $filter_args );
}
//Send admin/contact emails if this isn't the event owner or an events admin
if( $email_admin && !empty($msg['admin']['subject']) ){ //emails won't be sent if admin is logged in unless they book themselves
$result = $this->email_admins( $msg );
}
do_action('em_booking_email_after_send', $this);
if( !EM_ML::$is_ml && is_admin() ) EM_ML::restore_locale(); // restore the locale back for the rest of the site, which will happen if we switched it earlier
}
return apply_filters('em_booking_email', $result, $this, $email_admin, $force_resend, $email_attendee);
//TODO need error checking for booking mail send
}
function email_attendee( $msg, $filter_args = null ){
$result = true;
if( !$filter_args ){
$filter_args = array('email_admin'=> true, 'force_resend' => true, 'email_attendee' => false, 'msg' => $msg );
}
$msg['user']['subject'] = $this->output($msg['user']['subject'], 'raw');
$msg['user']['body'] = $this->output($msg['user']['body'], 'email');
$attachments = array();
if( !empty($msg['user']['attachments']) && is_array($msg['user']['attachments']) ){
$attachments = $msg['user']['attachments'];
}
//add extra args
$args = array();
if( get_option('dbem_bookings_replyto_owner') && $this->get_event()->get_contact()->user_email ){
$args['reply-to'] = $this->get_event()->get_contact()->user_email;
$args['reply-to-name'] = $this->get_event()->get_contact()->display_name;
}
$args = apply_filters('em_booking_email_user_args', $args, $filter_args, $this);
//Send to the person booking
if( !$this->email_send( $msg['user']['subject'], $msg['user']['body'], $this->get_person()->user_email, $attachments, $args) ){
$result = false;
}else{
$this->mails_sent++;
}
return $result;
}
function email_admins( $msg, $filter_args = null ){
$result = true;
$EM_Event = $this->get_event(); //We NEED event details here.
if( !$filter_args ){
$filter_args = array('email_admin'=> true, 'force_resend' => true, 'email_attendee' => false, 'msg' => $msg );
}
//get admin emails that need to be notified, hook here to add extra admin emails
$admin_emails = str_replace(' ','',get_option('dbem_bookings_notify_admin'));
$admin_emails = apply_filters('em_booking_admin_emails', explode(',', $admin_emails), $this); //supply emails as array
if( get_option('dbem_bookings_contact_email') == 1 && !empty($EM_Event->get_contact()->user_email) ){
//add event owner contact email to list of admin emails
$admin_emails[] = $EM_Event->get_contact()->user_email;
}
foreach($admin_emails as $key => $email){ if( !is_email($email) ) unset($admin_emails[$key]); } //remove bad emails
//add extra args
$args = array();
if( get_option('dbem_bookings_replyto_owner_admins') && $this->get_event()->get_contact()->user_email ){
$args['reply-to'] = $this->get_event()->get_contact()->user_email;
$args['reply-to-name'] = $this->get_event()->get_contact()->display_name;
}
$args = apply_filters('em_booking_email_admin_args', $args, $filter_args, $this);
//proceed to email admins if need be
if( !empty($admin_emails) ){
//Only gets sent if this is a pending booking, unless approvals are disabled.
$msg['admin']['subject'] = $this->output($msg['admin']['subject'],'raw');
$msg['admin']['body'] = $this->output($msg['admin']['body'], 'email');
$attachments = array();
if( !empty($msg['admin']['attachments']) && is_array($msg['admin']['attachments']) ){
$attachments = $msg['admin']['attachments'];
}
//email admins
if( !$this->email_send( $msg['admin']['subject'], $msg['admin']['body'], $admin_emails, $attachments, $args) && current_user_can('manage_options') ){
$this->errors[] = __('Confirmation email could not be sent to admin. Registrant should have gotten their email (only admin see this warning).','events-manager');
$result = false;
}else{
$this->mails_sent++;
}
}
return $result;
}
function email_messages(){
$msg = array( 'user'=> array('subject'=>'', 'body'=>'', 'attachments' => array()), 'admin'=> array('subject'=>'', 'body'=>'', 'attachments' => array())); //blank msg template
//admin messages won't change whether pending or already approved
switch( $this->booking_status ){
case 0:
case 5: //TODO remove offline status from here and move to pro
$msg['user']['subject'] = get_option('dbem_bookings_email_pending_subject');
$msg['user']['body'] = get_option('dbem_bookings_email_pending_body');
//admins should get something (if set to)
$msg['admin']['subject'] = get_option('dbem_bookings_contact_email_pending_subject');
$msg['admin']['body'] = get_option('dbem_bookings_contact_email_pending_body');
break;
case 1:
$msg['user']['subject'] = get_option('dbem_bookings_email_confirmed_subject');
$msg['user']['body'] = get_option('dbem_bookings_email_confirmed_body');
//admins should get something (if set to)
$msg['admin']['subject'] = get_option('dbem_bookings_contact_email_confirmed_subject');
$msg['admin']['body'] = get_option('dbem_bookings_contact_email_confirmed_body');
break;
case 2:
$msg['user']['subject'] = get_option('dbem_bookings_email_rejected_subject');
$msg['user']['body'] = get_option('dbem_bookings_email_rejected_body');
//admins should get something (if set to)
$msg['admin']['subject'] = get_option('dbem_bookings_contact_email_rejected_subject');
$msg['admin']['body'] = get_option('dbem_bookings_contact_email_rejected_body');
break;
case 3:
$msg['user']['subject'] = get_option('dbem_bookings_email_cancelled_subject');
$msg['user']['body'] = get_option('dbem_bookings_email_cancelled_body');
//admins should get something (if set to)
$msg['admin']['subject'] = get_option('dbem_bookings_contact_email_cancelled_subject');
$msg['admin']['body'] = get_option('dbem_bookings_contact_email_cancelled_body');
break;
}
return apply_filters('em_booking_email_messages', $msg, $this);
}
/**
* Returns an EM_DateTime representation of when booking was made in UTC timezone. If no valid date defined, false will be returned
* @param boolean $utc_timezone
* @return EM_DateTime
* @throws Exception
*/
public function date( $utc_timezone = false ){
if( empty($this->date) || !$this->date->valid ){
if( !empty($this->booking_date ) ){
$this->date = new EM_DateTime($this->booking_date, 'UTC');
}else{
//we retrn a date regardless but it's not based on a 'valid' booking date
$this->date = new EM_DateTime();
$this->date->valid = false;
}
}
//Set to UTC timezone if requested, local blog time by default
if( $utc_timezone ){
$timezone = 'UTC';
}else{
//we could set this to false but this way we might avoid creating a new timezone if it's already in this one
$timezone = get_option( 'timezone_string' );
if( !$timezone ) $timezone = get_option('gmt_offset');
}
$this->date->setTimezone($timezone);
return $this->date;
}
/**
* Can the user manage this event?
*/
function can_manage( $owner_capability = false, $admin_capability = false, $user_to_check = false ){
return $this->get_event()->can_manage('manage_bookings','manage_others_bookings') || empty($this->booking_id) || !empty($this->manage_override);
}
/**
* Returns this object in the form of an array
* @return array
*/
function to_array($person = false){
$booking = array();
//Core Data
$booking = parent::to_array();
//Person Data
if($person && is_object($this->person)){
$person = $this->person->to_array();
$booking = array_merge($booking, $person);
}
return $booking;
}
function to_api( $args = array('event' => true), $version = 'v1' ){
$booking = array (
'id' => $this->booking_id,
'event_id' => $this->event_id,
'uuid' => $this->booking_uuid,
'person_id' => $this->person_id,
'status' => $this->booking_status,
'spaces' => $this->booking_spaces,
'price' => $this->get_price(),
'tax_rate' => $this->get_tax_rate(true), // returned as decimal/percen
'taxes' => $this->booking_taxes,
'comment' => $this->booking_comment,
'meta' => $this->booking_meta,
'tickets' => array(),
'datetime' => $this->booking_date,
);
// add tickets
foreach ( $this->get_tickets_bookings() as $EM_Ticket_Bookings ){
$booking['tickets'][$EM_Ticket_Bookings->ticket_id] = array(
'name' => $EM_Ticket_Bookings->get_ticket()->ticket_name,
'description' => $EM_Ticket_Bookings->get_ticket()->ticket_name,
'spaces' => $EM_Ticket_Bookings->get_spaces(),
'price' => $EM_Ticket_Bookings->get_price(),
'attendees' => array(),
);
foreach ( $EM_Ticket_Bookings as $EM_Ticket_Booking ){
$booking['tickets'][$EM_Ticket_Bookings->ticket_id]['attendees'][] = array(
'uuid' => $EM_Ticket_Booking->ticket_uuid,
'price' => $EM_Ticket_Booking->ticket_booking_price,
'meta' => $EM_Ticket_Booking->meta,
);
}
}
// if event data should be sent
if( !empty($args['event']) ) {
$booking['event'] = $this->get_event()->to_api();
}
// user
$booking['person'] = array(
'guest' => false,
'email' => $this->get_person()->user_email,
'name' => $this->get_person()->get_name(),
);
if( $this->get_person()->phone ){
$booking['person']['phone'] = $this->get_person()->phone;
}
return apply_filters('em_booking_to_api', $booking, array(), $this);
}
}
?>