You are here

rooms.module in Rooms - Drupal Booking for Hotels, B&Bs and Vacation Rentals 7

Provides basic underlying functionality and configuration options used by all Rooms modules

File

rooms.module
View source
<?php

/**
 * @file
 * Provides basic underlying functionality and configuration options used
 * by all Rooms modules
 */
define('ROOMS_CHILDREN_FEATURES', 'children_features');
define('ROOMS_ADD', 'add');
define('ROOMS_ADD_DAILY', 'add-daily');
define('ROOMS_SUB', 'sub');
define('ROOMS_SUB_DAILY', 'sub-daily');
define('ROOMS_REPLACE', 'replace');
define('ROOMS_INCREASE', 'increase');
define('ROOMS_DECREASE', 'decrease');
define('ROOMS_PRICE_SINGLE_OCCUPANCY', 'single_occupancy');
define('ROOMS_DYNAMIC_MODIFIER', 'dynamic_modifier');
define('ROOMS_ACCESS_ALLOW', 'allow');
define('ROOMS_ACCESS_DENY', 'deny');
define('ROOMS_ACCESS_IGNORE', NULL);
define('ROOMS_THIS_PAGE', 'this');
define('ROOMS_ALL_PAGES', 'all');
define('ROOMS_NONE', 'none');
define('ROOMS_SIZE_FAILURE', 0);
define('ROOMS_AVAILABILITY_FAILURE', 1);
define('ROOMS_NO_ROOMS', 2);
define('ROOMS_PER_TYPE', 'rooms_per_type');
define('ROOMS_INDIVIDUAL', 'rooms_individual');
define('ROOMS_ENQ_CHECKOUT', 'rooms_enq_checkout');
define('ROOMS_COMMERCE_CHECKOUT', 'rooms_commerce_checkout');
define('ROOMS_DISPLAY_CHILDREN', 1);
define('ROOMS_DISPLAY_CHILDREN_NO', 0);
define('FULL_PAYMENT', 10);
define('PERCENT_PAYMENT', 11);
define('FIRST_NIGHT_PAYMENT', 12);
define('ROOMS_DISPLAY_TYPE_SELECTOR', 1);
define('ROOMS_DISPLAY_TYPE_SELECTOR_NO', 0);
define('ROOMS_OPTION_OPTIONAL', 'optional');
define('ROOMS_OPTION_MANDATORY', 'mandatory');
define('ROOMS_OPTION_ONREQUEST', 'on_request');
define('ROOMIFY_STORE_URI', 'https://store.roomify.us');

/**
 * Implements hook_permission().
 */
function rooms_permission() {
  $permissions = array(
    'configure room settings' => array(
      'title' => t('Configure Rooms'),
      'description' => t('Allows users to manage site-wide Rooms configurations.'),
      'restrict access' => TRUE,
    ),
  );
  return $permissions;
}

/**
 * Implements hook_menu().
 */
function rooms_menu() {
  $items['rooms_options/ajax'] = array(
    'title' => 'Remove item callback',
    'page callback' => 'rooms_options_remove_js',
    'delivery callback' => 'ajax_deliver',
    'access callback' => TRUE,
    'theme callback' => 'ajax_base_page_theme',
    'type' => MENU_CALLBACK,
    'file path' => 'includes',
    'file' => 'form.inc',
  );
  return $items;
}

/**
 * Implements hook_libraries_info().
 */
function rooms_libraries_info() {
  $libraries['fullcalendar'] = array(
    'name' => 'FullCalendar',
    'vendor url' => 'http://fullcalendar.io',
    'download url' => 'https://github.com/arshaw/fullcalendar/releases/download/v3.1.0/fullcalendar-3.1.0.zip',
    'version arguments' => array(
      'file' => 'fullcalendar.js',
      // 3.1.0
      'pattern' => '/v(\\d+\\.\\d+\\.\\d)/',
      'lines' => 3,
    ),
    'files' => array(
      'js' => array(
        'lib/moment.min.js',
        'fullcalendar.js',
        'gcal.js',
      ),
      'css' => array(
        'fullcalendar.css',
      ),
    ),
    'variants' => array(
      'minified' => array(
        'files' => array(
          'js' => array(
            'lib/moment.min.js',
            'fullcalendar.min.js',
            'gcal.js',
          ),
          'css' => array(
            'fullcalendar.min.css',
          ),
        ),
      ),
      'source' => array(
        'files' => array(
          'js' => array(
            'lib/moment.min.js',
            'fullcalendar.js',
            'gcal.js',
          ),
          'css' => array(
            'fullcalendar.css',
          ),
        ),
      ),
    ),
  );
  return $libraries;
}

/**
 * Checks if the FullCalendar Library is loaded.
 *
 * @return bool
 *   A boolean indicating the FullCalendar status.
 */
function rooms_fullcalendar_loaded() {
  if (rooms_library_loaded('fullcalendar', 'minified')) {
    return TRUE;
  }
  else {

    // Alert the authorized user/administrator to the abscence of the library.
    drupal_set_message(t('The FullCalendar Library could not be found.
                          Please check the installation instructions and the <a href="@status">Status Report</a>.', array(
      '@status' => url('admin/reports/status'),
    )), 'warning');
    return FALSE;
  }
}

/**
 * Implements hook_theme().
 */
function rooms_theme() {
  return array(
    'rooms_three_month_calendar' => array(
      'template' => 'rooms_three_month_calendar',
      'variables' => array(
        'url' => NULL,
        'form' => NULL,
        'year' => NULL,
        'month' => NULL,
        'link_options' => NULL,
      ),
    ),
  );
}

/**
 * Helper function to check if a library is loaded properly or not.
 *
 * @return bool
 *   Boolean indicating if the library is properly loaded or not.
 */
function rooms_library_loaded($name, $variant = NULL) {
  return ($library = libraries_load($name, $variant)) && !empty($library['loaded']);
}

/**
 * Generic access control for Rooms entities.
 *
 * @param string $op
 *   The operation being performed. One of 'view', 'update', 'create' or
 *   'delete'.
 * @param object $entity
 *   Optionally an entity to check access for. If no entity is given, it will be
 *   determined whether access is allowed for all entities of the given type.
 * @param object $account
 *   The user to check for. Leave it to NULL to check for the global user.
 * @param string $entity_type
 *   The entity type of the entity to check for.
 *
 * @return bool
 *   Boolean indicating whether the user is granted or not.
 *
 * @see entity_access()
 */
function rooms_entity_access($op, $entity, $account, $entity_type) {
  $rights =& drupal_static(__FUNCTION__, array());
  global $user;
  $account = isset($account) ? $account : $user;
  $entity_info = entity_get_info($entity_type);

  // $entity may be either an object or a entity type. Since entity types cannot
  // be an integer, use either nid or type as the static cache id.
  $cid = is_object($entity) ? $entity->{$entity_info['entity keys']['id']} : $entity;

  // If we are creating a new entity make sure we set the type so permissions get
  // applied
  if ($op == 'create' && $cid == '') {
    $cid = $entity->type;
  }

  // If we've already checked access for this node, user and op, return from
  // cache.
  if (isset($rights[$account->uid][$cid][$op])) {
    return $rights[$account->uid][$cid][$op];
  }

  // Grant generic administrator level access.
  if (user_access('bypass ' . $entity_type . ' entities access', $account)) {
    $rights[$account->uid][$cid][$op] = TRUE;
    return TRUE;
  }
  if ($op == 'view') {
    if (isset($entity)) {

      // When trying to figure out access to an entity, query the base table
      // using our access control tag.
      if (!empty($entity_info['access arguments']['access tag']) && module_implements('query_' . $entity_info['access arguments']['access tag'] . '_alter')) {
        $query = db_select($entity_info['base table']);
        $query
          ->addExpression('1');
        $result = (bool) $query
          ->addTag($entity_info['access arguments']['access tag'])
          ->addMetaData('account', $account)
          ->condition($entity_info['entity keys']['id'], $entity->{$entity_info['entity keys']['id']})
          ->range(0, 1)
          ->execute()
          ->fetchField();
        $rights[$account->uid][$cid][$op] = $result;
        return $result;
      }
      else {
        $rights[$account->uid][$cid][$op] = TRUE;
        return TRUE;
      }
    }
    else {
      $access = user_access('view any ' . $entity_type . ' entity', $account);
      $rights[$account->uid][$cid][$op] = $access;
      return $access;
    }
  }
  else {

    // First grant access to the entity for the specified operation if no other
    // module denies it and at least one other module says to grant access.
    $access_results = module_invoke_all('rooms_entity_access', $op, $entity, $account, $entity_type);
    if (in_array(FALSE, $access_results, TRUE)) {
      $rights[$account->uid][$cid][$op] = FALSE;
      return FALSE;
    }
    elseif (in_array(TRUE, $access_results, TRUE)) {
      $rights[$account->uid][$cid][$op] = TRUE;
      return TRUE;
    }

    // Grant access based on entity type and bundle specific permissions with
    // special handling for the create operation since the entity passed in will
    // be initialized without ownership.
    if ($op == 'create') {

      // Assuming an entity was passed in and we know its bundle key, perform
      // the entity type and bundle-level access checks.
      if (isset($entity) && !empty($entity_info['entity keys']['bundle'])) {
        $access = user_access('create ' . $entity_type . ' entities', $account) || user_access('create ' . $entity_type . ' entities of bundle ' . $entity->{$entity_info['entity keys']['bundle']}, $account);
        $rights[$account->uid][$cid][$op] = $access;
        return $access;
      }
      else {

        // Otherwise perform an entity type-level access check.
        $access = user_access('create ' . $entity_type . ' entities', $account);
        $rights[$account->uid][$cid][$op] = $access;
        return $access;
      }
    }
    else {

      // Finally perform checks for the rest of operations. Begin by
      // extracting the bundle name from the entity if available.
      $bundle_name = '';
      if (isset($entity) && !empty($entity_info['entity keys']['bundle'])) {
        $bundle_name = $entity->{$entity_info['entity keys']['bundle']};
      }

      // For the delete and delete operations, first perform the entity type and
      // bundle-level access check for any entity.
      if (user_access($op . ' any ' . $entity_type . ' entity', $account) || user_access($op . ' any ' . $entity_type . ' entity of bundle ' . $bundle_name, $account)) {
        $rights[$account->uid][$cid][$op] = TRUE;
        return TRUE;
      }

      // Then check an authenticated user's access to delete his own entities.
      if ($account->uid && !empty($entity_info['access arguments']['user key']) && isset($entity->{$entity_info['access arguments']['user key']}) && $entity->{$entity_info['access arguments']['user key']} == $account->uid) {
        if (user_access($op . ' own ' . $entity_type . ' entities', $account) || user_access($op . ' own ' . $entity_type . ' entities of bundle ' . $bundle_name, $account)) {
          $rights[$account->uid][$cid][$op] = TRUE;
          return TRUE;
        }
      }
    }
  }
  return FALSE;
}

/**
 * Return permission names for a given entity type.
 */
function rooms_entity_access_permissions($entity_type) {
  $entity_info = entity_get_info($entity_type);
  $labels = $entity_info['permission labels'];
  $permissions = array();

  // General 'bypass' permission.
  $permissions['bypass ' . $entity_type . ' entities access'] = array(
    'title' => t('Bypass access to @entity_type', array(
      '@entity_type' => $labels['plural'],
    )),
    'description' => t('Allows users to perform any action on @entity_type.', array(
      '@entity_type' => $labels['plural'],
    )),
    'restrict access' => TRUE,
  );

  // Generic create and edit permissions.
  $permissions['create ' . $entity_type . ' entities'] = array(
    'title' => t('Create @entity_type of any type', array(
      '@entity_type' => $labels['plural'],
    )),
  );
  if (!empty($entity_info['access arguments']['user key'])) {
    $permissions['view own ' . $entity_type . ' entities'] = array(
      'title' => t('View own @entity_type of any type', array(
        '@entity_type' => $labels['plural'],
      )),
    );
  }
  $permissions['view any ' . $entity_type . ' entity'] = array(
    'title' => t('View any @entity_type of any type', array(
      '@entity_type' => $labels['singular'],
    )),
    'restrict access' => TRUE,
  );
  if (!empty($entity_info['access arguments']['user key'])) {
    $permissions['update own ' . $entity_type . ' entities'] = array(
      'title' => t('Edit own @entity_type of any type', array(
        '@entity_type' => $labels['plural'],
      )),
    );
  }
  $permissions['update any ' . $entity_type . ' entity'] = array(
    'title' => t('Edit any @entity_type of any type', array(
      '@entity_type' => $labels['singular'],
    )),
    'restrict access' => TRUE,
  );
  if (!empty($entity_info['access arguments']['user key'])) {
    $permissions['delete own ' . $entity_type . ' entities'] = array(
      'title' => t('Delete own @entity_type of any type', array(
        '@entity_type' => $labels['plural'],
      )),
    );
  }
  $permissions['delete any ' . $entity_type . ' entity'] = array(
    'title' => t('Delete any @entity_type of any type', array(
      '@entity_type' => $labels['singular'],
    )),
    'restrict access' => TRUE,
  );

  // Per-bundle create and edit permissions.
  if (!empty($entity_info['entity keys']['bundle'])) {
    foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
      $permissions['create ' . $entity_type . ' entities of bundle ' . $bundle_name] = array(
        'title' => t('Create %bundle @entity_type', array(
          '@entity_type' => $labels['plural'],
          '%bundle' => $bundle_info['label'],
        )),
      );
      if (!empty($entity_info['access arguments']['user key'])) {
        $permissions['view own ' . $entity_type . ' entities of bundle ' . $bundle_name] = array(
          'title' => t('View own %bundle @entity_type', array(
            '@entity_type' => $labels['plural'],
            '%bundle' => $bundle_info['label'],
          )),
        );
      }
      $permissions['view any ' . $entity_type . ' entity of bundle ' . $bundle_name] = array(
        'title' => t('View any %bundle @entity_type', array(
          '@entity_type' => $labels['singular'],
          '%bundle' => $bundle_info['label'],
        )),
        'restrict access' => TRUE,
      );
      if (!empty($entity_info['access arguments']['user key'])) {
        $permissions['update own ' . $entity_type . ' entities of bundle ' . $bundle_name] = array(
          'title' => t('Edit own %bundle @entity_type', array(
            '@entity_type' => $labels['plural'],
            '%bundle' => $bundle_info['label'],
          )),
        );
      }
      $permissions['update any ' . $entity_type . ' entity of bundle ' . $bundle_name] = array(
        'title' => t('Edit any %bundle @entity_type', array(
          '@entity_type' => $labels['singular'],
          '%bundle' => $bundle_info['label'],
        )),
        'restrict access' => TRUE,
      );
      if (!empty($entity_info['access arguments']['user key'])) {
        $permissions['delete own ' . $entity_type . ' entities of bundle ' . $bundle_name] = array(
          'title' => t('Delete own %bundle @entity_type', array(
            '@entity_type' => $labels['plural'],
            '%bundle' => $bundle_info['label'],
          )),
        );
      }
      $permissions['delete any ' . $entity_type . ' entity of bundle ' . $bundle_name] = array(
        'title' => t('Delete any %bundle @entity_type', array(
          '@entity_type' => $labels['singular'],
          '%bundle' => $bundle_info['label'],
        )),
        'restrict access' => TRUE,
      );
    }
  }
  return $permissions;
}

/**
 * Generic implementation of hook_query_alter() for Rooms entities.
 */
function rooms_entity_access_query_alter($query, $entity_type, $base_table = NULL, $account = NULL, $op = 'view') {
  global $user;

  // Read the account from the query if available or current user by default.
  if (!isset($account) && !($account = $query
    ->getMetaData('account'))) {
    $account = $user;
  }

  // Do not apply any conditions for users with administrative view permissions.
  if (user_access('bypass ' . $entity_type . ' access', $account) || user_access($op . ' any ' . $entity_type . ' entity', $account)) {
    return;
  }

  // Get the entity type info array for the current access check and prepare a
  // conditions object.
  $entity_info = entity_get_info($entity_type);

  // If a base table wasn't specified, attempt to read it from the query if
  // available, look for a table in the query's tables array that matches the
  // base table of the given entity type, or just default to the first table.
  if (!isset($base_table) && !($base_table = $query
    ->getMetaData('base_table'))) {

    // Initialize the base table to the first table in the array. If a table can
    // not be found that matches the entity type's base table, this will result
    // in an invalid query if the first table is not the table we expect,
    // forcing the caller to actually properly pass a base table in that case.
    $tables = $query
      ->getTables();
    reset($tables);
    $base_table = key($tables);
    foreach ($tables as $table_info) {
      if (!$table_info instanceof SelectQueryInterface) {

        // If this table matches the entity type's base table, use its table
        // alias as the base table for the purposes of bundle and ownership
        // access checks.
        if ($table_info['table'] == $entity_info['base table']) {
          $base_table = $table_info['alias'];
        }
      }
    }
  }

  // Prepare an OR container for conditions. Conditions will be added that seek
  // to grant access, meaning any particular type of permission check may grant
  // access even if none of the others apply. At the end of this function, if no
  // conditions have been added to the array, a condition will be added that
  // always returns FALSE (1 = 0).
  $conditions = db_or();

  // Perform bundle specific permission checks for the specified entity type.
  // In the event that the user has permission to view every bundle of the given
  // entity type, $really_restricted will remain FALSE, indicating that it is
  // safe to exit this function without applying any additional conditions. If
  // the user only had such permission for a subset of the defined bundles,
  // conditions representing those access checks would still be added.
  $really_restricted = FALSE;

  // Loop over every possible bundle for the given entity type.
  foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {

    // If the user has access to operation entities of the current bundle...
    if (user_access($op . ' any ' . $entity_type . ' entity of bundle ' . $bundle_name, $account)) {

      // Add a condition granting access if the entity specified by the view
      // query is of the same bundle.
      $conditions
        ->condition($base_table . '.' . $entity_info['entity keys']['bundle'], $bundle_name);
    }
    elseif ($account->uid && !empty($entity_info['access arguments']['user key']) && user_access($op . ' own ' . $entity_type . ' entities of bundle ' . $bundle_name, $account)) {

      // Add an AND condition group that grants access if the entity specified
      // by the view query matches the same bundle and belongs to the user.
      $conditions
        ->condition(db_and()
        ->condition($base_table . '.' . $entity_info['entity keys']['bundle'], $bundle_name)
        ->condition($base_table . '.' . $entity_info['access arguments']['user key'], $account->uid));
    }
  }

  // If the given entity type has a user ownership key...
  if (!empty($entity_info['access arguments']['user key'])) {

    // Perform 'operation own' access control for the entity in the query if the
    // user is authenticated.
    if ($account->uid && user_access($op . ' own ' . $entity_type . ' entities', $account)) {
      $conditions
        ->condition($base_table . '.' . $entity_info['access arguments']['user key'], $account->uid);
    }
  }

  // Prepare an array of condition alter hooks to invoke and an array of context
  // data for the current query.
  $hooks = array(
    'rooms_entity_access_' . $op . '_condition_' . $entity_type,
    'rooms_entity_access_' . $op . '_condition',
  );
  $context = array(
    'account' => $account,
    'entity_type' => $entity_type,
    'base_table' => $base_table,
  );

  // Allow other modules to add conditions to the array as necessary.
  drupal_alter($hooks, $conditions, $context);

  // If we have more than one condition based on the entity access permissions
  // and any hook implementations...
  if (count($conditions)) {

    // Add the conditions to the query.
    $query
      ->condition($conditions);
  }
  else {

    // Otherwise, since we don't have any possible conditions to match against,
    // we falsify this query. View checks are access grants, not access denials.
    $query
      ->where('1 = 0');
  }
}

/**
 * Implements hook_field_info().
 */
function rooms_field_info() {
  return array(
    'rooms_options' => array(
      'label' => t('Unit Options'),
      'description' => t('Bookable unit options.'),
      'settings' => array(),
      'default_widget' => 'rooms_options_combined',
      'default_formatter' => 'rooms_options_default',
      'property_type' => 'rooms_options',
      'property_callbacks' => array(
        'rooms_options_property_info_callback',
      ),
    ),
  );
}

/**
 * Property callback for the Entity Metadata framework.
 */
function rooms_options_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {

  // Apply the default.
  entity_metadata_field_default_property_callback($info, $entity_type, $field, $instance, $field_type);

  // Finally add in instance specific property info.
  $name = $field['field_name'];
  $property =& $info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];
  $property['type'] = $field['cardinality'] != 1 ? 'list<rooms_options>' : 'rooms_options';
  $property['property info'] = rooms_options_data_property_info('Rooms options');
  $property['getter callback'] = 'entity_metadata_field_verbatim_get';
  $property['setter callback'] = 'entity_metadata_field_verbatim_set';
}

/**
 * Defines info for the properties of the rooms_options data structure.
 */
function rooms_options_data_property_info($name = NULL) {

  // Build an array of basic property information for rooms_options.
  $properties = array(
    'name' => array(
      'label' => 'Name',
      'type' => 'text',
      'getter callback' => 'entity_property_verbatim_get',
      'setter callback' => 'entity_property_verbatim_set',
    ),
    'quantity' => array(
      'label' => 'Quantity',
      'type' => 'integer',
      'getter callback' => 'entity_property_verbatim_get',
      'setter callback' => 'entity_property_verbatim_set',
    ),
    'operation' => array(
      'label' => 'Operation',
      'type' => 'text',
      'getter callback' => 'entity_property_verbatim_get',
      'setter callback' => 'entity_property_verbatim_set',
    ),
    'value' => array(
      'label' => 'Value',
      'type' => 'integer',
      'getter callback' => 'entity_property_verbatim_get',
      'setter callback' => 'entity_property_verbatim_set',
    ),
    'type' => array(
      'label' => 'Type',
      'type' => 'text',
      'getter callback' => 'entity_property_verbatim_get',
      'setter callback' => 'entity_property_verbatim_set',
    ),
  );

  // Add the default values for each of the rooms_options properties.
  foreach ($properties as &$value) {
    $value += array(
      'description' => !empty($name) ? t('!label of field %name', array(
        '!label' => $value['label'],
        '%name' => $name,
      )) : '',
    );
  }
  return $properties;
}

/**
 * Implements hook_field_is_empty().
 */
function rooms_field_is_empty($item, $field) {
  return empty($item['name']) || empty($item['quantity']) || !(is_numeric($item['quantity']) && is_integer((int) $item['quantity'])) || empty($item['value']) || !is_numeric($item['value']) || empty($item['operation']) || !in_array($item['operation'], array_keys(rooms_price_options_options()));
}

/**
 * Implements hook_field_widget_info().
 */
function rooms_field_widget_info() {
  return array(
    'rooms_options_combined' => array(
      'label' => t('Combined text field'),
      'field types' => array(
        'rooms_options',
      ),
      'settings' => array(),
    ),
  );
}

/**
 * Implements hook_field_formatter_info().
 */
function rooms_field_formatter_info() {
  return array(
    'rooms_options_default' => array(
      'label' => t('Rooms Options Default'),
      'field types' => array(
        'rooms_options',
      ),
    ),
    'rooms_options_price' => array(
      'label' => t('Rooms Options Price'),
      'field types' => array(
        'rooms_options',
      ),
    ),
    'rooms_options_admin' => array(
      'label' => t('Rooms Options Administrator'),
      'field types' => array(
        'rooms_options',
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_view().
 */
function rooms_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  switch ($display['type']) {
    case 'rooms_options_default':
      foreach ($items as $delta => $item) {
        $element[$delta] = array(
          '#markup' => "{$item['quantity']} x {$item['name']}",
        );
      }
      break;
    case 'rooms_options_price':
      if (module_exists('commerce_multicurrency')) {
        $currency_code = commerce_multicurrency_get_user_currency_code();
      }
      else {
        $currency_code = commerce_default_currency();
      }
      foreach ($items as $delta => $item) {
        $item_price = $item['value'] * 100;
        if (module_exists('commerce_multicurrency')) {
          $item_price = commerce_currency_convert($item_price, commerce_default_currency(), $currency_code);
        }
        $price = commerce_currency_format($item_price, $currency_code);
        if ($item['value'] > 0) {
          $element[$delta] = array(
            '#markup' => "{$item['quantity']} x {$item['name']} - {$price}",
          );
        }
        else {
          $element[$delta] = array(
            '#markup' => "{$item['quantity']} x {$item['name']}",
          );
        }
      }
      break;
    case 'rooms_options_admin':
      foreach ($items as $delta => $item) {
        $element[$delta] = array(
          '#markup' => "{$item['quantity']} x {$item['name']} - {$item['operation']} {$item['value']}",
        );
      }
      break;
  }
  return $element;
}

/**
 * Implements hook_field_widget_form().
 */
function rooms_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  if ($instance['widget']['type'] == 'rooms_options_combined') {
    $field_parents = $element['#field_parents'];
    $field_name = $element['#field_name'];
    $language = $element['#language'];
    $parents = array_merge($field_parents, array(
      $field_name,
      $language,
      $delta,
    ));
    $element['name'] = array(
      '#type' => 'textfield',
      '#title' => t('Name'),
      '#default_value' => isset($items[$delta]['name']) ? $items[$delta]['name'] : NULL,
      '#attributes' => array(
        'class' => array(
          'rooms-option--name',
        ),
      ),
    );
    $element['quantity'] = array(
      '#type' => 'select',
      '#title' => t('Quantity'),
      '#options' => rooms_assoc_range(1, 10),
      '#default_value' => isset($items[$delta]['quantity']) ? $items[$delta]['quantity'] : NULL,
      '#description' => t('How many of this option should be available'),
      '#attributes' => array(
        'class' => array(
          'rooms-option--quantity',
        ),
      ),
    );
    $price_options = rooms_price_options_options();
    $element['operation'] = array(
      '#type' => 'select',
      '#title' => t('Operation'),
      '#options' => $price_options,
      '#default_value' => isset($items[$delta]['operation']) ? $items[$delta]['operation'] : NULL,
      '#attributes' => array(
        'class' => array(
          'rooms-option--operation',
        ),
      ),
    );
    $element['value'] = array(
      '#type' => 'textfield',
      '#title' => t('Value'),
      '#size' => 10,
      '#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL,
      '#element_validate' => array(
        'element_validate_number',
      ),
      '#attributes' => array(
        'class' => array(
          'rooms-option--value',
        ),
      ),
    );
    $type_options = array(
      ROOMS_OPTION_OPTIONAL => t('Optional'),
      ROOMS_OPTION_MANDATORY => t('Mandatory'),
      ROOMS_OPTION_ONREQUEST => t('On Request'),
    );
    $element['type'] = array(
      '#type' => 'select',
      '#title' => t('Type'),
      '#options' => $type_options,
      '#default_value' => isset($items[$delta]['type']) ? $items[$delta]['type'] : 'optional',
      '#attributes' => array(
        'class' => array(
          'rooms-option--type',
        ),
      ),
    );
    $element['remove'] = array(
      '#delta' => $delta,
      '#name' => implode('_', $parents) . '_remove_button',
      '#type' => 'submit',
      '#value' => t('Remove'),
      '#validate' => array(),
      '#submit' => array(
        'rooms_options_remove_submit',
      ),
      '#limit_validation_errors' => array(),
      '#ajax' => array(
        'path' => 'rooms_options/ajax',
        'effect' => 'fade',
      ),
      '#attributes' => array(
        'class' => array(
          'rooms-option--remove-button',
        ),
      ),
    );
    $element['#attached']['css'] = array(
      drupal_get_path('module', 'rooms') . '/css/rooms_options_widget.css',
    );
    return $element;
  }
}

/**
 * Returns the available price options for booking_unit options field.
 */
function rooms_price_options_options() {
  return array(
    ROOMS_ADD => t('Add to price'),
    ROOMS_ADD_DAILY => t('Add to price per night'),
    ROOMS_SUB => t('Subtract from price'),
    ROOMS_SUB_DAILY => t('Subtract from price per night'),
    ROOMS_REPLACE => t('Replace price'),
    ROOMS_INCREASE => t('Increase price by % amount'),
    ROOMS_DECREASE => t('Decrease price by % amount'),
  );
}

/**
 * Utility function that returns the last day of each month given a year.
 *
 * @param int $year
 *   The year to get the end of month dates for
 *
 * @return array
 *   An array keyed by months
 */
function rooms_end_of_month_dates($year) {
  $end_of_month_dates = array();
  for ($i = 1; $i <= 12; $i++) {
    $end_of_month_dates[$i] = date("t", mktime(0, 0, 0, $i, 1, $year));
  }
  return $end_of_month_dates;
}

/**
 * Utility function to create two related datepickers.
 *
 * We have a few forms that need a start and end date field
 * and we need to apply the same javascript to these forms in order to have a
 * specific consistent behaviour and groups the form elements and javascript
 * injection in one place.
 *
 * @param int $year
 * @param int $month
 *
 * @return array
 *   The array holding the field definitions
 */
function rooms_date_range_fields($year = NULL, $month = NULL) {
  $date_range_fields = array();
  $date_format = variable_get('rooms_date_format', 'd-m-Y');

  // Create unique ids and selectors for each picker.
  $start_date_id = drupal_html_id('datepicker-start-date');
  $start_date_selector = '#' . $start_date_id . ' .form-text';
  $end_date_id = drupal_html_id('datepicker-end-date');
  $end_date_selector = '#' . $start_date_id . ' .form-text';
  $days_in_advance = '+' . variable_get('rooms_booking_start_date', 1) . 'd';

  // Allow users with the "make same-day bookings" permission to make
  // bookings starting on any day.
  if (user_access('make same-day bookings')) {
    $days_in_advance = '+0d';
  }

  // Specify the default datepicker parameters (see date_popup_element_info())
  $datepicker_options = array(
    'dateFormat' => date_popup_format_to_popup($date_format),
    // Limit bookings to X days in advance, depending on the
    // chosen configuration in your Rooms installation, defaults
    // to one day in advance.
    'minDate' => $days_in_advance,
  );
  if ($year && $month) {

    // Calculate min and max dates of the specified year/month.
    $date = new DateTime();
    $date
      ->setDate($year, $month, 01);
    $min_date = $date
      ->format($date_format);
    $date
      ->modify('last day of this month');
    $max_date = $date
      ->format($date_format);
    $datepicker_options += array(
      'minDate' => $min_date,
      'maxDate' => $max_date,
      'defaultDate' => $min_date,
      'numberOfMonths' => 1,
    );
  }
  else {
    $datepicker_options += array(
      'minDate' => $days_in_advance,
    );
  }
  $date_range_fields['rooms_start_date'] = array(
    '#prefix' => '<div class="form-wrapper rooms-date-range"><div class="start-date" id="' . $start_date_id . '">',
    '#suffix' => '</div>',
    '#type' => 'date_popup',
    '#title' => variable_get_value('rooms_arrival_date'),
    '#date_type' => DATE_DATETIME,
    '#date_format' => $date_format,
    '#date_increment' => 1,
    '#date_year_range' => '-1:+3',
    // Default parameters defined above, with an additional parameter
    // linking to the jQuery selector for the end datepicker.
    '#datepicker_options' => array_merge($datepicker_options, array(
      'endDateSelector' => $end_date_selector,
    )),
    '#required' => TRUE,
  );
  $start_day = variable_get('rooms_booking_start_date', 1);

  // Allow users with the "make same-day bookings" permission to make
  // bookings starting on any day.
  if (user_access('make same-day bookings')) {
    $start_day = '0';
  }
  $date_range_fields['rooms_end_date'] = array(
    '#prefix' => '<div class="end-date" id="' . $end_date_id . '">',
    '#suffix' => '</div></div>',
    '#type' => 'date_popup',
    '#title' => variable_get_value('rooms_departure_date'),
    '#date_type' => DATE_DATETIME,
    '#date_format' => $date_format,
    '#date_increment' => 1,
    '#date_year_range' => '-1:+3',
    // Default parameters defined above, with an additional parameter
    // parameter linking to the jQuery selector for the start datepicker.
    '#datepicker_options' => array_merge($datepicker_options, array(
      'startDateSelector' => $start_date_selector,
    )),
    '#required' => TRUE,
    '#attached' => array(
      'css' => array(
        drupal_get_path('module', 'rooms') . '/css/rooms_date_range_fields.css',
      ),
      'js' => array(
        drupal_get_path('module', 'rooms') . '/js/rooms_date_popup.js',
        array(
          'data' => array(
            'rooms' => array(
              'roomsStartYear' => $year,
              'roomsStartMonth' => $month,
              'roomsBookingStartDay' => $start_day,
              'roomsDateFormat' => date_popup_format_to_popup($date_format),
              // Here we create a listing of all datepickers registered on the
              // current page. This is available for use in your own custom
              // jQuery scripts as Drupal.settings.rooms.datepickers.
              'datepickers' => array(
                $start_date_selector => array(
                  'endDateSelector' => $end_date_selector,
                ),
              ),
            ),
          ),
          'type' => 'setting',
        ),
      ),
    ),
  );
  return $date_range_fields;
}

/**
 * Given a form_state locate the start/end dates in the input array and
 * instantiate and return DateTime objects.
 */
function rooms_form_input_get_start_end_dates($form_state) {

  // If form_state['values']['rooms_X_date'] is not set it is an empty array
  // hence the need to check and set values so that _constructor below will not
  // fail.
  if (is_array($form_state['values']['rooms_start_date']) || is_array($form_state['values']['rooms_end_date'])) {
    $form_state['values']['rooms_start_date'] = '';
    $form_state['values']['rooms_end_date'] = '';
  }
  $start = new DateTime($form_state['values']['rooms_start_date']);
  $end = new DateTime($form_state['values']['rooms_end_date']);
  return array(
    $start,
    $end,
  );
}

/**
 * Given a form_state locate the start/end dates in the values array and
 * instantiate and return DateTime objects.
 */
function rooms_form_values_get_start_end_dates($form_state) {

  // As values dates has a format of year-month-day that is one of the default
  // expected formats there is no need to explicit define format.
  // http://www.php.net/manual/en/datetime.formats.date.php
  $start_date = $form_state['values']['rooms_start_date'];
  $end_date = $form_state['values']['rooms_end_date'];

  // If the input format is numeric we assume that is a unixtime seconds format.
  if (is_numeric($start_date) && is_numeric($end_date)) {

    // The @ indicate DateTime that the format is unixtime.
    $start_date = '@' . $start_date;
    $end_date = '@' . $end_date;
  }
  $start = new DateTime($start_date);
  $end = new DateTime($end_date);
  return array(
    $start,
    $end,
  );
}

/**
 * Validation callback that could be reused in all the forms that need to
 * validate dates. End date must be greater than start date.
 */
function rooms_form_start_end_dates_validate($form, &$form_state) {
  list($start_date, $end_date) = rooms_form_input_get_start_end_dates($form_state);
  $today_greater = FALSE;
  $single_day_bookings = FALSE;

  // Skip if no dates are provided.
  if (empty($start_date) || empty($end_date)) {
    form_set_error('date_range', t('Please choose dates.'));
    return;
  }

  // In case that this value is set trigger the today greater validation.
  if (isset($form_state['today_greater_validation'])) {
    $today_greater = TRUE;
  }

  // In case that this value is set trigger the today greater validation.
  if (isset($form_state['single_day_bookings'])) {
    $single_day_bookings = TRUE;
  }
  else {

    // Set the form state so that we can carry the value over to the results page
    $form_state['single_day_bookings'] = $single_day_bookings;
  }

  // Check date validity.
  $errors = rooms_check_dates_validity($start_date, $end_date, $today_greater, $single_day_bookings);

  // For some forms as rooms_availability_pricing_update_form and
  // rooms_availability_update_status_form we need to validate that the selected
  // date match with current values.
  if (isset($form_state['values']['curr_month']) && isset($form_state['values']['curr_year'])) {
    $curr_month = $form_state['values']['curr_month'];
    $curr_year = $form_state['values']['curr_year'];
    if ($start_date
      ->format('n') != $curr_month || $end_date
      ->format('n') != $curr_month || $start_date
      ->format('Y') != $curr_year || $end_date
      ->format('Y') != $curr_year) {
      $errors[] = t('Start and end date must be within the current month.');
    }
  }

  // When there are multiples errors for the same form element Drupal only
  // display the first. Here we concatenate to display all at once.
  if ($errors) {
    $error_msg = implode(' ', $errors);
    form_set_error('date_range', $error_msg);
  }
}

/**
 * Checks the logical validity of date values.
 *
 * @param DateTime $start_date
 *   The start date
 * @param DateTime $end_date
 *   The end date
 * @param bool $today_greater
 *   TRUE in to enforce validating that start_date must be greater than
 *   today.
 *
 * @return array
 *   An array with error messages.
 */
function rooms_check_dates_validity(DateTime $start_date, DateTime $end_date, $today_greater = FALSE, $single_day_bookings = FALSE) {
  $errors = array();

  // End date must be greater than start date unless single day bookings are
  // enabled, in which case it must not be before the start date.
  if ($end_date < $start_date || $end_date == $start_date && !$single_day_bookings) {
    if ($single_day_bookings) {
      $errors[] = t('End date must be on or after the start date.');
    }
    else {
      $errors[] = t('End date must be after start date.');
    }
  }

  // End date must not be more that the allowed number of days in advance for booking
  $current_date = new DateTime();
  $booking_interval = $current_date
    ->diff($end_date);

  // The following necessary to get a properly instantiated date interval when starting with
  // just seconds
  $d1 = new DateTime();
  $d2 = new DateTime();
  $d2
    ->add(new DateInterval('PT' . variable_get('rooms_advance_bookings_period', 15552000) . 'S'));
  $advanced_bookings_interval = $d2
    ->diff($d1);
  if ($booking_interval
    ->format('%a') > $advanced_bookings_interval
    ->format('%a') && !user_access('book in advance without limitation')) {
    $errors[] = 'You cannot book that far into the future - bookings can only be made ' . $advanced_bookings_interval
      ->format('%a') . ' days in advance';
  }

  // In case the date should be grater than today.
  if ($today_greater) {
    $now = new DateTime();
    $diff1 = $now
      ->setTime(0, 0, 0)
      ->diff($start_date);
    if ($diff1->invert) {
      $errors[] = t('Start date must be current or in the future.');
    }
  }
  return $errors;
}

/**
 * Alternative numeric range generator considering users that use PHP < v5.3.6
 * and experiment the bug: https://bugs.php.net/bug.php?id=51894
 *
 * @param int $start
 *   First value of the sequence.
 * @param int $end
 *   The sequence is ended upon reaching the end value.
 *
 * @return array
 *   The expected range.
 */
function rooms_range($start, $end) {
  if (version_compare(phpversion(), '5.3.6', '<')) {
    $range = array();
    for ($index = $start; $index <= $end; $index++) {
      $range[] = $index;
    }
    return $range;
  }
  else {
    return range($start, $end, 1);
  }
}

/**
 * Returns a range array keyed by value.
 *
 * @param int $start
 *   First value of the sequence.
 * @param int $end
 *   The sequence is ended upon reaching the end value.
 *
 * @return array
 *   An array of elements from start to end, inclusive keyed by values.
 */
function rooms_assoc_range($start, $end) {
  return drupal_map_assoc(rooms_range($start, $end));
}

/**
 * Converts Rooms option human name to its machine name.
 *
 * @param string $option_name
 *   The human option name.
 * @param string $pattern
 *   The pattern used to convert. By default "/[^a-z0-9_]+/".
 * @param string $replacement
 *   The replacement string. By default "_".
 *
 * @return string
 *   The option machine name.
 */
function rooms_options_machine_name($option_name, $pattern = '/[^a-z0-9_]+/', $replacement = '_') {
  return preg_replace($pattern, $replacement, drupal_strtolower($option_name));
}

/**
 * Helper validate function to ensure that at least one room is selected.
 */
function _rooms_select_rooms_validation($form_state) {
  $select_rooms = FALSE;
  if ($form_state['values']['select-all'] == ROOMS_ALL_PAGES || $form_state['values']['select-all'] == ROOMS_THIS_PAGE) {
    $select_rooms = TRUE;
  }
  else {
    foreach ($form_state['values'] as $key => $value) {
      if (strpos($key, 'rooms-') === 0 && $value == '1') {
        $select_rooms = TRUE;
      }
    }
  }
  if (!$select_rooms) {
    form_set_error('select-all', t('You have to select at least one unit to update.'));
  }
}
function rooms_filter_month_form($form, &$form_state, $month, $year, $unit_types = NULL) {
  $form['#attributes']['class'][] = 'rooms-management-form rooms-filter-month-form';
  $month_options = array(
    1 => t('January'),
    2 => t('February'),
    3 => t('March'),
    4 => t('April'),
    5 => t('May'),
    6 => t('June'),
    7 => t('July'),
    8 => t('August'),
    9 => t('September'),
    10 => t('October'),
    11 => t('November'),
    12 => t('December'),
  );
  $form['rooms_availability_filter_month']['month'] = array(
    '#title' => t('Month'),
    '#type' => 'select',
    '#options' => $month_options,
    '#default_value' => $month,
  );
  $year_options = range(date('Y', time()) - 2, date('Y', time()) + 5);
  $form['rooms_availability_filter_month']['year'] = array(
    '#title' => t('Year'),
    '#type' => 'select',
    '#options' => $year_options,
    '#default_value' => $year - date('Y', time()) + 2,
  );
  $type_options['all'] = t('All types');
  $unit_types = isset($unit_types) ? $unit_types : rooms_unit_get_types();
  foreach ($unit_types as $unit_type_name => $unit_type) {
    $type_options[$unit_type_name] = $unit_type->label;
  }
  $form['rooms_availability_filter_month']['type'] = array(
    '#title' => t('Type'),
    '#type' => 'select',
    '#options' => $type_options,
    '#default_value' => arg(6) == '' ? 'all' : arg(6),
  );
  $form['rooms_availability_filter_month']['actions'] = array(
    '#type' => 'container',
    '#attributes' => array(
      'class' => array(
        'form-actions',
      ),
    ),
    '#weight' => 400,
  );
  $form['rooms_availability_filter_month']['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Filter'),
  );
  hide($form['rooms_availability_filter_month']['actions']['submit']);
  return $form;
}

/**
 * Submit callback for rooms_filter_month_form form.
 *
 * @see rooms_filter_month_form()
 */
function rooms_filter_month_form_submit(&$form, &$form_state) {
  $year = $form_state['values']['year'] + date('Y', time()) - 2;
  $month = $form_state['values']['month'];
  $type = $form_state['values']['type'];
  $address = 'admin/rooms/units/' . arg(3) . '/' . $year . '/' . $month;
  if ($type != 'all') {
    $address .= '/' . $type;
  }
  if (arg(7) != '') {
    $address .= $type == 'all' ? '/all/' . arg(7) : $type . arg(7);
  }
  $form_state['redirect'] = $address;
}

/**
 * Default implementation of hook_preprocess_rooms_three_month_calendar().
 *
 * Here we setup the three-month calendar based on a specified year,
 * month, and url.
 */
function rooms_preprocess_rooms_three_month_calendar(&$vars) {

  // Load FullCalendar.
  rooms_fullcalendar_loaded();

  // Add css styles for three-month-calendar.
  drupal_add_css(drupal_get_path('module', 'rooms_availability') . '/css/rooms_three_month_calendar.css');
  drupal_add_css(drupal_get_path('module', 'rooms_availability') . '/css/fullcalendar.theme.css');

  // If dates are not provided then use the current date.
  $year = empty($vars['year']) ? date('Y', time()) : check_plain($vars['year']);
  $month = empty($vars['month']) ? date('n', time()) : check_plain($vars['month']);

  // Inject settings in javascript that will be used to setup the three months
  // display.
  drupal_add_js(array(
    'roomsCalendar' => array(
      'currentMonth' => intval($month),
    ),
  ), 'setting');
  drupal_add_js(array(
    'roomsCalendar' => array(
      'currentYear' => intval($year),
    ),
  ), 'setting');
  drupal_add_js(array(
    'roomsCalendar' => array(
      'firstDay' => intval(variable_get('date_first_day', 0)),
    ),
  ), 'setting');

  // Calculate forward and back dates for the 3-month view calendar.
  $date1 = new DateTime("{$year}-{$month}-1");
  $date2 = new DateTime("{$year}-{$month}-1");
  $date_current = new DateTime("now");
  $forward = $date1
    ->add(new DateInterval('P3M'));
  $backward = $date2
    ->sub(new DateInterval('P3M'));

  // Create the links based off the url variable passed in.
  if (!isset($vars['url'])) {
    $vars['url'] = '';
  }
  $forward_path = $vars['url'] . '/' . $forward
    ->format('Y') . '/' . $forward
    ->format('n');
  $backward_path = $vars['url'] . '/' . $backward
    ->format('Y') . '/' . $backward
    ->format('n');
  $current_path = $vars['url'] . '/' . $date_current
    ->format('Y') . '/' . $date_current
    ->format('n');
  if (!is_array($vars['link_options']) || empty($vars['link_options'])) {
    $vars['link_options'] = array();
  }
  $vars['forward_link'] = l(t('Forward'), $forward_path, $vars['link_options']);
  $vars['backward_link'] = l(t('Back'), $backward_path, $vars['link_options']);
  $vars['current_link'] = l(t('Current'), $current_path, $vars['link_options']);
}

/**
 * Form element validation handler for integer elements greater than or equal 
 * to 0.
 */
function rooms_element_validate_integer_zero_positive($element, &$form_state) {
  $value = $element['#value'];
  if ($value !== '' && (!is_numeric($value) || intval($value) != $value || $value < 0)) {
    form_error($element, t('%name must be a positive integer.', array(
      '%name' => $element['#title'],
    )));
  }
}

/**
 * Form element validation handler for integer elements positive less than 100.
 */
function rooms_element_validate_integer_positive_less_than_100($element, &$form_state) {
  $value = $element['#value'];
  if ($value !== '' && (!is_numeric($value) || intval($value) != $value || $value < 0 || $value > 100)) {
    if ($value < 0) {
      form_error($element, t('%name must be greater than 0.', array(
        '%name' => $element['#title'],
      )));
    }
    elseif ($value > 100) {
      form_error($element, t('%name must be less than 100.', array(
        '%name' => $element['#title'],
      )));
    }
  }
}

/**
 * A string transformation function specific to Rooms to allow modules to use
 * contextual information about bookings to change what is shown to the user.
 *
 * @param $string - The string to be altered.
 * @param $context - Contextual information about the string.
 *
 * @return mixed
 */
function rooms_string($string, $context) {

  // The string_suggestions array will hold information about all the
  // suggestions coming from modules. The default behaviour now is to simply
  // return the suggestion made by the module with the highest weight.
  $string_suggestions = array();
  $string_suggestions[] = $string;
  drupal_alter('rooms_string', $string_suggestions, $context);

  // Return the highest option string.
  return max($string_suggestions);
}

/**
 * Page callback to handle AJAX for removing a rooms options item.
 *
 * This is a direct page callback. The actual job of deleting the item is
 * done in the submit handler for the button, so all we really need to
 * do is process the form and then generate output. We generate this
 * output by doing a replace command on the id of the entire form element.
 */
function rooms_options_remove_js() {

  // drupal_html_id() very helpfully ensures that all html IDS are unique
  // on a page. Unfortunately what it doesn't realize is that the IDs
  // we are generating are going to replace IDs that already exist, so
  // this actually works against us.
  if (isset($_POST['ajax_html_ids'])) {
    unset($_POST['ajax_html_ids']);
  }
  list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
  drupal_process_form($form['#form_id'], $form, $form_state);

  // Get the information on what we're removing.
  $button = $form_state['triggering_element'];

  // Go two levels up in the form, to the whole widget.
  $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -3));

  // Now send back the proper AJAX command to replace it.
  $commands[] = ajax_command_replace('#' . $element['#id'], drupal_render($element));
  $return = array(
    '#type' => 'ajax',
    '#commands' => $commands,
  );

  // Because we're doing this ourselves, messages aren't automatic. We have
  // to add them.
  $messages = theme('status_messages');
  if ($messages) {
    $return['#commands'][] = ajax_command_prepend('#' . $element['#id'], $messages);
  }
  return $return;
}

/**
 * Submit callback to remove an item from the field UI multiple wrapper.
 *
 * When a remove button is submitted, we need to find the item that it
 * referenced and delete it. Since field UI has the deltas as a straight
 * unbroken array key, we have to renumber everything down. Since we do this
 * we *also* need to move all the deltas around in the $form_state['values'],
 * $form_state['input'], and $form_state['field'] so that user changed values
 * follow. This is a bit of a complicated process.
 */
function rooms_options_remove_submit($form, &$form_state) {
  $button = $form_state['triggering_element'];
  $delta = $button['#delta'];

  // Where in the form we'll find the parent element.
  $address = array_slice($button['#array_parents'], 0, -2);

  // Go one level up in the form, to the widgets container.
  $parent_element = drupal_array_get_nested_value($form, $address);
  $field_name = $parent_element['#field_name'];
  $langcode = $parent_element['#language'];
  $parents = $parent_element['#field_parents'];
  $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);

  // Go ahead and renumber everything from our delta to the last
  // item down one. This will overwrite the item being removed.
  for ($i = $delta; $i <= $field_state['items_count']; $i++) {
    $old_element_address = array_merge($address, array(
      $i + 1,
    ));
    $new_element_address = array_merge($address, array(
      $i,
    ));
    $moving_element = drupal_array_get_nested_value($form, $old_element_address);
    $moving_element_value = drupal_array_get_nested_value($form_state['values'], $old_element_address);
    $moving_element_input = drupal_array_get_nested_value($form_state['input'], $old_element_address);
    $moving_element_field = drupal_array_get_nested_value($form_state['field'], $old_element_address);

    // Tell the element where it's being moved to.
    $moving_element['#parents'] = $new_element_address;

    // Move the element around.
    form_set_value($moving_element, $moving_element_value, $form_state);
    drupal_array_set_nested_value($form_state['input'], $moving_element['#parents'], $moving_element_input);
    drupal_array_set_nested_value($form_state['field'], $moving_element['#parents'], $moving_element_field);
  }
  $form_state['rooms_unit_type']->rooms_booking_unit_options = array();

  // Then remove the last item. But we must not go negative.
  if ($field_state['items_count'] > 0) {
    $field_state['items_count']--;
  }

  // Fix the weights. Field UI lets the weights be in a range of
  // (-1 * item_count) to (item_count). This means that when we remove one,
  // the range shrinks; weights outside of that range then get set to
  // the first item in the select by the browser, floating them to the top.
  // We use a brute force method because we lost weights on both ends
  // and if the user has moved things around, we have to cascade because
  // if I have items weight weights 3 and 4, and I change 4 to 3 but leave
  // the 3, the order of the two 3s now is undefined and may not match what
  // the user had selected.
  $input = drupal_array_get_nested_value($form_state['input'], $address);

  // Sort by weight
  uasort($input, '_field_sort_items_helper');

  // Reweight everything in the correct order.
  $weight = -1 * $field_state['items_count'];
  foreach ($input as $key => $item) {
    if ($item) {
      $input[$key]['_weight'] = $weight++;
    }
  }
  drupal_array_set_nested_value($form_state['input'], $address, $input);
  field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
  $form_state['rebuild'] = TRUE;
}

Functions

Namesort descending Description
rooms_assoc_range Returns a range array keyed by value.
rooms_check_dates_validity Checks the logical validity of date values.
rooms_date_range_fields Utility function to create two related datepickers.
rooms_element_validate_integer_positive_less_than_100 Form element validation handler for integer elements positive less than 100.
rooms_element_validate_integer_zero_positive Form element validation handler for integer elements greater than or equal to 0.
rooms_end_of_month_dates Utility function that returns the last day of each month given a year.
rooms_entity_access Generic access control for Rooms entities.
rooms_entity_access_permissions Return permission names for a given entity type.
rooms_entity_access_query_alter Generic implementation of hook_query_alter() for Rooms entities.
rooms_field_formatter_info Implements hook_field_formatter_info().
rooms_field_formatter_view Implements hook_field_formatter_view().
rooms_field_info Implements hook_field_info().
rooms_field_is_empty Implements hook_field_is_empty().
rooms_field_widget_form Implements hook_field_widget_form().
rooms_field_widget_info Implements hook_field_widget_info().
rooms_filter_month_form
rooms_filter_month_form_submit Submit callback for rooms_filter_month_form form.
rooms_form_input_get_start_end_dates Given a form_state locate the start/end dates in the input array and instantiate and return DateTime objects.
rooms_form_start_end_dates_validate Validation callback that could be reused in all the forms that need to validate dates. End date must be greater than start date.
rooms_form_values_get_start_end_dates Given a form_state locate the start/end dates in the values array and instantiate and return DateTime objects.
rooms_fullcalendar_loaded Checks if the FullCalendar Library is loaded.
rooms_libraries_info Implements hook_libraries_info().
rooms_library_loaded Helper function to check if a library is loaded properly or not.
rooms_menu Implements hook_menu().
rooms_options_data_property_info Defines info for the properties of the rooms_options data structure.
rooms_options_machine_name Converts Rooms option human name to its machine name.
rooms_options_property_info_callback Property callback for the Entity Metadata framework.
rooms_options_remove_js Page callback to handle AJAX for removing a rooms options item.
rooms_options_remove_submit Submit callback to remove an item from the field UI multiple wrapper.
rooms_permission Implements hook_permission().
rooms_preprocess_rooms_three_month_calendar Default implementation of hook_preprocess_rooms_three_month_calendar().
rooms_price_options_options Returns the available price options for booking_unit options field.
rooms_range Alternative numeric range generator considering users that use PHP < v5.3.6 and experiment the bug: https://bugs.php.net/bug.php?id=51894
rooms_string A string transformation function specific to Rooms to allow modules to use contextual information about bookings to change what is shown to the user.
rooms_theme Implements hook_theme().
_rooms_select_rooms_validation Helper validate function to ensure that at least one room is selected.

Constants