rooms_availability_reference.module in Rooms - Drupal Booking for Hotels, B&Bs and Vacation Rentals 7
Defines a field type for referencing availability information
File
modules/rooms_availability_reference/rooms_availability_reference.moduleView source
<?php
/**
* @file
* Defines a field type for referencing availability information
*/
/**
* Implements hook_permission().
*/
function rooms_availability_reference_permission() {
$permissions = array(
'reference booking unit availability' => array(
'title' => t('Reference unit availability'),
'description' => t('Allows users to embed availability information in other nodes.'),
),
);
return $permissions;
}
/**
* Implements hook_menu().
*/
function rooms_availability_reference_menu() {
$items['rooms_availability_reference/autocomplete/%/%/%'] = array(
'page callback' => 'rooms_availability_reference_autocomplete',
'page arguments' => array(
2,
3,
4,
),
'access arguments' => array(
'reference booking unit availability',
),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implements hook_field_info().
*/
function rooms_availability_reference_field_info() {
return array(
'rooms_availability_reference' => array(
'label' => t('Rooms Availability Reference'),
'description' => t('Display availability information embedded from other fieldable content.'),
'settings' => array(
'referenceable_unit_types' => array(),
),
'default_widget' => 'rooms_availability_reference_autocomplete',
'default_formatter' => 'rooms_availability_reference_default',
),
);
}
/**
* Implements hook_field_is_empty().
*/
function rooms_availability_reference_field_is_empty($item, $field) {
return empty($item['unit_id']);
}
/**
* Implements hook_field_formatter_info().
*/
function rooms_availability_reference_field_formatter_info() {
$ret = array(
'rooms_availability_reference_default' => array(
'label' => t('Rooms Availability Calendar'),
'description' => t('Displays availability information on a calendar.'),
'field types' => array(
'rooms_availability_reference',
'entityreference',
),
),
);
return $ret;
}
/**
* Implements hook_field_formatter_prepare_view().
*/
function rooms_availability_reference_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
if ($field['type'] == 'entityreference') {
entityreference_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, $items, $displays);
}
}
/**
* Implements hook_field_formatter_view().
*
* @todo parametrize date range
*/
function rooms_availability_reference_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$result = array();
$element = array();
$js_settings = array();
switch ($display['type']) {
case 'rooms_availability_reference_default':
if ($field['type'] == 'entityreference' && $field['settings']['target_type'] != 'rooms_unit') {
return $element;
}
rooms_fullcalendar_loaded();
// Full day events on calendar.
if (variable_get('rooms_calendar_events_view', '0') == '1') {
drupal_add_js(drupal_get_path('module', 'rooms_availability_reference') . '/js/rooms_availability_reference_full_day.js');
}
else {
drupal_add_js(drupal_get_path('module', 'rooms_availability_reference') . '/js/rooms_availability_reference.js');
}
drupal_add_css(drupal_get_path('module', 'rooms_availability') . '/css/fullcalendar.theme.css');
drupal_add_css(drupal_get_path('module', 'rooms_availability_reference') . '/css/rooms_availability_reference_calendar.css');
$unit_names = array();
$unit_ids = array();
$id = drupal_html_id($field['field_name'] . '-availability-formatter');
foreach ($items as $delta => $item) {
if ($field['type'] == 'entityreference') {
if (empty($item['access'])) {
continue;
}
$unit = $item['entity'];
}
else {
$unit = rooms_unit_load($item['unit_id']);
}
if ($unit) {
$unit_names[] = $unit->name;
$unit_ids[] = $unit->unit_id;
}
}
if (!empty($unit_ids)) {
$result[] = array(
'#prefix' => '<div id="' . $id . '" class="availability-title">',
'#markup' => '<h2>' . implode(', ', $unit_names) . '</h2>',
'#suffix' => '</div>',
);
// Inject settings in javascript that we will use.
$js_settings[$id] = array(
'unitID' => $unit_ids,
'style' => ROOMS_AVAILABILITY_GENERIC_STYLE,
'firstDay' => intval(variable_get('date_first_day', 0)),
);
}
break;
}
if (!empty($result)) {
$element[] = array(
'#theme' => 'rooms_availability_field_calendar',
'calendar' => $result,
'#attached' => array(
'js' => array(
array(
'data' => array(
'roomsAvailabilityRef' => $js_settings,
),
'type' => 'setting',
),
),
),
);
}
return $element;
}
/**
* Implements hook_field_settings_form().
*/
function rooms_availability_reference_field_settings_form($field, $instance, $has_data) {
$settings = $field['settings'];
$form = array();
$form['referenceable_unit_types'] = array(
'#type' => 'checkboxes',
'#title' => t('Unit types that can be referenced'),
'#multiple' => TRUE,
'#default_value' => isset($settings['referenceable_unit_types']) ? $settings['referenceable_unit_types'] : array(),
'#options' => array_map('check_plain', rooms_unit_types_ids()),
);
return $form;
}
/**
* Implements hook_field_validate().
*/
function rooms_availability_reference_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
// Extract unit ids to check.
$ids = array();
// Check for non-numeric values.
// First check non-numeric "nid's to avoid losing time with them.
foreach ($items as $delta => $item) {
if (is_array($item) && !empty($item['unit_id'])) {
if (is_numeric($item['unit_id'])) {
$ids[] = $item['unit_id'];
}
else {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'invalid_unit_id',
'message' => t('%name: invalid input.', array(
'%name' => $instance['label'],
)),
);
}
}
}
}
/**
* Retrieves an array of candidate referenceable booking units.
*
* This info is used in various places (allowed values, autocomplete
* results, input validation...). Some of them only need the nids,
* others nid + titles, others yet nid + titles + rendered row (for
* display in widgets).
*
* The array we return contains all the potentially needed information,
* and lets consumers use the parts they actually need.
*
* @param array $field
* The field definition.
* @param array $options
* An array of options to limit the scope of the returned list. The following
* key/value pairs are accepted:
* - string: string to filter unit names on (used by autocomplete).
* - match: operator to match the above string against, can be any of:
* 'contains', 'equals', 'starts_with'. Defaults to 'contains'.
* - ids: array of specific unit ids to lookup.
* - limit: maximum size of the the result set. Defaults to 0 (no limit).
*
* @return array
* An array of valid units in the form:
* array(
* unit_id => array(
* 'name' => The unit title,
* 'rendered' => The text to display in widgets (can be HTML)
* ),
* ...
* )
*/
function rooms_availability_reference_potential_references($field, $options = array()) {
// Fill in default options.
$options += array(
'string' => '',
'match' => 'contains',
'ids' => array(),
'limit' => 0,
);
$results =& drupal_static(__FUNCTION__, array());
// Create unique id for static cache.
$cid = $field['field_name'] . ':' . $options['match'] . ':' . ($options['string'] !== '' ? $options['string'] : implode('-', $options['ids'])) . ':' . $options['limit'];
if (!isset($results[$cid])) {
$references = FALSE;
if ($references === FALSE) {
$references = _rooms_availability_reference_potential_references($field, $options);
}
// Store the results.
$results[$cid] = !empty($references) ? $references : array();
}
return $results[$cid];
}
/**
* Helper function for node_reference_potential_references().
*
* List of referenceable nodes defined by content types.
*/
function _rooms_availability_reference_potential_references($field, $options) {
// Avoid useless work.
if (!isset($field['settings']['referenceable_unit_types'])) {
return array();
}
if (!count($field['settings']['referenceable_unit_types'])) {
return array();
}
$query = db_select('rooms_units', 'u');
$unit_unit_id_alias = $query
->addField('u', 'unit_id');
$unit_name_alias = $query
->addField('u', 'name', 'name');
$unit_type_alias = $query
->addField('u', 'type', 'type');
if (is_array($field['settings']['referenceable_unit_types'])) {
$query
->condition('u.type', $field['settings']['referenceable_unit_types'], 'IN');
}
if ($options['string'] !== '') {
switch ($options['match']) {
case 'contains':
$query
->condition('u.name', '%' . $options['string'] . '%', 'LIKE');
break;
case 'starts_with':
$query
->condition('u.name', $options['string'] . '%', 'LIKE');
break;
case 'equals':
default:
// No match type or incorrect match type: use "=".
$query
->condition('u.name', $options['string']);
break;
}
}
if ($options['ids']) {
$query
->condition('u.unit_id', $options['ids'], 'IN');
}
if ($options['limit']) {
$query
->range(0, $options['limit']);
}
$query
->orderBy($unit_name_alias)
->orderBy($unit_type_alias);
$result = $query
->execute()
->fetchAll();
$references = array();
foreach ($result as $unit) {
$references[$unit->unit_id] = array(
'title' => $unit->name,
'rendered' => check_plain($unit->name),
);
}
return $references;
}
/**
* Menu callback for the autocomplete results.
*/
function rooms_availability_reference_autocomplete($entity_type, $bundle, $field_name, $string = '') {
$field = field_info_field($field_name);
$instance = field_info_instance($entity_type, $field_name, $bundle);
$options = array(
'string' => $string,
'match' => $instance['widget']['settings']['autocomplete_match'],
'limit' => 10,
);
$references = rooms_availability_reference_potential_references($field, $options);
$matches = array();
foreach ($references as $id => $row) {
// Markup is fine in autocompletion results (might happen when rendered
// through Views) but we want to remove hyperlinks.
$suggestion = preg_replace('/<a href="([^<]*)">([^<]*)<\\/a>/', '$2', $row['rendered']);
// Add a class wrapper for a few required CSS overrides.
$matches[$row['title'] . " [unit_id:{$id}]"] = '<div class="reference-autocomplete">' . $suggestion . '</div>';
}
drupal_json_output($matches);
}
/**
* Implements hook_field_widget_info().
*/
function rooms_availability_reference_field_widget_info() {
return array(
'rooms_availability_reference_autocomplete' => array(
'label' => t('Autocomplete text field'),
'description' => t('Display the list of referenceable units as a textfield with autocomplete behaviour.'),
'field types' => array(
'rooms_availability_reference',
),
'settings' => array(
'autocomplete_match' => 'contains',
'size' => 60,
'autocomplete_path' => 'rooms_availability_reference/autocomplete',
),
),
);
}
/**
* Implements hook_field_widget_form().
*/
function rooms_availability_reference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
switch ($instance['widget']['type']) {
case 'rooms_availability_reference_autocomplete':
$element += array(
'#type' => 'textfield',
'#default_value' => isset($items[$delta]['unit_id']) ? $items[$delta]['unit_id'] : NULL,
'#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $instance['entity_type'] . '/' . $instance['bundle'] . '/' . $field['field_name'],
'#size' => $instance['widget']['settings']['size'],
'#maxlength' => 255,
'#element_validate' => array(
'rooms_availability_reference_autocomplete_validate',
),
'#value_callback' => 'rooms_availability_reference_autocomplete_value',
);
break;
}
return array(
'unit_id' => $element,
);
}
/**
* Value callback for a rooms_availability_reference autocomplete element.
*
* Replace the unit id with a unit name.
*/
function rooms_availability_reference_autocomplete_value($element, $input = FALSE, $form_state) {
if ($input === FALSE) {
// We're building the displayed 'default value': expand the raw unit id into
// "unit name [unit_id:n]".
$unit_id = $element['#default_value'];
if (!empty($unit_id)) {
$unit = rooms_unit_load($unit_id);
$value = $unit->name;
$value .= ' [unit_id:' . $unit_id . ']';
return $value;
}
}
}
/**
* Validation callback for a node_reference autocomplete element.
*/
function rooms_availability_reference_autocomplete_validate($element, &$form_state, $form) {
$field = field_widget_field($element, $form_state);
$instance = field_widget_instance($element, $form_state);
$value = $element['#value'];
$unit_id = NULL;
if (!empty($value)) {
// Check whether we have an explicit "[unit_id:n]" input.
preg_match('/^(?:\\s*|(.*) )?\\[\\s*unit_id\\s*:\\s*(\\d+)\\s*\\]$/', $value, $matches);
if (!empty($matches)) {
// Explicit nid. Check that the 'title' part matches the actual title for
// the nid.
list(, $title, $unit_id) = $matches;
if (!empty($title)) {
$unit = rooms_unit_load($unit_id);
$real_title = $unit->name;
if (trim($title) != trim($real_title)) {
form_error($element, t('%name: title mismatch. Please check your selection.', array(
'%name' => $instance['label'],
)));
}
}
}
else {
// No explicit unit_id (the submitted value was not populated by
// autocomplete selection). Get the unit_id of a referenceable node from
// the entered title.
$options = array(
'string' => $value,
'match' => 'equals',
'limit' => 1,
);
$references = rooms_availability_reference_potential_references($field, $options);
if ($references) {
// @todo The best thing would be to present the user with an
// additional form, allowing the user to choose between valid
// candidates with the same title. ATM, we pick the first
// matching candidate...
$unit_id = key($references);
}
else {
form_error($element, t('%name: unable to find a unit with that title.', array(
'%name' => $instance['label'],
)));
}
}
}
// Set the element's value as the node id that was extracted from the entered
// input.
form_set_value($element, $unit_id, $form_state);
}
/**
* Implements hook_field_widget_error().
*/
function rooms_availability_reference_field_widget_error($element, $error, $form, &$form_state) {
form_error($element['unit_id'], $error['message']);
}
/**
* Implements hook_theme().
*/
function rooms_availability_reference_theme() {
return array(
'rooms_availability_field_calendar' => array(
'render element' => 'rooms_availability_field_calendar',
'template' => 'rooms_availability_field_calendar',
),
);
}