class UnitPricingCalendar in Rooms - Drupal Booking for Hotels, B&Bs and Vacation Rentals 7
Handles querying and updating the pricing information relative to a single bookable unit.
Hierarchy
- class \RoomsCalendar implements RoomsCalendarInterface
- class \UnitPricingCalendar implements UnitPricingCalendarInterface
Expanded class hierarchy of UnitPricingCalendar
File
- modules/
rooms_pricing/ includes/ rooms_pricing.unit_pricing_calendar.inc, line 12 - Contains UnitPricingCalendar.
View source
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;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
RoomsCalendar:: |
protected | property | The base table where calendar data is stored. | |
RoomsCalendar:: |
protected | property | The default state for the room if it has no specific booking. | 1 |
RoomsCalendar:: |
protected | property | The default state for the room if it has no specific booking. | |
RoomsCalendar:: |
public | function |
Adds an event to the calendar Overrides RoomsCalendarInterface:: |
|
RoomsCalendar:: |
public | function |
Checks if a month exists. Overrides RoomsCalendarInterface:: |
|
UnitPricingCalendar:: |
protected | property | The default price for the room | |
UnitPricingCalendar:: |
protected | property | Price modifiers - an array of operations to be performed to the price. Operations are performed in the sequence they are found in the array | |
UnitPricingCalendar:: |
protected | property | The actual unit relevant to this Calendar. | |
UnitPricingCalendar:: |
public | function |
Apply price modifiers to base price. Overrides UnitPricingCalendarInterface:: |
|
UnitPricingCalendar:: |
public | function |
Given a date range determine the cost of the room over that period. Overrides UnitPricingCalendarInterface:: |
|
UnitPricingCalendar:: |
public | function |
Get a set of PricingEvent between start_date and end_date filtered by days. Overrides UnitPricingCalendarInterface:: |
|
UnitPricingCalendar:: |
public | function |
Given a date range returns an array of RoomEvents. The heavy lifting really takes place in
the getRawDayData function - here we are simply acting as a factory for event objects Overrides RoomsCalendar:: |
|
UnitPricingCalendar:: |
public | function |
Given a date range it returns all data within that range including the
start and end dates of states. The MySQL queries are kept simple and then
the data is cleared up. Overrides RoomsCalendar:: |
|
UnitPricingCalendar:: |
protected | function |
Given an event it prepares the entire month array for it
assuming no other events in the month and days where there
is no event get set to the default state. Overrides RoomsCalendar:: |
|
UnitPricingCalendar:: |
protected | function |
Given an event it prepares a partial array covering just the days
for which the event is involved Overrides RoomsCalendar:: |
|
UnitPricingCalendar:: |
public | function |
Given an array of RoomEvents the calendar is updated with regards to the
events that are relevant to the Unit this calendar refers to Overrides RoomsCalendar:: |
|
UnitPricingCalendar:: |
public | function | Constructs a UnitPricingCalendar instance. |