'Javascript Choice Form', 'page callback' => 'merci_choice_js', 'access arguments' => array('access content'), 'file' => 'includes/menu.inc', 'type' => MENU_CALLBACK, ); $items['merci/taxonomy/%node/%/%'] = array( 'title' => 'JSON interface for node taxonomy', 'description' => 'Takes a node ID and returns taxonomy data as JSON', 'page callback' => 'merci_taxonomy_json', 'access arguments' => array('manage reservations'), 'page arguments' => array(2,3,4), 'file' => 'includes/menu.inc', 'type' => MENU_CALLBACK, ); // Adds Manage Equipment to Admin Interfaces // $items['admin/merci/manage'] = array( 'title' => 'Manage Equipment', 'description' => 'Manage Equipment Reservations, Checkout and Inventory (MERCI)', 'position' => 'right', 'page callback' => 'system_admin_menu_block_page', 'access arguments' => array('manage reservations'), 'file' => 'system.admin.inc', 'file path' => drupal_get_path('module', 'system'), 'weight' => -19, ); $items['admin/merci/manage/current_inventory'] = array( 'title' => 'Current Inventory', 'description' => 'Displays list', 'page callback' => 'merci_current_inventory', 'access arguments' => array('manage reservations'), 'file' => 'includes/menu.inc', 'type' => MENU_NORMAL_ITEM, ); // Standard Administration settings. $items['admin/settings/merci'] = array( 'title' => 'MERCI Configuration', 'page callback' => 'drupal_get_form', 'page arguments' => array('merci_admin_settings'), 'access callback' => 'user_access', 'access arguments' => array('administer MERCI'), 'description' => 'Configure system settings for MERCI.', 'file' => 'includes/merci.admin.inc', ); //TODO move to merci_debug module. $items['admin/merci/unlock'] = array( 'title' => 'Unlock Core MERCI Fields', 'page callback' => 'merci_unlock', 'access arguments' => array('access administration pages'), 'file' => 'includes/merci.admin.inc', 'type' => MENU_CALLBACK, ); $items['admin/merci/lock'] = array( 'title' => 'Lock Core MERCI Fields', 'page callback' => 'merci_lock', 'access arguments' => array('access administration pages'), 'file' => 'includes/merci.admin.inc', 'type' => MENU_CALLBACK, ); $items['admin/settings/merci/edit'] = array( 'title' => 'Edit', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); return $items; } /** * 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_type(). */ function merci_node_type($op, $info) { switch ($op) { case 'update': if (isset($info->old_type) && $info->type != $info->old_type) { merci_node_type_update($info); } break; case 'delete': merci_node_type_delete($info->type); break; } } /** * 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') { if (user_access('manage reservations')) { return TRUE; } elseif (user_access('view all reservations') && $op == 'view') { return TRUE; } elseif (user_access('create reservations') and !user_access('suspend MERCI access')) { //users working with their own reservations access reservation //additional check in merci_form permission to edit confirmed reservations // Users without administer or manage reservations permission can only alter their own Unconfirmed Reservations. if ($op == 'edit' && isset($node->merci_reservation_status) && $node->merci_reservation_status != MERCI_STATUS_UNCONFIRMED && !user_access('create confirmed reservations')) { return FALSE; } if ($uid === FALSE || $uid == $account->uid) { return TRUE; } } return FALSE; } } /** * Implementation of hook_load(). */ function merci_load($node) { if ($node->type == 'merci_reservation') { $return = new stdClass(); $return->merci_reservation_status = merci_reservation_status($node); // TODO get rid of merci array. should match form api post fields. $return->merci_reservation_items = merci_reservation_items($node); return $return; } } /** * Implementation of hook_prepare(). */ function merci_prepare(&$node) { if(!isset($node->merci_reservation_status)){ $node->merci_reservation_status = variable_get('merci_default_reservation_status', strval(MERCI_STATUS_UNCONFIRMED)); } } $merci_debug_setting = variable_get('merci_debug', 0); function merci_debug($string) { //set debug state global $merci_debug_setting; if ($merci_debug_setting) watchdog($string); } function merci_validate_merci_reservation_date($form, &$form_state) { $node = (object) $form_state['values']; // **** // 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'); // Hours of operation restrictions, max days, and closed dates checks // Users in role with Administer MERCI permssion or outside hours of operation skip these checks if (user_access('create reservations outside hours of operation')) { merci_debug('merci', 'SKIP Hours of Operation Check'); merci_debug('merci', 'SKIP Max Days Check'); merci_debug('merci', 'SKIP Closed Dates Check'); //check to see if warning should be displayed if (strtotime(date('G:i', strtotime($start . ' UTC'))) < strtotime($start_hours['open']) || strtotime($start_hours['close']) < strtotime(date('G:i', strtotime($end . ' UTC')))) { 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 { // Reservation start date cannot exceed the max advance merci_debug('merci', 'CHECKING Max Days'); if ($max_days) { $max_date = new DateTime("+$max_days day"); //$max_date = date('m-d-Y', mktime(0, 0, 0, date("m"), date("d")+$max_days, date("Y"))); if ($start_object > $max_date) { form_set_error('field_merci_date][0][value][date', 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. merci_debug('merci', 'CHECKING 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. merci_debug('merci', 'CHECKING 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 at a time before %start.', array('%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 at a time after %end.', array('%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 at a time before %start.', array('%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 at a time after %end.', array('%end' => merci_format_time($end_hours['close'])))); } } } // Hours of operation restrictions, max days, and closed dates checks } function merci_validate_status($form, &$form_state) { $node = (object) $form_state['values']; // Reservations with a checked out status. if ($node->merci_reservation_status == MERCI_STATUS_CHECKED_OUT) { // Make sure all existing bucket reservations have an item assigned. if (empty($node->merci_reservation_items)) { form_set_error('merci_reservation_status', t('You can not finalize a reservation that has no reserved items.')); } else { foreach ($node->merci_reservation_items as $did => $item) { if ($item['merci_item_nid'] == "0") { form_set_error("merci_reservation_items][$did][merci_item_nid", t("The reservation for %title must have an item associated with it for finalized reservations.", array( '%title' => $item['name']))); } // Can't add a bucket item and finalize at the same time. if (!is_numeric($item['merci_item_nid']) and strlen($item['merci_item_nid'])) { form_set_error("merci_reservation_items][$did][merci_item_nid", t("You cannot finalize a reservation while adding a bucket item.")); } } } } // Prevent status changes on reservations that have past. $current_status = $node->merci_original_reservation_status; if ($current_status && $current_status != $node->merci_reservation_status && time() > strtotime($node->field_merci_date[0]['value2']) && !in_array((int) $node->merci_reservation_status, array(MERCI_STATUS_CANCELLED, MERCI_STATUS_CHECKED_IN, MERCI_STATUS_DENIED))) { $statuses = merci_record_status(); form_set_error('merci_reservation_status', t('You cannot change the status to %status for a reservation that has past.', array('%status' => $statuses[$node->merci_reservation_status]))); } } function merci_validate_merci_selected_items($form, &$form_state) { $node = (object) $form_state['values']; $choices = $node->merci_reservation_items; $reservation_nid = $node->nid; // **** // Build date objects we'll need for our different validations. // **** $start = $node->field_merci_date[0]['value']; $end = $node->field_merci_date[0]['value2']; // Do this even for reservations with merci status of canceled and checked in. $choice_counts = array(); $total_counts = array(); $messages = array(); foreach ($choices as $did => $item) { if (is_array($item)) { $value = $item['merci_item_nid']; } else { $value = $item; } if (is_numeric($did) and !$value) { $value = $item['type']; } if ($value) { // Only include active buckets, and content types the user // can reserve. $messages[$did] = ''; if (is_numeric($value)) { $new_item = node_load($value); $title = $new_item->title; $type = $new_item->type; } else { $title = node_get_types('name',$value); $type = $value; } $content_settings = merci_load_item_settings($type); // Is this content type active? if ($content_settings->merci_active_status != MERCI_STATUS_ACTIVE) { $messages[$did] = '
' . t("%name is not active.", array('%name' => $title)) . '
'; continue; } // Does the user have access to manage reservations or this content type? if(!user_access('manage reservations') && !merci_check_content_type_user_permissions($type)) { $messages[$did] = '
' . t("You do not have permission to reserve %name.", array('%name' => $title)) . '
'; continue; } // Do we have the item available at this time? if(!isset($total_counts[$type])) { if (is_numeric($value)) { $item_options = merci_get_reservable_items($type, $start, $end, $reservation_nid); if (merci_type_setting($type) == 'bucket') { $total_counts[$type][$type] = $choice_counts[$type][$type] = merci_get_available_bucket_count($type, $start, $end, $node->vid) - $content_settings->merci_spare_items; } foreach($item_options as $nid => $item_nid){ $total_counts[$type][$nid] = $choice_counts[$type][$nid] = 1; } } else { $total_counts[$type][$value] = $choice_counts[$type][$value] = merci_get_available_bucket_count($type, $start, $end, $node->vid) - $content_settings->merci_spare_items; } } if (!$choice_counts[$type][$value] and $total_counts[$type][$value]) { $messages[$did] = '
' . t("You've selected too many %name's. We only have %amount available at the times you've selected.", array('%name' => $title, '%amount' => $total_counts[$type][$value])) . '
'; } elseif (!$choice_counts[$type][$value]) { $messages[$did] .= theme('merci_conflict_grid', $type, $title, $start, $end, $value,$node->vid); } else { //drupal_set_message(t('There are no time conflicts with this Reservation.')); } $choice_counts[$type][$value] --; if (is_numeric($value) ) { $choice_counts[$type][$type] --; } // Check item restrictions. max hours, etc. $restrictions = merci_check_content_type_restrictions($type, $start, $end); if (!empty($restrictions)) { foreach ($restrictions as $restriction) { $messages[$did] .= '
' . strtr($restriction, array('%name' => theme('placeholder', $title))) . '
'; } } if(!empty($messages[$did])) { form_set_error("merci_reservation_items][$did][merci_item_nid", $messages[$did]); } } } return $messages; } /** * Implementation of hook_validate(). */ function merci_node_validate($form, &$form_state) { if (!empty($form_state['ahah_submission'])) { return; } // No validation necessary on deletion. if ($form_state['clicked_button']['#id'] == 'edit-delete') { return; } // Do no validation if their errors from the main validation function. if (form_get_errors()) { return; } merci_validate_status($form, $form_state); merci_validate_merci_reservation_date($form, $form_state); // Tests for existing items. //merci_validate_existing_items($form, $form_state); merci_validate_merci_selected_items($form, &$form_state); //merci_validate_merci_choices($form, $form_state); } /** * Implementation of hook_insert(). */ function merci_insert($node) { if ($node->type == 'merci_reservation') { drupal_write_record("merci_reservation",$node); merci_add_reservation_items($node); } } /** * Implementation of hook_update(). */ function merci_update($node) { if ($node->type == 'merci_reservation') { if ($node->revision) { drupal_write_record("merci_reservation",$node); } else { drupal_write_record("merci_reservation",$node,"vid"); } merci_add_reservation_items($node); } } /** * Implementation of hook_delete(). */ function merci_delete($node) { if ($node->type == 'merci_reservation') { if ($node->merci_reservation_status == MERCI_STATUS_CHECKED_OUT) { foreach ($node->merci_reservation_items as $item) { $update = array( 'nid' => $item['merci_item_nid'], 'merci_item_status' => MERCI_ITEM_STATUS_AVAILABLE, ); drupal_write_record('merci_reservation_item_node', $update, 'nid'); } } // Delete all reservation placeholder nodes for the reservation. $placeholders = db_query("SELECT DISTINCT(merci_placeholder_nid) AS nid FROM {merci_reservation_detail} WHERE nid = %d", $node->nid); while ($placeholder = db_result($placeholders)) { node_delete($placeholder); } merci_delete_record('merci_reservation',$node,'nid'); merci_delete_record('merci_reservation_detail',$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_reservation_status'] = array( '#value' => drupal_get_form('merci_display_reservation_status', merci_record_status($node->merci_reservation_status)), '#weight' => 0, ); if ($page) { $reservation_table = drupal_get_form('merci_build_reservation_table_form', $node); $node = node_prepare($node, $teaser); $node->content['merci_reservation_items'] = array( '#value' => $reservation_table, '#weight' => 1, ); } } return $node; } /** * Implementation of hook_form(). */ function merci_form(&$node, $form_state) { merci_check_default_timezone(); $form = node_content_form($node, $form_state); if (isset($form_state['node'])) { $node = $form_state['node'] + (array)$node; } $node = (object) $node; // Add a wrapper for the choices and more button. $form['choice_wrapper'] = array( '#tree' => FALSE, '#prefix' => '
', '#suffix' => '
', ); // Build existing reserved items table on existing reservations. //if (isset($node->nid)) { $form['choice_wrapper']['merci_reservation_items'] = merci_build_reservation_table_form($form_state, $node, TRUE); //} // Choice adding code mostly stolen from poll module. $choice_count = empty($node->choice_count) ? 3 : $node->choice_count; if ($form_state['clicked_button']['#value'] == "Add more items") { $choice_count += 3; } $form['choice_wrapper']['choice_count'] = array( '#type' => 'value', '#value' => $choice_count ); // Add the current choices to the form. for ($delta = 1; $delta <= $choice_count; $delta++) { $default = isset($node->merci_reservation_items["choice_" . $delta]['merci_item_nid']) ? $node->merci_reservation_items["choice_" . $delta]['merci_item_nid'] : ''; $form['choice_wrapper']['merci_reservation_items']["choice_".$delta]['merci_item_nid'] = _merci_choice_form($node, $form_state, $delta, $default); } $options = array(); for ($i = 1; $i < 20; $i++) { $options[$i]=$i; } // 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 no javascript action. '#ahah' => array( 'path' => 'merci/js', 'wrapper' => 'merci-choice-wrapper', 'method' => 'replace', 'effect' => 'fade', ), ); if (user_access('manage reservations')) { $form['merci_reservation_status'] = array( '#title' => t('Status'), '#type' => 'radios', '#options' => merci_record_status(), '#default_value' => $node->merci_reservation_status, '#description' => t('Finalized bookings can not have time conflicts with each other.'), ); } else { $form['merci_reservation_status'] = array( '#type' => 'value', '#value' => $node->merci_reservation_status, ); } $form['merci_original_reservation_status'] = array( '#type' => 'value', '#value' => $node->merci_original_reservation_status ? $node->merci_original_reservation_status : $node->merci_reservation_status, ); // Since hook_validate is broken in 6.x, we add our own // custom validation here. // TODO check if this fixed. $form['#validate'][] = 'merci_node_validate'; $form['#cache'] = TRUE; // Make sure the form is cached. // Pull the correct action out of form_state if it's there to avoid AHAH+Validation action-rewrite. if (isset($form_state['action'])) { $form['#action'] = $form_state['action']; } return $form; } /** * Implementation of hook_form_alter(). */ function merci_form_alter(&$form, $form_state, $form_id) { // Node add/edit forms. $type = $form['type']['#value']; switch ($form_id) { // Node settings form. case $type . '_node_form': if (merci_is_merci_type($type)) { $node = (object) $form['#node']; $sub_type = $node->merci_sub_type ? $node->merci_sub_type : MERCI_SUB_TYPE_ITEM; $default_availability = $node->merci_default_availability ? $node->merci_default_availability : MERCI_AVA_F; if($sub_type == MERCI_SUB_TYPE_ITEM) { if(empty($form['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, ); } $form['merci_sub_type'] = array( '#type' => 'value', '#value' => $sub_type, ); merci_add_settings_form(&$form, $form_state); } else if ($type == 'merci_reservation') { $form['#after_build'][] = '_merci_after_build'; } break; 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; } $type = $form['old_type']['#value']; // If any nodes have already been created, lock the type setting. $nodes = db_result(db_query("SELECT COUNT(nid) FROM {node} WHERE type = '%s'",$type)); if ($type) { $settings = merci_load_item_settings($type); } if(empty($settings) and $nodes) { return; } $warning = '
' . t(' WARNING: changing this setting has no effect on existing reserved items.') . '
'; $options = array( 'disabled' => t('Disabled'), 'bucket' => t('Bucket'), 'resource' => t('Resource'), ); $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 ($nodes) { $form['merci']['merci_type_setting'] = array( '#type' => 'value', '#value' => $settings->merci_type_setting, ); $form['merci']['merci_type_setting_display'] = array( '#type' => 'item', '#title' => t('Reservable item type'), '#value' => $options[$settings->merci_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->merci_type_setting : 'disabled', '#description' => filter_xss(theme('item_list', $description_items)), ); } $status = array( MERCI_STATUS_ACTIVE => t('Active'), MERCI_STATUS_INACTIVE => t('Inactive'), ); $form['merci']['merci_active_status'] = array( '#type' => 'radios', '#title' => t('Status'), '#options' => $status, '#default_value' => isset($settings->merci_active_status) ? intval($settings->merci_active_status) : MERCI_STATUS_ACTIVE, '#description' => t('Set to active to allow this type to be reserved.'), ); // This setting is only valid for buckets. if (!isset($settings->merci_type_setting) || $settings->merci_type_setting == 'bucket') { $form['merci']['merci_spare_items'] = array( '#type' => 'textfield', '#title' => t('Spare items'), '#size' => 10, '#default_value' => $settings ? $settings->merci_spare_items : 0, '#element_validate' => array('merci_is_numeric_validate'), '#description' => filter_xss(t("Set this to the number of items of this type that should always be unavailable and thus unreservable. This way you'll still have enough items for future reservations in case something breaks.") . $warning), ); $form['merci']['merci_auto_assign_bucket_item'] = array( '#type' => 'checkbox', '#title' => t('Automatically assign a bucket item'), '#default_value' => $settings ? $settings->merci_auto_assign_bucket_item : 0, '#description' => t('Automatically assign the best fit bucket item when reserving a new bucket item.'), ); } $vid = variable_get('merci_equipment_grouping_vid', 0); $form['merci']['merci_grouping'] = taxonomy_form( $vid, variable_get('merci_grouping_'.$type,0), 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.'), t('Grouping') ); $form['merci']['merci_max_hours_per_reservation'] = array( '#type' => 'textfield', '#title' => t('Maximum hours per reservation'), '#size' => 10, '#default_value' => $settings ? $settings->merci_max_hours_per_reservation : 0, '#element_validate' => array('merci_is_numeric_validate'), '#description' => filter_xss(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->merci_allow_overnight : 0, '#description' => filter_xss(t('Allow a reservation to continue over multiple days. If this is not checked, items in this content type must be returned before the checkout closes.') . $warning), ); $form['merci']['merci_allow_weekends'] = array( '#type' => 'checkbox', '#title' => t('Allow weekend reservation'), '#default_value' => $settings ? $settings->merci_allow_weekends : 0, '#description' => filter_xss(t('Allow a reservation to be made over days defined as weekend.') . $warning), ); merci_add_settings_form(&$form, $form_state); $form['#validate'][] = 'merci_node_type_save_validate'; $form['#submit'][] = 'merci_node_type_save_submit'; break; case 'node_delete_confirm': $node = node_load((int) arg(1)); if (!merci_delete_item_validate($node)) { unset($form['actions']['submit']); } break; case 'node_type_delete_confirm': $type = str_replace('-', '_', arg(3)); merci_delete_node_type_validate($form); break; case 'node_admin_content': if (!isset($form['#validate'])) { $form['#validate'] = array(); } $form['#validate'][] = 'merci_node_admin_delete_validate'; break; } } // Loads the current settings for reservable item nodes. /* If you just want the content type settings just pass only node->type. */ function merci_load_item_settings($object) { if (is_string($object)) { $type = $object; } else { $node = (array) $object; $type = $node['type']; } $item_settings = array(); // Settings from the content type edit page. $content_settings = merci_content_types($type); if(empty($content_settings)) { $content_settings = array(); } if ($node['nid']) { // Settings common to all merci item nodes. // resource or bucket. $merci_type = $content_settings['merci_type_setting']; $item_settings = db_fetch_array(db_query("SELECT merci_default_availability, merci_sub_type, merci_item_status FROM {merci_reservation_item_node} WHERE vid = %d", $node['vid'])); switch ($merci_type) { case 'bucket': // TODO: move to seperate module. if($item_settings['merci_sub_type'] == MERCI_SUB_TYPE_RESERVATION) { unset($item_settings['merci_default_availability']); unset($item_settings['merci_item_status']); $item_settings += db_fetch_array(db_query("SELECT merci_late_fee_per_hour, merci_rate_per_hour, merci_fee_free_hours FROM {merci_bucket_node} WHERE vid = %d", $node['vid'])); } break; case 'resource': // TODO: move to seperate module. $item_settings += db_fetch_array(db_query("SELECT merci_late_fee_per_hour, merci_rate_per_hour, merci_fee_free_hours, merci_min_cancel_hours, merci_autocheckout, merci_autocheckin, merci_selfcheckout FROM {merci_resource_node} WHERE vid = %d", $node['vid'])); break; } } if ($item_settings) { return (object)($item_settings + $content_settings); } else { return (object)($content_settings); } } /** * Implementation of hook_nodeapi(). */ function merci_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { // Process active MERCI node types and reservation nodes. if (merci_is_merci_type($node->type) || $node->type == 'merci_reservation') { // reservation, bucket or resource. $type = $node->type == 'merci_reservation' ? 'reservation' : merci_type_setting($node->type); switch ($op) { //case 'prepare': prepare and load are always called one after the other. Just need to use one. case 'load': // Merge in reservable items settings or else just use the default defined in the content type. if ($type != 'reservation') { return (array)(merci_load_item_settings($node)); } break; case 'validate': if ($type != 'reservation') { merci_validate_default_availability($node); } break; case 'insert': case 'update': if ($type != 'reservation') { if ($op == 'insert' || $node->revision) { drupal_write_record('merci_'.$type.'_node',$node); drupal_write_record('merci_reservation_item_node',$node); } else { drupal_write_record('merci_'.$type.'_node',$node,'vid'); drupal_write_record('merci_reservation_item_node',$node,'vid'); } } break; case 'delete': // In the case were a reservation placeholder node is being // deleted, remove it from the detail table here. if ($type != 'reservation') { $node->merci_placeholder_nid = $node->nid; merci_delete_record('merci_reservation_detail', $node, 'merci_placeholder_nid'); merci_delete_record('merci_' . $type . '_node', $node, 'nid'); merci_delete_record('merci_reservation_item_node',$node,'nid'); } break; case 'delete revision': if ($type != 'reservation') { merci_delete_record('merci_' . $type . '_node', $node, 'vid'); merci_delete_record('merci_reservation_item_node',$node,'vid'); } else { merci_delete_record('merci_reservation',$node,'vid'); merci_delete_record('merci_reservation_detail',$node,'vid'); } break; } } } /** * 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), 'file' => 'theme/theme.inc', ), 'merci_conflict_grid' => array ( 'template' => 'merci_conflict_grid', 'arguments' => array('type' => NULL, 'title' => NULL, 'start' => NULL, 'end' => NULL, 'nid' => NULL, 'reservation_vid' => NULL), 'path' => drupal_get_path('module', 'merci') . '/theme', 'file' => 'theme.inc', ), 'merci_reservation_table' => array( 'template' => 'merci_reservation_table', 'path' => drupal_get_path('module', 'merci') . '/theme', 'arguments' => array('reservations' => NULL, 'count' => NULL, 'hours' => NULL, 'title' => NULL), ), ); } /** * Implementation of hook_content_extra_fields. */ function merci_content_extra_fields() { $extras['merci'] = array( 'label' => t('MERCI Settings'), 'description' => t('Allows user to select Reservation status.'), 'weight' => 100, ); $extras['choice_wrapper'] = array( 'label' => t('MERCI Choices'), 'description' => t('Child items included in the Reservation.'), 'weight' => 80, ); return $extras; } /** * Implementation of hook_node_operations(). */ function merci_node_operations($return=NULL) { $operations = array( 'merci_update' => array( 'label' => t('Confirm Reservation(s)'), 'callback' => 'merci_operations_update', ), ); return $operations; } /** * Implementation of hook_cron(). */ function merci_cron() { // 2009-05-22 20:45:00 $time = gmdate('Y-m-j H:i: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_start_date = $db_info['columns']['value']['column']; $column_end_date = $db_info['columns']['value2']['column']; $args = array($time, $time, MERCI_STATUS_UNCONFIRMED, MERCI_STATUS_PENDING); // Select reservation nodes where all reserved items and resources are autocheckout. $reservations = db_query("SELECT n.nid FROM {node} AS n INNER JOIN {" . $table . "} ct ON ct.vid = n.vid INNER JOIN {merci_reservation} AS mr ON n.vid = mr.vid WHERE $column_start_date <= '%s' AND $column_end_date > '%s' AND mr.merci_reservation_status IN (%d, %d)", $args); while ($reservation_nid = db_result($reservations)) { $reservation = node_load($reservation_nid); //check child items of that reservations for autocheckout foreach ($reservaton->merci_reservation_items as $item) { $node = node_load($item['merci_item_nid']); if (!$node or !$node->merci_autocheckout) { // skip out to the next reservation. continue 2; } } //after checking all of the autocheckout settings for all the child items, are they all autocheckout? watchdog('merci', "Setting node " . $reservation_nid . " to checked out"); $node->merci_reservation_status = MERCI_STATUS_CHECKED_OUT; node_save($node); } $args = array($time, MERCI_STATUS_CHECKED_OUT); // Select reservation nodes where all reserved items and resources are autocheckin. $reservations = db_query("SELECT n.nid FROM {node} AS n INNER JOIN {" . $table . "} ct ON ct.vid = n.vid INNER JOIN {merci_reservation} AS mr ON n.vid = mr.vid WHERE $column_end_date <= '%s' AND mr.merci_reservation_status IN (%d)", $args); while ($reservation_nid = db_result($reservations)) { //check child items of that reservations for autocheckin $reservation = node_load($reservation_nid); foreach ($reservaton->merci_reservation_items as $item) { $node = node_load($item['merci_item_nid']); if (!$node or !$node->merci_autocheckin) { // skip out to the next reservation. continue 2; } } //after checking all of the autocheckout settings for all the child items, are they all autocheckout? watchdog('merci', "Setting node " . $reservation_nid . " to checked in"); $node->merci_reservation_status = MERCI_STATUS_CHECKED_IN; node_save($node); } // Give no shows a one hour grace period. // TODO: move grace period to admin option. // 2009-05-22 20:45:00 $time = gmdate('Y-m-j H:i:s', time() + 3600); //find all pending reservations that have started and set their stauts to no show $args = array($time, MERCI_STATUS_PENDING); // Select reservation nodes where all reserved items and resources are autocheckin. $reservations = db_query("SELECT n.nid FROM {node} AS n INNER JOIN {" . $table . "} ct ON ct.vid = n.vid INNER JOIN { merci_reservation } AS mr ON n.vid = mr.vid WHERE $column_start_date <= '%s' AND mr.merci_reservation_status IN (%d) ", $args); while ($nid = db_result($reservations)) { watchdog('merci', "Setting node " . $nid . " to no show"); $node = node_load($nid); $node->merci_reservation_status = MERCI_STATUS_NO_SHOW; node_save($node); } } /** * Implementation of hook_token_list(). * */ function merci_token_list($type = 'all') { if ($type == 'node' || $type == 'all') { //@TODO: Fix This Token //$tokens['node']['merci_resources'] = t('Reserved resource'); 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'] = ''; // 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['merci_placeholder_nid']); if ($item['item_title'] != '') { $titles[] = $item['item_title']; } else { $titles[] = $item['merci_placeholder_title']; } } $values['merci_resources'] = check_plain(implode(", ", $titles)); return $values; } } } /** * 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( 'views_handler_field_item_node_nid' => array( 'parent' => 'views_handler_field_prerender_list', 'file' => 'views_handler_field_item_node_nid.inc', ), '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', ), 'merci_handler_field_merci_reservation_item_node_item_status' => array( 'parent' => 'views_handler_field', 'file' => 'merci_handler_field.inc', ), 'merci_handler_filter_merci_reservation_item_node_item_status' => array( 'parent' => 'views_handler_filter_in_operator', 'file' => 'merci_handler_filter_in_operator.inc', ), ), ); } /** * 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); } /** * 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 = '', $reset = NULL) { static $options = array(); // We'll manually set the #parents property of these fields so that // their values appear in the $form_state['values']['choice'] array. //$buckets = t('Buckets'); if (empty($options) or $reset) { // NOTE: we don't filter by node here because we only want items not //reserved by any node including the node calling the function. $options = merci_build_reservable_items($node, $form_state, NULL); // Group the buckets. $vid = variable_get('merci_equipment_grouping_vid', 0); $sorted = array(); foreach ($options['options'] as $grouping => $items) { if(!is_array($items)) { continue; } foreach($items as $id => $name) { if(is_numeric($id)) { // Resource $type = db_result(db_query('SELECT type FROM {node} WHERE nid = %d', $id)); } else { $type = $id; } $tid = variable_get('merci_grouping_'.$type,0); if ($tid) { $term = taxonomy_get_term($tid); if ($term) { $sorted[$term->name][$id] = $name; unset ($options['options'][$grouping][$id]); } } } if(empty($options['options'][$grouping])) { unset($options['options'][$grouping]); } } // Order them. $terms = taxonomy_get_tree($vid); foreach ($terms as $term) { if(isset($sorted[$term->name])) { $options['options'][$term->name] = $sorted[$term->name]; } } } $form = array( '#type' => 'select', '#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. */ 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']; } // Dates loaded from the reservation are next. elseif (isset($node->nid)) { $date_info = $node->field_merci_date[0]; $start = $date_info['value']; $end = $date_info['value2']; } // New reservation, so no date filtering. else { $is_new = TRUE; } $options = array(); $options['options'] = array('' => t('')); $options += merci_get_available_bucket_items($node, $item['type']); $form['merci_item_nid'] = array( '#type' => 'select', '#options' => $options, '#default_value' => $item['merci_item_nid'], ); if ($node->merci_reservation_status >= MERCI_STATUS_CHECKED_OUT) { $form['merci_item_nid']['#disabled'] = true; } } } //$form['#table'][$did]['accessories'] = $accessories; $table[$did] = $form; } return $table; } /** * 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_type, $start, $end, $node->nid); return $options; } /** * Pulls an array of items that are reservable for the content type and date range. * * @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($content_type, $start=NULL, $end=NULL, $reservation_nid = NULL, $overdue=TRUE) { $merci_type = merci_type_setting($content_type); // 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 or checked in 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 or checked out 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. // 4. Exclude items from past reservations where the item is in a checked out state. $query = "SELECT n.nid, n.title FROM {node} n INNER JOIN {merci_reservation_item_node} m ON n.vid = m.vid WHERE m.merci_default_availability IN (%d, %d) AND n.type = '%s' AND m.merci_sub_type = %d "; $args = array(MERCI_AVA_F, MERCI_AVA_T, $content_type, MERCI_SUB_TYPE_ITEM); if ($start) { // 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_merge($args,array($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. $query .= " AND n.nid NOT IN (SELECT md2.merci_item_nid FROM {" . $table . "} ct INNER JOIN {merci_reservation_detail} md2 ON ct.vid = md2.vid INNER JOIN {merci_reservation_item_node} m2 ON md2.merci_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.merci_item_status <= %d ) "; if($overdue) { $start2 = date_create($start, timezone_open("UTC")) >= date_create("now") ? gmdate("Y-m-d H:i:s") : $start; $args[] = $start2; $args[] = MERCI_ITEM_STATUS_CHECKED_OUT; $query .= " OR ($column_end_date <= '%s' AND md2.merci_item_status = %d) "; } if ($reservation_nid) { $where = ' AND md2.nid <> %d'; $args[] = $reservation_nid; } $query .= " ) $where ) "; } $query .= " ORDER BY n.title"; $items = db_query($query, $args); $options = array(); while ($item = db_fetch_object($items)) { $options[$item->nid] = $item->title; } return $options; } //TODO: the following three functions look very much a like. function merci_reserved_bucket_items($content_type, $start = NULL, $end = NULL, $reservation_vid = 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']; // Get all assignable nodes for this bucket item. $total_items_nodes = db_query("SELECT n.nid FROM {node} n INNER JOIN {merci_reservation_item_node} m ON n.vid = m.vid WHERE n.type = '%s' AND m.merci_sub_type = %d AND m.merci_default_availability IN (%d, %d)", $content_type, MERCI_SUB_TYPE_ITEM, MERCI_AVA_F, MERCI_AVA_S); $total_items_array = array(); while ($ctnodes = db_fetch_array($total_items_nodes)) { $total_items_array[$ctnodes['nid']] = array(); } $args = array($start, $end, $start, $end, $start, $end, $content_type, MERCI_ITEM_STATUS_AVAILABLE); // If we're checking an existing reservation, exclude it from the // reserved items. if (isset($reservation_vid)) { $where = ' AND ct.vid <> %d'; $args[] = $reservation_vid; } else { $where = ''; } // pull reservations with assigned nodes and status of MERCI_ITEM_STATUS_RESERVED or MERCI_ITEM_STATUS_CHECKED_OUT $reserved_nodes = db_query(" SELECT ct.nid,field_merci_date_value,field_merci_date_value2,md.merci_item_nid FROM {" . $table . "} ct INNER JOIN {merci_reservation_detail} md on ct.vid = md.vid INNER JOIN {merci_bucket_node} m on md.merci_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 md.merci_item_nid !=0 AND NOT md.merci_item_status <= %d $where", $args ); // Use up items for assigned nodes. while ($node = db_fetch_object($reserved_nodes)) { // If item is assigned then item is in use by this node. $total_items_array[$node->merci_item_nid][$node->nid] = $node; } // pull reservations in the past which are still checked out. $start2 = date_create($start, timezone_open("UTC")) >= date_create("now") ? gmdate("Y-m-d H:i:s") : $start; $args2 = array($start2, $content_type, MERCI_ITEM_STATUS_CHECKED_OUT); // If we're checking an existing reservation, exclude it from the // reserved items. if (isset($reservation_vid)) { $args2[] = $reservation_vid; } $reserved_nodes = db_query(" SELECT ct.nid,field_merci_date_value,field_merci_date_value2,md.merci_item_nid FROM {" . $table . "} ct INNER JOIN {merci_reservation_detail} md on ct.vid = md.vid INNER JOIN {merci_bucket_node} m on md.merci_placeholder_nid = m.nid INNER JOIN {node} ctn on ct.vid = ctn.vid INNER JOIN {node} mn on m.vid = mn.vid WHERE ($column_end_date <= '%s') AND mn.type = '%s' AND md.merci_item_nid !=0 AND md.merci_item_status = %d $where", $args2 ); /* if (count($reserved_nodes) and !user_access('administrate MERCI')) { drupal_set_message(t("There are items you can't reserve because they have not been returned.")); } */ // Use up items for assigned nodes. while ($node = db_fetch_object($reserved_nodes)) { $total_items_array[$node->merci_item_nid][$node->nid] = $node; } // pull reservations without assigned nodes and not status of MERCI_ITEM_STATUS_CHECKED_IN $reserved_nodes = db_query(" SELECT ct.nid,field_merci_date_value,field_merci_date_value2,md.merci_item_nid FROM {" . $table . "} ct INNER JOIN {merci_reservation_detail} md ON ct.vid = md.vid INNER JOIN {merci_bucket_node} m ON md.merci_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 md.merci_item_nid = 0 AND NOT md.merci_item_status < %d $where", $args ); // Temporarily assign an item for these nodes. while ($node = db_fetch_object($reserved_nodes)) { // Eat up a bucket item for this node. // If item is not assigned then temporarily add one. foreach ($total_items_array as $item_nid => $reservations) { $willitfit = TRUE; foreach ($reservations as $oldnode) { // Does the start date overlap this reservation. if ( ( date_create($node->field_merci_date_value) > date_create($oldnode->field_merci_date_value) and date_create($node->field_merci_date_value) < date_create($oldnode->field_merci_date_value2) ) or // Does the end date overlap this reservation. ( date_create($node->field_merci_date_value2) > date_create($oldnode->field_merci_date_value) and date_create($node->field_merci_date_value2) < date_create($oldnode->field_merci_date_value2) ) or // Does the start and end date overlap this reservation. ( date_create($node->field_merci_date_value) <= date_create($oldnode->field_merci_date_value) and date_create($node->field_merci_date_value2) >= date_create($oldnode->field_merci_date_value2) ) ) { // Can't use this item for this reservation. So try another. $willitfit = FALSE; break; } } if ($willitfit) { $total_items_array[$item_nid][$node->nid] = $node; break; } } } return $total_items_array; } function merci_get_suggested_bucket_item($content_type, $start , $end , $items = array()) { $total_items_array = merci_reserved_bucket_items($content_type, $start, $end ); foreach ($total_items_array as $item_nid => $node) { if (empty($total_items_array[$item_nid]) && !in_array($item_nid, $items)) { return $item_nid; } } return 0; } // 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 = NULL, $end = NULL, $reservation = NULL) { //if there are no dates, return the active total if (!$start) { //if user is admin/manager and merci template is installed $count = db_fetch_object(db_query("SELECT COUNT(n.nid) as total FROM {node} n LEFT JOIN {merci_bucket_node} mbn ON n.vid = mbn.vid WHERE n.type = '%s' AND n.status = 1 AND mbn.merci_default_availability = 1", $content_type )); return $count->total; } $total_items_array = merci_reserved_bucket_items($content_type, $start, $end,$reservation ); foreach ($total_items_array as $item_nid => $reservations) { if (!empty($reservations)) { $reserved_items++; } } return sizeof($total_items_array) - $reserved_items; } /** * merci_get_count ($content_type, $default_availability) * returns total number of items available for check out. * @ $content_type resource to be counted * @ $default_availability */ function merci_get_count($type,$default_availability=MERCI_AVA_F) { return db_result(db_query("SELECT COUNT(n.nid) as total FROM {node} n LEFT JOIN {merci_". $type['merci_type_setting'] ."_node} mbn ON n.vid = mbn.vid WHERE n.type = '%s' AND mbn.merci_default_availability = %d AND mbn.merci_sub_type = %d", $type['type'],$default_availability,MERCI_SUB_TYPE_ITEM )); } /** * merci_get_reservation_count($content_type[string]) * returns total number of checked out items for content type. * @ $content_type resource to be counted */ /** * Builds an array representing reservations for a Resource 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_node_in_timespan($item_nid, $type, $start_date, $end_date, $reservation_vid = 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']; /* */ $type_settings = merci_type_setting($type); if ($type_settings == 'bucket') { return merci_reserved_bucket_items($type, $start_date, $end_date, $reservation_vid); } else { $sql = "SELECT r.nid, r.field_merci_date_value, r.field_merci_date_value2, merci_item_nid FROM {node} n JOIN {merci_reservation_detail} d ON n.nid = d.merci_item_nid JOIN {content_type_merci_reservation} r ON d.vid = r.vid WHERE n.nid = %d AND (($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')) "; } $args = array($item_nid, $start_date, $end_date, $start_date, $end_date, $start_date, $end_date); // If we're checking an existing reservation, exclude it from the // reserved items. if ($reservation_vid) { $sql .= " AND d.vid <> %d"; $args[] = $reservation_vid; } $sql .= " ORDER BY r.field_merci_date_value "; $reservations = db_query($sql, $args); while ($reservation = db_fetch_object($reservations)) { $return[$item_nid][$reservation->nid] = $reservation; } return $return; } /** * 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', ); $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 function merci_check_default_timezone() { $default = variable_get('date_default_timezone_name', ''); if (empty($default)) { drupal_set_message(t('No site wide default timezone configured. Please configure one in order for the MERCI reservation system to operate correctly.', array('@link' => url('admin/settings/date-time')))); } } /** * 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)) { // 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(t('No nodes selected.'), 'error'); drupal_goto('admin/content/node'); } } /** * 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 /** * 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; } /** * Callback function for updating Reservation status from VBO. */ function merci_operations_update($nodes) { foreach ($nodes as $nid) { merci_confirm_reservation($nid); } } /** * Callback function for updating Reservation status. */ function merci_confirm_reservation($nid) { $node = node_load($nid); //only update if MERCI Status is Unconfirmed if ($node->merci_reservation_status == MERCI_STATUS_UNCONFIRMED) { $node->merci_reseravation_status = MERCI_STATUS_PENDING; node_save($node); return TRUE; } } function merci_add_settings_form(&$form, $form_state) { // Only admin can edit these values. if(!user_access('administer MERCI')) return; $type = array_key_exists('old_type',$form) ? $form['old_type']['#value'] : $form['type']['#value']; //$merci_settings = mnerci_content_types($type); //if(!$merci_settings) return; if($form['#id'] == 'node-type-form') { $node = merci_load_item_settings($type); $node = (object) $node; } else { $node = (object) $form['#node']; } //merci_load_item_settings($node); // New nodes are always sub type item. $sub_type = $node->merci_sub_type ? $node->merci_sub_type : MERCI_SUB_TYPE_ITEM; if(empty($form['merci'])) { $form['merci'] = array( '#type' => 'fieldset', '#title' => t('MERCI settings'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); } // Bucket item nodes have no individual pricing, so just zero these values out. // Althought you can override them on the reserervation nodes. if ($node->merci_type_setting == 'resource' or $form['#id'] == 'node-type-form' or $sub_type == MERCI_SUB_TYPE_RESERVATION ) { $form['merci']['merci_rate_per_hour'] = array( '#type' => 'textfield', '#title' => t('Rate per hour'), '#size' => 10, '#default_value' => $node->merci_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' => $node->merci_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' => $node->merci_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.'), ); } if (($node->merci_type_setting == 'resource' and $sub_type == MERCI_SUB_TYPE_ITEM) or $form['#id'] == 'node-type-form') { $form['merci']['merci_min_cancel_hours'] = array( '#type' => 'textfield', '#title' => t('Minimum hours for cancelation without No Show'), '#size' => 10, '#default_value' => $node->merci_min_cancel_hours, '#element_validate' => array('merci_is_numeric_validate'), '#description' => t('Minimum number of hours before the start time a user may cancel a reservation for the item.'), ); $form['merci']['merci_autocheckout'] = array( '#type' => 'checkbox', '#title' => t('Auto checkout'), '#default_value' => $node->merci_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' => $node->merci_autocheckin, '#description' => t('Automatically check this item in when the Reservation ends.'), ); $form['merci']['merci_selfcheckout'] = array( '#type' => 'checkbox', '#title' => t('Self checkout'), '#default_value' => $node->merci_selfcheckout, '#description' => t('Manage checkout with additional code.'), ); } } /** * Does the very standard things that must be done in any normal callback. * Used by each callback in this example module. */ function merci_ahah_get_form() { // The form is generated in an include file which we need to include manually. include_once 'modules/node/node.pages.inc'; $form_state = array('storage' => NULL, 'submitted' => FALSE); //$form_state = array('storage' => TRUE, 'submitted' => TRUE); $form_build_id = $_POST['form_build_id']; $form = form_get_cache($form_build_id, $form_state); $args = $form['#parameters']; $form_id = array_shift($args); $form_state['post'] = $form['#post'] = $_POST; // Enable the submit/validate handlers to determine whether AHAH-submittted. $form_state['ahah_submission'] = TRUE; $form['#programmed'] = $form['#redirect'] = FALSE; // Stash original form action to avoid overwriting with drupal_rebuild_form(). $form_state['action'] = $form['#action']; //$form_state['submitted'] = TRUE; // Continued from the above: when an AHAH update of the form is triggered // without using a button, you generally don't want any validation to kick // in. A typical example is adding new fields, possibly even required ones. // You don't want errors to be thrown at the user until they actually submit // their values. (Well, actually you want to be smart about this: sometimes // you do want instant validation, but that's an even bigger pain to solve // here so I'll leave that for later…) // For the default "{$form_id}_validate" and "{$form_id}_submit" handlers. // Disable #required and #element_validate validation. // TODO figure out how to turn off validation for date_combo. /* $form['#validate'] = NULL; $form['#submit'] = NULL; // For customly set #validate and #submit handlers. $form_state['submit_handlers'] = NULL; $form_state['validate_handlers'] = NULL; require_once( drupal_get_path('module', 'date') .'/date_elements.inc'); merci_disable_validation($form); $element = $form['field_merci_date'][0]; date_combo_validate($element,$form_state); */ drupal_process_form($form_id, $form, $form_state); $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id); return array($form, $form_state); } function merci_disable_validation(&$form) { foreach (element_children($form) as $child) { $form[$child]['#validated'] = TRUE; merci_disable_validation(&$form[$child]); } } function _merci_after_build($form, &$form_state) { $weight = ($form['field_merci_date']['#weight']+1); $form['field_merci_date'][0]['field_merci_date_button'] = array( '#type' => 'submit', '#value' => t('Limit Lists to Available Items'), '#weight' => $weight, '#submit' => array('merci_date_filter'), '#ahah' => array( 'path' => 'merci/js', 'wrapper' => 'merci-choice-wrapper', 'method' => 'replace', 'effect' => 'fade', ), ); return $form; }