rooms_pricing.unit_pricing_calendar.inc in Rooms - Drupal Booking for Hotels, B&Bs and Vacation Rentals 7
Contains UnitPricingCalendar.
File
modules/rooms_pricing/includes/rooms_pricing.unit_pricing_calendar.incView source
<?php
/**
* @file
* Contains UnitPricingCalendar.
*/
/**
* Handles querying and updating the pricing information
* relative to a single bookable unit.
*/
class UnitPricingCalendar extends RoomsCalendar implements UnitPricingCalendarInterface {
/**
* The actual unit relevant to this Calendar.
*/
protected $unit;
/**
* The default price for the room
*
* @var float
*/
protected $default_price;
/**
* Price modifiers - an array of operations to be performed to the price.
* Operations are performed in the sequence they are found in the array
*
* @var array
*/
protected $price_modifiers;
/**
* Constructs a UnitPricingCalendar instance.
*
* @param int $unit_id
* The unit ID.
* @param array $price_modifiers
* The price modifiers to apply.
*/
public function __construct($unit_id, $price_modifiers = array()) {
$this->unit_id = $unit_id;
// Load the booking unit.
$this->unit = rooms_unit_load($unit_id);
$this->default_state = $this->unit->default_state;
$this->default_price = $this->unit->base_price;
$this->price_modifiers = $price_modifiers;
$this->base_table = 'rooms_pricing';
}
/**
* {@inheritdoc}
*/
public function calculatePrice(DateTime $start_date, DateTime $end_date, $persons = 0, $children = 0, $children_ages = array()) {
if ($persons == 0) {
$persons = $this->unit->max_sleeps;
}
$price = 0;
$booking_price = 0;
$booking_days = 0;
// Setup pricing reply and log
$reply = array(
'full_price' => $price,
'booking_price' => $booking_price,
'log' => array(),
);
// Get settings to add to log
$reply['log']['rooms_children_discount_options'] = variable_get('rooms_children_discount_options', array());
$reply['log']['rooms_price_calculation'] = variable_get('rooms_price_calculation', ROOMS_PER_NIGHT);
$pricing_events = $this
->getEvents($start_date, $end_date);
$reply['log']['pricing_events'] = $pricing_events;
foreach ($pricing_events as $event) {
$days = $event
->diff()->days + 1;
$booking_days += $days;
if (variable_get('rooms_price_calculation', ROOMS_PER_NIGHT) == ROOMS_PER_PERSON) {
$children_discount_options = variable_get('rooms_children_discount_options', array());
$price = $price + $days * $event->amount * ($persons - $children);
foreach ($children_ages as $age) {
$reply['log']['children'][$age]['pre'] = $price;
if (is_array($age)) {
$age = $age['value'];
}
$discount = 0;
foreach ($children_discount_options as $option) {
if ($age >= $option['start'] && $age <= $option['end']) {
$discount = $option['discount'];
break;
}
}
$price = $price + $days * $event->amount * (100 - $discount) / 100;
$reply['log']['children'][$age]['post'] = $price;
}
}
else {
$price = $price + $days * $event->amount;
}
}
$booking_info = array(
'unit' => $this->unit,
'start_date' => $start_date,
'end_date' => $end_date,
'booking_parameters' => array(
'group_size' => $persons,
'group_size_children' => $children,
'childrens_age' => $children_ages,
),
);
drupal_alter('rooms_booking_amount_before_modifiers', $price, $booking_info);
$price = $this
->applyPriceModifiers($price, $booking_days, $reply);
$payment_option = variable_get('rooms_payment_options', FULL_PAYMENT);
$reply['rooms_payment_option'] = variable_get('rooms_payment_options', FULL_PAYMENT);
switch ($payment_option) {
case FULL_PAYMENT:
$booking_price = $price;
break;
case PERCENT_PAYMENT:
$reply['rooms_payment_option'][PERCENT_PAYMENT] = variable_get('rooms_payment_options_percentual');
$booking_price = $price / 100 * variable_get('rooms_payment_options_percentual');
break;
case FIRST_NIGHT_PAYMENT:
$booking_price = $pricing_events[0]->amount;
$reply['rooms_payment_option'][FIRST_NIGHT_PAYMENT] = $booking_price;
break;
}
$reply['full_price'] = $price;
$reply['booking_price'] = $booking_price;
return $reply;
}
/**
* {@inheritdoc}
*/
public function applyPriceModifiers($base_price, $days, &$reply) {
$price = $base_price;
if (!empty($this->price_modifiers)) {
foreach ($this->price_modifiers as $source => $modifier) {
if ($modifier['#type'] == ROOMS_PRICE_SINGLE_OCCUPANCY) {
$reply['log']['modifiers'][$source][$mod_count][ROOMS_PRICE_SINGLE_OCCUPANCY]['pre'] = $price;
$reply['log']['modifiers'][$source][$mod_count][ROOMS_PRICE_SINGLE_OCCUPANCY]['amount'] = $this->unit->data['singlediscount'];
$reply['log']['modifiers'][$source][$mod_count][ROOMS_PRICE_SINGLE_OCCUPANCY]['modifier'] = $modifier;
$this->unit->data['singlediscount'];
$price -= $base_price * $this->unit->data['singlediscount'] / 100;
$reply['log']['modifiers'][$source][ROOMS_PRICE_SINGLE_OCCUPANCY]['post'] = $price;
}
elseif ($modifier['#type'] == ROOMS_DYNAMIC_MODIFIER) {
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['modifier'] = $modifier;
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['pre'] = $price;
switch ($modifier['#op_type']) {
case ROOMS_ADD:
$price += $modifier['#amount'] * $modifier['#quantity'];
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['op'] = ROOMS_ADD;
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['post'] = $price;
break;
case ROOMS_ADD_DAILY:
$price += $modifier['#amount'] * $modifier['#quantity'] * $days;
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['op'] = ROOMS_ADD_DAILY;
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['post'] = $price;
break;
case ROOMS_SUB:
$price -= $modifier['#amount'] * $modifier['#quantity'];
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['op'] = ROOMS_SUB;
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['post'] = $price;
break;
case ROOMS_SUB_DAILY:
$price -= $modifier['#amount'] * $modifier['#quantity'] * $days;
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['op'] = ROOMS_SUB_DAILY;
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['post'] = $price;
break;
case ROOMS_REPLACE:
$price = $modifier['#amount'];
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['op'] = ROOMS_REPLACE;
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['post'] = $price;
break;
case ROOMS_INCREASE:
$price += $base_price * ($modifier['#amount'] * $modifier['#quantity']) / 100;
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['op'] = ROOMS_INCREASE;
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['post'] = $price;
break;
case ROOMS_DECREASE:
$price -= $base_price * ($modifier['#amount'] * $modifier['#quantity']) / 100;
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['op'] = ROOMS_DECREASE;
$reply['log']['modifiers'][$source][ROOMS_DYNAMIC_MODIFIER]['post'] = $price;
break;
}
}
}
}
return $price;
}
/**
* {@inheritdoc}
*/
public function getEvents(DateTime $start_date, DateTime $end_date) {
// Get the raw day results.
$results = $this
->getRawDayData($start_date, $end_date);
$events = array();
foreach ($results[$this->unit_id] as $year => $months) {
foreach ($months as $mid => $month) {
// Event array gives us the start days for each event within a month.
$start_days = array_keys($month['states']);
foreach ($month['states'] as $state) {
// Create a booking event.
$start = $state['start_day'];
$end = $state['end_day'];
$sd = new DateTime("{$year}-{$mid}-{$start}");
$ed = new DateTime("{$year}-{$mid}-{$end}");
$amount = commerce_currency_amount_to_decimal($state['state'], commerce_default_currency());
$event = new PricingEvent($this->unit_id, $amount, $sd, $ed);
$events[] = $event;
}
}
}
return $events;
}
/**
* {@inheritdoc}
*/
public function getRawDayData(DateTime $start_date, DateTime $end_date) {
// To handle single-day bookings (Tours) we pretend that they are overnight
// bookings.
if ($end_date < $start_date) {
$end_date
->add(new DateInterval('P1D'));
}
// Create a dummy PricingEvent to represent the range we are searching over.
// This gives us access to handy functions that PricingEvents have.
$s = new PricingEvent($this->unit_id, 0, $start_date, $end_date);
$results = array();
// If search across the same year do a single query.
if ($s
->sameYear()) {
$query = db_select($this->base_table, 'a');
$query
->fields('a');
$query
->condition('a.unit_id', $this->unit_id);
$query
->condition('a.year', $s
->startYear());
$query
->condition('a.month', $s
->startMonth(), '>=');
$query
->condition('a.month', $s
->endMonth(), '<=');
$months = $query
->execute()
->fetchAll(PDO::FETCH_ASSOC);
if (count($months) > 0) {
foreach ($months as $month) {
$m = $month['month'];
$y = $month['year'];
$id = $month['unit_id'];
// Remove the three first rows and just keep the days.
unset($month['month']);
unset($month['year']);
unset($month['unit_id']);
$results[$id][$y][$m]['days'] = $month;
}
}
}
else {
for ($j = $s
->startYear(); $j <= $s
->endYear(); $j++) {
$query = db_select($this->base_table, 'a');
$query
->fields('a');
$query
->condition('a.unit_id', $this->unit_id);
$query
->condition('a.year', $j);
if ($j == $s
->startYear()) {
$query
->condition('a.month', $s
->startMonth(), '>=');
}
elseif ($j == $s
->endYear()) {
$query
->condition('a.month', $s
->endMonth(), '<=');
}
$months = $query
->execute()
->fetchAll(PDO::FETCH_ASSOC);
if (count($months) > 0) {
foreach ($months as $month) {
$m = $month['month'];
$y = $month['year'];
$id = $month['unit_id'];
unset($month['month']);
unset($month['year']);
unset($month['unit_id']);
$results[$id][$y][$m]['days'] = $month;
}
}
}
}
// With the results from the db in place fill in any missing months
// with the default state for the unit.
for ($j = $s
->startYear(); $j <= $s
->endYear(); $j++) {
$eod = rooms_end_of_month_dates($j);
// We start by setting the expected start and end months for each year.
if ($s
->sameYear()) {
$expected_months = $s
->endMonth() - $s
->startMonth() + 1;
$sm = $s
->startMonth();
$em = $s
->endMonth();
}
elseif ($j == $s
->endYear()) {
$expected_months = $s
->endMonth();
$sm = 1;
$em = $s
->endMonth();
}
elseif ($j == $s
->startYear()) {
$expected_months = 12 - $s
->startMonth() + 1;
$em = 12;
$sm = $s
->startMonth();
}
else {
$expected_months = 12;
$sm = 1;
$em = 12;
}
// We check to see if the months we have already fit our expectations.
$actual_months = isset($result[$this->unit_id][$j]) ? count($results[$id][$j]) : 0;
if ($expected_months > $actual_months) {
// We have missing months so lets go fill them.
for ($i = $sm; $i <= $em; $i++) {
if (!isset($results[$this->unit_id][$j][$i])) {
$last_day = $eod[$i];
$month = $this
->prepareFullMonthArray(new PricingEvent($this->unit_id, $this->default_price, new DateTime("{$j}-{$i}-1"), new DateTime("{$j}-{$i}-{$last_day}")));
// Add the month in its rightful position.
$results[$this->unit_id][$j][$i]['days'] = $month;
// And sort months.
ksort($results[$this->unit_id][$j]);
}
}
}
}
// With all the months in place we now need to clean results to set the
// right start and end date for each month - this will save code downstream
// from having to worry about it.
foreach ($results[$this->unit_id] as $year => $months) {
foreach ($months as $mid => $days) {
// Get the end of month values again to make sure we have the right year
// because it might change for queries spanning years.
$eod = rooms_end_of_month_dates($year);
// There is undoubtetly a smarter way to do this.
if (count($days['days']) != $eod[$mid]) {
switch ($eod[$mid]) {
case 30:
unset($results[$this->unit_id][$year][$mid]['days']['d31']);
break;
case 29:
unset($results[$this->unit_id][$year][$mid]['days']['d31']);
unset($results[$this->unit_id][$year][$mid]['days']['d30']);
break;
case 28:
unset($results[$this->unit_id][$year][$mid]['days']['d31']);
unset($results[$this->unit_id][$year][$mid]['days']['d30']);
unset($results[$this->unit_id][$year][$mid]['days']['d29']);
break;
}
}
if ($year == $s
->startYear() && $mid == $s
->startMonth()) {
// We know we have the entire months over the range so we just unset
// all the dates from the start of the month to the actual start day.
for ($i = 1; $i < $s
->startDay(); $i++) {
unset($results[$this->unit_id][$year][$mid]['days']['d' . $i]);
}
}
if ($year == $s
->endYear() && $mid == $s
->endMonth()) {
// And from the end of the month back to the actual end day.
for ($i = $s
->endDay() + 1; $i <= $eod[$mid]; $i++) {
unset($results[$this->unit_id][$year][$mid]['days']['d' . $i]);
}
}
}
}
// We store -1 instead of the default price in the DB so this is our chance to get the default price back
// cycling through the data and replace -1 with the current default price of the unit.
foreach ($results[$this->unit_id] as $year => $months) {
foreach ($months as $mid => $days) {
// The number of days in the month we are interested in eventing.
$j = count($days);
// The start date.
$i = substr(key($days['days']), 1);
while ($j <= count($days['days'])) {
if ($days['days']['d' . $i] == -1) {
$results[$this->unit_id][$year][$mid]['days']['d' . $i] = commerce_currency_decimal_to_amount($this->default_price, commerce_default_currency());
}
$i++;
$j++;
}
}
}
// With the results in place we do a states array with the start and
// end dates of each event.
foreach ($results[$this->unit_id] as $year => $months) {
foreach ($months as $mid => $days) {
// The number of days in the month we are interested in eventing.
$j = count($days);
// The start date.
$i = substr(key($days['days']), 1);
$start_day = $i;
$end_day = NULL;
$unique_states = array();
$old_state = $days['days']['d' . $i];
$state = $days['days']['d' . $i];
while ($j <= count($days['days'])) {
$state = $days['days']['d' . $i];
if ($state != $old_state) {
$unique_states[] = array(
'state' => $old_state,
'start_day' => $start_day,
'end_day' => $i - 1,
);
$end_day = $i - 1;
$start_day = $i;
$old_state = $state;
}
$i++;
$j++;
}
// Get the last event in.
$unique_states[] = array(
'state' => $state,
'start_day' => isset($end_day) ? $end_day + 1 : $start_day,
'end_day' => $i - 1,
);
$results[$this->unit_id][$year][$mid]['states'] = $unique_states;
}
}
return $results;
}
/**
* {@inheritdoc}
*/
public function updateCalendar($events) {
foreach ($events as $event) {
// Make sure event refers to the unit for this calendar.
if ($event->unit_id == $this->unit_id) {
// Get all the pricing events that fit within this event.
$affected_events = $this
->getEvents($event->start_date, $event->end_date);
$monthly_events = array();
foreach ($affected_events as $a_event) {
/** @var PricingEventInterface $a_event */
// Apply the operation.
$a_event
->applyOperation($event->amount, $event->operation);
// If the event is in the same month span just queue to be added.
if ($a_event
->sameMonth()) {
$monthly_events[] = $a_event;
}
else {
// Check if multi-year - if not just create monthly events.
if ($a_event
->sameYear()) {
$monthly_events_tmp = $a_event
->transformToMonthlyEvents();
$monthly_events = array_merge($monthly_events, $monthly_events_tmp);
}
else {
// Else transform to single years and then to monthly.
$yearly_events = $a_event
->transformToYearlyEvents();
foreach ($yearly_events as $ye) {
$monthly_events_tmp = $ye
->transformToMonthlyEvents();
$monthly_events = array_merge($monthly_events, $monthly_events_tmp);
}
}
}
}
foreach ($monthly_events as $event) {
$this
->addMonthEvent($event);
}
}
}
}
/**
* {@inheritdoc}
*/
protected function prepareFullMonthArray(RoomsEventInterface $event) {
$days = array();
$eod = rooms_end_of_month_dates($event
->startYear());
$last_day = $eod[$event
->startMonth()];
for ($i = 1; $i <= $last_day; $i++) {
if ($i >= $event
->startDay() && $i <= $event
->endDay()) {
$days['d' . $i] = commerce_currency_decimal_to_amount($event->amount, commerce_default_currency());
}
else {
// When we are writing a new month to the DB make sure to have the placeholder value -1 for the days where the
// default price is in effect. This means as a user changes the default price we will take it into account even
// though the price data is now in a DB row.
$days['d' . $i] = -1;
}
}
return $days;
}
/**
* {@inheritdoc}
*/
protected function preparePartialMonthArray(RoomsEventInterface $event) {
$days = array();
for ($i = $event
->startDay(); $i <= $event
->endDay(); $i++) {
$days['d' . $i] = commerce_currency_decimal_to_amount($event->amount, commerce_default_currency());
}
return $days;
}
/**
* {@inheritdoc}
*/
public function calculatePricingEvents($unit_id, $amount, DateTime $start_date, DateTime $end_date, $operation, $days) {
$s_timestamp = $start_date
->getTimestamp();
$e_timestamp = $end_date
->getTimestamp();
$events = array();
do {
$s_date = getdate($s_timestamp);
$wday_start = $s_date['wday'];
if (in_array($wday_start + 1, $days)) {
$events[] = new PricingEvent($unit_id, $amount, new DateTime(date('Y-m-d', $s_timestamp)), new DateTime(date('Y-m-d', $s_timestamp)), $operation, $days);
}
$s_timestamp = strtotime('+1 days', $s_timestamp);
} while ($s_timestamp <= $e_timestamp);
return $events;
}
}
Classes
Name | Description |
---|---|
UnitPricingCalendar | Handles querying and updating the pricing information relative to a single bookable unit. |