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; } 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']; } 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; } 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; } } // 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, ); } 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.'), ); } } 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, ); } } } } switch ($form_id) { // Add check availability button inside date selector. case 'merci_reservation_node_form': $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 = '
'. t(' WARNING: changing this setting has no effect on existing reserved items.') .'
'; $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 cannot be change 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, ); } $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 on a weekend day.') . $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.'), ); 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, ); return $items; } /** * Builds the MERCI admininstration settings form. */ function merci_admin_settings() { $form = array(); $form['merci_hours_mon'] = array( '#type' => 'textfield', '#title' => t('Monday hours'), '#size' => 11, '#maxlength' => 11, '#default_value' => variable_get('merci_hours_mon', ''), '#description' => t('
Enter military time for both opening and closing time, separated by a dash, in the format hh:mm-hh:mm
ex. 09:00-17:00 would be open at 9AM, close at 5PM. Leave blank to indicate not being open.'), ); $form['merci_hours_tue'] = array( '#type' => 'textfield', '#title' => t('Tuesday hours'), '#size' => 11, '#maxlength' => 11, '#default_value' => variable_get('merci_hours_tue', ''), '#description' => t('
Enter military time for both opening and closing time, separated by a dash, in the format hh:mm-hh:mm
ex. 09:00-17:00 would be open at 9AM, close at 5PM. Leave blank to indicate not being open.'), ); $form['merci_hours_wed'] = array( '#type' => 'textfield', '#title' => t('Wednesday hours'), '#size' => 11, '#maxlength' => 11, '#default_value' => variable_get('merci_hours_wed', ''), '#description' => t('
Enter military time for both opening and closing time, separated by a dash, in the format hh:mm-hh:mm
-- ex. 09:00-17:00 would be open at 9AM, close at 5PM. Leave blank to indicate not being open.'), ); $form['merci_hours_thu'] = array( '#type' => 'textfield', '#title' => t('Thursday hours'), '#size' => 11, '#maxlength' => 11, '#default_value' => variable_get('merci_hours_thu', ''), '#description' => t('
Enter military time for both opening and closing time, separated by a dash, in the format hh:mm-hh:mm
ex. 09:00-17:00 would be open at 9AM, close at 5PM. Leave blank to indicate not being open.'), ); $form['merci_hours_fri'] = array( '#type' => 'textfield', '#title' => t('Friday hours'), '#size' => 11, '#maxlength' => 11, '#default_value' => variable_get('merci_hours_fri', ''), '#description' => t('
Enter military time for both opening and closing time, separated by a dash, in the format hh:mm-hh:mm
ex. 09:00-17:00 would be open at 9AM, close at 5PM. Leave blank to indicate not being open.'), ); $form['merci_hours_sat'] = array( '#type' => 'textfield', '#title' => t('Saturday hours'), '#size' => 11, '#maxlength' => 11, '#default_value' => variable_get('merci_hours_sat', ''), '#description' => t('
Enter military time for both opening and closing time, separated by a dash, in the format hh:mm-hh:mm
ex. 09:00-17:00 would be open at 9AM, close at 5PM. Leave blank to indicate not being open.'), ); $form['merci_hours_sun'] = array( '#type' => 'textfield', '#title' => t('Sunday hours'), '#size' => 11, '#maxlength' => 11, '#default_value' => variable_get('merci_hours_sun', ''), '#description' => t('
Enter military time for both opening and closing time, separated by a dash, in the format hh:mm-hh:mm
ex. 09:00-17:00 would be open at 9AM, close at 5PM. Leave blank to indicate not being open.'), ); $form['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('
Enter dates which are closed regardless of the day of the week, one date per line, in the format mm-dd
ex. 07-04 would mean July 4th is always closed, regardless of what day of the week it falls on.'), ); $form['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_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_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. Allows HTML.'), ); 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); // 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'] = 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' => '
', '#suffix' => '
', ); // Container for just the item selector. $form['choice_wrapper']['choice'] = array( '#prefix' => '
', '#suffix' => '
', '#theme' => 'merci_choices', ); // Add the current choices to the form. for ($delta = 0; $delta < $choice_count; $delta++) { $default = isset($node->choice[$delta]['item']) ? $node->choice[$delta]['item'] : ''; $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 amount 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-choices', 'method' => 'replace', 'effect' => 'fade', ),*/ ); 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 cannot 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']; } // 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('')); $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; } /** * 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 .= '
'. $table_caption .'
'; $output .= theme('table', $header, $rows); $output .= '
Printable Contract
'; } 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) { $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]; //Users in role with Administer MERCI permssion are exempt from content type and hours of operation restrictions if (!user_access('administer MERCI')) { // 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:s'); 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' => date("g:i a", strtotime($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' => date("g:i a", strtotime($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:s'); 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' => $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' => $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; } // Bucket. 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. $type_settings = merci_load_content_type_settings($type); $restrictions = merci_check_content_type_restrictions($type_settings, $start, $end); if (!empty($restrictions)) { $message = ''; foreach ($restrictions as $restriction) { $message .= '
'. strtr($restriction, array('%name' => theme('placeholder', $title))) .'
'; } } // It's not a content type restriction, so it's a date conflict. 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); } // Check to make sure the currently assigned bucket item is still // reservable with the submitted dates. 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; } // Bucket. 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. $type_settings = merci_load_content_type_settings($type); $restrictions = merci_check_content_type_restrictions($type_settings, $start, $end); if (!empty($restrictions)) { $message = ''; foreach ($restrictions as $restriction) { $message .= '
'. strtr($restriction, array('%name' => theme('placeholder', $title))) .'
'; } } // Either a date conflict, or the content type has been deactivated. else { $count_sql = "SELECT COUNT(n.nid) FROM {node} n JOIN {merci_node_type} t ON t.type = n.type WHERE n.status = 1 AND ( ( t.type_setting = 'bucket' AND n.type = '%s' ) OR ( t.type_setting = 'resource' AND n.title = '%s' ) )"; $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 = '
' . t("The dates and times for %name conflict with one or more existing reservations", array('%name' => $title)) . '
'; $message.= '
= available = unavailable
'; foreach ( $reservations as $date => $times ) { $date_timestamp = strtotime( $date ); $hours_date = $hours[ date( 'w' , $date_timestamp ) ]; if ( isset( $hours_date['open'] ) ) { $message.= ''; $message.= ''; $time = $hours_date['open']; while ( $time < $hours_date['close'] ) { $message.= ''; $time = date( 'H:i' , strtotime( $time . ' +1 hour' ) ); } $message.= ''; for ( $i = 1; $i <= $count; $i++ ) { $message.= ''; $time = $hours_date['open']; while ( $time < $hours_date['close'] ) { if ( $times[ $time . ':00' ] >= $i ) $message.= ''; else $message.= ''; $time = date( 'H:i' , strtotime( $time . ' +15 minutes' ) ); } // while $message.= ''; } // for $message.= '
' . date( 'm/d/Y' , $date_timestamp ) . '' . date( 'g:i a' , strtotime( $time ) ) . '
' . htmlspecialchars( $title ); if ( $count > 1 ) $message.= ' ' . $i . '/' . $count; $message.= '
'; } // 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. To continue with the process, Save the 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 FROM merci_reservation_detail d JOIN node n ON n.nid = d.placeholder_nid JOIN content_type_merci_reservation r ON r.vid = d.vid WHERE n.type = '%s' AND r.field_merci_date_value < '%s' 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 counts 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() { $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', ''); $closed_days = array(); $parts = explode("\n", $closed_days_raw); foreach ($parts as $date) { $date = trim($date); if (drupal_strlen($date) == 5) { $closed_days[] = $date; } } $hours_of_operation['closed_days'] = $closed_days; return $hours_of_operation; } /** * 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.0', ); } /** * 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 '$type' and status = 0 AND created > $startdate AND created < $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; } // foreach if ( $required_sent ) { // Save new node revision, clear taxonomy, and re-add sent terms 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; } // if else { // Missing required vocabulary $output['status'] = 'failed'; $output['error'] = 'Required fields not selected.'; } // else } // if 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 } // if else { // No permission $output['error'] = 'Permission denied.'; } // else echo json_encode( $output ); } // merci_taxonomy_json function merci_printable_contract( $node_id ) { $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); $username = $ob[first_name] . ' ' . $ob[middle_name] . ' ' . $ob[last_name]; $phone = $ob[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 ); $header = variable_get( 'merci_contract_header' , '' ); ?> Contract
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 * $fee_hours : 0; $commercial_cost_total+= $commercial_cost; $member_cost_total+= $member_cost; if($item -> ttitle){ $ttitle = htmlspecialchars( $item -> ttitle ); } else { $ttitle = 'SPECIFIC ITEM NOT SELECTED FROM BUCKET'; } ?>
Item Commercial Cost Member Cost
taxonomy ) > 0 ) { ?>
    taxonomy as $accessory ) { ?>
  • name; ?>
$ $
Total $ $