/**
  * Calculate costs from posted values
  * @param  array $posted
  * @return string cost
  */
 public function calculate_booking_cost($posted)
 {
     if (!empty($this->booking_cost)) {
         return $this->booking_cost;
     }
     // Get costs
     $costs = $this->product->get_costs();
     // Get posted data
     $data = $this->get_posted_data($posted);
     $validate = $this->is_bookable($data);
     if (is_wp_error($validate)) {
         return $validate;
     }
     $base_cost = max(0, $this->product->wc_booking_cost);
     $base_block_cost = max(0, $this->product->wc_booking_base_cost);
     $total_block_cost = 0;
     $person_block_costs = 0;
     // See if we have an auto_assigned_resource_id
     if (isset($this->auto_assigned_resource_id)) {
         $data['_resource_id'] = $this->auto_assigned_resource_id;
     }
     // Get resource cost
     if (isset($data['_resource_id'])) {
         $resource = $this->product->get_resource($data['_resource_id']);
         $base_block_cost += $resource->get_block_cost();
         $base_cost += $resource->get_base_cost();
     }
     // Potentially increase costs if dealing with persons
     if (!empty($data['_persons'])) {
         if ($this->product->has_person_types()) {
             foreach ($data['_persons'] as $person_id => $person_count) {
                 $person_cost = get_post_meta($person_id, 'cost', true);
                 $person_block_cost = get_post_meta($person_id, 'block_cost', true);
                 // Only a single cost - multiplication comes later if wc_booking_person_cost_multiplier is enabled
                 if ($person_count > 0) {
                     if ($person_cost > 0) {
                         $base_cost += $person_cost * $person_count;
                     }
                     if ($person_block_cost > 0) {
                         $person_block_costs += $person_block_cost * $person_count;
                     }
                 }
             }
         }
     }
     $this->applied_cost_rules = array();
     $block_duration = $this->product->get_duration();
     $block_unit = $this->product->get_duration_unit();
     $blocks_booked = isset($data['_duration']) ? absint($data['_duration']) : $block_duration;
     $block_timestamp = $data['_start_date'];
     if ($this->product->is_duration_type('fixed')) {
         $blocks_booked = ceil($blocks_booked / $block_duration);
     }
     // Evaluate costs for each booked block
     for ($block = 0; $block < $blocks_booked; $block++) {
         $block_cost = $base_block_cost + $person_block_costs;
         $block_start_time_offset = $block * $block_duration;
         $block_end_time_offset = ($block + 1) * $block_duration;
         $block_start_time = $this->get_formatted_times(strtotime("+{$block_start_time_offset} {$block_unit}", $block_timestamp));
         $block_end_time = $this->get_formatted_times(strtotime("+{$block_end_time_offset} {$block_unit}", $block_timestamp));
         foreach ($costs as $rule_key => $rule) {
             $type = $rule[0];
             $rules = $rule[1];
             if (strrpos($type, 'time') === 0) {
                 if (!in_array($this->product->get_duration_unit(), array('minute', 'hour'))) {
                     continue;
                 }
                 if (!empty($rules['day'])) {
                     if ($rules['day'] != $block_start_time['day_of_week']) {
                         continue;
                     }
                 }
                 $rule_start_time_hi = date("YmdHi", strtotime(str_replace(':', '', $rules['from']), $block_start_time['timestamp']));
                 $rule_end_time_hi = date("YmdHi", strtotime(str_replace(':', '', $rules['to']), $block_start_time['timestamp']));
                 $matched = false;
                 // Reverse time rule - The end time is tomorrow e.g. 16:00 today - 12:00 tomorrow
                 if ($rule_end_time_hi <= $rule_start_time_hi) {
                     if ($block_end_time['time'] > $rule_start_time_hi) {
                         $matched = true;
                     }
                     if ($block_start_time['time'] >= $rule_start_time_hi && $block_end_time['time'] >= $rule_end_time_hi) {
                         $matched = true;
                     }
                     if ($block_start_time['time'] <= $rule_start_time_hi && $block_end_time['time'] <= $rule_end_time_hi) {
                         $matched = true;
                     }
                     // Normal rule
                 } else {
                     if ($block_start_time['time'] >= $rule_start_time_hi && $block_end_time['time'] <= $rule_end_time_hi) {
                         $matched = true;
                     }
                 }
                 if ($matched) {
                     $block_cost = $this->apply_cost($block_cost, $rules['rule']['block'][0], $rules['rule']['block'][1]);
                     $base_cost = $this->apply_base_cost($base_cost, $rules['rule']['base'][0], $rules['rule']['base'][1], $rule_key);
                 }
             } else {
                 switch ($type) {
                     case 'months':
                     case 'weeks':
                     case 'days':
                         $check_date = $block_start_time['timestamp'];
                         while ($check_date < $block_end_time['timestamp']) {
                             $checking_date = $this->get_formatted_times($check_date);
                             $date_key = $type == 'days' ? 'day_of_week' : substr($type, 0, -1);
                             if (isset($rules[$checking_date[$date_key]])) {
                                 $rule = $rules[$checking_date[$date_key]];
                                 $block_cost = $this->apply_cost($block_cost, $rule['block'][0], $rule['block'][1]);
                                 $base_cost = $this->apply_base_cost($base_cost, $rule['base'][0], $rule['base'][1], $rule_key);
                             }
                             $check_date = strtotime("+1 {$type}", $check_date);
                         }
                         break;
                     case 'custom':
                         $check_date = $block_start_time['timestamp'];
                         while ($check_date < $block_end_time['timestamp']) {
                             $checking_date = $this->get_formatted_times($check_date);
                             if (isset($rules[$checking_date['year']][$checking_date['month']][$checking_date['day']])) {
                                 $rule = $rules[$checking_date['year']][$checking_date['month']][$checking_date['day']];
                                 $block_cost = $this->apply_cost($block_cost, $rule['block'][0], $rule['block'][1]);
                                 $base_cost = $this->apply_base_cost($base_cost, $rule['base'][0], $rule['base'][1], $rule_key);
                             }
                             $check_date = strtotime("+1 day", $check_date);
                         }
                         break;
                     case 'persons':
                         if (!empty($data['_persons'])) {
                             if ($rules['from'] <= array_sum($data['_persons']) && $rules['to'] >= array_sum($data['_persons'])) {
                                 $block_cost = $this->apply_cost($block_cost, $rules['rule']['block'][0], $rules['rule']['block'][1]);
                                 $base_cost = $this->apply_base_cost($base_cost, $rules['rule']['base'][0], $rules['rule']['base'][1], $rule_key);
                             }
                         }
                         break;
                     case 'blocks':
                         if (!empty($data['_duration'])) {
                             if ($rules['from'] <= $data['_duration'] && $rules['to'] >= $data['_duration']) {
                                 $block_cost = $this->apply_cost($block_cost, $rules['rule']['block'][0], $rules['rule']['block'][1]);
                                 $base_cost = $this->apply_base_cost($base_cost, $rules['rule']['base'][0], $rules['rule']['base'][1], $rule_key);
                             }
                         }
                         break;
                 }
             }
         }
         $total_block_cost += $block_cost;
     }
     // Person multiplier mutliplies all costs
     $this->booking_cost = max(0, $total_block_cost + $base_cost);
     if (!empty($data['_persons'])) {
         if ('yes' === $this->product->wc_booking_person_cost_multiplier) {
             $this->booking_cost = $this->booking_cost * array_sum($data['_persons']);
         }
     }
     return apply_filters('booking_form_calculated_booking_cost', $this->booking_cost, $this, $posted);
 }
 /**
  * Checks booking data is correctly set, and that the chosen blocks are indeed available.
  *
  * @param  array $data
  * @return WP_Error on failure, true on success
  */
 public function is_bookable($data)
 {
     // Validate resources are set
     if ($this->product->has_resources() && $this->product->is_resource_assignment_type('customer')) {
         if (empty($data['_resource_id'])) {
             return new WP_Error('Error', __('Please choose a resource type', 'woocommerce-bookings'));
         }
     } elseif ($this->product->has_resources() && $this->product->is_resource_assignment_type('automatic')) {
         $data['_resource_id'] = 0;
     } else {
         $data['_resource_id'] = '';
     }
     // Validate customer set durations
     if ($this->product->is_duration_type('customer')) {
         if (empty($data['_duration'])) {
             return new WP_Error('Error', __('Duration is required - please enter a duration greater than zero above', 'woocommerce-bookings'));
         }
         if ($data['_duration'] > $this->product->get_max_duration()) {
             return new WP_Error('Error', sprintf(__('The maximum duration is %d', 'woocommerce-bookings'), $this->product->wc_booking_max_duration));
         }
         if ($data['_duration'] < $this->product->get_min_duration()) {
             return new WP_Error('Error', sprintf(__('The minimum duration is %d', 'woocommerce-bookings'), $this->product->wc_booking_min_duration));
         }
     }
     // Validate date and time
     if (empty($data['date'])) {
         return new WP_Error('Error', __('Date is required - please choose one above', 'woocommerce-bookings'));
     }
     if (in_array($this->product->get_duration_unit(), array('minute', 'hour')) && empty($data['time'])) {
         return new WP_Error('Error', __('Time is required - please choose one above', 'woocommerce-bookings'));
     }
     if ($data['_date'] && date('Ymd', strtotime($data['_date'])) < date('Ymd', current_time('timestamp'))) {
         return new WP_Error('Error', __('You must choose a future date and time.', 'woocommerce-bookings'));
     }
     if ($data['_date'] && !empty($data['_time']) && date('YmdHi', strtotime($data['_date'] . ' ' . $data['_time'])) < date('YmdHi', current_time('timestamp'))) {
         return new WP_Error('Error', __('You must choose a future date and time.', 'woocommerce-bookings'));
     }
     // Validate min date and max date
     if ($min = $this->product->get_min_date()) {
         $min_date = strtotime("+{$min['value']} {$min['unit']}", current_time('timestamp'));
         if (strtotime($data['_date'] . ' ' . $data['_time']) < $min_date) {
             return new WP_Error('Error', sprintf(__('The earliest booking possible is currently %s.', 'woocommerce-bookings'), date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $min_date)));
         }
     }
     if ($max = $this->product->get_max_date()) {
         $max_date = strtotime("+{$max['value']} {$max['unit']}", current_time('timestamp'));
         if (strtotime($data['_date'] . ' ' . $data['_time']) > $max_date) {
             return new WP_Error('Error', sprintf(__('The latest booking possible is currently %s.', 'woocommerce-bookings'), date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $max_date)));
         }
     }
     // Validate persons
     if ($this->product->has_persons()) {
         $persons = array_sum($data['_persons']);
         if (empty($persons)) {
             return new WP_Error('Error', __('Persons are required - please enter the number of persons above', 'woocommerce-bookings'));
         }
         if ($persons > $this->product->get_max_persons()) {
             return new WP_Error('Error', sprintf(__('The maximum persons per group is %d', 'woocommerce-bookings'), $this->product->wc_booking_max_persons_group));
         }
         if ($persons < $this->product->get_min_persons()) {
             return new WP_Error('Error', sprintf(__('The minimum persons per group is %d', 'woocommerce-bookings'), $this->product->wc_booking_min_persons_group));
         }
         if ($this->product->has_person_types()) {
             $person_types = $this->product->get_person_types();
             foreach ($person_types as $person) {
                 $person_max = get_post_meta($person->ID, 'max', true);
                 if (is_numeric($person_max) && $data['_persons'][$person->ID] > $person_max) {
                     return new WP_Error('Error', sprintf(__('The maximum %s per group is %d', 'woocommerce-bookings'), $person->post_title, $person_max));
                 }
                 $person_min = get_post_meta($person->ID, 'min', true);
                 if (is_numeric($person_min) && $data['_persons'][$person->ID] < $person_min) {
                     return new WP_Error('Error', sprintf(__('The minimum %s per group is %d', 'woocommerce-bookings'), $person->post_title, $person_min));
                 }
             }
         }
     }
     // Get availability for the dates
     $available_bookings = $this->product->get_available_bookings($data['_start_date'], $data['_end_date'], $data['_resource_id'], $data['_qty']);
     if (is_array($available_bookings)) {
         $this->auto_assigned_resource_id = current(array_keys($available_bookings));
     }
     if (is_wp_error($available_bookings)) {
         return $available_bookings;
     } elseif (!$available_bookings) {
         return new WP_Error('Error', __('Sorry, the selected block is not available', 'woocommerce-bookings'));
     }
     return true;
 }