You are here

api.inc in MERCI (Manage Equipment Reservations, Checkout and Inventory) 7.2

Same filename and directory in other branches
  1. 6.2 includes/api.inc

MERCI - Managed Equipment Reservation Checkout and Inventory

File

includes/api.inc
View source
<?php

// Report all PHP errors
error_reporting(-1);

/**
 * @file
 * MERCI - Managed Equipment Reservation Checkout and Inventory
 */
function merci_validate_merci_selected_items($form, &$form_state) {
  $node = (object) $form_state['values'];
  $langcode = $node->language;

  //  $start = drupal_array_get_nested_value($node->field_merci_date,$parents,$exists);
  // ****
  // Build date objects we'll need for our different validations.
  // ****
  $start = $node->field_merci_date[LANGUAGE_NONE][0]['value'];
  $end = $node->field_merci_date[LANGUAGE_NONE][0]['value2'];
  $messages = array();
  foreach ($node->merci_reservation_items as $did => $item) {
    if (empty($item['merci_item_nid'])) {
      continue;
    }

    // Bucket choice?
    if (!is_numeric($item['merci_item_nid'])) {
      $item['type'] = $item['merci_item_nid'];
      $item['merci_item_nid'] = 0;
    }

    // Get the title of the item.
    // Also get the content type for new resource items.
    if (!isset($item['item_title']) or !isset($item['type'])) {
      $new_item = node_load($item['merci_item_nid']);
      if ($new_item) {
        $item['item_title'] = $new_item->title;
        $item['type'] = $new_item->type;
      }
      else {

        //TODO: should not be doing theming here.
        $content_settings = merci_load_item_settings($item['type']);
        $item['item_title'] = $content_settings->type_name;
      }
    }
    $messages[$did] = '';
    if (isset($item['type'])) {
      $type = $item['type'];
      $item_nid = $item['merci_item_nid'];
      $title = $item['item_title'];

      // Is this content type active?
      $content_settings = merci_load_item_settings($type);
      if ($content_settings->merci_active_status != MERCI_STATUS_ACTIVE) {
        $messages[$did] = '<div> ' . t("%name is not active.", array(
          '%name' => $title,
        )) . '</div>';
        form_set_error("merci_reservation_items][{$did}][merci_item_nid", $messages[$did]);
        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] = '<div> ' . t("You do not have permission to reserve %name.", array(
          '%name' => $title,
        )) . '</div>';
        form_set_error("merci_reservation_items][{$did}][merci_item_nid", $messages[$did]);
        continue;
      }

      // Did the user select too many of the same bucket item?
      if (merci_type_setting($type) == 'bucket') {
        $selected_count[$type] = isset($selected_count[$type]) ? $selected_count[$type] + 1 : 1;
        if (!isset($inventory_count[$type])) {
          $inventory_count[$type] = merci_get_available_bucket_count($type);
        }
        if ($selected_count[$type] > $inventory_count[$type]) {
          $messages[$did] = '<div> ' . t("You've selected too many %name's.  We only have %amount in the current inventory.", array(
            '%name' => $title,
            '%amount' => $inventory_count[$type],
          )) . '</div>';
          form_set_error("merci_reservation_items][{$did}][merci_item_nid", $messages[$did]);
          continue;
        }
      }

      // Did the user select too many of the same item?
      if ($item_nid) {
        if (isset($selected_count[$item_nid])) {
          $inventory_count = 1;
          $messages[$did] = '<div> ' . t("You've selected too many %name's.  We only have %amount in the current inventory.", array(
            '%name' => $title,
            '%amount' => $inventory_count,
          )) . '</div>';
          form_set_error("merci_reservation_items][{$did}][merci_item_nid", $messages[$did]);
          continue;
        }
        else {
          $selected_count[$item_nid] = 1;
        }
      }

      // Is it available?
      // Are we checking an existing item?
      $nid = isset($item['did']) ? $node->nid : NULL;
      if (merci_type_setting($type) == 'bucket') {
        $nid = $node->nid;
      }

      // Is the item available at this time?
      $count = merci_is_item_reservable($item_nid, $type, $start, $end, $nid);
      if (!$count or empty($item_nid) and merci_type_setting($type) == 'bucket' and $count - $selected_count[$type] < 0) {
        $messages[$did] = merci_theme_conflict_grid($type, $title, $start, $end, $item_nid, $node->nid);
        form_set_error("merci_reservation_items][{$did}][merci_item_nid", $messages[$did]);
        continue;
      }

      // How many items are overdue and thus unavailable at this time?
      $overdue_items_array = merci_overdue_items($type, $start, $node->nid);
      $overdue_count = 0;
      $overdue_message = '';
      if (empty($item_nid) and !empty($overdue_items_array) or !empty($overdue_items_array) and !empty($item_nid) and array_key_exists($item_nid, $overdue_items_array)) {
        $overdue_message = '<div> ' . t("%name is not available because it is still checked out by:", array(
          '%name' => $title,
        )) . '</div>';
        foreach ($overdue_items_array as $reservations) {
          foreach (array_keys($reservations) as $nid) {
            $overdue = node_load($nid);
            $overdue_message .= '<div> ' . l($overdue->title, 'node/' . $overdue->nid) . '</div>';

            // Item is overdue so decriment the number of items available.
            $overdue_count++;
          }
        }
      }

      // Show the error if conflict due to overdue.
      if ($overdue_count and $count - $overdue_count <= 0) {

        // But also show conflict grid if bucket items not available due to other reservations at the same time.
        // Only done for buckets because resources always only have a count of 1 (if available) or 0 (not available).
        if (merci_type_setting($type) == 'bucket') {
          if ($inventory_count[$type] - $count > $overdue_count) {
            $overdue_message .= merci_theme_conflict_grid($type, $title, $start, $end, $item_nid, $node->vid);
            form_set_error("merci_reservation_items][{$did}][merci_item_nid", $messages[$did]);
          }
        }
        form_set_error("merci_reservation_items][{$did}][merci_item_nid", $overdue_message);
        continue;
      }

      // Check item restrictions.  max hours, etc.
      $restrictions = merci_check_content_type_restrictions($type, $start, $end, $title);
      if (!empty($restrictions)) {
        foreach ($restrictions as $restriction) {
          $messages[$did] .= '<div>' . t("@restriction", array(
            '@restriction' => $restriction,
          )) . '</div>';
        }
        form_set_error("merci_reservation_items][{$did}][merci_item_nid", $messages[$did]);
        continue;
      }
    }
  }
  return $messages;
}
function merci_validate_merci_reservation_date($form, &$form_state) {
  $node = (object) $form_state['values'];
  $langcode = $form_state['node']->language;

  // ****
  // Build date objects we'll need for our different validations.
  // ****
  $start = $node->field_merci_date[LANGUAGE_NONE][0]['value'];
  $end = $node->field_merci_date[LANGUAGE_NONE][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_verbose_logging('SKIP Hours of Operation Check, Max Days Check, and Closed Dates Check because user has create reservations outside hours of operation permission');

    //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('<b>' . t('You are making a Reservation outside the normal hours of operation.  This may impact access to the items you are reserving.') . '</b>');
    }
  }
  else {

    // Reservation start date cannot exceed the max advance
    merci_verbose_logging('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_verbose_logging('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_verbose_logging('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'];
  $langcode = $form_state['node']->language;

  // 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."));
        }
        if (!empty($item['merci_item_nid'])) {
          $bad_reservations = merci_checked_out_reservations_for_item_nid($item['merci_item_nid'], $node->nid);
          if (!empty($bad_reservations)) {
            $output = '<ul>';
            foreach ($bad_reservations as $reservation) {
              $output .= '<li>' . $reservation . '</li>';
            }
            $output .= '</ul>';
            form_set_error("merci_reservation_items][{$did}][merci_item_nid", t("Cannot check out this item as it is currently checked out by:") . $output);
          }
        }
      }
    }
  }

  // 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[LANGUAGE_NONE][0]['value2'] . ' UTC') && !in_array((int) $node->merci_reservation_status, array(
    MERCI_STATUS_CANCELLED,
    MERCI_STATUS_CHECKED_IN,
    MERCI_STATUS_DENIED,
    MERCI_STATUS_NO_SHOW,
  ))) {
    $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],
    )));
  }
}

/**
 * Validates the state change of a reservable item.
 *
 * @param $node
 *   The item node.
 */
function merci_validate_default_availability($element, &$form_state) {

  // Only perform the check if the item is set to an unavailable state.
  if (in_array((int) $element['#value'], array(
    MERCI_UNA_F,
    MERCI_UNA_S,
  ))) {
    $bad_reservations = merci_incomplete_reservations_for_item_nid($form_state['values']['nid']);
    if (!empty($bad_reservations)) {
      $output = '<ul>';
      foreach ($bad_reservations as $node) {
        $output .= '<li>' . $node . '</li>';
      }
      $output .= '</ul>';
      form_set_error('merci_default_availability', t('%title can not be set to an unavailable status until it is removed from the following reservations:', array(
        '%title' => $form_state['values']['title'],
      )) . $output);
    }
  }
}

/**
 * Validation for numeric textfields.
 */
function merci_is_numeric_validate($form) {
  if ($form['#value'] && !is_numeric($form['#value'])) {
    form_set_error($form['#name'], t('%title must be a number.', array(
      '%title' => $form['#title'],
    )));
  }
}

/**
 * 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');
  }
}
function merci_validate_empty_reservation_items($form, &$form_state) {
  $node = (object) $form_state['values'];
  $choices = $node->merci_reservation_items;
  $unselected = 0;
  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) {
      $unselected++;
    }
  }
  if ($unselected == count($choices)) {
    $choice_keys = array_keys($choices);
    $first = reset($choice_keys);
    form_set_error("merci_reservation_items][{$first}][merci_item_nid", t("You cannot create a reservation without any items selected."));
  }
}

/**
 * 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) {
  $form_state['rebuild'] = TRUE;
}

/**
 * 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) {
  $form_state['rebuild'] = TRUE;
}

/**
 * writes additional info to log to aid in troubleshoot configuration
 */
function merci_verbose_logging($string) {
  if (variable_get('merci_verbose_logging', 0)) {
    watchdog('merci', "@string", array(
      '@string' => $string,
    ));
  }
}

/**
 * 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) {
  $langcode = $node->language;

  // Newly set dates take precedence.
  if (isset($form_state['values']['field_merci_date'])) {
    $start = $form_state['values']['field_merci_date'][LANGUAGE_NONE][0]['value'];
    $end = $form_state['values']['field_merci_date'][LANGUAGE_NONE][0]['value2'];
  }
  elseif (isset($node->nid)) {
    $date_info = $node->field_merci_date[LANGUAGE_NONE][0];
    $start = $date_info['value'];
    $end = $date_info['value2'];
  }
  else {
    $is_new = TRUE;
    $start = NULL;
    $end = NULL;
  }
  $options = array();
  $options['options'] = array(
    '' => t('<Select>'),
  );
  $merci_types = merci_content_types();
  $bucket_options = array();

  // Group the buckets.
  $vid = variable_get('merci_equipment_grouping_vid', 0);

  // With the correct weight.
  $terms = taxonomy_get_tree($vid);
  foreach ($terms as $term) {
    $options['options'][$term->name] = array();
  }

  // This array holds all reservable items the user may reserve.
  // Loop through each bucket type.
  foreach ($merci_types as $type => $value) {
    if ($value['merci_active_status'] != MERCI_STATUS_ACTIVE) {
      continue;
    }
    if (!merci_check_content_type_user_permissions($type)) {
      continue;
    }
    if (empty($is_new)) {
      $restrictions = merci_check_content_type_restrictions($type, $start, $end);
      if (!empty($restrictions)) {
        continue;
      }
    }
    if ($value['merci_type_setting'] == 'bucket') {

      // Check for available items in the bucket.
      $available_bucket_items = merci_get_available_bucket_count($type, $start, $end, $reservation_nid) - $value['merci_spare_items'];
      if ($available_bucket_items) {
        $options['options'][$value['merci_item_grouping']][$type] = $value['type_name'];
      }
    }
    else {
      if ($value['merci_type_setting'] == 'resource') {

        // No date filtering for new reservations.
        $item_options = merci_get_reservable_items($type, $start, $end, $reservation_nid);
        if (!empty($item_options)) {
          foreach ($item_options as $key => $item) {
            $options['options'][$value['merci_item_grouping']][$key] = $item;
          }
        }
      }
    }
  }

  // Remove grouping keys with no items.
  foreach ($terms as $term) {
    if (empty($options['options'][$term->name])) {
      unset($options['options'][$term->name]);
    }
  }
  return $options;
}

/**
 * Checks for reservation restrictions for a content type.
 *
 * These include maximum hours per reservation, and if the bucket/resource
 * is reservable overnight and/or on weekends.
 *
 * @param $content_type
 *   The content type to be checked.
 * @param $start
 *   The start date of the reservation in DATETIME format and UTC timezone.
 * @param $end
 *   The end date of the reservation in DATETIME format and UTC timezone.
 *
 * @return
 *   An array of warning messages for any restrictions found.
 */
function merci_check_content_type_restrictions($content_type, $start, $end, $title = '') {
  if (!user_access("manage reservations")) {

    //TODO I don't like this.
    $type_settings = merci_load_item_settings($content_type);
    $return = array();

    // Convert start/end dates to local time.
    // TODO clean this up.
    $start_object = merci_create_local_date_object($start);
    $end_object = merci_create_local_date_object($end);

    // We want these timestamps generated in UTC.
    $old_timezone = date_default_timezone_get();
    date_default_timezone_set('UTC');
    $start_timestamp = strtotime($start);
    $end_timestamp = strtotime($end);
    date_default_timezone_set($old_timezone);
    $reserved_hours = ($end_timestamp - $start_timestamp) / (60 * 60);
    $start_day_of_week = date_format($start_object, 'w');
    $end_day_of_week = date_format($end_object, 'w');

    // Make sure max hours aren't exceeded.
    if ($type_settings->merci_max_hours_per_reservation && $reserved_hours > $type_settings->merci_max_hours_per_reservation) {

      // Override max_hours_per_reservation if we can reserve this over the weekend
      // Validate allow_weekend.
      if (user_access('override max hours over closed days') || $type_settings->merci_allow_weekends) {
        $closed_days = array();

        // Do we allow extending this reservation over days checked as a weekend in addition to days we are closed?
        if ($type_settings->merci_allow_weekends) {
          $i = 0;
          foreach (array(
            'sunday',
            'monday',
            'tuesday',
            'wednesday',
            'thursday',
            'friday',
            'saturday',
          ) as $day) {
            if (variable_get('merci_' . $day . '_is_weekend', 0)) {
              $closed_days[$i] = TRUE;
            }
            $i++;
          }
        }

        // Do we allow extending a reservtion over days we are closed?
        if (user_access('override max hours over closed days')) {
          $hours_of_operation = merci_load_hours_of_operation($content_type);
          for ($i = 1; $i <= 6; $i++) {
            if (empty($hours_of_operation[$i])) {
              $closed_days[$i] = TRUE;
            }
          }
        }

        // Only extend if the following day is closed/weekend.
        // TODO check that the end time is not the same day and within the hours of being open.
        if (!array_key_exists(date('w', $start_timestamp + 86400), $closed_days)) {
          $return[] = t('%name cannot be reserved for more than %hours hours.', array(
            '%name' => $title,
            '%hours' => $type_settings->merci_max_hours_per_reservation,
          ));
        }

        // Only extend the max time if the default max time falls on a weekend.
        if (array_key_exists(date('w', $start_timestamp + $type_settings->merci_max_hours_per_reservation * 60 * 60), $closed_days)) {

          //Find the next day we are open.

          //  Keep adding 24 hours to start_time until we find our next opened day.
          for ($i = 1; $i <= 6; $i++) {

            // Are we at a day which is not closed.
            if (!array_key_exists(date('w', $start_timestamp + $i * 86400), $closed_days)) {

              // Does the end_day fall here?
              // TODO force time to be exactly when open.
              if ($end_day_of_week != date('w', $start_timestamp + $i * 86400)) {
                $return[] = t('%name cannot be reserved more then one day after a weekend.', array(
                  '%name' => $title,
                  '%hours' => $type_settings->merci_max_hours_per_reservation,
                ));
              }
              break;
            }
          }
        }
      }
      else {
        $return[] = t('%name cannot be reserved for more than %hours hours.', array(
          '%name' => $title,
          '%hours' => $type_settings->merci_max_hours_per_reservation,
        ));
      }
    }

    // Validate allow_overnight.
    if (!$type_settings->merci_allow_overnight) {

      // Need the 48 hour check in case somebody starts and ends their
      // reservation on the same day.
      if ($start_day_of_week != $end_day_of_week || $reserved_hours > 48) {
        $return[] = t('%name cannot be reserved overnight.', array(
          '%name' => $title,
        ));
      }
    }
  }
  return isset($return) ? $return : NULL;
}

// merci_check_content_type_restrictions

/**
 * Ensures the user has 'edit own [type] content' and 'delete own [type] content'
 * permissions, otherwise they are not allowed to reserve the content type.
 *
 * @return TRUE if the user has access to reserve the content type, FALSE
 *   otherwise.
 */
function merci_check_content_type_user_permissions($type) {
  return user_access("edit own {$type} content") && user_access("delete own {$type} content");
}
function merci_is_merci_type($type) {
  return merci_type_setting($type) != 'disabled';
}
function merci_type_setting($type) {
  return variable_get('merci_type_setting_' . $type, 'disabled');
}

/**
 * Return a list of all merci content types.
 *
 * @param $content_type_name
 *   If set, return information on just this type.
 *
 * Do some type checking and set up empty arrays for missing
 * info to avoid foreach errors elsewhere in the code.
 */
function merci_content_types($type_name = NULL) {

  // handle type name with either an underscore or a dash
  $type_name = !empty($type_name) ? str_replace('-', '_', $type_name) : NULL;
  $info = _merci_content_type_info();
  if (!isset($type_name)) {
    return $info;
  }
  else {
    return isset($info[$type_name]) ? $info[$type_name] : array();
  }
}
function merci_convert_date_popup($dates, $date_format = 'm/d/Y g:ia') {
  module_load_include('inc', 'date_api', 'date_api_elements');
  $date_timezone = variable_get('date_default_timezone', 'UTC');
  $start = new DateObject($dates['value']['date'] . ' ' . $dates['value']['time'], $date_timezone, $date_format);
  $end = new DateObject($dates['value2']['date'] . ' ' . $dates['value2']['time'], $date_timezone, $date_format);
  date_timezone_set($start, timezone_open('UTC'));
  date_timezone_set($end, timezone_open('UTC'));

  //$start = $start->format(DATE_DATETIME);

  //$end = $end->format(DATE_DATETIME);
  $start = $start
    ->format($date_format);
  $end = $end
    ->format($date_format);
  return array(
    'value' => $start,
    'value2' => $end,
  );

  //@TODO: Figure out why the rest of this even here?
  $start = array(
    '#value' => array(
      'date' => $dates['value']['date'],
      'time' => $dates['value']['time'],
    ),
    '#date_timezone' => $date_timezone,
    '#date_format' => $date_format,
  );
  $end = array(
    '#value' => array(
      'date' => $dates['value2']['date'],
      'time' => $dates['value2']['time'],
    ),
    '#date_timezone' => $date_timezone,
    '#date_format' => $date_format,
  );
  $start = date_popup_input_value($start);
  $end = date_popup_input_value($end);
  $start = date_create($start);
  $end = date_create($end);
  date_timezone_set($start, timezone_open('UTC'));
  date_timezone_set($end, timezone_open('UTC'));
  $start = date_convert($start, DATE_OBJECT, DATE_DATETIME);
  $end = date_convert($end, DATE_OBJECT, DATE_DATETIME);
  return array(
    'value' => $start,
    'value2' => $end,
  );
}
function merci_node_type_status($code = NULL) {
  $statuses = array(
    MERCI_STATUS_ACTIVE => t('Active'),
    MERCI_STATUS_INACTIVE => t('Inactive'),
  );
  if (isset($code)) {
    return $statuses[$code];
  }
  else {
    return $statuses;
  }
}

/**
 * Return the name of a type code.
 *
 * @param string|int $code
 *  if int, will return translated name of the code.
 *  if NULL, returns array of codes as keys, and translated strings as value
 *
 * @return string|int
 */
function merci_item_status($code = NULL) {
  $statuses = array(
    MERCI_AVA_F => t('Available'),
    MERCI_UNA_F => t('Unavailable'),
    MERCI_AVA_T => t('Template Only'),
    MERCI_UNA_S => t('No Longer in Inventory'),
  );
  if (isset($code)) {
    return $statuses[$code];
  }
  else {
    return $statuses;
  }
}
function merci_item_reservation_status($code = NULL) {

  // Item status for reservations.
  $statuses = array(
    MERCI_ITEM_STATUS_AVAILABLE => t('Available'),
    MERCI_ITEM_STATUS_CHECKED_OUT => t('Checked Out'),
  );
  if (isset($code)) {
    return $statuses[$code];
  }
  else {
    return $statuses;
  }
}

/**
 * Return the name of a status code.
 *
 * @param string|int $code
 *  if int, will return translated name of the code.
 *  if NULL, returns array of codes as keys, and translated strings as value
 *
 * @return string|int
 */
function merci_record_status($code = NULL) {
  $types = array(
    MERCI_STATUS_UNCONFIRMED => t('Unconfirmed'),
    MERCI_STATUS_PENDING => t('Confirmed'),
    MERCI_STATUS_CHECKED_OUT => t('Checked out'),
    MERCI_STATUS_CHECKED_IN => t('Checked in'),
    MERCI_STATUS_CANCELLED => t('Cancelled'),
    MERCI_STATUS_DENIED => t('Denied'),
    MERCI_STATUS_NO_SHOW => t('No Show'),
  );
  if (isset($code)) {
    return $types[$code];
  }
  else {
    return $types;
  }
}

/**
 * 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) {

  // field_merci_date is not translatable.
  $date_info = $node->field_merci_date[LANGUAGE_NONE][0];
  $start = $date_info['value'];
  $end = $date_info['value2'];
  $options = merci_get_reservable_items($bucket_type, $start, $end, $node->nid);
  return $options;
}
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;
}

/**
 * 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 = '') {
  return variable_get('merci_hours_operation', '');
}
function merci_hours_str_to_array($str) {
  if (drupal_strlen($str) == 11) {
    $parts = explode('-', $str);
    if (count($parts) == 2) {
      return array(
        'open' => $parts[0],
        'close' => $parts[1],
      );
    }
  }
  return FALSE;
}

// merci_hours_str_to_array

/**
 * Creates a date object based on the site's local timezone.
 *
 * @param $datetime
 *   A date in DATETIME format, UTC timezone.
 *
 * @return
 *   A php date object in the site's timezone.
 */
function merci_create_local_date_object($datetime) {
  $date_object = date_create($datetime, timezone_open('UTC'));
  $timezone = variable_get('date_default_timezone', 'UTC');
  date_timezone_set($date_object, timezone_open($timezone));
  return $date_object;
}

/**
 * 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_reservation_status = MERCI_STATUS_PENDING;
    node_save($node);
    return TRUE;
  }
}

// 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) {

  // Allow either a node object or type name to be passed in.
  if (is_string($object)) {
    $type = $object;
  }
  else {
    $node = (array) $object;
    $type = $node['type'];
  }

  // Initialize here in case there is no nid.
  $item_settings = array();

  // Settings from the content type edit page.
  $content_settings = merci_content_types($type);
  if (isset($node['nid'])) {

    // Settings common to all merci item nodes.
    // resource or bucket.
    $merci_type = isset($content_settings['merci_type_setting']) ? $content_settings['merci_type_setting'] : '';
    $vid = $node['vid'];
    $item_settings = merci_reservation_item_node_settings($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 += merci_bucket_node_settings($vid);
        }
        break;
      case 'resource':

        // TODO: move to seperate module.
        $item_settings += merci_resource_node_settings($vid);
        break;
    }
  }
  return (object) ($item_settings + $content_settings);
}

/**
 * Adds items to reservation on creation/update.
 *
 * @param $node
 *   The reservation node.
 */
function merci_add_reservation_items($node) {
  $member_total = 0;
  $commercial_total = 0;
  $langcode = $node->language;
  $hours = round(strtotime($node->field_merci_date[LANGUAGE_NONE][0]['value2']) - strtotime($node->field_merci_date[LANGUAGE_NONE][0]['value'])) / 3600;
  $exempt_items = array();

  // Update existing items or add new ones.
  if (isset($node->merci_reservation_items)) {
    foreach ($node->merci_reservation_items as $did => $item) {
      if (empty($item['merci_item_nid']) and !isset($item['type'])) {
        continue;
      }

      // If we are copying a reservation.  I.e. via node_repeat
      // Also copy over the placeholder nodes.
      if (isset($item['merci_placeholder_nid']) and $node->is_new) {
        $placeholder_node = node_load($item['merci_placeholder_nid']);
        if ($placeholder_node) {
          $placeholder_node->nid = NULL;
          $placeholder_node->vid = NULL;
          $placeholder_node = node_submit($placeholder_node);
          node_save($placeholder_node);
          $item['merci_placeholder_nid'] = $placeholder_node->nid;
        }
      }

      // Create a placeholder node if we don't have one yet.
      if (!isset($item['merci_placeholder_nid']) or empty($item['merci_placeholder_nid'])) {

        // Resource.
        if (is_numeric($item['merci_item_nid'])) {
          $item_node = node_load($item['merci_item_nid']);
          $item['type'] = $item_node->type;
          $item['item_title'] = $item_node->title;
          $settings = $item_node;
        }
        elseif ($item['merci_item_nid']) {
          $item['type'] = $item['merci_item_nid'];
          $settings = merci_load_item_settings($item['type']);
          $item['name'] = $settings->type_name;
          if ($settings->merci_auto_assign_bucket_item) {
            $date_info = $node->field_merci_date[LANGUAGE_NONE][0];
            $start = $date_info['value'];
            $end = $date_info['value2'];
            $item['merci_item_nid'] = merci_get_suggested_bucket_item($item['type'], $start, $end, $exempt_items);
            $exempt_items[] = $item['merci_item_nid'];
            $item_node = node_load($item['merci_item_nid']);
            $item['item_title'] = $item_node->title;
            $settings = $item_node;
          }
        }
        else {
          break;
        }
        $title = isset($item['item_title']) ? $item['item_title'] : $item['name'];

        // Build the item's placeholder node.
        $reservation = new stdClass();
        $reservation->type = $item['type'];
        $reservation->name = $node->name;
        $reservation->uid = $node->uid;
        $reservation->title = "{$title} " . t('(Reservation)');
        $reservation->body = '';
        $reservation->status = 0;
        $reservation->promote = 0;
        $reservation->sticky = 0;

        // MERCI specific data.
        $reservation->merci_default_availability = MERCI_AVA_F;
        $reservation->merci_sub_type = MERCI_SUB_TYPE_RESERVATION;

        // Use the item specific accounting data if an item is assigned,
        // otherwise fall back to the content type defaults.
        // TODO move to nodeapi insert and update ops.

        //Add to commerical rate

        //print '<pre>';

        //print_r();
        $rate = isset($settings->merci_rate_per_hour) ? $settings->merci_rate_per_hour : 0;
        $commercial_total = $commercial_total + $hours * $rate;
        $reservation->merci_late_fee_per_hour = isset($settings->merci_late_fee_per_hour) ? $settings->merci_late_fee_per_hour : 0;
        $reservation->merci_rate_per_hour = isset($settings->merci_rate_per_hour) ? $settings->merci_rate_per_hour : 0;
        $reservation->merci_fee_free_hours = isset($settings->merci_fee_free_hours) ? $settings->merci_fee_free_hours : 0;
        $reservation->merci_autocheckin = isset($settings->merci_autocheckin) ? $settings->merci_autocheckin : 0;
        $reservation->merci_autocheckout = isset($settings->merci_autocheckout) ? $settings->merci_autocheckout : 0;

        // TODO not sure why this is not set automatically.
        $reservation->language = $langcode;
        $reservation = node_submit($reservation);
        node_save($reservation);
        $item['merci_placeholder_nid'] = $reservation->nid;
      }

      // Update the state of all items with associations.
      switch ((int) $node->merci_reservation_status) {
        case MERCI_STATUS_UNCONFIRMED:
        case MERCI_STATUS_PENDING:
          $item_status = MERCI_ITEM_STATUS_RESERVED;
          break;
        case MERCI_STATUS_CHECKED_OUT:
          $item_status = MERCI_ITEM_STATUS_CHECKED_OUT;
          break;
        case MERCI_STATUS_CHECKED_IN:
          $item_status = MERCI_ITEM_STATUS_CHECKED_IN;
          break;
        case MERCI_STATUS_CANCELLED:
          $item_status = MERCI_ITEM_STATUS_CANCELED;
          break;
        case MERCI_STATUS_DENIED:
          $item_status = MERCI_ITEM_STATUS_AVAILABLE;
          break;
      }

      // If we have an item assigned.  Set status to reserved.
      $item['merci_item_status'] = $item_status;
      if (!empty($node->revision) or is_string($did) or $node->is_new) {
        if (array_key_exists('did', $item)) {
          unset($item['did']);
        }
        $item['nid'] = $node->nid;
        $item['vid'] = $node->vid;
        drupal_write_record('merci_reservation_detail', $item);
      }
      else {
        $item['did'] = $did;
        drupal_write_record('merci_reservation_detail', $item, 'did');
      }

      // Update merci_item_status to Checked out or available.
      $item_status = $item_status == MERCI_ITEM_STATUS_CHECKED_OUT ? MERCI_ITEM_STATUS_CHECKED_OUT : MERCI_ITEM_STATUS_AVAILABLE;
      if (is_numeric($item['merci_item_nid']) and $item['merci_item_nid'] > 0) {

        // Only set to available if previously this reservation was checked out.
        // Or if this reservation is being set to checked out.
        if ($item_status == MERCI_ITEM_STATUS_CHECKED_OUT or $node->merci_original_reservation_status == MERCI_STATUS_CHECKED_OUT and $item_status == MERCI_ITEM_STATUS_AVAILABLE) {
          $update = array();
          $update['nid'] = $item['merci_item_nid'];
          $update['merci_item_status'] = $item_status;
          drupal_write_record('merci_reservation_item_node', $update, 'nid');
        }
      }
    }
  }
}

Functions

Namesort descending Description
merci_add_reservation_items Adds items to reservation on creation/update.
merci_build_reservable_items Builds the list of all currently reservable items, filtered by date.
merci_by_vid Sort by vid
merci_check_content_type_restrictions Checks for reservation restrictions for a content type.
merci_check_content_type_user_permissions Ensures the user has 'edit own [type] content' and 'delete own [type] content' permissions, otherwise they are not allowed to reserve the content type.
merci_confirm_reservation Callback function for updating Reservation status.
merci_content_types Return a list of all merci content types.
merci_convert_date_popup
merci_create_local_date_object Creates a date object based on the site's local timezone.
merci_date_filter Submit handler to date filter items on a reservation form. It makes changes to the form state and the entire form is rebuilt during the page reload.
merci_format_time Formats a time value into the site's preferred format.
merci_get_available_bucket_items Pulls items available to assign to a bucket for a reservation.
merci_get_suggested_bucket_item
merci_hours_str_to_array
merci_is_merci_type
merci_is_numeric_validate Validation for numeric textfields.
merci_item_reservation_status
merci_item_status Return the name of a type code.
merci_load_hours_of_operation Builds an array representing the hours of operation for the facility.
merci_load_item_settings
merci_more_choices_submit Submit handler to add more choices to a reservation form. This handler is used when javascript is not available. It makes changes to the form state and the entire form is rebuilt during the page reload.
merci_node_admin_delete_validate Custom validation function to protect merci nodes from mass deletion.
merci_node_type_status
merci_operations_update Callback function for updating Reservation status from VBO.
merci_record_status Return the name of a status code.
merci_time_format Calculates the short hour/minute time format based on the site settings.
merci_type_setting
merci_validate_default_availability Validates the state change of a reservable item.
merci_validate_empty_reservation_items
merci_validate_merci_reservation_date
merci_validate_merci_selected_items @file MERCI - Managed Equipment Reservation Checkout and Inventory
merci_validate_status
merci_verbose_logging writes additional info to log to aid in troubleshoot configuration