merci.module in MERCI (Manage Equipment Reservations, Checkout and Inventory) 6
Same filename and directory in other branches
MERCI - Managed Equipment Reservation Checkout and Inventory
File
merci.moduleView source
<?php
/**
* @file
* MERCI - Managed Equipment Reservation Checkout and Inventory
*/
// Item default availability options.
define('MERCI_AVA_F', 1);
define('MERCI_UNA_F', 2);
define('MERCI_AVA_S', 3);
define('MERCI_UNA_S', 4);
// Reservation status options.
define('MERCI_STATUS_UNCONFIRMED', 1);
define('MERCI_STATUS_PENDING', 2);
define('MERCI_STATUS_CHECKED_OUT', 3);
define('MERCI_STATUS_CHECKED_IN', 4);
define('MERCI_STATUS_CANCELLED', 5);
define('MERCI_STATUS_DENIED', 6);
define('MERCI_STATUS_NO_SHOW', 7);
// Bucket/resource status options.
define('MERCI_STATUS_ACTIVE', 1);
define('MERCI_STATUS_INACTIVE', 2);
// Bucket/resource sub-types.
define('MERCI_SUB_TYPE_ITEM', 1);
define('MERCI_SUB_TYPE_RESERVATION', 2);
// Item status for reservations.
define('MERCI_ITEM_STATUS_AVAILABLE', 0);
define('MERCI_ITEM_STATUS_RESERVED', 1);
define('MERCI_ITEM_STATUS_CHECKED_OUT', 2);
define('MERCI_ITEM_STATUS_CHECKED_IN', -1);
/**
* Implementation of hook_init().
*/
function merci_init() {
drupal_add_css(drupal_get_path('module', 'merci') . '/merci.css');
drupal_add_js(drupal_get_path('module', 'merci') . '/merci.js');
}
/**
* Implementation of hook_perm().
*/
function merci_perm() {
return array(
'create reservations',
'create confirmed reservations',
'membership discount',
'suspend MERCI access',
'administer MERCI',
'create reservations outside hours of operation',
);
}
/**
* Implementation of hook_form_alter().
*/
function merci_form_alter(&$form, $form_state, $form_id) {
// Node add/edit forms.
if (isset($form['type']) && isset($form['#node'])) {
$type = $form['type']['#value'];
if ("{$type}_node_form" == $form_id && $form_id != 'merci_reservation_node_form') {
$merci_settings = merci_load_content_type_settings($type);
// Only active MERCI node types get processed.
if ($merci_settings->type_setting != 'disabled') {
// Cast to object here, as it can come in both ways.
$node = (object) $form['#node'];
// Make the data representation consistent.
if (isset($node->merci_sub_type)) {
$sub_type = $node->merci_sub_type;
$default_availability = $node->merci_default_availability;
$late_fee_per_hour = $node->merci_late_fee_per_hour;
$rate_per_hour = $node->merci_rate_per_hour;
$fee_free_hours = $node->merci_fee_free_hours;
$min_cancel_hours = $node->merci_min_cancel_hours;
$autocheckout = $node->merci_autocheckout;
$autocheckin = $node->merci_autocheckin;
$selfcheckout = $node->merci_selfcheckout;
}
elseif (isset($node->merci['sub_type'])) {
$sub_type = $node->merci['sub_type'];
$default_availability = $node->merci['default_availability'];
$late_fee_per_hour = $node->merci['late_fee_per_hour'];
$rate_per_hour = $node->merci['rate_per_hour'];
$fee_free_hours = $node->merci['fee_free_hours'];
$min_cancel_hours = $node->merci['min_cancel_hours'];
$autocheckout = $node->merci['autocheckout'];
$autocheckin = $node->merci['autocheckin'];
$selfcheckout = $node->merci['selfcheckout'];
}
else {
$sub_type = MERCI_SUB_TYPE_ITEM;
$default_availability = MERCI_AVA_F;
// Only resource types have individual pricing data.
if ($merci_settings->type_setting == 'bucket') {
$rate_per_hour = 0;
$late_fee_per_hour = 0;
$fee_free_hours = 0;
$min_cancel_hours = 0;
$autocheckout = 0;
$autocheckin = 0;
$selfcheckout = 0;
}
else {
$rate_per_hour = $merci_settings->rate_per_hour;
$late_fee_per_hour = $merci_settings->late_fee_per_hour;
$fee_free_hours = $merci_settings->fee_free_hours;
$min_cancel_hours = $merci_settings->min_cancel_hours;
$autocheckout = $node->merci_autocheckout;
$autocheckin = $node->merci_autocheckin;
$selfcheckout = $node->merci_selfcheckout;
}
}
// New nodes are always sub type item.
$form['merci_sub_type'] = array(
'#type' => 'value',
'#value' => $sub_type,
);
if (user_access('administer MERCI')) {
$form['merci'] = array(
'#type' => 'fieldset',
'#title' => t('MERCI settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['merci']['merci_default_availability'] = array(
'#title' => t('Default booking availability'),
'#type' => 'radios',
'#options' => merci_item_status(),
'#description' => t('If no availability information is defined for a given time, the resource falls back onto this setting.'),
'#default_value' => $default_availability,
);
// Bucket item nodes have no individual pricing, so just zero these values out.
if ($merci_settings->type_setting == 'bucket' && $sub_type == MERCI_SUB_TYPE_ITEM) {
$form['merci_rate_per_hour'] = array(
'#type' => 'value',
'#value' => $rate_per_hour,
);
$form['merci_late_fee_per_hour'] = array(
'#type' => 'value',
'#value' => $late_fee_per_hour,
);
$form['merci_fee_free_hours'] = array(
'#type' => 'value',
'#value' => $fee_free_hours,
);
$form['merci_min_cancel_hours'] = array(
'#type' => 'value',
'#value' => $min_cancel_hours,
);
$form['merci_autocheckout'] = array(
'#type' => 'value',
'#value' => $autocheckout,
);
$form['merci_autocheckin'] = array(
'#type' => 'value',
'#value' => $autocheckin,
);
$form['merci_selfcheckout'] = array(
'#type' => 'value',
'#value' => $selfcheckout,
);
}
else {
$form['merci']['merci_rate_per_hour'] = array(
'#type' => 'textfield',
'#title' => t('Rate per hour'),
'#size' => 10,
'#default_value' => $rate_per_hour,
'#element_validate' => array(
'merci_is_numeric_validate',
),
'#description' => t('The per hour rental fee for the item.'),
);
$form['merci']['merci_late_fee_per_hour'] = array(
'#type' => 'textfield',
'#title' => t('Late fee per hour'),
'#size' => 10,
'#default_value' => $late_fee_per_hour,
'#element_validate' => array(
'merci_is_numeric_validate',
),
'#description' => t('The per hour fee for returning the item late.'),
);
$form['merci']['merci_fee_free_hours'] = array(
'#type' => 'textfield',
'#title' => t('Fee free hours'),
'#size' => 10,
'#default_value' => $fee_free_hours,
'#element_validate' => array(
'merci_is_numeric_validate',
),
'#description' => t('The number of hours the item can be used before fees are charged.'),
);
$form['merci']['merci_autocheckout'] = array(
'#type' => 'checkbox',
'#title' => t('Auto checkout'),
'#default_value' => $autocheckout,
'#description' => t('Automatically check this item out when the Reservation starts.'),
);
$form['merci']['merci_autocheckin'] = array(
'#type' => 'checkbox',
'#title' => t('Auto checkin'),
'#default_value' => $autocheckout,
'#description' => t('Automatically check this item in when the Reservation ends.'),
);
$form['merci']['merci_selfcheckout'] = array(
'#type' => 'checkbox',
'#title' => t('Self checkout'),
'#default_value' => $autocheckout,
'#description' => t('Manage checkout with additional code.'),
);
}
}
else {
$form['merci_default_availability'] = array(
'#type' => 'value',
'#value' => $default_availability,
);
$form['merci_rate_per_hour'] = array(
'#type' => 'value',
'#value' => $rate_per_hour,
);
$form['merci_late_fee_per_hour'] = array(
'#type' => 'value',
'#value' => $late_fee_per_hour,
);
$form['merci_fee_free_hours'] = array(
'#type' => 'value',
'#value' => $fee_free_hours,
);
$form['merci_min_cancel_hours'] = array(
'#type' => 'value',
'#value' => $min_cancel_hours,
);
$form['merci_autocheckout'] = array(
'#type' => 'value',
'#value' => $autocheckout,
);
$form['merci_autocheckin'] = array(
'#type' => 'value',
'#value' => $autocheckin,
);
$form['merci_selfcheckout'] = array(
'#type' => 'value',
'#value' => $selfcheckout,
);
}
}
}
}
switch ($form_id) {
// Add check availability button inside date selector.
case 'merci_reservation_node_form':
//Hide member cost and commerical cost. These are only here to make Views easier.
unset($form['field_merci_member_cost']);
unset($form['field_merci_commercial_cost']);
//Users without administer MERCI permission can only alter Unconfirmed Reservations.
if (!user_access('administer MERCI') && $form['merci']['merci_status']['#default_value']) {
unset($form['buttons']['preview']);
unset($form['buttons']['submit']);
}
if (user_access('suspend MERCI access') && !user_access('administer MERCI')) {
form_set_error('merci_status', t('Your access to make new Reservations or edit existing Reservations has been suspended.'));
}
else {
$form['field_merci_date'][0]['merci_date_filter'] = array(
'#type' => 'submit',
'#value' => t('Check availability'),
'#weight' => 10,
'#submit' => array(
'merci_date_filter',
),
);
// Since hook_validate is broken in 6.x, we add our own
// custom validation here.
$form['#validate'][] = 'merci_node_validate';
}
break;
// Node settings form.
case 'node_type_form':
// Reservation content type can't used for other MERCI functionality.
if (isset($form['#node_type']->type) && $form['#node_type']->type == 'merci_reservation') {
return;
}
$warning = '<div>' . t('<strong> WARNING:</strong> changing this setting has no effect on existing reserved items.') . '</div>';
$type = $form['old_type']['#value'];
$settings = db_fetch_object(db_query("SELECT * FROM {merci_node_type} WHERE type = '%s'", $type));
$options = array(
'disabled' => t('Disabled'),
'bucket' => t('Bucket'),
'resource' => t('Resource'),
);
$form['#validate'][] = 'merci_node_type_save_validate';
$form['#submit'][] = 'merci_node_type_save_submit';
$form['merci'] = array(
'#type' => 'fieldset',
'#title' => t('MERCI settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
// If any nodes have already been created, lock the type setting.
if (merci_check_existing_bucket_items($type)) {
$form['merci']['merci_type_setting'] = array(
'#type' => 'value',
'#value' => $settings->type_setting,
);
$form['merci']['merci_type_setting_display'] = array(
'#type' => 'item',
'#title' => t('Reservable item type'),
'#value' => $options[$settings->type_setting],
'#description' => t('The setting can not be changed because content already exists for this type.'),
);
}
else {
$description_items = array(
t('Resource: Use this content type to create unique items that can be reserved.'),
t('Bucket: Use this content type to create interchangable items that can be reserved (ex. Camera). Buckets reference interchangable items. The actual item does not have to be chosen until the reservation is checked out.'),
);
$form['merci']['merci_type_setting'] = array(
'#type' => 'radios',
'#title' => t('Reservable item type'),
'#options' => $options,
'#default_value' => $settings ? $settings->type_setting : 'disabled',
'#description' => theme('item_list', $description_items),
);
}
$status = array(
MERCI_STATUS_ACTIVE => t('Active'),
MERCI_STATUS_INACTIVE => t('Inactive'),
);
$form['merci']['merci_status'] = array(
'#type' => 'radios',
'#title' => t('Status'),
'#options' => $status,
'#default_value' => isset($settings->status) && $settings->status ? $settings->status : MERCI_STATUS_ACTIVE,
'#description' => t('Set to active to allow this type to be reserved.'),
);
// This setting is only valid for buckets.
if (!$settings || $settings->type_setting == 'bucket') {
$form['merci']['merci_spare_items'] = array(
'#type' => 'textfield',
'#title' => t('Spare items'),
'#size' => 10,
'#default_value' => $settings ? $settings->spare_items : 0,
'#element_validate' => array(
'merci_is_numeric_validate',
),
'#description' => t("Set this to the number of items of this type that should always be available even when it's fully reserved.") . $warning,
);
}
else {
$form['merci']['merci_spare_items'] = array(
'#type' => 'value',
'#value' => 0,
);
}
//Grouping
$items = array();
$terms = taxonomy_get_tree(variable_get('merci_equipment_grouping_vid', 37));
$groupings[0] = 'None';
foreach ($terms as $term) {
$groupings[$term->tid] = $term->name;
}
$form['merci']['merci_grouping'] = array(
'#type' => 'select',
'#title' => t('Grouping'),
'#default_value' => $settings ? $settings->grouping : 0,
'#options' => $groupings,
'#description' => t('This will alter order the content types are displayed to users reserving items from buckets. Terms added to the MERCI Equipment Groupings taxonomy will appear here.'),
);
$form['merci']['merci_max_hours_per_reservation'] = array(
'#type' => 'textfield',
'#title' => t('Maximum hours per reservation'),
'#size' => 10,
'#default_value' => $settings ? $settings->max_hours_per_reservation : 0,
'#element_validate' => array(
'merci_is_numeric_validate',
),
'#description' => t('The maximum hours the item can be reserved for in one reservation. Set to zero for no limit.') . $warning,
);
$form['merci']['merci_allow_overnight'] = array(
'#type' => 'checkbox',
'#title' => t('Allow overnight reservation'),
'#default_value' => $settings ? $settings->allow_overnight : 0,
'#description' => t('Allow a reservation to continue over multiple days.') . $warning,
);
$form['merci']['merci_allow_weekends'] = array(
'#type' => 'checkbox',
'#title' => t('Allow weekend reservation'),
'#default_value' => $settings ? $settings->allow_weekends : 0,
'#description' => t('Allow a reservation to be made over days defined as weekend.') . $warning,
);
$form['merci']['merci_rate_per_hour'] = array(
'#type' => 'textfield',
'#title' => t('Rate per hour'),
'#size' => 10,
'#default_value' => $settings ? $settings->rate_per_hour : 0,
'#element_validate' => array(
'merci_is_numeric_validate',
),
'#description' => t('The per hour rental fee for the item.'),
);
$form['merci']['merci_late_fee_per_hour'] = array(
'#type' => 'textfield',
'#title' => t('Late fee per hour'),
'#size' => 10,
'#default_value' => $settings ? $settings->late_fee_per_hour : 0,
'#element_validate' => array(
'merci_is_numeric_validate',
),
'#description' => t('The per hour fee for returning the item late.'),
);
$form['merci']['merci_fee_free_hours'] = array(
'#type' => 'textfield',
'#title' => t('Fee free hours'),
'#size' => 10,
'#default_value' => $settings ? $settings->fee_free_hours : 0,
'#element_validate' => array(
'merci_is_numeric_validate',
),
'#description' => t('The number of hours the item can be used before fees are charged.'),
);
$form['merci']['merci_min_cancel_hours'] = array(
'#type' => 'textfield',
'#title' => t('Minimum hours for cancelation without No Show'),
'#size' => 10,
'#default_value' => $settings ? $settings->min_cancel_hours : 0,
'#element_validate' => array(
'merci_is_numeric_validate',
),
'#description' => t('Minimum number of hours a user can cancel a reservation for the item.'),
);
$form['merci']['merci_autocheckout'] = array(
'#type' => 'checkbox',
'#title' => t('Automatic Checkout'),
'#default_value' => $settings ? $settings->autocheckout : 0,
'#description' => t('Automatically check item out when reservation starts. Use for resources like meetings rooms that a user does not actually take.') . $warning,
);
$form['merci']['merci_autocheckin'] = array(
'#type' => 'checkbox',
'#title' => t('Automatic Checkin'),
'#default_value' => $settings ? $settings->autocheckin : 0,
'#description' => t('Automatically check item in when reservation ends. Use for resources like meetings rooms that a user does not actually return.') . $warning,
);
$form['merci']['merci_selfcheckout'] = array(
'#type' => 'checkbox',
'#title' => t('Self Checkout'),
'#default_value' => $settings ? $settings->selfcheckout : 0,
'#description' => t('The checkout and checkin process for this resource is managed with additional software (ie. lab computer where login process can by linked to checkout status).') . $warning,
);
break;
case 'node_delete_confirm':
$node = node_load((int) arg(1));
merci_delete_item_validate($node);
break;
case 'node_type_delete_confirm':
$type = str_replace('-', '_', arg(3));
merci_delete_node_type_validate($type);
break;
case 'node_admin_content':
if (!isset($form['#validate'])) {
$form['#validate'] = array();
}
$form['#validate'][] = 'merci_node_admin_delete_validate';
break;
}
}
/**
* Implementation of hook_menu().
*/
function merci_menu() {
$admin = array(
'administer MERCI',
);
// Callback for AJAX adding of item selectors.
$items['merci/js'] = array(
'title' => 'Javascript Choice Form',
'page callback' => 'merci_choice_js',
'access arguments' => array(
'access content',
),
'type' => MENU_CALLBACK,
);
// Administration settings.
$items['admin/settings/merci'] = array(
'title' => 'MERCI',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'merci_admin_settings',
),
'access callback' => 'user_access',
'access arguments' => $admin,
'description' => t('Configure system settings for MERCI.'),
);
$items['merci/taxonomy'] = array(
'title' => 'JSON interface for node taxonomy',
'description' => 'Takes a node ID and returns taxonomy data as JSON',
'page arguments' => array(
2,
),
'page callback' => 'merci_taxonomy_json',
'access arguments' => array(
'access content',
),
'type' => MENU_CALLBACK,
);
$items['merci/contract'] = array(
'title' => 'Printable contract',
'description' => 'Takes a node ID and returns a printable contract',
'page arguments' => array(
2,
),
'page callback' => 'merci_printable_contract',
'access arguments' => array(
'access content',
),
'type' => MENU_CALLBACK,
);
$items['merci/confirm'] = array(
'title' => 'Confirm MERCI Reservation',
'description' => 'Takes a node ID and returns a page confirming Reservation',
'page arguments' => array(
2,
),
'page callback' => 'merci_confirm_reservation',
'access arguments' => array(
'access content',
),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Builds the MERCI admininstration settings form.
*/
function merci_admin_settings() {
$form = array();
$statuses = array();
$statuses[MERCI_STATUS_UNCONFIRMED] = t('Unconfirmed');
$statuses[MERCI_STATUS_PENDING] = t('Pending');
$statuses[MERCI_STATUS_CHECKED_OUT] = t('Checked Out');
$form['merci_content_types'] = array(
'#type' => 'fieldset',
'#title' => t('Content types'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$header = array(
t('Type'),
t('Usage'),
t('Operations'),
);
$rows = array();
$merci_types = array();
$merci_type_result = db_query("SELECT type, type_setting FROM {merci_node_type} WHERE type_setting = 'resource' OR type_setting = 'bucket'");
while ($merci_type = db_fetch_object($merci_type_result)) {
$merci_types[$merci_type->type] = $merci_type->type_setting;
}
// while
foreach (node_get_types() as $type) {
$type_url_str = str_replace('_', '-', $type->type);
$usage = 'Not used by MERCI';
if (isset($merci_types[$type->type])) {
$usage = 'MERCI ' . $merci_types[$type->type];
}
// if
$operations = l(t('Edit'), 'admin/content/node-type/' . $type_url_str, array(
'query' => drupal_get_destination(),
));
if (module_exists('merci_inventory') && merci_inventory_node_type_menu_access($type->type)) {
$operations .= ' | ' . l(t('Inventory Sync'), 'admin/content/node-type/' . $type_url_str . '/merci_inventory', array(
'query' => drupal_get_destination(),
));
}
// if
$rows[] = array(
$type->name,
$usage,
$operations,
);
}
// foreach
$form['merci_content_types']['content_types_table'] = array(
'#value' => theme('table', $header, $rows),
);
$form['merci_general'] = array(
'#type' => 'fieldset',
'#title' => t('General settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['merci_general']['merci_default_reservation_status'] = array(
'#type' => 'select',
'#title' => t('Default Reservation Status'),
'#options' => $statuses,
'#default_value' => variable_get('merci_default_reservation_status', ''),
'#description' => t('New Reservations will be set to this by default. Use Checked Out if you are using MERCI to manage the checkout, but do not allow actual Reservations.'),
);
$form['merci_general']['merci_max_days_advance_reservation'] = array(
'#type' => 'textfield',
'#title' => t('Max Advance'),
'#size' => 11,
'#maxlength' => 11,
'#default_value' => variable_get('merci_max_days_advance_reservation', '0'),
'#description' => t('Maximum number of days a Reservation can be made in advance. Set to 0 if to allow Reservations to be made an unlimitted time into the future.'),
);
$form['merci_general']['merci_email_receipt'] = array(
'#type' => 'checkbox',
'#title' => t('Send Email Receipt'),
'#default_value' => variable_get('merci_email_receipt', 0),
'#description' => t('Use when default Reservation Status is Unconfirmed'),
);
$form['merci_general']['merci_email_confirmation'] = array(
'#type' => 'checkbox',
'#title' => t('Send Email Confirmation'),
'#default_value' => variable_get('merci_email_confirmation', 0),
'#description' => t('Add link to change reservations from Unconfirmed to Confirmed'),
);
// Weekend
$form['merci_general']['merci_saturday_is_weekend'] = array(
'#type' => 'checkbox',
'#title' => t('Saturday is a considered weekend'),
'#default_value' => variable_get('merci_saturday_is_weekend', 1),
);
$form['merci_general']['merci_sunday_is_weekend'] = array(
'#type' => 'checkbox',
'#title' => t('Sunday is a considered weekend'),
'#default_value' => variable_get('merci_sunday_is_weekend', 1),
);
$form['merci_general']['merci_monday_is_weekend'] = array(
'#type' => 'checkbox',
'#title' => t('Monday is considered a weekend'),
'#default_value' => variable_get('merci_monday_is_weekend', 0),
);
$form['merci_general']['merci_tuesday_is_weekend'] = array(
'#type' => 'checkbox',
'#title' => t('Tuesday is considered a weekend'),
'#default_value' => variable_get('merci_tuesday_is_weekend', 0),
);
$form['merci_general']['merci_wednesday_is_weekend'] = array(
'#type' => 'checkbox',
'#title' => t('Wednesday is considered a weekend'),
'#default_value' => variable_get('merci_wednesday_is_weekend', 0),
);
$form['merci_general']['merci_thursday_is_weekend'] = array(
'#type' => 'checkbox',
'#title' => t('Thursday is considered a weekend'),
'#default_value' => variable_get('merci_thrusday_is_weekend', 0),
);
$form['merci_general']['merci_friday_is_weekend'] = array(
'#type' => 'checkbox',
'#title' => t('Firday is a considered weekend'),
'#default_value' => variable_get('merci_firday_is_weekend', 0),
);
// Hours of operation
$hours_description = t('<div>Enter military time for both opening and closing time, separated by a dash, in the format <em>hh:mm-hh:mm</em></div>ex. <em>09:00-17:00</em> would be open at 9AM, close at 5PM. Leave blank to indicate not being open.');
$form['merci_general']['merci_hours_mon'] = array(
'#type' => 'textfield',
'#title' => t('Monday hours'),
'#size' => 11,
'#maxlength' => 11,
'#default_value' => variable_get('merci_hours_mon', ''),
'#description' => $hours_description,
);
$form['merci_general']['merci_hours_tue'] = array(
'#type' => 'textfield',
'#title' => t('Tuesday hours'),
'#size' => 11,
'#maxlength' => 11,
'#default_value' => variable_get('merci_hours_tue', ''),
'#description' => $hours_description,
);
$form['merci_general']['merci_hours_wed'] = array(
'#type' => 'textfield',
'#title' => t('Wednesday hours'),
'#size' => 11,
'#maxlength' => 11,
'#default_value' => variable_get('merci_hours_wed', ''),
'#description' => $hours_description,
);
$form['merci_general']['merci_hours_thu'] = array(
'#type' => 'textfield',
'#title' => t('Thursday hours'),
'#size' => 11,
'#maxlength' => 11,
'#default_value' => variable_get('merci_hours_thu', ''),
'#description' => $hours_description,
);
$form['merci_general']['merci_hours_fri'] = array(
'#type' => 'textfield',
'#title' => t('Friday hours'),
'#size' => 11,
'#maxlength' => 11,
'#default_value' => variable_get('merci_hours_fri', ''),
'#description' => $hours_description,
);
$form['merci_general']['merci_hours_sat'] = array(
'#type' => 'textfield',
'#title' => t('Saturday hours'),
'#size' => 11,
'#maxlength' => 11,
'#default_value' => variable_get('merci_hours_sat', ''),
'#description' => $hours_description,
);
$form['merci_general']['merci_hours_sun'] = array(
'#type' => 'textfield',
'#title' => t('Sunday hours'),
'#size' => 11,
'#maxlength' => 11,
'#default_value' => variable_get('merci_hours_sun', ''),
'#description' => $hours_description,
);
$form['merci_general']['merci_hours_admin'] = array(
'#type' => 'textfield',
'#title' => t('Admin hours'),
'#size' => 11,
'#maxlength' => 11,
'#default_value' => variable_get('merci_hours_admin', ''),
'#description' => t('This setting controls the hours shown on the conflict grid for users with Adminster MERCI permessions. The conflict grid is normally limited to the days and hours the set above. Because users with Admister MERCI rights can create Resevervations outside the normal hours of operation, they need to see conflicts during those times as well.'),
);
$form['merci_general']['merci_closed_dates'] = array(
'#type' => 'textarea',
'#title' => t('Closed dates'),
'#rows' => 10,
'#cols' => 5,
// TODO: this doesn't seem to work...
'#default_value' => variable_get('merci_closed_dates', ''),
'#description' => t('<div>Enter dates which are closed regardless of the day of the week, one date per line, in the format <em>mm-dd</em></div>ex. <em>07-04</em> would mean July 4th is always closed, regardless of what day of the week it falls on.'),
);
$form['merci_general']['merci_membership_discount'] = array(
'#type' => 'textfield',
'#title' => t('Membership discount'),
'#size' => 11,
'#maxlength' => 11,
'#default_value' => variable_get('merci_membership_discount', ''),
'#description' => t('Percent comercial rate is reduced for users in a role with member discount permission. Enter as decimal. 60% would be .60. An item with a commercial rate of $100 would cost a member $40 for any hours beyond the fee free hours for that reservation.'),
);
$form['merci_general']['merci_contract_header'] = array(
'#type' => 'textarea',
'#title' => t('Contract header'),
'#rows' => 10,
'#cols' => 5,
// TODO: this doesn't seem to work...
'#default_value' => variable_get('merci_contract_header', ''),
'#description' => t('Header portion of printable contract. Allows HTML.'),
);
$form['merci_general']['merci_contract_boilerplate'] = array(
'#type' => 'textarea',
'#title' => t('Contract boilerplate'),
'#rows' => 10,
'#cols' => 5,
// TODO: this doesn't seem to work...
'#default_value' => variable_get('merci_contract_boilerplate', ''),
'#description' => t('Legalese that makes the contract legally binding.'),
);
$form['merci_general']['merci_contract_footer'] = array(
'#type' => 'textarea',
'#title' => t('Contract footer'),
'#rows' => 10,
'#cols' => 5,
// TODO: this doesn't seem to work...
'#default_value' => variable_get('merci_contract_footer', ''),
'#description' => t('Footer portion of printable contract. Normally includes signature lines. HTML allowed.'),
);
if (module_exists('token')) {
$form['merci_general']['token_help'] = array(
'#title' => t('Replacement patterns'),
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['merci_general']['token_help']['help'] = array(
'#value' => theme('token_help', 'node'),
);
}
return system_settings_form($form);
}
/**
* Implementation of hook_node_info().
*/
function merci_node_info() {
return array(
// Reservation nodes.
'merci_reservation' => array(
'name' => t('Reservation'),
'module' => 'merci',
'has_body' => FALSE,
'description' => t("A reservation reserves a resource or group of resources for some period of time."),
),
);
}
/**
* Implementation of hook_node_info().
*/
function merci_node_type($op, $info) {
switch ($op) {
case 'update':
// If type was edited, update it.
if (isset($info->old_type) && $info->type != $info->old_type) {
db_query("UPDATE {merci_node_type} SET type = '%s' WHERE type = '%s'", $info->type, $info->old_type);
}
break;
case 'delete':
db_query("DELETE FROM {merci_node_type} WHERE type = '%s'", $info->type);
break;
}
}
/**
* Implementation of hook_form().
*/
function merci_form(&$node, $form_state) {
$form = node_content_form($node, $form_state);
$template = array();
// Build existing reserved items table on existing reservations.
if (isset($node->nid)) {
$form['existing_items'] = merci_build_reservation_table_form($form_state, $node, TRUE);
$merci = $node->merci;
}
else {
$merci['status'] = variable_get('merci_default_reservation_status', MERCI_STATUS_UNCONFIRMED);
}
// Choice adding code mostly stolen from poll module.
if (isset($form_state['choice_count'])) {
$choice_count = $form_state['choice_count'];
}
else {
$choice_count = max(3, empty($node->choice) ? 3 : count($node->choice));
}
// Add a wrapper for the choices and more button.
$form['choice_wrapper'] = array(
'#tree' => FALSE,
'#prefix' => '<div class="clear-block" id="merci-choice-wrapper">',
'#suffix' => '</div>',
);
// Container for just the item selector.
$form['choice_wrapper']['choice'] = array(
'#prefix' => '<div id="merci-choices">',
'#suffix' => '</div>',
'#theme' => 'merci_choices',
);
if (isset($_GET['template'])) {
$templates = db_query("SELECT nr.body FROM {node_revisions} nr WHERE nid = %d ORDER BY vid DESC LIMIT 1", intval($_GET['template']));
while ($template_object = db_fetch_object($templates)) {
$template = explode(',', $template_object->body);
}
// while
}
// if
// Add the current choices to the form.
for ($delta = 0; $delta < $choice_count || $delta < count($template); $delta++) {
$default = isset($node->choice[$delta]['item']) ? $node->choice[$delta]['item'] : '';
if ($default == '' && isset($template[$delta])) {
$default = $template[$delta];
}
// if
$form['choice_wrapper']['choice'][$delta] = _merci_choice_form($node, $form_state, $delta, $default);
}
// We name our button 'merci_more' to avoid conflicts with other modules using
// AHAH-enabled buttons with the id 'more'.
$form['choice_wrapper']['merci_more'] = array(
'#type' => 'submit',
'#value' => t('Add more items'),
'#description' => t("If the number of items above isn't enough, click here to add more items."),
'#weight' => 1,
'#submit' => array(
'merci_more_choices_submit',
),
);
if (user_access('administer MERCI')) {
$form['merci'] = array(
'#type' => 'fieldset',
'#title' => t('MERCI settings'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['merci']['merci_status'] = array(
'#title' => t('Status'),
'#type' => 'radios',
'#options' => merci_record_status(),
'#default_value' => $merci['status'],
'#description' => t('Finalized bookings can not have time conflicts with each other.'),
);
}
else {
$form['merci_status'] = array(
'#type' => 'value',
'#value' => $merci['status'],
);
}
return $form;
}
/**
* Submit handler to add more choices to a reservation form. This handler is used when
* javascript is not available. It makes changes to the form state and the
* entire form is rebuilt during the page reload.
*/
function merci_more_choices_submit($form, &$form_state) {
// Set the form to rebuild and run submit handlers.
node_form_submit_build_node($form, $form_state);
// Make the changes we want to the form state.
if ($form_state['values']['merci_more']) {
$form_state['choice_count'] = count($form_state['values']['choice']) + 5;
}
}
/**
* Submit handler to date filter items on a reservation form.
* It makes changes to the form state and the entire form is
* rebuilt during the page reload.
*/
function merci_date_filter($form, &$form_state) {
// Set the form to rebuild and run submit handlers.
node_form_submit_build_node($form, $form_state);
}
/**
* Builds an individual item selector.
*
* @param $node
* The reservation node object.
* @param $form_state
* Current form state array.
* @param $delta
* Which selector number to build.
* @param $default
* Default value for the select.
* @return
* The form array for the selector.
*/
function _merci_choice_form($node, $form_state, $delta, $default = '') {
$form = array(
'#tree' => TRUE,
);
// We'll manually set the #parents property of these fields so that
// their values appear in the $form_state['values']['choice'] array.
$options = merci_build_reservable_items($node, $form_state);
$form['item'] = array(
'#type' => 'select',
'#title' => t('Reserve item @n', array(
'@n' => $delta + 1,
)),
'#options' => $options['options'],
'#default_value' => $default,
'#parents' => array(
'choice',
$delta,
'item',
),
);
return $form;
}
/**
* Builds the list of all currently reservable items, filtered by date.
*
* @param $node
* The reservation node object.
* @param $form_state
* Current form state array.
* @param $reservation_nid
* (Optional) The nid of a reservation to ignore in the options exclusions.
* @return
* An associative array with the following key/value pairs:
* 'options' => An array of available items, in the format used
* for the item selector.
* 'flat_options' => An array of available items, whose values are the
* nids of the items.
*/
function merci_build_reservable_items($node, $form_state, $reservation_nid = NULL) {
// Newly set dates take precedence.
if (isset($form_state['values']['field_merci_date'])) {
$start = $form_state['values']['field_merci_date'][0]['value'];
$end = $form_state['values']['field_merci_date'][0]['value2'];
}
elseif (isset($node->nid)) {
$date_info = $node->field_merci_date[0];
$start = $date_info['value'];
$end = $date_info['value2'];
}
else {
$is_new = TRUE;
}
$options = array();
$options['options'] = array(
'' => t('<Select>'),
);
// Buckets.
$buckets = merci_load_merci_type_settings('bucket');
$bucket_options = array();
// Loop through each bucket type.
while ($bucket = db_fetch_object($buckets)) {
// Only include active buckets, and content types the user
// can reserve.
if ($bucket->status == MERCI_STATUS_ACTIVE && merci_check_content_type_user_permissions($bucket->type)) {
// No filtering for new reservations.
if (isset($is_new)) {
// Only add the bucket if there is at least one published item.
if (merci_check_existing_bucket_items($bucket->type, TRUE)) {
$bucket_options[$bucket->type] = $bucket->name;
}
// if
}
else {
// Check bucket restrictions.
// DIFF??
$restrictions = merci_check_content_type_restrictions($bucket->type, $start, $end);
if (empty($restrictions)) {
// Check for available items in the bucket.
$available_bucket_items = merci_get_available_bucket_count($bucket->type, $start, $end, $reservation_nid) > $bucket->spare_items;
if ($available_bucket_items) {
$bucket_options[$bucket->type] = $bucket->name;
}
// if
}
// if
}
// else
}
// if
}
// while
// FIX - NEED TO CHANGE FORMATTING HERE
$options['options'][t('Buckets')] = $bucket_options;
$options['flat_options'] = array_keys($bucket_options);
// Resources.
$resources = merci_load_merci_type_settings('resource');
// Loop through each resource type.
while ($resource = db_fetch_object($resources)) {
$item_options = array();
// Only include active resources, and content types the user can reserve.
if ($resource->status == MERCI_STATUS_ACTIVE && merci_check_content_type_user_permissions($resource->type)) {
// No filtering for new reservations.
if (isset($is_new)) {
// Filter out any placeholder nodes for this resource.
$items = db_query("SELECT n.nid, n.title FROM {node} n INNER JOIN {merci_resource_node} m ON n.vid = m.vid WHERE n.type = '%s' AND n.status = %d AND m.sub_type = %d ORDER BY n.title", $resource->type, 1, MERCI_SUB_TYPE_ITEM);
while ($item = db_fetch_object($items)) {
$item_options[$item->nid] = $item->title;
}
// while
}
else {
// Check resource restrictions.
//DIFF?
$restrictions = merci_check_content_type_restrictions($resource->type, $start, $end);
if (empty($restrictions)) {
$item_options = merci_get_reservable_items('resource', $resource->type, $start, $end, $reservation_nid);
}
// if
}
// else
$options['options'][$resource->name] = $item_options;
$options['flat_options'] = array_merge($options['flat_options'], array_keys($item_options));
}
// if
}
// while
return $options;
}
/**
* Checks for reservation restrictions for a content type.
*
* These include maximum hours per reservation, and if the bucket/resource
* is reservable overnight and/or on weekends.
*
* @param $content_type
* The content type to be checked.
* @param $start
* The start date of the reservation in DATETIME format and UTC timezone.
* @param $end
* The end date of the reservation in DATETIME format and UTC timezone.
* @return
* An array of warning messages for any restrictions found.
*/
//DIFF?
function merci_check_content_type_restrictions($content_type, $start, $end) {
// Users in role with administer MERCI or create reservations outside hours of operation permssion
// are exempt from content type restriction for max hours and the checkout's hours of operation
if (user_access('administer MERCI') || user_access('create reservations outside hours of operation')) {
//drupal_set_message(t('You are making a Reservation outside the normal hours of operation. This may impact access to the items you are reserving.'));
}
else {
//DIFF?
$type_settings = merci_content_type_rules($content_type);
$hours_of_operation = merci_load_hours_of_operation($content_type);
$return = array();
// Convert start/end dates to local time.
$start_object = merci_create_local_date_object($start);
$end_object = merci_create_local_date_object($end);
// Convert start/end dates to local time.
$start_object = merci_create_local_date_object($start);
$end_object = merci_create_local_date_object($end);
// We want these timestamps generated in UTC.
$old_timezone = date_default_timezone_get();
date_default_timezone_set('UTC');
$start_timestamp = strtotime($start);
$end_timestamp = strtotime($end);
date_default_timezone_set($old_timezone);
$reserved_hours = ($end_timestamp - $start_timestamp) / (60 * 60);
$start_day_of_week = date_format($start_object, 'w');
$end_day_of_week = date_format($end_object, 'w');
// Make sure max hours aren't exceeded.
if ($type_settings->max_hours_per_reservation && $reserved_hours > $type_settings->max_hours_per_reservation) {
$return[] = t('%name cannot be reserved for more than %hours hours.', array(
'%hours' => $type_settings->max_hours_per_reservation,
));
}
// Validate allow_overnight.
if (!$type_settings->allow_overnight) {
// Need the 48 hour check in case somebody starts and ends their
// reservation on the same day.
if ($start_day_of_week != $end_day_of_week || $reserved_hours > 48) {
$return[] = t('%name cannot be reserved overnight.');
}
}
// Validate allow_weekend.
if (!$type_settings->allow_weekends) {
$on_weekend = FALSE;
// Check the start and end dates for the reservation first.
if (in_array($start_day_of_week, array(
'6',
'0',
)) || in_array($end_day_of_week, array(
'6',
'0',
))) {
$on_weekend = TRUE;
}
// Check all dates between the start and end dates for the reservation next.
if (!$on_weekend) {
$day = 60 * 60 * 24;
$counter = $start_timestamp + $day;
while ($counter <= $end_timestamp) {
$utc_datetime = gmdate('Y-m-d H:i:s', $counter);
$local_date = merci_create_local_date_object($utc_datetime);
$day_of_week = date_format($local_date, 'w');
if (in_array($day_of_week, array(
'6',
'0',
))) {
$on_weekend = TRUE;
break;
}
$counter += $day;
}
}
if ($on_weekend) {
$return[] = t('%name cannot be reserved on weekends.');
}
}
}
return $return;
}
// merci_check_content_type_restrictions
/**
* Ensures the user has 'edit own [type] content' and 'delete own [type] content'
* permissions, otherwise they are not allowed to reserve the content type.
*
* @return TRUE if the user has access to reserve the content type, FALSE
* otherwise.
*/
function merci_check_content_type_user_permissions($type) {
return user_access("edit own {$type} content") && user_access("delete own {$type} content");
}
/**
* Loads MERCI rules for a content type.
*
* @param $content_type
* The type to load.
* @return
* An object of rules.
*/
function merci_content_type_rules($content_type) {
// Load default rules
$rules = merci_load_content_type_settings($content_type);
$rules->hours_mon = variable_get('merci_hours_mon', '');
$rules->hours_tue = variable_get('merci_hours_tue', '');
$rules->hours_wed = variable_get('merci_hours_wed', '');
$rules->hours_thu = variable_get('merci_hours_thu', '');
$rules->hours_fri = variable_get('merci_hours_fri', '');
$rules->hours_sat = variable_get('merci_hours_sat', '');
$rules->hours_sun = variable_get('merci_hours_sun', '');
// Allow any other modules (e.g. merci_rro) to change the rules
module_invoke_all('merci_rules_alter', $rules);
return $rules;
}
// merci_content_type_rules
/**
* Loads the settings for a single MERCI content type.
*
* @param $content_type
* The type to load.
* @return
* An object of type settings.
*/
function merci_load_content_type_settings($content_type) {
return db_fetch_object(db_query("SELECT nt.type, nt.name, m.type_setting, m.max_hours_per_reservation, m.allow_overnight, m.allow_weekends, m.late_fee_per_hour, m.rate_per_hour, m.fee_free_hours, m.status, m.spare_items, m.min_cancel_hours, m.autocheckout, m.autocheckin, m.selfcheckout FROM {node_type} nt INNER JOIN {merci_node_type} m ON nt.type = m.type WHERE nt.type = '%s'", $content_type));
//return db_fetch_object(db_query("SELECT nt.type, nt.name, m.type_setting, m.max_hours_per_reservation, m.allow_overnight, m.allow_weekends, m.late_fee_per_hour, m.rate_per_hour, m.fee_free_hours, m.status, m.spare_items, m.min_cancel_hours, m.autocheckout, m.autocheckin, m.selfcheckout, m.grouping FROM {node_type} nt INNER JOIN {merci_node_type} m ON nt.type = m.type WHERE nt.type = '%s'", $content_type));
}
/**
* Loads the settings for an entire MERCI type (bucket/resource).
*
* @param $merci_type
* The MERCI type: bucket|resource.
* @return
* A database object containing all content types for the
* specified MERCI type.
*/
function merci_load_merci_type_settings($merci_type) {
return db_query("SELECT nt.type, nt.name, m.type_setting, m.max_hours_per_reservation, m.allow_overnight, m.allow_weekends, m.late_fee_per_hour, m.rate_per_hour, m.fee_free_hours, m.status, m.spare_items, m.min_cancel_hours, m.autocheckout, m.autocheckin, m.selfcheckout FROM {node_type} nt INNER JOIN {merci_node_type} m ON nt.type = m.type WHERE m.type_setting = '%s' ORDER BY nt.name", $merci_type);
}
/**
* Menu callback for AHAH additions.
*/
function merci_choice_js() {
$delta = count($_POST['choice']);
$nid = isset($_POST['nid']) ? $_POST['nid'] : 0;
if ((int) $nid) {
$node = node_load($nid);
}
else {
$node = new stdClass();
}
$dates = $_POST['field_merci_date'][0];
// If a start and end date exist, we have to massage them
// into the proper format from user input.
// TODO: is there a more elegant way to do this?
if ($dates['value']['date'] && $dates['value']['time'] && $dates['value2']['date'] && $dates['value2']['time']) {
module_load_include('inc', 'date_api', 'date_api_elements');
$date_timezone = date_default_timezone_name();
$date_format = 'm/d/Y g:ia';
$start = array(
'#value' => array(
'date' => $dates['value']['date'],
'time' => $dates['value']['time'],
),
'#date_timezone' => $date_timezone,
'#date_format' => $date_format,
);
$end = array(
'#value' => array(
'date' => $dates['value2']['date'],
'time' => $dates['value2']['time'],
),
'#date_timezone' => $date_timezone,
'#date_format' => $date_format,
);
$form_state['values']['field_merci_date'][0]['value'] = date_popup_input_value($start);
$form_state['values']['field_merci_date'][0]['value2'] = date_popup_input_value($end);
}
else {
$form_state = array();
}
// Build our new form element.
$form_element = _merci_choice_form($node, $form_state, $delta);
drupal_alter('form', $form_element, array(), 'merci_choice_js');
// Build the new form.
$form_state = array(
'submitted' => FALSE,
);
$form_build_id = $_POST['form_build_id'];
// Add the new element to the stored form. Without adding the element to the
// form, Drupal is not aware of this new elements existence and will not
// process it. We retreive the cached form, add the element, and resave.
if (!($form = form_get_cache($form_build_id, $form_state))) {
exit;
}
$form['choice_wrapper']['choice'][$delta] = $form_element;
form_set_cache($form_build_id, $form, $form_state);
$form += array(
'#post' => $_POST,
'#programmed' => FALSE,
);
// Rebuild the form.
$form = form_builder('merci_reservation_node_form', $form, $form_state);
// Render the new output.
$choice_form = $form['choice_wrapper']['choice'];
unset($choice_form['#prefix'], $choice_form['#suffix']);
// Prevent duplicate wrappers.
$choice_form[$delta]['#attributes']['class'] = empty($choice_form[$delta]['#attributes']['class']) ? 'ahah-new-content' : $choice_form[$delta]['#attributes']['class'] . ' ahah-new-content';
$output = theme('status_messages') . drupal_render($choice_form);
drupal_json(array(
'status' => TRUE,
'data' => $output,
));
}
/**
* Theme the reservation form for choices.
*/
function theme_merci_choices($form) {
// Change the button title to reflect the behavior when using JavaScript.
//drupal_add_js('if (Drupal.jsEnabled) { $(document).ready(function() { $("#edit-merci-more").val("'. t('Add another item') .'"); }); }', 'inline');
$output = '';
$output .= drupal_render($form);
return $output;
}
/**
* Implementation of hook_nodeapi().
*/
function merci_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
if (($cache = cache_get('merci_' . $node->type . '_data')) && !empty($cache->data)) {
$merci_node_type = $cache->data;
}
else {
// Load MERCI node type settings.
$merci_node_type = db_fetch_array(db_query("SELECT * FROM {merci_node_type} WHERE type = '%s' AND type_setting <> 'disabled'", $node->type));
// Do your expensive calculations here, and populate $my_data
// with the correct stuff..
if ($merci_node_type) {
cache_set('merci_' . $node->type . '_data', $merci_node_type);
}
}
// Process active MERCI node types and reservation nodes.
if ($merci_node_type || $node->type == 'merci_reservation') {
$type = $node->type == 'merci_reservation' ? 'reservation' : $merci_node_type['type_setting'];
switch ($op) {
case 'prepare':
case 'load':
if (isset($node->nid)) {
switch ($type) {
case 'bucket':
$merci_node = db_fetch_array(db_query("SELECT default_availability, late_fee_per_hour, rate_per_hour, fee_free_hours, min_cancel_hours, autocheckout, autocheckin, selfcheckout, sub_type FROM {merci_bucket_node} WHERE vid = %d", $node->vid));
break;
case 'resource':
$merci_node = db_fetch_array(db_query("SELECT default_availability, late_fee_per_hour, rate_per_hour, fee_free_hours, min_cancel_hours, autocheckout, autocheckin, selfcheckout, sub_type FROM {merci_resource_node} WHERE vid = %d", $node->vid));
break;
}
}
if ($merci_node_type) {
if (isset($merci_node)) {
$node->merci = array_merge($merci_node_type, $merci_node);
}
else {
$node->merci = $merci_node_type;
}
}
break;
case 'validate':
if ($type != 'reservation') {
merci_validate_default_availability($node);
}
break;
case 'insert':
case 'update':
if ($op == 'insert' || $node->revision) {
switch ($type) {
case 'bucket':
db_query("INSERT INTO {merci_bucket_node} (nid, vid, default_availability, late_fee_per_hour, rate_per_hour, fee_free_hours, min_cancel_hours, autocheckout, autocheckin, selfcheckout, sub_type) VALUES (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", $node->nid, $node->vid, $node->merci_default_availability, $node->merci_late_fee_per_hour, $node->merci_rate_per_hour, $node->merci_fee_free_hours, $node->merci_min_cancel_hours, $node->merci_autocheckout, $node->merci_autocheckin, $node->merci_selfcheckout, $node->merci_sub_type);
break;
case 'resource':
db_query("INSERT INTO {merci_resource_node} (nid, vid, default_availability, late_fee_per_hour, rate_per_hour, fee_free_hours, min_cancel_hours, autocheckout, autocheckin, selfcheckout, sub_type) VALUES (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", $node->nid, $node->vid, $node->merci_default_availability, $node->merci_late_fee_per_hour, $node->merci_rate_per_hour, $node->merci_fee_free_hours, $node->merci_min_cancel_hours, $node->merci_autocheckout, $node->merci_autocheckin, $node->merci_selfcheckout, $node->merci_sub_type);
break;
}
}
else {
switch ($type) {
case 'bucket':
db_query("UPDATE {merci_bucket_node} SET default_availability = %d, late_fee_per_hour = %d, rate_per_hour = %d, fee_free_hours = %d, autocheckout = %d, autocheckin = %d, selfcheckout = %d WHERE vid = %d", $node->merci_default_availability, $node->merci_late_fee_per_hour, $node->merci_rate_per_hour, $node->merci_fee_free_hours, $node->merci_min_cancel_hours, $node->merci_autocheckout, $node->merci_autocheckin, $node->merci_selfcheckout, $node->vid);
break;
case 'resource':
db_query("UPDATE {merci_resource_node} SET default_availability = %d, late_fee_per_hour = %d, rate_per_hour = %d, fee_free_hours = %d, autocheckout = %d, autocheckin = %d, selfcheckout = %d WHERE vid = %d", $node->merci_default_availability, $node->merci_late_fee_per_hour, $node->merci_rate_per_hour, $node->merci_fee_free_hours, $node->merci_min_cancel_hours, $node->merci_autocheckout, $node->merci_autocheckin, $node->merci_selfcheckout, $node->vid);
break;
}
}
break;
case 'delete':
// In the case were a reservation placeholder node is being
// deleted, remove it from the detail table here.
if ($type != 'reservation') {
db_query("DELETE FROM {merci_reservation_detail} WHERE placeholder_nid = %d", $node->nid);
}
switch ($type) {
case 'bucket':
db_query("DELETE FROM {merci_bucket_node} WHERE nid = %d", $node->nid);
break;
case 'resource':
db_query("DELETE FROM {merci_resource_node} WHERE nid = %d", $node->nid);
break;
}
break;
case 'delete revision':
switch ($type) {
case 'bucket':
db_query("DELETE FROM {merci_bucket_node} WHERE vid = %d", $node->vid);
break;
case 'resource':
db_query("DELETE FROM {merci_resource_node} WHERE vid = %d", $node->vid);
break;
case 'reservation':
db_query("DELETE FROM {merci_reservation} WHERE vid = %d", $node->vid);
db_query("DELETE FROM {merci_reservation_detail} WHERE vid = %d", $node->vid);
break;
}
break;
}
}
}
/**
* Validates the state change of a reservable item.
*
* @param $node
* The item node.
*/
function merci_validate_default_availability($node) {
// Only perform the check if the item is set to an unavailable state.
if (in_array((int) $node->merci_default_availability, array(
MERCI_UNA_F,
MERCI_UNA_S,
))) {
// Determine CCK table and columns the date data is stored in.
$field = content_fields('field_merci_date');
$db_info = content_database_info($field);
$table = $db_info['table'];
$column_end_date = $db_info['columns']['value2']['column'];
$time = gmdate('Y-m-d H:i:s');
// Pull any incomplete reservations that use the item in question
$reservations = db_query("SELECT ctn.nid, ctn.title FROM {" . $table . "} ct INNER JOIN {merci_reservation_detail} md ON ct.vid = md.vid INNER JOIN {node} ctn ON ct.vid = ctn.vid WHERE md.item_nid = %d AND {$column_end_date} >= '%s' AND NOT (md.item_status <= %d)", $node->nid, $time, MERCI_ITEM_STATUS_AVAILABLE);
$bad_reservations = array();
while ($reservation = db_fetch_object($reservations)) {
$bad_reservations[] = l($reservation->title, "node/{$reservation->nid}/edit", array(
'query' => drupal_get_destination(),
));
}
if (!empty($bad_reservations)) {
form_set_error('merci_default_availability', t('%title can not be set to an unavailable status until it is removed from the following reservations:', array(
'%title' => $node->title,
)) . theme('item_list', $bad_reservations));
}
}
}
/**
* Validates if an item node can be deleted.
*
* @param $node
* The item node.
* @param $single
* TRUE if a single item node deletion is being processed, FALSE otherwise.
* @return
* TRUE if the item can be deleted, FALSE otherwise.
*/
function merci_delete_item_validate($node, $single = TRUE) {
// Only validate bucket/resource items.
if ($node->type != 'merci_reservation' && $node->merci['type_setting'] != 'disabled' && $node->merci['sub_type'] == MERCI_SUB_TYPE_ITEM) {
// Determine CCK table and columns the date data is stored in.
$field = content_fields('field_merci_date');
$db_info = content_database_info($field);
$table = $db_info['table'];
// Join on nid here so that any version of the reservation that contain
// the item is caught.
$reservations = db_query("SELECT ctn.nid, ctn.title FROM {" . $table . "} ct INNER JOIN {merci_reservation_detail} md ON ct.vid = md.vid INNER JOIN {node} ctn ON ct.nid = ctn.nid WHERE md.item_nid = %d ORDER BY ct.nid, ct.vid", $node->nid);
$bad_reservations = array();
while ($reservation = db_fetch_object($reservations)) {
// Key by nid to prevent duplicate revisions from appearing.
$bad_reservations[$reservation->nid] = l($reservation->title, "node/{$reservation->nid}/edit", array(
'query' => drupal_get_destination(),
));
}
if (!empty($bad_reservations)) {
drupal_set_message(t('%title can not be deleted, because it is associated with the following reservations:', array(
'%title' => $node->title,
)) . theme('item_list', $bad_reservations), 'error');
// Lock out single deletion attempts here.
if ($single) {
module_invoke_all('exit');
drupal_access_denied();
}
else {
return FALSE;
}
}
}
return TRUE;
}
/**
* Validates saving of MERCI node types.
*/
function merci_node_type_save_validate($form, &$form_state) {
$values = $form_state['values'];
// Only validate node types set to an inactive status.
if ($values['merci_type_setting'] != 'disabled' && (int) $values['merci_status'] == MERCI_STATUS_INACTIVE) {
// Determine CCK table and columns the date data is stored in.
$field = content_fields('field_merci_date');
$db_info = content_database_info($field);
$table = $db_info['table'];
$column_end_date = $db_info['columns']['value2']['column'];
$time = gmdate('Y-m-d H:i:s');
$type_setting = $values['merci_type_setting'];
// Pull all active reservations that use the node type.
$reservations = db_query("SELECT ctn.nid, ctn.title FROM {" . $table . "} ct INNER JOIN {merci_reservation_detail} md ON ct.vid = md.vid INNER JOIN {node} ctn ON ct.vid = ctn.vid INNER JOIN {merci_{$type_setting}_node} m ON md.placeholder_nid = m.nid INNER JOIN {node} mn ON m.vid = mn.vid WHERE mn.type = '%s' AND m.sub_type = %d AND {$column_end_date} >= '%s' AND NOT (md.item_status <= %d)", $values['old_type'], MERCI_SUB_TYPE_RESERVATION, $time, MERCI_ITEM_STATUS_CHECKED_IN);
$bad_reservations = array();
while ($reservation = db_fetch_object($reservations)) {
$bad_reservations[] = l($reservation->title, "node/{$reservation->nid}/edit", array(
'query' => drupal_get_destination(),
));
}
if (!empty($bad_reservations)) {
form_set_error('merci_status', t('@type_setting can not be set to an inactive status until all @type_setting items are removed from the following reservations:', array(
'@type_setting' => $type_setting,
)) . theme('item_list', $bad_reservations));
}
}
}
/**
* Validates deletion of node types.
*
* @param $type
* The type being deleted.
*/
function merci_delete_node_type_validate($type) {
$settings = merci_load_content_type_settings($type);
// Only validate active MERCI node types.
if ($settings->type_setting != 'disabled') {
// Determine CCK table and columns the date data is stored in.
$field = content_fields('field_merci_date');
$db_info = content_database_info($field);
$table = $db_info['table'];
// Join on nid here so that any version of the reservation that contain
// the bucket/resource is caught.
$reservations = db_query("SELECT ctn.nid, ctn.title FROM {" . $table . "} ct INNER JOIN {merci_reservation_detail} md ON ct.vid = md.vid INNER JOIN {node} ctn ON ct.nid = ctn.nid INNER JOIN {merci_{$settings->type_setting}_node} m ON md.placeholder_nid = m.nid INNER JOIN {node} mn ON m.vid = mn.vid WHERE mn.type = '%s' AND m.sub_type = %d ORDER BY ct.nid, ct.vid", $settings->type, MERCI_SUB_TYPE_RESERVATION);
$bad_reservations = array();
while ($reservation = db_fetch_object($reservations)) {
$bad_reservations[$reservation->nid] = l($reservation->title, "node/{$reservation->nid}/edit", array(
'query' => drupal_get_destination(),
));
}
if (!empty($bad_reservations)) {
drupal_set_message(t('@type can not be deleted because it is associated with the following reservations:', array(
'@type' => $settings->name,
)) . theme('item_list', $bad_reservations), 'error');
module_invoke_all('exit');
drupal_access_denied();
}
}
}
/**
* Implementation of hook_theme().
*/
function merci_theme() {
return array(
'merci_choices' => array(
'arguments' => array(
'form' => NULL,
),
),
'merci_build_reservation_table_form' => array(
'arguments' => array(
'form' => NULL,
),
),
'merci_reservation_table' => array(
'template' => 'merci_reservation_table',
'path' => drupal_get_path('module', 'merci') . '/templates',
'arguments' => array(
'reservations' => NULL,
'count' => NULL,
'hours' => NULL,
'title' => NULL,
),
),
);
}
/**
* Return the name of a type code.
*
* @param string|int $code
* if int, will return translated name of the code.
* if NULL, returns array of codes as keys, and translated strings as value
* @return string|int
*/
function merci_item_status($code = NULL) {
$statuses = array(
MERCI_AVA_F => t('Available'),
MERCI_UNA_F => t('Unavailable'),
MERCI_AVA_S => t('Strictly Available'),
MERCI_UNA_S => t('No Longer in Inventory'),
);
if (isset($code)) {
return $statuses[$code];
}
else {
return $statuses;
}
}
/**
* Return the name of a status code.
*
* @param string|int $code
* if int, will return translated name of the code.
* if NULL, returns array of codes as keys, and translated strings as value
* @return string|int
*/
function merci_record_status($code = NULL) {
$types = array(
MERCI_STATUS_UNCONFIRMED => t('Unconfirmed'),
MERCI_STATUS_PENDING => t('Pending'),
MERCI_STATUS_CHECKED_OUT => t('Checked out'),
MERCI_STATUS_CHECKED_IN => t('Checked in'),
MERCI_STATUS_CANCELLED => t('Cancelled'),
MERCI_STATUS_DENIED => t('Denied'),
MERCI_STATUS_DENIED => t('No Show'),
);
if (isset($code)) {
return $types[$code];
}
else {
return $types;
}
}
/**
* Submit handler for saving MERCI node type data.
*/
function merci_node_type_save_submit($form, &$form_state) {
$merci = $form_state['values'];
// This hack is necessary because the node type form submit
// automatically saves all remaining form items to {variable}
// We're doing custom storage, so remove these.
$variables = array(
'merci_type_setting_',
'merci_max_hours_per_reservation_',
'merci_allow_overnight_',
'merci_allow_weekends_',
'merci_late_fee_per_hour_',
'merci_rate_per_hour_',
'merci_fee_free_hours_',
'merci_status_',
'merci_spare_items_',
'merci_min_cancel_hours_',
'merci_autocheckout_',
'merci_autocheckin_',
'merci_selfcheckout_',
'merci_grouping_',
);
foreach ($variables as $variable) {
variable_del($variable . $merci['type']);
}
if ($form_state['clicked_button']['#value'] == t('Save content type')) {
db_query("UPDATE {merci_node_type} SET type_setting = '%s', max_hours_per_reservation = %d, allow_overnight = %d, allow_weekends = %d, late_fee_per_hour = %f, rate_per_hour = %f, fee_free_hours = %d, status = %d, spare_items = %d, min_cancel_hours = %d, autocheckout = %d, autocheckin = %d, selfcheckout = %d, grouping = %d WHERE type = '%s'", $merci['merci_type_setting'], $merci['merci_max_hours_per_reservation'], $merci['merci_allow_overnight'], $merci['merci_allow_weekends'], $merci['merci_late_fee_per_hour'], $merci['merci_rate_per_hour'], $merci['merci_fee_free_hours'], $merci['merci_status'], $merci['merci_spare_items'], $merci['merci_min_cancel_hours'], $merci['merci_autocheckout'], $merci['merci_autocheckin'], $merci['merci_selfcheckout'], $merci['merci_grouping'], $merci['type']);
if (!db_affected_rows()) {
db_query("INSERT INTO {merci_node_type} (type, type_setting, max_hours_per_reservation, allow_overnight, allow_weekends, late_fee_per_hour, rate_per_hour, fee_free_hours, status, spare_items, min_cancel_hours, autocheckout, autocheckin, selfcheckout, grouping) VALUES ('%s', '%s', %d, %d, %d, %d, %f, %f, %d, %d, %d, %d, %d, %d, %d)", $merci['type'], $merci['merci_type_setting'], $merci['merci_max_hours_per_reservation'], $merci['merci_allow_overnight'], $merci['merci_allow_weekends'], $merci['merci_late_fee_per_hour'], $merci['merci_rate_per_hour'], $merci['merci_fee_free_hours'], $merci['merci_status'], $merci['merci_spare_items'], $merci['merci_min_cancel_hours'], $merci['merci_autocheckout'], $merci['merci_autocheckin'], $merci['merci_selfcheckout'], $merci['merci_grouping']);
}
}
cache_clear_all('merci', 'cache', TRUE);
}
/**
* Validation for numeric textfields.
*/
function merci_is_numeric_validate($form) {
if ($form['#value'] && !is_numeric($form['#value'])) {
form_set_error($form['#name'], t('%title must be a number.', array(
'%title' => $form['#title'],
)));
}
}
/**
* Implementation of hook_insert().
*/
function merci_insert($node) {
if ($node->type == 'merci_reservation') {
db_query("INSERT INTO {merci_reservation} (nid, vid, status) VALUES (%d, %d, %d)", $node->nid, $node->vid, $node->merci_status);
merci_add_reservation_items($node);
}
}
/**
* Implementation of hook_update().
*/
function merci_update($node) {
if ($node->type == 'merci_reservation') {
if ($node->revision) {
db_query("INSERT INTO {merci_reservation} (nid, vid, status) VALUES (%d, %d, %d)", $node->nid, $node->vid, $node->merci_status);
}
else {
db_query("UPDATE {merci_reservation} SET status = %d WHERE vid = %d", $node->merci_status, $node->vid);
}
merci_add_reservation_items($node);
}
}
/**
* Adds items to reservation on creation/update.
*
* @param $node
* The reservation node.
*/
function merci_add_reservation_items($node) {
// Update existing items.
if (isset($node->existing_items['items'])) {
foreach ($node->existing_items['items'] as $did => $item_nid) {
// Only selected items get their status bumped to reserved.
if ($item_nid) {
$item_status = MERCI_ITEM_STATUS_RESERVED;
}
else {
$item_status = MERCI_ITEM_STATUS_AVAILABLE;
}
if ($node->revision) {
db_query("INSERT INTO {merci_reservation_detail} (nid, vid, placeholder_nid, item_nid, item_status) VALUES (%d, %d, %d, %d, %d)", $node->nid, $node->vid, $node->existing_items['placeholders'][$did], $item_nid, $item_status);
}
else {
db_query("UPDATE {merci_reservation_detail} SET item_nid = %d, item_status = %d WHERE did = %d", $item_nid, $item_status, $did);
}
}
}
// New items.
foreach ($node->choice as $num => $choice) {
// Resource.
if (is_numeric($choice['item'])) {
$item = db_fetch_object(db_query("SELECT type, title FROM {node} WHERE nid = %d", $choice['item']));
$type = $item->type;
$title = $item->title;
$item_nid = $choice['item'];
$item_node = node_load($item_nid);
$item_status = MERCI_ITEM_STATUS_RESERVED;
}
elseif ($choice['item']) {
$type = $choice['item'];
$title = db_result(db_query("SELECT name FROM {node_type} WHERE type = '%s'", $type));
$item_nid = 0;
$item_node = FALSE;
$item_status = MERCI_ITEM_STATUS_AVAILABLE;
}
else {
// Nothing selected -- move along...
continue;
}
// Build the item's placeholder node.
$reservation = new stdClass();
$reservation->type = $type;
$reservation->name = $node->name;
$reservation->uid = $node->uid;
$reservation->title = "{$title} " . t('(Reservation)');
$reservation->body = '';
$reservation->status = 0;
$reservation->promote = 0;
$reservation->sticky = 0;
// MERCI specific data.
$merci_settings = merci_load_content_type_settings($type);
$reservation->merci_default_availability = MERCI_AVA_F;
$reservation->merci_sub_type = MERCI_SUB_TYPE_RESERVATION;
// Use the item specific accounting data if an item is assigned,
// otherwise fall back to the content type defaults.
$reservation->merci_late_fee_per_hour = $item_node ? $item_node->merci['late_fee_per_hour'] : $merci_settings->late_fee_per_hour;
$reservation->merci_rate_per_hour = $item_node ? $item_node->merci['rate_per_hour'] : $merci_settings->rate_per_hour;
$reservation->merci_fee_free_hours = $item_node ? $item_node->merci['fee_free_hours'] : $merci_settings->fee_free_hours;
$reservation->merci_min_cancel_hours = $item_node ? $item_node->merci['min_cancel_hours'] : $merci_settings->min_cancel_hours;
$reservation->merci_autocheckout = $item_node ? $item_node->merci['autocheckout'] : $merci_settings->autocheckout;
$reservation->merci_autocheckin = $item_node ? $item_node->merci['autocheckin'] : $merci_settings->autocheckin;
$reservation->merci_selfcheckout = $item_node ? $item_node->merci['selfcheckout'] : $merci_settings->selfcheckout;
$reservation = node_submit($reservation);
node_save($reservation);
db_query("INSERT INTO {merci_reservation_detail} (nid, vid, placeholder_nid, item_nid, item_status) VALUES (%d, %d, %d, %d, %d)", $node->nid, $node->vid, $reservation->nid, $item_nid, $item_status);
}
// Update the state of all items with associations.
switch ((int) $node->merci_status) {
case MERCI_STATUS_UNCONFIRMED:
case MERCI_STATUS_PENDING:
$item_status = MERCI_ITEM_STATUS_RESERVED;
break;
case MERCI_STATUS_CHECKED_OUT:
$item_status = MERCI_ITEM_STATUS_CHECKED_OUT;
break;
case MERCI_STATUS_CHECKED_IN:
$item_status = MERCI_ITEM_STATUS_CHECKED_IN;
break;
case MERCI_STATUS_CANCELLED:
case MERCI_STATUS_DENIED:
$item_status = MERCI_ITEM_STATUS_AVAILABLE;
break;
}
db_query("UPDATE {merci_reservation_detail} SET item_status = %d WHERE vid = %d AND item_nid <> 0", $item_status, $node->vid);
}
/**
* Implementation of hook_delete().
*/
function merci_delete($node) {
if ($node->type == 'merci_reservation') {
// Delete all reservation placeholder nodes for the reservation.
$placeholders = db_query("SELECT DISTINCT(placeholder_nid) AS nid FROM {merci_reservation_detail} WHERE nid = %d", $node->nid);
while ($placeholder = db_fetch_object($placeholders)) {
node_delete($placeholder->nid);
}
db_query("DELETE FROM {merci_reservation} WHERE nid = %d", $node->nid);
db_query("DELETE FROM {merci_reservation_detail} WHERE nid = %d", $node->nid);
}
}
/**
* Implementation of hook_view().
*/
function merci_view($node, $teaser = FALSE, $page = FALSE) {
// TODO: should we fix node previews?
if ($node->type == 'merci_reservation' && !isset($node->preview)) {
$node->content['merci_status'] = array(
'#value' => drupal_get_form('merci_display_reservation_status', merci_record_status($node->merci['status'])),
'#weight' => 0,
);
if ($page) {
$reservation_table = drupal_get_form('merci_build_reservation_table_form', $node);
$node = node_prepare($node, $teaser);
$node->content['reservation_items'] = array(
'#value' => $reservation_table,
'#weight' => 1,
);
}
}
return $node;
}
/**
* Builds the form item for the status display.
*
* @param $form_state
* Current form state.
* @param $status
* Current status
* @return
* The form array.
*/
function merci_display_reservation_status(&$form_state, $status) {
$form['merci_status'] = array(
'#type' => 'item',
'#title' => t('Status'),
'#value' => $status,
);
return $form;
}
/**
* Builds the table of existing reserved items.
*
* @param $form_state
* Current form state.
* @param $node
* The reservation node.
* @param $edit_page
* TRUE if the table is on the edit page for the reservation, FALSE otherwise.
* @return
* The form array.
*/
function merci_build_reservation_table_form(&$form_state, $node, $edit_page = FALSE) {
$form = array();
$form['#theme'] = 'merci_build_reservation_table_form';
$form['#node'] = $node;
$form['#tree'] = TRUE;
$form['#table'] = array();
$form['#header'] = array(
t('Item'),
t('Type'),
t('Operations'),
);
$reservation_items = array();
$items = $node->merci['reservation_items'];
foreach ($items as $did => $item) {
// Use item title, fall back to bucket/resource content type name.
$title = isset($item->ttitle) ? $item->ttitle : $item->name;
$nid = isset($item->tnid) ? $item->tnid : $item->pnid;
$operations = '';
$placeholder_node = node_load($item->pnid);
if (node_access('update', $placeholder_node)) {
if (merci_has_accessories($item->type)) {
$operations .= '<span class="edit-details" id="merci-id-' . $item->pnid . '">' . l(t('Add accessories'), "node/{$item->pnid}/edit", array(
'query' => drupal_get_destination(),
)) . '</span>';
}
}
if ($edit_page && node_access('delete', $placeholder_node)) {
$operations .= ' ' . l(t('delete'), "node/{$item->pnid}/delete", array(
'query' => drupal_get_destination(),
));
}
$merci_settings = merci_load_content_type_settings($item->type);
// Only MERCI admins can change the bucket item assignment.
if ($edit_page && user_access('administer MERCI') && $merci_settings->type_setting == 'bucket') {
$options = array(
0 => t('<Select>'),
);
$default = isset($item->tnid) ? $item->tnid : 0;
$options += merci_get_available_bucket_items($node, $item->type);
$form['items'][$did] = array(
'#type' => 'select',
'#options' => $options,
'#default_value' => $default,
);
}
else {
$form['items'][$did] = array(
'#type' => 'value',
'#value' => $item->tnid,
);
$form['#table'][$did]['display_item'] = l($title, "node/{$item->pnid}");
}
$form['placeholders'][$did] = array(
'#type' => 'value',
'#value' => $item->pnid,
);
$bucket_resource = $merci_settings->type_setting == 'bucket' ? $item->type : $item->tnid;
$form['bucket_resource'][$did] = array(
'#type' => 'value',
'#value' => $bucket_resource,
);
$form['#table'][$did]['type'] = check_plain($item->name);
$form['#table'][$did]['ops'] = $operations;
}
return $form;
}
/**
* Pulls items available to assign to a bucket for a reservation.
*
* @param $node
* The reservation node.
* @param $bucket_type
* The bucket type.
* @return
* An array of available items, in select options format.
*/
function merci_get_available_bucket_items($node, $bucket_type) {
$date_info = $node->field_merci_date[0];
$start = $date_info['value'];
$end = $date_info['value2'];
$options = merci_get_reservable_items('bucket', $bucket_type, $start, $end, $node->nid);
return $options;
}
/**
* Pulls an array of items that are reservable for the content type and date range.
*
* @param $merci_type
* The MERCI type. bucket|resource
* @param $content_type
* The content type name of the bucket/resource.
* @param $start
* Start time in DATETIME format UTC timezone.
* @param $end
* End time in DATETIME format UTC timezone.
* @param $reservation_nid
* (Optional) A reservation nid to exclude from the reserved items.
* @return
* An array of reservable items, in select option format.
*/
function merci_get_reservable_items($merci_type, $content_type, $start, $end, $reservation_nid = NULL) {
// Determine CCK table and columns the date data is stored in.
$field = content_fields('field_merci_date');
$db_info = content_database_info($field);
$table = $db_info['table'];
$column_start_date = $db_info['columns']['value']['column'];
$column_end_date = $db_info['columns']['value2']['column'];
$args = array(
MERCI_AVA_F,
MERCI_AVA_S,
$content_type,
MERCI_SUB_TYPE_ITEM,
$start,
$end,
$start,
$end,
$start,
$end,
MERCI_ITEM_STATUS_AVAILABLE,
);
// If there's an already selected bucket item, then we need to make sure we
// include it in the list of available items.
$where = '';
$inner_where = '';
if ($reservation_nid) {
$inner_where = ' AND md2.nid <> %d';
$args[] = $reservation_nid;
}
// Pull reservable items. This query takes the following into consideration:
// 1. Pulls all all item nodes of the content type that are in an available state,
// 2. Excludes all item nodes that have associated reservations in the date range
// of the this reservation where the item is in an already reserved state.
// 3. Allows a reservation to be excluded from the exclusions if necessary (this
// is usually used to allow an already assigned item to not conflict with itself.
$items = db_query("SELECT n.nid, n.title FROM {node} n INNER JOIN {merci_{$merci_type}_node} m ON n.vid = m.vid WHERE (m.default_availability IN (%d, %d) AND n.type = '%s' AND m.sub_type = %d AND n.nid NOT IN (SELECT md2.item_nid FROM {" . $table . "} ct INNER JOIN {merci_reservation_detail} md2 ON ct.vid = md2.vid INNER JOIN {merci_{$merci_type}_node} m2 ON md2.item_nid = m2.nid INNER JOIN {node} ctn ON ctn.vid = ct.vid INNER JOIN {node} m2n ON m2.vid = m2n.vid WHERE (({$column_start_date} >= '%s' AND {$column_start_date} <= '%s') OR ({$column_end_date} >= '%s' AND {$column_end_date} <= '%s') OR ({$column_start_date} <= '%s' AND {$column_end_date} >= '%s')) AND NOT (md2.item_status <= %d){$inner_where})) ORDER BY n.title", $args);
$options = array();
while ($item = db_fetch_object($items)) {
$options[$item->nid] = $item->title;
}
return $options;
}
// merci_get_reservable_items
/**
* Calculates the total number of available bucket items for a reservation.
*
* @param $content_type
* The bucket content type.
* @param $start
* Start time in DATETIME format UTC timezone.
* @param $end
* End time in DATETIME format UTC timezone.
* @param $reservation_nid
* (Optional) A reservation nid to exclude from the reserved items.
* @return
* The number of available bucket items.
*/
function merci_get_available_bucket_count($content_type, $start, $end, $reservation = NULL) {
// Determine CCK table and columns the date data is stored in.
$field = content_fields('field_merci_date');
$db_info = content_database_info($field);
$table = $db_info['table'];
$column_start_date = $db_info['columns']['value']['column'];
$column_end_date = $db_info['columns']['value2']['column'];
// Pull all active items for this bucket.
$total_items = (int) db_result(db_query("SELECT COUNT(n.nid) FROM {node} n INNER JOIN {merci_bucket_node} m ON n.vid = m.vid WHERE n.type = '%s' AND m.sub_type = %d AND m.default_availability IN (%d, %d)", $content_type, MERCI_SUB_TYPE_ITEM, MERCI_AVA_F, MERCI_AVA_S));
$args = array(
$start,
$end,
$start,
$end,
$start,
$end,
$content_type,
MERCI_ITEM_STATUS_CHECKED_IN,
);
// If we're checking an existing reservation, exclude it from the
// reserved items.
if (isset($reservation)) {
$where = ' AND ct.nid <> %d';
$args[] = $reservation;
}
else {
$where = '';
}
// Pull reserved bucket items for the period of the reservation.
$reserved_items = (int) db_result(db_query("SELECT COUNT(md.nid) FROM {" . $table . "} ct INNER JOIN {merci_reservation_detail} md ON ct.vid = md.vid INNER JOIN {merci_bucket_node} m ON md.placeholder_nid = m.nid INNER JOIN {node} ctn ON ct.vid = ctn.vid INNER JOIN {node} mn ON m.vid = mn.vid WHERE (({$column_start_date} >= '%s' AND {$column_start_date} <= '%s') OR ({$column_end_date} >= '%s' AND {$column_end_date} <= '%s') OR ({$column_start_date} <= '%s' AND {$column_end_date} >= '%s')) AND mn.type = '%s' AND NOT (md.item_status <= %d){$where}", $args));
return $total_items - $reserved_items;
}
/**
* Builds the reserved items table.
*/
function theme_merci_build_reservation_table_form($form) {
$output = '';
$header = $form['#header'];
$node = $form['#node'];
$rows = array();
foreach ($form['#table'] as $did => $columns) {
$item = drupal_render($form['items'][$did]);
// The content type name.
if (isset($columns['display_item'])) {
$item .= $columns['display_item'];
}
$rows[] = array(
$item,
$form['#table'][$did]['type'],
$form['#table'][$did]['ops'],
);
}
if (!empty($rows)) {
$table_caption = in_array((int) $node->merci['status'], array(
MERCI_STATUS_CHECKED_OUT,
MERCI_STATUS_CHECKED_IN,
)) ? t('Checked out items') : t('Currently reserved items');
$output .= '<div class="existing-items-table-header">' . $table_caption . '</div>';
$output .= theme('table', $header, $rows);
if (user_access('administer MERCI')) {
$output .= '<div><a href="/merci/contract/' . $node->nid . '">Printable Contract</a></div>';
}
}
return $output;
}
/**
* Implementation of hook_load().
*/
function merci_load($node) {
if ($node->type == 'merci_reservation') {
$return = new stdClass();
$return->merci = db_fetch_array(db_query("SELECT status FROM {merci_reservation} WHERE vid = %d", $node->vid));
$reservation_items = array();
// Pull both the general placeholder node and the item nid so we
// can use whichever we need.
$items = db_query("SELECT m.did, m.item_status, pn.nid AS pnid, pn.title AS ptitle, tn.nid AS tnid, tn.title AS ttitle, nt.type, nt.name FROM {merci_reservation_detail} m INNER JOIN {node} pn ON m.placeholder_nid = pn.nid INNER JOIN {node_type} nt ON pn.type = nt.type LEFT JOIN {node} tn ON m.item_nid = tn.nid WHERE m.vid = %d", $node->vid);
while ($item = db_fetch_object($items)) {
$reservation_items[$item->did] = $item;
}
$return->merci['reservation_items'] = $reservation_items;
return $return;
}
}
/**
* Implementation of hook_access().
*/
function merci_access($op, $node, $account) {
global $user;
$type = isset($node->type) ? $node->type : $node;
$uid = isset($node->uid) ? $node->uid : FALSE;
if ($type == 'merci_reservation') {
// MERCI admins and users working with their own reservations have all access.
if (user_access('administer MERCI')) {
return TRUE;
}
elseif (user_access('create reservations')) {
if ($uid === FALSE || $uid == $account->uid) {
return TRUE;
}
}
return FALSE;
}
}
/**
* Implementation of hook_validate().
*/
function merci_node_validate($form, &$form_state) {
if (user_access('suspend MERCI access') && !user_access('administer MERCI')) {
form_set_error('merci_status', t('Your access to make new Reservations or edit existing Reservations has been suspended.'));
}
else {
$node = (object) $form_state['values'];
// No validation necessary on deletion.
if ($form_state['clicked_button']['#id'] != 'edit-delete') {
// Reservations with a checked out status.
if ($node->merci_status == MERCI_STATUS_CHECKED_OUT) {
// Make sure all existing bucket reservations have an item assigned.
if (isset($node->existing_items['items'])) {
foreach ($node->existing_items['items'] as $did => $item_nid) {
if (!$item_nid) {
form_set_error("existing_items][items][{$did}", t("The bucket reservation must have an item associated with it for finalized reservations."));
}
}
}
else {
form_set_error('merci_status', t('You can not finalize a reservation that has no reserved items.'));
}
// Can't add a bucket item and finalize at the same time.
foreach ($node->choice as $num => $choice) {
$item = $choice['item'];
if ($item && !is_numeric($item)) {
form_set_error("choice][{$num}][item", t("You cannot finalize a reservation while adding a bucket item."));
}
}
}
// Build date objects we'll need for our different validations.
$start = $node->field_merci_date[0]['value'];
$end = $node->field_merci_date[0]['value2'];
$start_object = merci_create_local_date_object($start);
$end_object = merci_create_local_date_object($end);
$hours_of_operation = merci_load_hours_of_operation();
$start_day_of_week = (int) date_format($start_object, 'w');
$end_day_of_week = (int) date_format($end_object, 'w');
$start_month_day = date_format($start_object, 'm-d');
$end_month_day = date_format($end_object, 'm-d');
$start_hours = $hours_of_operation[$start_day_of_week];
$end_hours = $hours_of_operation[$end_day_of_week];
$start_date = date_format($start_object, 'm-d-Y');
$max_days = variable_get("merci_max_days_advance_reservation", '0');
//Users in role with Administer MERCI permssion are exempt from content type and hours of operation restrictions
if (user_access('administer MERCI') || user_access('create reservations outside hours of operation')) {
drupal_set_message(t('You may be making a Reservation outside the normal hours of operation. This may impact access to the items you are reserving.'));
}
else {
// Reservation start date cannot exceed the max advance
if ($max_days) {
$max_date = new DateTime("+{$max_days} day");
if ($start_object > $max_date) {
form_set_error('merci_status', t('You cannot make a Reservation more than %days days in advance. Start the Reservation before %date.', array(
'%days' => $max_days,
'%date' => date_format($max_date, 'm-d-Y'),
)));
}
}
// Can't start or end a reservation on days that are
// closed dates.
if (in_array($start_month_day, $hours_of_operation['closed_days'])) {
$name = date_format($start_object, 'F jS');
form_set_error('field_merci_date][0][value][date', t('Sorry, but we are closed on %day for a holiday or special event.', array(
'%day' => $name,
)));
}
if (in_array($end_month_day, $hours_of_operation['closed_days'])) {
$name = date_format($end_object, 'F jS');
form_set_error('field_merci_date][0][value2][date', t('Sorry, but we are closed on %day for a holiday or special event.', array(
'%day' => $name,
)));
}
// Can't start or end a reservation on a day the facility
// has no hours of operation, or outside hours of operation.
$start_name = date_format($start_object, 'l');
if (!$hours_of_operation[$start_day_of_week]) {
form_set_error('field_merci_date][0][value][date', t('Reservations cannot start on a %day.', array(
'%day' => $start_name,
)));
}
else {
$start_time = date_format($start_object, 'H:i');
if ($start_time < $start_hours['open']) {
form_set_error('field_merci_date][0][value][time', t('Reservations cannot start on a %day before %start.', array(
'%day' => $start_name,
'%start' => merci_format_time($start_hours['open']),
)));
}
elseif ($start_time > $start_hours['close']) {
form_set_error('field_merci_date][0][value][time', t('Reservations cannot start on a %day after %end.', array(
'%day' => $start_name,
'%end' => merci_format_time($start_hours['close']),
)));
}
}
$end_name = date_format($end_object, 'l');
if (!$hours_of_operation[$end_day_of_week]) {
form_set_error('field_merci_date][0][value2][date', t('Reservations cannot end on a %day.', array(
'%day' => $end_name,
)));
}
else {
$end_time = date_format($end_object, 'H:i');
if ($end_time < $end_hours['open']) {
form_set_error('field_merci_date][0][value2][time', t('Reservations cannot end on a %day before %start.', array(
'%day' => $end_name,
'%start' => merci_format_time($end_hours['open']),
)));
}
elseif ($end_time > $end_hours['close']) {
form_set_error('field_merci_date][0][value2][time', t('Reservations cannot end on a %day after %end.', array(
'%day' => $end_name,
'%end' => merci_format_time($end_hours['close']),
)));
}
}
}
// Tests for existing items.
if (isset($node->nid)) {
// For saved reservations, include the items already reserved
// in the available list.
$options = merci_build_reservable_items($node, $form_state, $node->nid);
}
else {
$options = merci_build_reservable_items($node, $form_state);
}
$flat_options = $options['flat_options'];
if (isset($node->existing_items)) {
// Check each reserved item.
foreach ($node->existing_items['bucket_resource'] as $did => $value) {
// The item is no longer reservable, so figure out why.
if (!in_array($value, $flat_options)) {
// Resource.
if (is_numeric($value)) {
$new_item = db_fetch_object(db_query("SELECT title, type FROM {node} WHERE nid = %d", $value));
$title = $new_item->title;
$type = $new_item->type;
}
elseif ($value) {
$title = db_result(db_query("SELECT name FROM {node_type} WHERE type = '%s'", $value));
$type = $value;
}
// Make sure the item still passes content type restrictions.
$restrictions = merci_check_content_type_restrictions($type, $start, $end);
if (!empty($restrictions)) {
$message = '';
foreach ($restrictions as $restriction) {
$message .= '<div>' . strtr($restriction, array(
'%name' => theme('placeholder', $title),
)) . '</div>';
}
}
else {
$message = t("The existing reservation for %name is no longer reservable with your current date settings.", array(
'%name' => $title,
));
}
form_set_error("existing_items][placeholders][{$did}", $message);
}
elseif (!is_numeric($value)) {
$bucket_items = array_keys(merci_get_available_bucket_items($node, $value));
$assigned_item = (int) $node->existing_items['items'][$did];
if ($assigned_item && !in_array($assigned_item, $bucket_items)) {
$title_name = db_fetch_object(db_query("SELECT n.title, nt.name FROM {node} n INNER JOIN {node_type} nt ON n.type = nt.type WHERE n.nid = %d", $assigned_item));
form_set_error("existing_items][placeholders][{$did}", t("The assignment of %item for the %bucket reservation is no longer reservable with your current date settings.", array(
'%item' => $title_name->title,
'%bucket' => $title_name->name,
)));
}
}
}
}
// Tests for new items.
if (isset($node->nid)) {
// Only need to rebuild this again for existing nodes.
$options = merci_build_reservable_items($node, $form_state);
$flat_options = $options['flat_options'];
}
// Check each new item.
foreach ($node->choice as $num => $choice) {
// The item is no longer reservable, so figure out why.
if ($choice['item'] && !in_array($choice['item'], $flat_options)) {
// Resource.
if (is_numeric($choice['item'])) {
$new_item = db_fetch_object(db_query("SELECT title, type FROM {node} WHERE nid = %d", $choice['item']));
$title = $new_item->title;
$type = $new_item->type;
}
elseif ($choice['item']) {
$title = db_result(db_query("SELECT name FROM {node_type} WHERE type = '%s'", $choice['item']));
$type = $choice['item'];
}
// Make sure the item still passes content type restrictions.
$restrictions = merci_check_content_type_restrictions($type, $start, $end);
if (!empty($restrictions)) {
$message = '';
foreach ($restrictions as $restriction) {
$message .= '<div>' . strtr($restriction, array(
'%name' => theme('placeholder', $title),
)) . '</div>';
}
}
else {
$count_sql = "SELECT COUNT(n.nid) \n FROM {node} n \n JOIN {merci_node_type} t\n ON t.type = n.type\n WHERE n.status = 1 AND\n (\n (\n t.type_setting = 'bucket'\n AND n.type = '%s'\n ) OR\n (\n t.type_setting = 'resource'\n AND n.title = '%s'\n )\n )";
$count = db_result(db_query($count_sql, $type, $title));
$start_mysql = date('Y-m-d', strtotime($start));
$end_mysql = date('Y-m-d', strtotime($end . ' +1 day'));
$reservations = merci_load_reservations_for_type_in_timespan($type, $start_mysql, $end_mysql);
$reservations_by_date = array();
$hours = merci_load_hours_of_operation();
$message = '<div> ' . t("The dates and times for %name conflict with one or more existing reservations", array(
'%name' => $title,
)) . '</div>';
$message .= '<div class="merci-availability-key"><span class="available"></span> = available <span class="unavailable"></span> = unavailable</div>';
foreach ($reservations as $date => $times) {
$date_timestamp = strtotime($date);
$hours_date = $hours[date('w', $date_timestamp)];
if (user_access('administer MERCI') || user_access('create reservations outside hours of operation')) {
$adminhours = explode('-', variable_get('merci_hours_admin', '07:00-23:00'));
$hours_date['open'] = $adminhours[0];
$hours_date['close'] = $adminhours[1];
}
if (isset($hours_date['open'])) {
$message .= '<table class="merci-availability-schedule"><thead><tr>';
$message .= '<th>' . date('m/d/Y', $date_timestamp) . '</th>';
$time = $hours_date['open'];
while ($time < $hours_date['close']) {
$message .= '<th colspan="4">' . date('g:i a', strtotime($time)) . '</th>';
$time = date('H:i', strtotime($time . ' +1 hour'));
}
$message .= '</tr></thead><tbody>';
for ($i = 1; $i <= $count; $i++) {
$message .= '<tr><th>' . htmlspecialchars($title);
if ($count > 1) {
$message .= ' ' . $i . '/' . $count;
}
$message .= '</th>';
$time = $hours_date['open'];
while ($time < $hours_date['close']) {
if ($times[$time . ':00'] >= $i) {
$message .= '<td class="unavailable"></td>';
}
else {
$message .= '<td class="available"></td>';
}
$time = date('H:i', strtotime($time . ' +15 minutes'));
}
// while
$message .= '</tr>';
}
// for
$message .= '</tbody></table>';
}
// if
}
// foreach
}
form_set_error("choice][{$num}][item", $message);
}
}
//if message wasn't set by a validation function
if (!$message) {
drupal_set_message(t('There are no conflicts with this Reservation.'));
}
// Prevent status changes on reservations that have past.
$current_status = db_result(db_query("SELECT m.status FROM {node} n INNER JOIN {merci_reservation} m ON n.vid = m.vid WHERE n.nid = %d", $node->nid));
if ($current_status && $current_status != $node->merci_status && time() > strtotime($node->field_merci_date[0]['value2']) && !in_array((int) $node->merci_status, array(
MERCI_STATUS_CANCELLED,
MERCI_STATUS_CHECKED_IN,
MERCI_STATUS_DENIED,
))) {
$statuses = merci_record_status();
form_set_error('merci_status', t('You cannot change the status to %status for a reservation that has past.', array(
'%status' => $statuses[$node->merci_status],
)));
}
}
}
}
/**
* Builds an array representing reservations for a given bucket within a given timespan
*
* @return
* An associative array with keys as times (in MySQL datetime format) and values as number of reservations.
*/
function merci_load_reservations_for_type_in_timespan($type, $start_date, $end_date) {
$timezone_offset = variable_get('date_default_timezone', 0);
$reservation_counts = array();
$datetime = strtotime($start_date . ' 00:00:00');
while (date('Y-m-d', $datetime) < $end_date) {
$date = date('Y-m-d', $datetime);
$time = date('H:i:s', $datetime);
if (!isset($reservation_counts[$date])) {
$reservation_counts[$date] = array();
}
$reservation_counts[$date][$time] = 0;
$datetime = strtotime($date . ' ' . $time . ' +15 minutes');
}
// while
// ! Get reservation times from database
$reservation_times = array();
$sql = "SELECT r.field_merci_date_value AS start, r.field_merci_date_value2 AS end\n FROM {merci_reservation_detail} d\n JOIN {node} n\n ON n.nid = d.placeholder_nid\n JOIN {content_type_merci_reservation} r\n ON r.vid = d.vid\n WHERE n.type = '%s'\n AND r.field_merci_date_value < '%s'\n AND r.field_merci_date_value2 >= '%s'";
$times = db_query($sql, $type, $end_date, $start_date);
while ($reservation_time = db_fetch_object($times)) {
$reservation_times[] = $reservation_time;
}
// ! Update reservation count
foreach ($reservation_times as $reservation_time) {
$datetime = $reservation_time->start;
while ($datetime < $reservation_time->end) {
list($date, $time) = explode(' ', $datetime);
$time = date('H:i:s', strtotime($time) + $timezone_offset);
if (isset($reservation_counts[$date][$time])) {
$reservation_counts[$date][$time]++;
}
$datetime = date('Y-m-d H:i:s', strtotime($datetime . ' +15 minutes'));
}
// while
}
// foreach
return $reservation_counts;
}
// merci_load_reservations_for_type_in_timespan
/**
* Builds an array representing the hours of operation for the facility.
*
* @return
* An associative array with the following key/value pairs:
* [php_day_of_week_number_as_in_date_function] => An associative
* array with the following key/values pairs:
* 'open' => Opening time (military).
* 'close' => Closing time (military).
* 'closed_days' => An array of closed dates in mm-dd format.
*/
function merci_load_hours_of_operation($content_type = '') {
$days_of_the_week = array(
'sun',
'mon',
'tue',
'wed',
'thu',
'fri',
'sat',
);
if (!empty($content_type)) {
$rules = merci_content_type_rules($content_type);
}
$hours_of_operation = array();
foreach ($days_of_the_week as $num => $day) {
$hours = variable_get("merci_hours_{$day}", '');
if (drupal_strlen($hours) == 11) {
$parts = explode('-', $hours);
if (count($parts == 2)) {
$hours_of_operation[$num] = array(
'open' => $parts[0],
'close' => $parts[1],
);
}
else {
$hours_of_operation[$num] = FALSE;
}
}
else {
$hours_of_operation[$num] = FALSE;
}
}
$closed_days_raw = variable_get('merci_closed_dates', '');
$hours_of_operation['closed_days'] = array();
$parts = explode("\n", $closed_days_raw);
foreach ($parts as $date) {
$date = trim($date);
if (drupal_strlen($date) == 5) {
$hours_of_operation['closed_days'][] = $date;
}
}
return $hours_of_operation;
}
function merci_hours_str_to_array($str) {
if (drupal_strlen($str) == 11) {
$parts = explode('-', $str);
if (count($parts) == 2) {
return array(
'open' => $parts[0],
'close' => $parts[1],
);
}
}
return FALSE;
}
// merci_hours_str_to_array
/**
* Creates a date object based on the site's local timezone.
*
* @param $datetime
* A date in DATETIME format, UTC timezone.
* @return
* A php date object in the site's timezone.
*/
function merci_create_local_date_object($datetime) {
$date_object = date_create($datetime, timezone_open('UTC'));
date_timezone_set($date_object, timezone_open(date_default_timezone_name()));
return $date_object;
}
/**
* Custom validation function to protect merci nodes from mass deletion.
*/
function merci_node_admin_delete_validate($form, &$form_state) {
// Look only for delete op.
$operation = $form_state['values']['operation'];
if ($operation != 'delete') {
return;
}
// Get the checked nodes.
$nids = array_filter($form_state['values']['nodes']);
// Perform the check for each submitted node.
foreach ($nids as $nid) {
$node = node_load($nid);
// Check to see if any of the nodes should not be deleted.
if (!merci_delete_item_validate($node, FALSE)) {
// If so, then unset the checked node so it will not be processed, and display a warning.
// Note that the array element has to be completely removed here in order to prevent the
// node from being deleted, due to the nature of the mass deletion callback.
unset($form_state['values']['nodes'][$nid]);
unset($nids[$nid]);
}
}
// If we've unset all of the nodes that were checked, then don't continue with the form processing.
if (!count($nids)) {
drupal_set_message('No nodes selected.', 'error');
drupal_goto('admin/content/node');
}
}
/**
* Implementation of hook_simpletest().
*/
function merci_simpletest() {
$dir = drupal_get_path('module', 'merci') . '/tests';
$tests = file_scan_directory($dir, '\\.test$');
return array_keys($tests);
}
/**
* Implementation of hook_views_api().
*/
function merci_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'merci'),
);
}
/**
* Implementation of hook_views_handlers().
*/
function merci_views_handlers() {
return array(
'info' => array(
'path' => drupal_get_path('module', 'merci') . '/handlers',
),
'handlers' => array(
'merci_handler_field_merci_node_type_type_setting' => array(
'parent' => 'views_handler_field',
'file' => 'merci_handler_field.inc',
),
'merci_handler_filter_merci_node_type_type_setting' => array(
'parent' => 'views_handler_filter_in_operator',
'file' => 'merci_handler_filter_in_operator.inc',
),
'merci_handler_field_merci_node_type_status' => array(
'parent' => 'views_handler_field',
'file' => 'merci_handler_field.inc',
),
'merci_handler_filter_merci_node_type_status' => array(
'parent' => 'views_handler_filter_in_operator',
'file' => 'merci_handler_filter_in_operator.inc',
),
'merci_handler_field_merci_reservation_status' => array(
'parent' => 'views_handler_field',
'file' => 'merci_handler_field.inc',
),
'merci_handler_filter_merci_reservation_status' => array(
'parent' => 'views_handler_filter_in_operator',
'file' => 'merci_handler_filter_in_operator.inc',
),
'merci_handler_field_merci_bucket_resource_node_default_availability' => array(
'parent' => 'views_handler_field',
'file' => 'merci_handler_field.inc',
),
'merci_handler_filter_merci_bucket_resource_node_default_availability' => array(
'parent' => 'views_handler_filter_in_operator',
'file' => 'merci_handler_filter_in_operator.inc',
),
'merci_handler_field_merci_bucket_resource_node_sub_type' => array(
'parent' => 'views_handler_field',
'file' => 'merci_handler_field.inc',
),
'merci_handler_filter_merci_bucket_resource_node_sub_type' => array(
'parent' => 'views_handler_filter_in_operator',
'file' => 'merci_handler_filter_in_operator.inc',
),
),
);
}
/**
* Check for existing items in a bucket.
*
* @param $type
* The bucket node type.
* @param $status
* TRUE to restrict to published items, FALSE otherwise.
* @return
* TRUE if any items exist, FALSE otherwise.
*/
function merci_check_existing_bucket_items($type, $status = FALSE) {
$where = $status ? ' AND status = 1' : '';
$existing_items = db_result(db_query("SELECT nid FROM {node} WHERE type = '%s'{$where}", $type));
return $existing_items;
}
/**
* Returns totals for reporting.
*
* @param $type
* The bucket or resrouce node type.
* @param $startdate
* TRUE to restrict to published items, FALSE otherwise.
* @return
* Total reservation number for that type betweent the start and end dates
*/
function merci_reservation_totals($type, $startdate, $enddate) {
$result = db_query("SELECT COUNT(nid) as total FROM {node} WHERE type LIKE '%s' and status = 0 AND created > %d AND created < %d", $type, $startdate, $enddate);
$reservationnode = db_fetch_object($result);
return $reservationnode->total;
}
/**
* Sort by vid
*
* @param $a
* The first object.
* @param $b
* The second object
* @return
* 0,1, or -1 indicating which object has a higher VID
*/
function merci_by_vid() {
if ($a->vid == $b->vid) {
return 0;
}
return $a->vid > $b->vid ? -1 : 1;
}
// merci_by_vid
/**
* Get taxonomy data as JSON for a node
*
* @param $node_id
* The node ID.
* @return
* JSON string of taxonomy data
*/
function merci_taxonomy_json($node_id) {
$node = node_load($node_id);
$output = array();
if (node_access('update', $node)) {
// Current user has access to update this node
$vocabularies = taxonomy_get_vocabularies($node->type);
if (isset($_POST['taxonomy'])) {
// Changes to taxonomy sent
$required_sent = TRUE;
// Make sure all required vocabularies were sent
foreach ($vocabularies as $vocabulary) {
if ($vocabulary->required && !isset($_POST['taxonomy'][$vocabulary->vid])) {
$required_sent = FALSE;
}
// if
}
// foreach
if ($required_sent) {
// Save new node revision, clear taxonomy, and re-add sent term
node_save($node);
taxonomy_node_delete_revision($node);
foreach ($_POST['taxonomy'] as $vocabulary => $terms) {
foreach ($terms as $id => $term_id) {
db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $term_id);
}
// foreach
}
// foreach
$output['status'] = 'success';
$output['node'] = $node_id;
}
else {
// Missing required vocabulary
$output['status'] = 'failed';
$output['error'] = 'Required fields not selected.';
//DIFF??
}
// else
}
else {
// Get last author admin status and node ID
$user = user_load($node->revision_uid);
$output['node'] = $node_id;
$output['vocabularies'] = array();
$output['admin'] = user_access('administer MERCI', $user);
usort($vocabularies, 'merci_by_vid');
// Get the select HTML and previously selected options for each vocabulary
foreach ($vocabularies as $vocabulary) {
$select = taxonomy_form($vocabulary->vid);
$select['#name'] = 'taxonomy[' . $vocabulary->vid . ']';
$select['#parents'] = array();
$select_html = theme_select($select);
$vocabulary_output = array(
'select' => $select_html,
'selected' => array(),
);
foreach ($node->taxonomy as $tid => $term) {
$vocabulary_output['selected'][] = $tid;
}
$output['vocabularies'][] = $vocabulary_output;
}
// foreach
}
// else
}
else {
// No permission
$output['error'] = 'Permission denied.';
}
// else
drupal_json($output);
}
// merci_taxonomy_json
function merci_printable_contract($node_id) {
global $base_path;
$node = node_load($node_id);
$user = user_load($node->uid);
$username = $user->name;
$email = $user->mail;
if (module_exists('civicrm')) {
civicrm_initialize(TRUE);
global $civicrm_root;
include_once $civicrm_root . '/api/UFGroup.php';
$userID = crm_uf_get_match_id($user->uid);
$cg = array(
'contact_id' => $userID,
);
include_once $civicrm_root . '/api/v2/Contact.php';
$ob = civicrm_contact_get($cg);
//print '<pre>';
//print_r($ob);
//print '</pre>';
$username = $ob[$userID]['display_name'];
//print $username;
$phone = $ob[$userID]['phone'];
}
$items = $node->merci['reservation_items'];
// We want these timestamps generated in UTC.
$old_timezone = date_default_timezone_get();
date_default_timezone_set('UTC');
$starthour = strtotime($node->field_merci_date[0]['value']);
$endhour = strtotime($node->field_merci_date[0]['value2']);
date_default_timezone_set($old_timezone);
$hours = round(($endhour - $starthour) / 3600, 2);
$logo = theme_get_setting('logo_path', '');
?>
<html>
<head>
<title>Contract</title>
<link type="text/css" rel="stylesheet" href="/<?php
echo drupal_get_path('module', 'merci');
?>/contract.css" />
</head>
<body>
<div id="page">
<div id="header">
<?php
if ($logo) {
?>
<img src="<?php
print $base_path;
print $logo;
?>">
<?php
}
?>
<h2><?php
print variable_get('site_name', '');
?> Equipment Rental Contract</h2>
<?php
if (module_exists('token')) {
print token_replace(variable_get('merci_contract_header', ''), 'node', $node);
}
else {
print variable_get('merci_contract_header');
}
?>
Start: <?php
print date("F j, Y, g:i a", $starthour) . '<br />';
?>
Returned by: <?php
print date("F j, Y, g:i a", $endhour) . '<br />';
?>
Name: <?php
print $username;
?><br />
Email: <?php
print $email;
?><br />
Phone: <?php
print $phone;
?><br />
</div>
<table id="cost">
<thead>
<tr>
<th>Item</th>
<th>Commercial Cost</th>
<th>Member Cost</th>
</tr>
</thead>
<tbody>
<?php
$discount = variable_get('merci_membership_discount', 1);
$commercial_cost_total = 0;
$member_cost_total = 0;
$even_odd = 'even';
foreach ($items as $item) {
$item_node = node_load($item->pnid);
$type = merci_load_content_type_settings($item->type);
$fee_hours = $hours - $type->fee_free_hours;
$commercial_cost = $type->rate_per_hour * $hours;
$member_cost = $fee_hours > 0 ? $type->rate_per_hour * $discount * $fee_hours : 0;
$commercial_cost_total += $commercial_cost;
$member_cost_total += $member_cost;
if ($item->ttitle) {
$ttitle = htmlspecialchars($item->ttitle);
}
else {
$ttitle = '<b>SPECIFIC ITEM NOT SELECTED FROM BUCKET</b>';
}
?>
<tr class="<?php
print $even_odd;
?>">
<td>
<div><?php
print $ttitle;
?></div>
<?php
if (count($item_node->taxonomy) > 0) {
?>
<ul class="accessories">
<?php
foreach ($item_node->taxonomy as $accessory) {
?>
<li><?php
print $accessory->name;
?></li>
<?php
}
// foreach
?>
</ul>
<?php
}
// if
?>
</td>
<td>$<?php
echo $commercial_cost;
?></td>
<td>$<?php
echo $member_cost;
?></td>
</tr>
<?php
$even_odd = $even_odd == 'even' ? 'odd' : 'even';
}
// foreach
?>
</tbody>
<tfoot>
<tr class="<?php
echo $even_odd;
?>">
<th>Total</th>
<td>$<?php
echo $commercial_cost_total;
?></td>
<td>$<?php
echo $member_cost_total;
?></td>
</tr>
<tfoot>
</table>
<div id="boilerplate"><?php
if (module_exists('token')) {
echo token_replace(variable_get('merci_contract_boilerplate', ''), 'node', $node);
}
else {
echo variable_get('merci_contract_boilerplate');
}
?></div>
<div id="footer"><?php
if (module_exists('token')) {
echo token_replace(variable_get('merci_contract_footer', ''), 'node', $node);
}
else {
echo variable_get('merci_contract_footer');
}
?></div>
</div>
</body>
<?php
}
// merci_printable_contract
// Changes the reservation from UNCONFIRMED to CONFIRMED.
// Normally accessed from email sent to user
function merci_confirm_reservation($node_id) {
print 'nothing here yet';
}
// merci_confirm_reservation
/**
* Implementation of hook_cron().
*/
function merci_cron() {
//print('merci cron running<br />');
//find all autocheckouts that have started and set their stauts to checked out
//only change status for unconfirmed and pending reservations
$old_timezone = date_default_timezone_get();
date_default_timezone_set('UTC');
$time = date('Y-m-j H:m:s');
//2009-05-22 20:45:00
date_default_timezone_set($old_timezone);
//print('localized time: ' . $time . '<br />');
//print('set to checked out <br />');
// Reservation status options.
//define('MERCI_STATUS_UNCONFIRMED', 1);
//define('MERCI_STATUS_PENDING', 2);
//define('MERCI_STATUS_CHECKED_OUT', 3);
//define('MERCI_STATUS_CHECKED_IN', 4);
//define('MERCI_STATUS_CANCELLED', 5);
//define('MERCI_STATUS_DENIED', 6);
//define('MERCI_STATUS_NO_SHOW', 7);
$reservations = db_query("SELECT ctmr.nid FROM {content_type_merci_reservation} ctmr JOIN {merci_reservation_detail} mrd ON ctmr.nid = mrd.nid JOIN {merci_reservation} mr ON ctmr.nid = mr.nid WHERE mr.status < 3 AND field_merci_date_value < '%s'", $time);
while ($reservation = db_fetch_object($reservations)) {
//print($reservation->nid . '<br />');
}
//find all autocheckins that have ended and set their stauts to checkedin
//only change status for checked reservations
//print('set to checked in <br />');
$reservations = db_query("SELECT * FROM {content_type_merci_reservation} WHERE field_merci_date_value2 > '%s'", $time);
while ($reservation = db_fetch_object($reservations)) {
//print($reservation->nid . '<br />');
}
//find all unconfirmed and pending reservations that have started and set their stauts to no show
}
function merci_has_accessories($content_type) {
return db_fetch_object(db_query("SELECT * FROM {vocabulary_node_types} WHERE type = '%s'", $content_type));
}
/**
* Implementation of hook_token_list().
*
*/
function merci_token_list($type = 'all') {
if ($type == 'node' || $type == 'all') {
//$tokens['node']['merci_resources'] = t('Reserved resource');
$tokens['node']['merci_commercial_cost'] = t('Commercial cost');
$tokens['node']['merci_member_cost'] = t('Member cost');
return $tokens;
}
}
/**
* Implementation of hook_token_values().
* @see {merci_token_list}
*/
function merci_token_values($type, $object = NULL, $options = array()) {
switch ($type) {
case 'node':
$node = merci_load($object);
if ($node) {
$values['merci_resources'] = '';
$values['merci_commercial_cost'] = 0;
$values['merci_member_cost'] = 0;
$discount = variable_get('merci_membership_discount', 1);
// We want these timestamps generated in UTC.
$old_timezone = date_default_timezone_get();
date_default_timezone_set('UTC');
$starthour = strtotime($node->field_merci_date[0]['value']);
$endhour = strtotime($node->field_merci_date[0]['value2']);
date_default_timezone_set($old_timezone);
$hours = round(($endhour - $starthour) / 3600, 2);
$titles = array();
foreach ($node->merci['reservation_items'] as $item) {
$item_node = node_load($item->pnid);
$type = merci_load_content_type_settings($item->type);
$fee_hours = $hours - $type->fee_free_hours;
$values['merci_commercial_cost'] += $type->rate_per_hour * $hours;
$values['merci_member_cost'] += $fee_hours > 0 ? $type->rate_per_hour * $discount * $fee_hours : 0;
if ($item->ttitle != '') {
$titles[] = $item->ttitle;
}
else {
$titles[] = $item->ptitle;
}
}
$values['merci_resources'] = check_plain(implode(", ", $titles));
return $values;
break;
}
}
}
/**
* Calculates the short hour/minute time format based on the site settings.
*/
function merci_time_format() {
static $time_only_format = NULL;
if (empty($time_only_format)) {
$short_date_format = variable_get('date_format_short', 'm/d/Y - H:i');
$time_only_format = date_limit_format($short_date_format, array(
'hour',
'minute',
));
}
return $time_only_format;
}
/**
* Formats a time value into the site's preferred format.
*
* @param object $hours_minutes
* A string of the form 'H:MM' or 'HH:MM'
* @return
* A string in 12- or 24-hour format with no leading zero.
*/
function merci_format_time($hours_minutes) {
$return = date(merci_time_format(), strtotime($hours_minutes));
if ($return[0] == '0') {
return substr($return, 1);
}
return $return;
}
Functions
Name | Description |
---|---|
merci_access | Implementation of hook_access(). |
merci_add_reservation_items | Adds items to reservation on creation/update. |
merci_admin_settings | Builds the MERCI admininstration settings form. |
merci_build_reservable_items | Builds the list of all currently reservable items, filtered by date. |
merci_build_reservation_table_form | Builds the table of existing reserved items. |
merci_by_vid | Sort by vid |
merci_check_content_type_restrictions | |
merci_check_content_type_user_permissions | Ensures the user has 'edit own [type] content' and 'delete own [type] content' permissions, otherwise they are not allowed to reserve the content type. |
merci_check_existing_bucket_items | Check for existing items in a bucket. |
merci_choice_js | Menu callback for AHAH additions. |
merci_confirm_reservation | |
merci_content_type_rules | Loads MERCI rules for a content type. |
merci_create_local_date_object | Creates a date object based on the site's local timezone. |
merci_cron | Implementation of hook_cron(). |
merci_date_filter | Submit handler to date filter items on a reservation form. It makes changes to the form state and the entire form is rebuilt during the page reload. |
merci_delete | Implementation of hook_delete(). |
merci_delete_item_validate | Validates if an item node can be deleted. |
merci_delete_node_type_validate | Validates deletion of node types. |
merci_display_reservation_status | Builds the form item for the status display. |
merci_form | Implementation of hook_form(). |
merci_format_time | Formats a time value into the site's preferred format. |
merci_form_alter | Implementation of hook_form_alter(). |
merci_get_available_bucket_count | Calculates the total number of available bucket items for a reservation. |
merci_get_available_bucket_items | Pulls items available to assign to a bucket for a reservation. |
merci_get_reservable_items | Pulls an array of items that are reservable for the content type and date range. |
merci_has_accessories | |
merci_hours_str_to_array | |
merci_init | Implementation of hook_init(). |
merci_insert | Implementation of hook_insert(). |
merci_is_numeric_validate | Validation for numeric textfields. |
merci_item_status | Return the name of a type code. |
merci_load | Implementation of hook_load(). |
merci_load_content_type_settings | Loads the settings for a single MERCI content type. |
merci_load_hours_of_operation | Builds an array representing the hours of operation for the facility. |
merci_load_merci_type_settings | Loads the settings for an entire MERCI type (bucket/resource). |
merci_load_reservations_for_type_in_timespan | Builds an array representing reservations for a given bucket within a given timespan |
merci_menu | Implementation of hook_menu(). |
merci_more_choices_submit | Submit handler to add more choices to a reservation form. This handler is used when javascript is not available. It makes changes to the form state and the entire form is rebuilt during the page reload. |
merci_nodeapi | Implementation of hook_nodeapi(). |
merci_node_admin_delete_validate | Custom validation function to protect merci nodes from mass deletion. |
merci_node_info | Implementation of hook_node_info(). |
merci_node_type | Implementation of hook_node_info(). |
merci_node_type_save_submit | Submit handler for saving MERCI node type data. |
merci_node_type_save_validate | Validates saving of MERCI node types. |
merci_node_validate | Implementation of hook_validate(). |
merci_perm | Implementation of hook_perm(). |
merci_printable_contract | |
merci_record_status | Return the name of a status code. |
merci_reservation_totals | Returns totals for reporting. |
merci_simpletest | Implementation of hook_simpletest(). |
merci_taxonomy_json | Get taxonomy data as JSON for a node |
merci_theme | Implementation of hook_theme(). |
merci_time_format | Calculates the short hour/minute time format based on the site settings. |
merci_token_list | Implementation of hook_token_list(). |
merci_token_values | Implementation of hook_token_values(). |
merci_update | Implementation of hook_update(). |
merci_validate_default_availability | Validates the state change of a reservable item. |
merci_view | Implementation of hook_view(). |
merci_views_api | Implementation of hook_views_api(). |
merci_views_handlers | Implementation of hook_views_handlers(). |
theme_merci_build_reservation_table_form | Builds the reserved items table. |
theme_merci_choices | Theme the reservation form for choices. |
_merci_choice_form | Builds an individual item selector. |