diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..2a8e53f --- /dev/null +++ b/composer.json @@ -0,0 +1,14 @@ +{ + "name": "drupal/reserve", + "type": "drupal-module", + "description": "Reservation system.", + "keywords": ["Drupal"], + "license": "GPL-2.0+", + "homepage": "https://www.drupal.org/project/reserve", + "minimum-stability": "dev", + "support": { + "issues": "https://www.drupal.org/project/issues/reserve", + "source": "http://cgit.drupalcode.org/reserve" + }, + "require": { } +} diff --git a/config/install/core.entity_form_display.reserve_category.reserve_category.default.yml b/config/install/core.entity_form_display.reserve_category.reserve_category.default.yml new file mode 100644 index 0000000..3af8f7f --- /dev/null +++ b/config/install/core.entity_form_display.reserve_category.reserve_category.default.yml @@ -0,0 +1,63 @@ +langcode: en +status: true +dependencies: + config: + - field.field.reserve_category.reserve_category.reserve_setup_buffer + - field.field.reserve_category.reserve_category.reserve_takedown_buffer + - field.field.reserve_category.reserve_category.reserve_minadv_std + - field.field.reserve_category.reserve_category.reserve_minadv_ext + - field.field.reserve_category.reserve_category.reserve_maxadv_std + - field.field.reserve_category.reserve_category.reserve_maxadv_ext + module: + - reserve +id: reserve_category.reserve_category.default +targetEntityType: reserve_category +bundle: reserve_category +mode: default +content: + name: + weight: -10 + type: string_textfield + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + reserve_setup_buffer: + weight: 1 + settings: { } + third_party_settings: { } + type: options_select + region: content + reserve_takedown_buffer: + weight: 2 + settings: { } + third_party_settings: { } + type: options_select + region: content + reserve_minadv_std: + weight: 3 + settings: { } + third_party_settings: { } + type: options_select + region: content + reserve_minadv_ext: + weight: 4 + settings: { } + third_party_settings: { } + type: options_select + region: content + reserve_maxadv_std: + weight: 5 + settings: { } + third_party_settings: { } + type: options_select + region: content + reserve_maxadv_ext: + weight: 6 + settings: { } + third_party_settings: { } + type: options_select + region: content +hidden: + user_id: true \ No newline at end of file diff --git a/config/install/core.entity_form_display.reserve_reservation.reserve_reservation.default.yml b/config/install/core.entity_form_display.reserve_reservation.reserve_reservation.default.yml new file mode 100644 index 0000000..98a2e0c --- /dev/null +++ b/config/install/core.entity_form_display.reserve_reservation.reserve_reservation.default.yml @@ -0,0 +1,108 @@ +langcode: en +status: true +dependencies: + config: + - field.field.reserve_reservation.reserve_reservation.reservable_content_type + - field.field.reserve_reservation.reserve_reservation.reservable_id + - field.field.reserve_reservation.reserve_reservation.reservation_date + - field.field.reserve_reservation.reserve_reservation.reservation_ebundle + - field.field.reserve_reservation.reserve_reservation.reservation_length + - field.field.reserve_reservation.reserve_reservation.reservation_private + - field.field.reserve_reservation.reserve_reservation.reservation_repeat_type + - field.field.reserve_reservation.reserve_reservation.reservation_repeat_until + - field.field.reserve_reservation.reserve_reservation.reservation_series_id + - field.field.reserve_reservation.reserve_reservation.reservation_time + module: + - datetime + - reserve +_core: + default_config_hash: LCDU3FONhj8ylh6Px2atCBl0dR8sFemI3vnXDt5TyCE +id: reserve_reservation.reserve_reservation.default +targetEntityType: reserve_reservation +bundle: reserve_reservation +mode: default +content: + name: + type: string_textfield + weight: 0 + settings: + size: 60 + placeholder: '' + third_party_settings: { } + region: content + reservable_content_type: + type: options_select + weight: 9 + region: content + settings: { } + third_party_settings: { } + reservable_id: + weight: 9 + settings: + placeholder: '' + third_party_settings: { } + type: number + region: content + reservation_date: + weight: 2 + settings: { } + third_party_settings: { } + type: datetime_default + region: content + reservation_length: + weight: 4 + settings: { } + third_party_settings: { } + type: options_select + region: content + reservation_private: + weight: 1 + settings: + display_label: true + third_party_settings: { } + type: boolean_checkbox + region: content + reservation_repeat_type: + weight: 5 + settings: { } + third_party_settings: { } + type: options_select + region: content + reservation_repeat_until: + weight: 6 + settings: { } + third_party_settings: { } + type: datetime_default + region: content + reservation_series_id: + weight: 7 + settings: + placeholder: '' + third_party_settings: { } + type: number + region: content + reservation_time: + weight: 3 + settings: + size: 60 + placeholder: '' + third_party_settings: { } + type: string_textfield + region: content + user_id: + type: entity_reference_autocomplete + weight: 8 + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + third_party_settings: { } + region: content + reservation_ebundle: + weight: 9 + settings: + size: 60 + placeholder: '' + third_party_settings: { } + type: string_textfield + region: content diff --git a/config/install/core.entity_view_display.reserve_category.reserve_category.default.yml b/config/install/core.entity_view_display.reserve_category.reserve_category.default.yml new file mode 100644 index 0000000..9bf5717 --- /dev/null +++ b/config/install/core.entity_view_display.reserve_category.reserve_category.default.yml @@ -0,0 +1,72 @@ +langcode: en +status: true +dependencies: + config: + - field.field.reserve_category.reserve_category.reserve_setup_buffer + - field.field.reserve_category.reserve_category.reserve_takedown_buffer + - field.field.reserve_category.reserve_category.reserve_minadv_std + - field.field.reserve_category.reserve_category.reserve_minadv_ext + - field.field.reserve_category.reserve_category.reserve_maxadv_std + - field.field.reserve_category.reserve_category.reserve_maxadv_ext + module: + - options + - reserve + - user +id: reserve_category.reserve_category.default +targetEntityType: reserve_category +bundle: reserve_category +mode: default +content: + name: + weight: -10 + label: hidden + type: string + region: content + settings: + link_to_entity: false + third_party_settings: { } + reserve_setup_buffer: + weight: 1 + label: inline + type: list_default + region: content + settings: { } + third_party_settings: { } + reserve_takedown_buffer: + weight: 2 + label: inline + type: list_default + region: content + settings: { } + third_party_settings: { } + reserve_minadv_std: + weight: 3 + label: inline + type: list_default + region: content + settings: { } + third_party_settings: { } + reserve_minadv_ext: + weight: 4 + label: inline + type: list_default + region: content + settings: { } + third_party_settings: { } + reserve_maxadv_std: + weight: 5 + label: inline + type: list_default + region: content + settings: { } + third_party_settings: { } + reserve_maxadv_ext: + weight: 6 + label: inline + type: list_default + region: content + settings: { } + third_party_settings: { } +hidden: + user_id: true + diff --git a/config/install/core.entity_view_display.reserve_reservation.reserve_reservation.default.yml b/config/install/core.entity_view_display.reserve_reservation.reserve_reservation.default.yml new file mode 100644 index 0000000..9d53bbe --- /dev/null +++ b/config/install/core.entity_view_display.reserve_reservation.reserve_reservation.default.yml @@ -0,0 +1,104 @@ +langcode: en +status: true +dependencies: + config: + - field.field.reserve_reservation.reserve_reservation.reservable_content_type + - field.field.reserve_reservation.reserve_reservation.reservable_id + - field.field.reserve_reservation.reserve_reservation.reservation_date + - field.field.reserve_reservation.reserve_reservation.reservation_ebundle + - field.field.reserve_reservation.reserve_reservation.reservation_length + - field.field.reserve_reservation.reserve_reservation.reservation_private + - field.field.reserve_reservation.reserve_reservation.reservation_repeat_type + - field.field.reserve_reservation.reserve_reservation.reservation_repeat_until + - field.field.reserve_reservation.reserve_reservation.reservation_series_id + - field.field.reserve_reservation.reserve_reservation.reservation_time + module: + - datetime + - options + - reserve + - user +_core: + default_config_hash: 2s_-pGyf2O4EgKGyzwqMfNGNDoQcLHAdTET2ioShrjo +id: reserve_reservation.reserve_reservation.default +targetEntityType: reserve_reservation +bundle: reserve_reservation +mode: default +content: + name: + label: above + type: string + weight: -4 + settings: + link_to_entity: false + third_party_settings: { } + region: content + reservation_date: + weight: 7 + label: above + settings: + format_type: medium + timezone_override: '' + third_party_settings: { } + type: datetime_default + region: content + reservation_length: + weight: 2 + label: above + settings: { } + third_party_settings: { } + type: list_default + region: content + reservation_private: + weight: 1 + label: above + settings: + format: default + format_custom_false: '' + format_custom_true: '' + third_party_settings: { } + type: boolean + region: content + reservation_repeat_type: + weight: 3 + label: above + settings: { } + third_party_settings: { } + type: list_default + region: content + reservation_repeat_until: + weight: 4 + label: above + settings: + format_type: medium + timezone_override: '' + third_party_settings: { } + type: datetime_default + region: content + reservation_series_id: + weight: 6 + label: above + settings: + thousand_separator: '' + prefix_suffix: true + third_party_settings: { } + type: number_integer + region: content + reservation_time: + weight: 5 + label: above + settings: + link_to_entity: false + third_party_settings: { } + type: string + region: content + user_id: + label: hidden + type: author + weight: 0 + settings: { } + third_party_settings: { } + region: content +hidden: + reservable_content_type: true + reservable_id: true + reservation_ebundle: true diff --git a/config/install/field.field.reserve_category.reserve_category.reserve_maxadv_ext.yml b/config/install/field.field.reserve_category.reserve_category.reserve_maxadv_ext.yml new file mode 100644 index 0000000..8d75cf9 --- /dev/null +++ b/config/install/field.field.reserve_category.reserve_category.reserve_maxadv_ext.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_category.reserve_maxadv_ext + module: + - options + - reserve +id: reserve_category.reserve_category.reserve_maxadv_ext +field_name: reserve_maxadv_ext +entity_type: reserve_category +bundle: reserve_category +label: 'Maximum Advanced Booking (extended)' +description: 'Maximum number of days in advance that a booking may be made (extended user). Default value is 180.' +required: true +translatable: false +default_value: + - + value: 180 +default_value_callback: '' +settings: { } +field_type: list_integer \ No newline at end of file diff --git a/config/install/field.field.reserve_category.reserve_category.reserve_maxadv_std.yml b/config/install/field.field.reserve_category.reserve_category.reserve_maxadv_std.yml new file mode 100644 index 0000000..b678dcc --- /dev/null +++ b/config/install/field.field.reserve_category.reserve_category.reserve_maxadv_std.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_category.reserve_maxadv_std + module: + - options + - reserve +id: reserve_category.reserve_category.reserve_maxadv_std +field_name: reserve_maxadv_std +entity_type: reserve_category +bundle: reserve_category +label: 'Maximum Advanced Booking (standard)' +description: 'Maximum number of days in advance that a booking may be made (standard user). Default value is 14.' +required: true +translatable: false +default_value: + - + value: 14 +default_value_callback: '' +settings: { } +field_type: list_integer \ No newline at end of file diff --git a/config/install/field.field.reserve_category.reserve_category.reserve_minadv_ext.yml b/config/install/field.field.reserve_category.reserve_category.reserve_minadv_ext.yml new file mode 100644 index 0000000..3ddcd9d --- /dev/null +++ b/config/install/field.field.reserve_category.reserve_category.reserve_minadv_ext.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_category.reserve_minadv_ext + module: + - options + - reserve +id: reserve_category.reserve_category.reserve_minadv_ext +field_name: reserve_minadv_ext +entity_type: reserve_category +bundle: reserve_category +label: 'Minimum Advance Booking (extended)' +description: 'Minimum number of days in advance that a booking may be made (extended user). Default value is 0.' +required: true +translatable: false +default_value: + - + value: 0 +default_value_callback: '' +settings: { } +field_type: list_integer \ No newline at end of file diff --git a/config/install/field.field.reserve_category.reserve_category.reserve_minadv_std.yml b/config/install/field.field.reserve_category.reserve_category.reserve_minadv_std.yml new file mode 100644 index 0000000..d1f544b --- /dev/null +++ b/config/install/field.field.reserve_category.reserve_category.reserve_minadv_std.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_category.reserve_minadv_std + module: + - options + - reserve +id: reserve_category.reserve_category.reserve_minadv_std +field_name: reserve_minadv_std +entity_type: reserve_category +bundle: reserve_category +label: 'Minimum Advanced Booking (standard)' +description: 'Minimum number of days in advance that a booking may be made (standard user). Default value is 0.' +required: true +translatable: false +default_value: + - + value: 0 +default_value_callback: '' +settings: { } +field_type: list_integer \ No newline at end of file diff --git a/config/install/field.field.reserve_category.reserve_category.reserve_setup_buffer.yml b/config/install/field.field.reserve_category.reserve_category.reserve_setup_buffer.yml new file mode 100644 index 0000000..3b1ac9e --- /dev/null +++ b/config/install/field.field.reserve_category.reserve_category.reserve_setup_buffer.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_category.reserve_setup_buffer + module: + - options + - reserve +id: reserve_category.reserve_category.reserve_setup_buffer +field_name: reserve_setup_buffer +entity_type: reserve_category +bundle: reserve_category +label: 'Setup Buffer' +description: 'Setup time required for reservations for all items in this category. This time will be added to each reservation to extend the reserved calendar time.' +required: true +translatable: false +default_value: + - + value: 0 +default_value_callback: '' +settings: { } +field_type: list_integer \ No newline at end of file diff --git a/config/install/field.field.reserve_category.reserve_category.reserve_takedown_buffer.yml b/config/install/field.field.reserve_category.reserve_category.reserve_takedown_buffer.yml new file mode 100644 index 0000000..597a5e6 --- /dev/null +++ b/config/install/field.field.reserve_category.reserve_category.reserve_takedown_buffer.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_category.reserve_takedown_buffer + module: + - options + - reserve +id: reserve_category.reserve_category.reserve_takedown_buffer +field_name: reserve_takedown_buffer +entity_type: reserve_category +bundle: reserve_category +label: 'Takedown Buffer' +description: 'Takedown time required for reservations for all items in this category. This time will be added to each reservation to extend the reserved calendar time.' +required: true +translatable: false +default_value: + - + value: 0 +default_value_callback: '' +settings: { } +field_type: list_integer \ No newline at end of file diff --git a/config/install/field.field.reserve_reservation.reserve_reservation.reservable_content_type.yml b/config/install/field.field.reserve_reservation.reserve_reservation.reservable_content_type.yml new file mode 100644 index 0000000..97794c0 --- /dev/null +++ b/config/install/field.field.reserve_reservation.reserve_reservation.reservable_content_type.yml @@ -0,0 +1,21 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_reservation.reservable_content_type + module: + - options + - reserve +id: reserve_reservation.reserve_reservation.reservable_content_type +field_name: reservable_content_type +entity_type: reserve_reservation +bundle: reserve_reservation +label: 'Reservable Entity Type' +description: 'Holds the type of content entity that this reservation is being made on. Set through the UI.' +required: false +translatable: false +default_value: +default_value_callback: '' +settings: { } +field_type: list_string +locked: true \ No newline at end of file diff --git a/config/install/field.field.reserve_reservation.reserve_reservation.reservable_id.yml b/config/install/field.field.reserve_reservation.reserve_reservation.reservable_id.yml new file mode 100644 index 0000000..6588ca6 --- /dev/null +++ b/config/install/field.field.reserve_reservation.reserve_reservation.reservable_id.yml @@ -0,0 +1,24 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_reservation.reservable_id + module: + - reserve +id: reserve_reservation.reserve_reservation.reservable_id +field_name: reservable_id +entity_type: reserve_reservation +bundle: reserve_reservation +label: 'Reservable ID' +description: 'Holds the entity id of the item being reserved. Automatically added through the calendar UI.' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + min: null + max: null + prefix: '' + suffix: '' +field_type: integer +locked: true \ No newline at end of file diff --git a/config/install/field.field.reserve_reservation.reserve_reservation.reservation_date.yml b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_date.yml new file mode 100644 index 0000000..d161969 --- /dev/null +++ b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_date.yml @@ -0,0 +1,21 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_reservation.reservation_date + module: + - datetime + - reserve +id: reserve_reservation.reserve_reservation.reservation_date +field_name: reservation_date +entity_type: reserve_reservation +bundle: reserve_reservation +label: Date +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: datetime +locked: true \ No newline at end of file diff --git a/config/install/field.field.reserve_reservation.reserve_reservation.reservation_ebundle.yml b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_ebundle.yml new file mode 100644 index 0000000..5e04ff6 --- /dev/null +++ b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_ebundle.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_reservation.reservation_ebundle + module: + - reserve +id: reserve_reservation.reserve_reservation.reservation_ebundle +field_name: reservation_ebundle +entity_type: reserve_reservation +bundle: reserve_reservation +label: Ebundle +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: string diff --git a/config/install/field.field.reserve_reservation.reserve_reservation.reservation_length.yml b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_length.yml new file mode 100644 index 0000000..d7f6d57 --- /dev/null +++ b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_length.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_reservation.reservation_length + module: + - options + - reserve +id: reserve_reservation.reserve_reservation.reservation_length +field_name: reservation_length +entity_type: reserve_reservation +bundle: reserve_reservation +label: 'Length' +description: '' +required: true +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: list_integer \ No newline at end of file diff --git a/config/install/field.field.reserve_reservation.reserve_reservation.reservation_private.yml b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_private.yml new file mode 100644 index 0000000..9e43a94 --- /dev/null +++ b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_private.yml @@ -0,0 +1,23 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_reservation.reservation_private + module: + - reserve +id: reserve_reservation.reserve_reservation.reservation_private +field_name: reservation_private +entity_type: reserve_reservation +bundle: reserve_reservation +label: Private +description: 'Check this to hide the Group Name for this reservation.' +required: false +translatable: false +default_value: + - + value: 0 +default_value_callback: '' +settings: + on_label: Private + off_label: Public +field_type: boolean diff --git a/config/install/field.field.reserve_reservation.reserve_reservation.reservation_repeat_type.yml b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_repeat_type.yml new file mode 100644 index 0000000..1cbf039 --- /dev/null +++ b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_repeat_type.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_reservation.reservation_repeat_type + module: + - options + - reserve +id: reserve_reservation.reserve_reservation.reservation_repeat_type +field_name: reservation_repeat_type +entity_type: reserve_reservation +bundle: reserve_reservation +label: 'Repeat Type' +description: '' +required: true +translatable: false +default_value: + - + value: 0 +default_value_callback: '' +settings: { } +field_type: list_integer diff --git a/config/install/field.field.reserve_reservation.reserve_reservation.reservation_repeat_until.yml b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_repeat_until.yml new file mode 100644 index 0000000..cd7e574 --- /dev/null +++ b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_repeat_until.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_reservation.reservation_repeat_until + module: + - datetime + - reserve +id: reserve_reservation.reserve_reservation.reservation_repeat_until +field_name: reservation_repeat_until +entity_type: reserve_reservation +bundle: reserve_reservation +label: 'Repeat Until' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: datetime \ No newline at end of file diff --git a/config/install/field.field.reserve_reservation.reserve_reservation.reservation_series_id.yml b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_series_id.yml new file mode 100644 index 0000000..8952176 --- /dev/null +++ b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_series_id.yml @@ -0,0 +1,23 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_reservation.reservation_series_id + module: + - reserve +id: reserve_reservation.reserve_reservation.reservation_series_id +field_name: reservation_series_id +entity_type: reserve_reservation +bundle: reserve_reservation +label: 'Series ID' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + min: null + max: null + prefix: '' + suffix: '' +field_type: integer \ No newline at end of file diff --git a/config/install/field.field.reserve_reservation.reserve_reservation.reservation_time.yml b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_time.yml new file mode 100644 index 0000000..59a1d04 --- /dev/null +++ b/config/install/field.field.reserve_reservation.reserve_reservation.reservation_time.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_reservation.reservation_time + module: + - reserve +id: reserve_reservation.reserve_reservation.reservation_time +field_name: reservation_time +entity_type: reserve_reservation +bundle: reserve_reservation +label: Time +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: string \ No newline at end of file diff --git a/config/install/field.storage.reserve_category.reserve_maxadv_ext.yml b/config/install/field.storage.reserve_category.reserve_maxadv_ext.yml new file mode 100644 index 0000000..d6eba50 --- /dev/null +++ b/config/install/field.storage.reserve_category.reserve_maxadv_ext.yml @@ -0,0 +1,34 @@ +langcode: en +status: true +dependencies: + module: + - options + - reserve +id: reserve_category.reserve_maxadv_ext +field_name: reserve_maxadv_ext +entity_type: reserve_category +type: list_integer +settings: + allowed_values: + - + value: 30 + label: 30 + - + value: 60 + label: 60 + - + value: 90 + label: 90 + - + value: 180 + label: 180 + - + value: 360 + label: 360 +module: options +locked: true +cardinality: 1 +translatable: false +indexes: { } +persist_with_no_fields: false +custom_storage: false \ No newline at end of file diff --git a/config/install/field.storage.reserve_category.reserve_maxadv_std.yml b/config/install/field.storage.reserve_category.reserve_maxadv_std.yml new file mode 100644 index 0000000..9b2bcc6 --- /dev/null +++ b/config/install/field.storage.reserve_category.reserve_maxadv_std.yml @@ -0,0 +1,34 @@ +langcode: en +status: true +dependencies: + module: + - options + - reserve +id: reserve_category.reserve_maxadv_std +field_name: reserve_maxadv_std +entity_type: reserve_category +type: list_integer +settings: + allowed_values: + - + value: 7 + label: 7 + - + value: 14 + label: 14 + - + value: 30 + label: 30 + - + value: 60 + label: 60 + - + value: 90 + label: 90 +module: options +locked: true +cardinality: 1 +translatable: false +indexes: { } +persist_with_no_fields: false +custom_storage: false \ No newline at end of file diff --git a/config/install/field.storage.reserve_category.reserve_minadv_ext.yml b/config/install/field.storage.reserve_category.reserve_minadv_ext.yml new file mode 100644 index 0000000..e809ce9 --- /dev/null +++ b/config/install/field.storage.reserve_category.reserve_minadv_ext.yml @@ -0,0 +1,34 @@ +langcode: en +status: true +dependencies: + module: + - options + - reserve +id: reserve_category.reserve_minadv_ext +field_name: reserve_minadv_ext +entity_type: reserve_category +type: list_integer +settings: + allowed_values: + - + value: 0 + label: 0 + - + value: 1 + label: 1 + - + value: 7 + label: 7 + - + value: 14 + label: 14 + - + value: 30 + label: 30 +module: options +locked: true +cardinality: 1 +translatable: false +indexes: { } +persist_with_no_fields: false +custom_storage: false \ No newline at end of file diff --git a/config/install/field.storage.reserve_category.reserve_minadv_std.yml b/config/install/field.storage.reserve_category.reserve_minadv_std.yml new file mode 100644 index 0000000..ad47b1d --- /dev/null +++ b/config/install/field.storage.reserve_category.reserve_minadv_std.yml @@ -0,0 +1,34 @@ +langcode: en +status: true +dependencies: + module: + - options + - reserve +id: reserve_category.reserve_minadv_std +field_name: reserve_minadv_std +entity_type: reserve_category +type: list_integer +settings: + allowed_values: + - + value: 0 + label: 0 + - + value: 1 + label: 1 + - + value: 7 + label: 7 + - + value: 14 + label: 14 + - + value: 30 + label: 30 +module: options +locked: true +cardinality: 1 +translatable: false +indexes: { } +persist_with_no_fields: false +custom_storage: false \ No newline at end of file diff --git a/config/install/field.storage.reserve_category.reserve_setup_buffer.yml b/config/install/field.storage.reserve_category.reserve_setup_buffer.yml new file mode 100644 index 0000000..23d54cb --- /dev/null +++ b/config/install/field.storage.reserve_category.reserve_setup_buffer.yml @@ -0,0 +1,35 @@ +langcode: en +status: true +dependencies: + module: + - options + - reserve +id: reserve_category.reserve_setup_buffer +field_name: reserve_setup_buffer +entity_type: reserve_category +type: list_integer +settings: + allowed_values: + - + value: 0 + label: none + - + value: 30 + label: '30 minutes' + - + value: 60 + label: '1 hour' + - + value: 90 + label: '90 minutes' + - + value: 120 + label: '2 hours' + allowed_values_function: '' +module: options +locked: true +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false \ No newline at end of file diff --git a/config/install/field.storage.reserve_category.reserve_takedown_buffer.yml b/config/install/field.storage.reserve_category.reserve_takedown_buffer.yml new file mode 100644 index 0000000..4923d57 --- /dev/null +++ b/config/install/field.storage.reserve_category.reserve_takedown_buffer.yml @@ -0,0 +1,35 @@ +langcode: en +status: true +dependencies: + module: + - options + - reserve +id: reserve_category.reserve_takedown_buffer +field_name: reserve_takedown_buffer +entity_type: reserve_category +type: list_integer +settings: + allowed_values: + - + value: 0 + label: none + - + value: 30 + label: '30 minutes' + - + value: 60 + label: '1 hour' + - + value: 90 + label: '90 minutes' + - + value: 120 + label: '2 hours' + allowed_values_function: '' +module: options +locked: true +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/install/field.storage.reserve_reservation.reservable_content_type.yml b/config/install/field.storage.reserve_reservation.reservable_content_type.yml new file mode 100644 index 0000000..2e15f54 --- /dev/null +++ b/config/install/field.storage.reserve_reservation.reservable_content_type.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - options + - reserve +id: reserve_reservation.reservable_content_type +field_name: reservable_content_type +entity_type: reserve_reservation +type: list_string +settings: + allowed_values_function: 'reserve_site_entity_types' +module: options +locked: true +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false \ No newline at end of file diff --git a/config/install/field.storage.reserve_reservation.reservable_id.yml b/config/install/field.storage.reserve_reservation.reservable_id.yml new file mode 100644 index 0000000..0a38f90 --- /dev/null +++ b/config/install/field.storage.reserve_reservation.reservable_id.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - reserve +id: reserve_reservation.reservable_id +field_name: reservable_id +entity_type: reserve_reservation +type: integer +settings: + unsigned: true + size: normal +module: core +locked: true +cardinality: 1 +translatable: false +indexes: { } +persist_with_no_fields: false +custom_storage: false \ No newline at end of file diff --git a/config/install/field.storage.reserve_reservation.reservation_date.yml b/config/install/field.storage.reserve_reservation.reservation_date.yml new file mode 100644 index 0000000..287ca8f --- /dev/null +++ b/config/install/field.storage.reserve_reservation.reservation_date.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - datetime + - reserve +id: reserve_reservation.reservation_date +field_name: reservation_date +entity_type: reserve_reservation +type: datetime +settings: + datetime_type: date +module: datetime +locked: true +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false \ No newline at end of file diff --git a/config/install/field.storage.reserve_reservation.reservation_ebundle.yml b/config/install/field.storage.reserve_reservation.reservation_ebundle.yml new file mode 100644 index 0000000..694e16a --- /dev/null +++ b/config/install/field.storage.reserve_reservation.reservation_ebundle.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + module: + - reserve +id: reserve_reservation.reservation_ebundle +field_name: reservation_ebundle +entity_type: reserve_reservation +type: string +settings: + max_length: 255 + is_ascii: false + case_sensitive: false +module: core +locked: true +cardinality: 1 +translatable: false +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/install/field.storage.reserve_reservation.reservation_length.yml b/config/install/field.storage.reserve_reservation.reservation_length.yml new file mode 100644 index 0000000..e3f8f85 --- /dev/null +++ b/config/install/field.storage.reserve_reservation.reservation_length.yml @@ -0,0 +1,49 @@ +langcode: en +status: true +dependencies: + module: + - reserve + - options +id: reserve_reservation.reservation_length +field_name: reservation_length +entity_type: reserve_reservation +type: list_integer +settings: + allowed_values: + - + value: 30 + label: '30 minutes' + - + value: 60 + label: '1 hour' + - + value: 90 + label: '1.5 hours' + - + value: 120 + label: '2 hours' + - + value: 150 + label: '2.5 hours' + - + value: 180 + label: '3 hours' + - + value: 210 + label: '3.5 hours' + - + value: 240 + label: '4 hours' + - + value: 270 + label: '4.5 hours' + - + value: 300 + label: '5 hours' +module: core +locked: true +cardinality: 1 +translatable: false +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/install/field.storage.reserve_reservation.reservation_private.yml b/config/install/field.storage.reserve_reservation.reservation_private.yml new file mode 100644 index 0000000..5ba9805 --- /dev/null +++ b/config/install/field.storage.reserve_reservation.reservation_private.yml @@ -0,0 +1,17 @@ +langcode: en +status: true +dependencies: + module: + - reserve +id: reserve_reservation.reservation_private +field_name: reservation_private +entity_type: reserve_reservation +type: boolean +settings: { } +module: core +locked: true +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/install/field.storage.reserve_reservation.reservation_repeat_type.yml b/config/install/field.storage.reserve_reservation.reservation_repeat_type.yml new file mode 100644 index 0000000..d881f82 --- /dev/null +++ b/config/install/field.storage.reserve_reservation.reservation_repeat_type.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + module: + - options + - reserve +id: reserve_reservation.reservation_repeat_type +field_name: reservation_repeat_type +entity_type: reserve_reservation +type: list_integer +settings: + allowed_values: + - + value: 0 + label: 'No repeat' + - + value: 1 + label: 'Repeat all days until' + - + value: 2 + label: 'Repeat this day of the week until' + allowed_values_function: '' +module: options +locked: true +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/install/field.storage.reserve_reservation.reservation_repeat_until.yml b/config/install/field.storage.reserve_reservation.reservation_repeat_until.yml new file mode 100644 index 0000000..75a48a8 --- /dev/null +++ b/config/install/field.storage.reserve_reservation.reservation_repeat_until.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - datetime + - reserve +id: reserve_reservation.reservation_repeat_until +field_name: reservation_repeat_until +entity_type: reserve_reservation +type: datetime +settings: + datetime_type: date +module: datetime +locked: true +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false \ No newline at end of file diff --git a/config/install/field.storage.reserve_reservation.reservation_series_id.yml b/config/install/field.storage.reserve_reservation.reservation_series_id.yml new file mode 100644 index 0000000..caabcb8 --- /dev/null +++ b/config/install/field.storage.reserve_reservation.reservation_series_id.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - reserve +id: reserve_reservation.reservation_series_id +field_name: reservation_series_id +entity_type: reserve_reservation +type: integer +settings: + unsigned: false + size: normal +module: core +locked: true +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false \ No newline at end of file diff --git a/config/install/field.storage.reserve_reservation.reservation_time.yml b/config/install/field.storage.reserve_reservation.reservation_time.yml new file mode 100644 index 0000000..f7818b0 --- /dev/null +++ b/config/install/field.storage.reserve_reservation.reservation_time.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + module: + - reserve +id: reserve_reservation.reservation_time +field_name: reservation_time +entity_type: reserve_reservation +type: string +settings: + max_length: 255 + is_ascii: false + case_sensitive: false +module: core +locked: true +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false \ No newline at end of file diff --git a/config/install/reserve.hours.yml b/config/install/reserve.hours.yml new file mode 100644 index 0000000..e69de29 diff --git a/config/install/reserve.settings.yml b/config/install/reserve.settings.yml new file mode 100644 index 0000000..6b5f883 --- /dev/null +++ b/config/install/reserve.settings.yml @@ -0,0 +1,3 @@ +hour_format: 0 +max_length_standard: 120 +max_length_admin: 180 \ No newline at end of file diff --git a/config/install/views.view.reservations.yml b/config/install/views.view.reservations.yml new file mode 100644 index 0000000..33d6770 --- /dev/null +++ b/config/install/views.view.reservations.yml @@ -0,0 +1,1161 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.reserve_reservation.reservable_content_type + - field.storage.reserve_reservation.reservation_date + - field.storage.reserve_reservation.reservation_ebundle + - field.storage.reserve_reservation.reservation_length + - field.storage.reserve_reservation.reservation_private + - field.storage.reserve_reservation.reservation_repeat_type + - field.storage.reserve_reservation.reservation_repeat_until + - field.storage.reserve_reservation.reservation_time + - system.menu.admin + module: + - datetime + - options + - reserve + - views_bulk_operations +_core: + default_config_hash: qtxsAzivRyJgkwE59iiTdA2ucLdj2OtA9MwdI-OUYzI +id: reservations +label: Reservations +module: views +description: '' +tag: '' +base_table: reserve_reservation_field_data +base_field: id +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: none + options: { } + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: full + options: + items_per_page: 40 + offset: 0 + id: 0 + total_pages: null + tags: + previous: ‹‹ + next: ›› + first: '« First' + last: 'Last »' + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + quantity: 9 + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + views_bulk_operations_bulk_form: views_bulk_operations_bulk_form + name: name + reservation_date: reservation_date + reservation_time: reservation_date + reservation_length: reservation_date + reservation_private: reservation_private + reservation_repeat_type: reservation_repeat_type + reservation_repeat_until: reservation_repeat_type + reservable_content_type: reservable_content_type + reservation_ebundle: reservable_content_type + operations: operations + info: + views_bulk_operations_bulk_form: + align: '' + separator: '' + empty_column: false + responsive: '' + name: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + reservation_date: + sortable: true + default_sort_order: asc + align: '' + separator: ' ' + empty_column: false + responsive: '' + reservation_time: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + reservation_length: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + reservation_private: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + reservation_repeat_type: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + reservation_repeat_until: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + reservable_content_type: + sortable: true + default_sort_order: asc + align: '' + separator: '
' + empty_column: false + responsive: '' + reservation_ebundle: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + operations: + align: '' + separator: '' + empty_column: false + responsive: '' + default: '-1' + empty_table: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + views_bulk_operations_bulk_form: + id: views_bulk_operations_bulk_form + table: views + field: views_bulk_operations_bulk_form + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + batch: true + batch_size: 50 + form_step: true + buttons: false + action_title: Action + selected_actions: + views_bulk_edit: views_bulk_edit + views_bulk_operations_delete_entity: views_bulk_operations_delete_entity + preconfiguration: + views_bulk_edit: + label_override: '' + get_bundles_from_results: 1 + views_bulk_operations_delete_entity: + label_override: '' + plugin_id: views_bulk_operations_bulk_form + name: + id: name + table: reserve_reservation_field_data + field: name + relationship: none + group_type: group + admin_label: '' + label: Group + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: + link_to_entity: false + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: null + entity_field: name + plugin_id: field + reservation_date: + id: reservation_date + table: reserve_reservation__reservation_date + field: reservation_date + relationship: none + group_type: group + admin_label: '' + label: When + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: datetime_default + settings: + timezone_override: '' + format_type: html_date + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field + reservation_time: + id: reservation_time + table: reserve_reservation__reservation_time + field: reservation_time + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: + link_to_entity: false + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field + reservation_length: + id: reservation_length + table: reserve_reservation__reservation_length + field: reservation_length + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: list_default + settings: { } + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field + reservation_private: + id: reservation_private + table: reserve_reservation__reservation_private + field: reservation_private + relationship: none + group_type: group + admin_label: '' + label: Private + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: default + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field + reservation_repeat_type: + id: reservation_repeat_type + table: reserve_reservation__reservation_repeat_type + field: reservation_repeat_type + relationship: none + group_type: group + admin_label: '' + label: Repeating + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: list_default + settings: { } + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field + reservation_repeat_until: + id: reservation_repeat_until + table: reserve_reservation__reservation_repeat_until + field: reservation_repeat_until + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: datetime_default + settings: + timezone_override: '' + format_type: html_date + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field + reservable_content_type: + id: reservable_content_type + table: reserve_reservation__reservable_content_type + field: reservable_content_type + relationship: none + group_type: group + admin_label: '' + label: 'Reservation Type' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: list_default + settings: { } + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field + reservation_ebundle: + id: reservation_ebundle + table: reserve_reservation__reservation_ebundle + field: reservation_ebundle + relationship: none + group_type: group + admin_label: '' + label: Ebundle + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: + link_to_entity: false + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field + operations: + id: operations + table: reserve_reservation + field: operations + relationship: none + group_type: group + admin_label: '' + label: Operations + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + destination: true + entity_type: reserve_reservation + plugin_id: entity_operations + filters: + name: + id: name + table: reserve_reservation_field_data + field: name + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: name_op + label: Group + description: '' + use_operator: false + operator: name_op + identifier: name + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: reserve_reservation + entity_field: name + plugin_id: string + reservation_date_value: + id: reservation_date_value + table: reserve_reservation__reservation_date + field: reservation_date_value + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: + min: '' + max: '' + value: '' + type: date + group: 1 + exposed: true + expose: + operator_id: reservation_date_value_op + label: Date + description: '' + use_operator: false + operator: reservation_date_value_op + identifier: reservation_date_value + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: datetime + sorts: + reservation_date_value: + id: reservation_date_value + table: reserve_reservation__reservation_date + field: reservation_date_value + relationship: none + group_type: group + admin_label: '' + order: ASC + exposed: false + expose: + label: '' + granularity: second + plugin_id: datetime + header: { } + footer: { } + empty: + area: + id: area + table: views + field: area + relationship: none + group_type: group + admin_label: '' + empty: true + tokenize: false + content: + value: 'The are no reservations.' + format: basic_html + plugin_id: text + relationships: { } + arguments: { } + display_extenders: { } + filter_groups: + operator: OR + groups: + 1: AND + title: 'My Rese' + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + tags: + - 'config:field.storage.reserve_reservation.reservable_content_type' + - 'config:field.storage.reserve_reservation.reservation_date' + - 'config:field.storage.reserve_reservation.reservation_ebundle' + - 'config:field.storage.reserve_reservation.reservation_length' + - 'config:field.storage.reserve_reservation.reservation_private' + - 'config:field.storage.reserve_reservation.reservation_repeat_type' + - 'config:field.storage.reserve_reservation.reservation_repeat_until' + - 'config:field.storage.reserve_reservation.reservation_time' + page_1: + display_plugin: page + id: page_1 + display_title: 'Manage (admin)' + position: 1 + display_options: + display_extenders: { } + path: admin/structure/reservations/reserve_reservations/manage-reservations + menu: + type: normal + title: 'Manage Reservations' + description: '' + expanded: false + parent: entity.reserve_reservation.collection + weight: 0 + context: '0' + menu_name: admin + display_description: '' + title: 'Manage Reservations' + defaults: + title: false + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + tags: + - 'config:field.storage.reserve_reservation.reservable_content_type' + - 'config:field.storage.reserve_reservation.reservation_date' + - 'config:field.storage.reserve_reservation.reservation_ebundle' + - 'config:field.storage.reserve_reservation.reservation_length' + - 'config:field.storage.reserve_reservation.reservation_private' + - 'config:field.storage.reserve_reservation.reservation_repeat_type' + - 'config:field.storage.reserve_reservation.reservation_repeat_until' + - 'config:field.storage.reserve_reservation.reservation_time' + page_2: + display_plugin: page + id: page_2 + display_title: 'My Reservations' + position: 2 + display_options: + display_extenders: { } + display_description: '' + path: reserve/%ebundle/my-reservations + arguments: + reservation_ebundle_value: + id: reservation_ebundle_value + table: reserve_reservation__reservation_ebundle + field: reservation_ebundle_value + relationship: none + group_type: group + admin_label: '' + default_action: default + exception: + value: all + title_enable: false + title: All + title_enable: false + title: '' + default_argument_type: raw + default_argument_options: + index: 1 + use_alias: false + default_argument_skip_url: false + summary_options: + base_path: '' + count: true + items_per_page: 25 + override: false + summary: + sort_order: asc + number_of_records: 0 + format: default_summary + specify_validation: false + validate: + type: none + fail: 'not found' + validate_options: { } + glossary: false + limit: 0 + case: none + path_case: none + transform_dash: false + break_phrase: false + plugin_id: string + user_id: + id: user_id + table: reserve_reservation_field_data + field: user_id + relationship: none + group_type: group + admin_label: '' + default_action: default + exception: + value: all + title_enable: false + title: All + title_enable: false + title: '' + default_argument_type: current_user + default_argument_options: { } + default_argument_skip_url: false + summary_options: + base_path: '' + count: true + items_per_page: 25 + override: false + summary: + sort_order: asc + number_of_records: 0 + format: default_summary + specify_validation: false + validate: + type: none + fail: 'not found' + validate_options: { } + break_phrase: false + not: false + entity_type: reserve_reservation + entity_field: user_id + plugin_id: numeric + defaults: + arguments: false + title: false + menu: + type: tab + title: 'My Reservations' + description: '' + expanded: false + parent: '' + weight: 0 + context: '0' + menu_name: main + title: 'My Reservations' + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + tags: + - 'config:field.storage.reserve_reservation.reservable_content_type' + - 'config:field.storage.reserve_reservation.reservation_date' + - 'config:field.storage.reserve_reservation.reservation_ebundle' + - 'config:field.storage.reserve_reservation.reservation_length' + - 'config:field.storage.reserve_reservation.reservation_private' + - 'config:field.storage.reserve_reservation.reservation_repeat_type' + - 'config:field.storage.reserve_reservation.reservation_repeat_until' + - 'config:field.storage.reserve_reservation.reservation_time' diff --git a/config/schema/reserve.schema.yml b/config/schema/reserve.schema.yml new file mode 100644 index 0000000..0d18981 --- /dev/null +++ b/config/schema/reserve.schema.yml @@ -0,0 +1,13 @@ +field.field_settings.reserve_category: + type: mapping + label: 'Reserve Category settings' + mapping: + categories: + type: int + label: 'Allowable Categories' + calendar_header: + type: text + label: 'Calendar Header' + reservation_instructions: + type: text + label: 'Reservation Instructions' \ No newline at end of file diff --git a/content/reserve_category/digital.json b/content/reserve_category/digital.json new file mode 100644 index 0000000..2116d3a --- /dev/null +++ b/content/reserve_category/digital.json @@ -0,0 +1,90 @@ +{ + "_links": { + "self": { + "href": "http:\/\/default\/admin\/structure\/reserve_category\/3?_format=hal_json" + }, + "type": { + "href": "http:\/\/drupal.org\/rest\/type\/reserve_category\/reserve_category" + }, + "http:\/\/drupal.org\/rest\/relation\/reserve_category\/reserve_category\/user_id": [ + { + "href": "http:\/\/default\/user\/1?_format=hal_json", + "lang": "en" + } + ] + }, + "id": [ + { + "value": 3 + } + ], + "uuid": [ + { + "value": "99996f75-ab71-4ab0-b8af-9c0df45c15c9" + } + ], + "langcode": [ + { + "value": "en", + "lang": "en" + } + ], + "_embedded": { + "http:\/\/drupal.org\/rest\/relation\/reserve_category\/reserve_category\/user_id": [ + { + "_links": { + "self": { + "href": "http:\/\/default\/user\/1?_format=hal_json" + }, + "type": { + "href": "http:\/\/drupal.org\/rest\/type\/user\/user" + } + }, + "uuid": [ + { + "value": "1b16703e-48a1-4e8b-9611-3b1efbafb90d" + } + ], + "lang": "en" + } + ] + }, + "name": [ + { + "value": "Digital" + } + ], + "status": [ + { + "value": true + } + ], + "created": [ + { + "value": "2018-01-10T20:41:46+00:00", + "format": "Y-m-d\\TH:i:sP" + } + ], + "changed": [ + { + "value": "2018-01-10T20:41:46+00:00", + "format": "Y-m-d\\TH:i:sP" + } + ], + "default_langcode": [ + { + "value": true, + "lang": "en" + } + ], + "reserve_setup_buffer": [ + { + "value": 0 + } + ], + "reserve_takedown_buffer": [ + { + "value": 0 + } + ] +} \ No newline at end of file diff --git a/content/reserve_category/instant.json b/content/reserve_category/instant.json new file mode 100644 index 0000000..46cd4f5 --- /dev/null +++ b/content/reserve_category/instant.json @@ -0,0 +1,90 @@ +{ + "_links": { + "self": { + "href": "http:\/\/default\/admin\/structure\/reserve_category\/4?_format=hal_json" + }, + "type": { + "href": "http:\/\/drupal.org\/rest\/type\/reserve_category\/reserve_category" + }, + "http:\/\/drupal.org\/rest\/relation\/reserve_category\/reserve_category\/user_id": [ + { + "href": "http:\/\/default\/user\/1?_format=hal_json", + "lang": "en" + } + ] + }, + "id": [ + { + "value": 4 + } + ], + "uuid": [ + { + "value": "c3c9f13e-d620-4ef0-983f-6372b6f00e70" + } + ], + "langcode": [ + { + "value": "en", + "lang": "en" + } + ], + "_embedded": { + "http:\/\/drupal.org\/rest\/relation\/reserve_category\/reserve_category\/user_id": [ + { + "_links": { + "self": { + "href": "http:\/\/default\/user\/1?_format=hal_json" + }, + "type": { + "href": "http:\/\/drupal.org\/rest\/type\/user\/user" + } + }, + "uuid": [ + { + "value": "1b16703e-48a1-4e8b-9611-3b1efbafb90d" + } + ], + "lang": "en" + } + ] + }, + "name": [ + { + "value": "Instant" + } + ], + "status": [ + { + "value": true + } + ], + "created": [ + { + "value": "2018-01-11T06:40:53+00:00", + "format": "Y-m-d\\TH:i:sP" + } + ], + "changed": [ + { + "value": "2018-01-11T06:40:53+00:00", + "format": "Y-m-d\\TH:i:sP" + } + ], + "default_langcode": [ + { + "value": true, + "lang": "en" + } + ], + "reserve_setup_buffer": [ + { + "value": 0 + } + ], + "reserve_takedown_buffer": [ + { + "value": 0 + } + ] +} \ No newline at end of file diff --git a/content/reserve_category/large.json b/content/reserve_category/large.json new file mode 100644 index 0000000..b63df0d --- /dev/null +++ b/content/reserve_category/large.json @@ -0,0 +1,90 @@ +{ + "_links": { + "self": { + "href": "http:\/\/default\/admin\/structure\/reserve_category\/1?_format=hal_json" + }, + "type": { + "href": "http:\/\/drupal.org\/rest\/type\/reserve_category\/reserve_category" + }, + "http:\/\/drupal.org\/rest\/relation\/reserve_category\/reserve_category\/user_id": [ + { + "href": "http:\/\/default\/user\/1?_format=hal_json", + "lang": "en" + } + ] + }, + "id": [ + { + "value": 1 + } + ], + "uuid": [ + { + "value": "148994b4-dece-4323-9dfa-d6e9f86b3395" + } + ], + "langcode": [ + { + "value": "en", + "lang": "en" + } + ], + "_embedded": { + "http:\/\/drupal.org\/rest\/relation\/reserve_category\/reserve_category\/user_id": [ + { + "_links": { + "self": { + "href": "http:\/\/default\/user\/1?_format=hal_json" + }, + "type": { + "href": "http:\/\/drupal.org\/rest\/type\/user\/user" + } + }, + "uuid": [ + { + "value": "1b16703e-48a1-4e8b-9611-3b1efbafb90d" + } + ], + "lang": "en" + } + ] + }, + "name": [ + { + "value": "Large" + } + ], + "status": [ + { + "value": true + } + ], + "created": [ + { + "value": "2018-01-02T23:05:14+00:00", + "format": "Y-m-d\\TH:i:sP" + } + ], + "changed": [ + { + "value": "2018-01-02T23:05:14+00:00", + "format": "Y-m-d\\TH:i:sP" + } + ], + "default_langcode": [ + { + "value": true, + "lang": "en" + } + ], + "reserve_setup_buffer": [ + { + "value": 0 + } + ], + "reserve_takedown_buffer": [ + { + "value": 0 + } + ] +} \ No newline at end of file diff --git a/content/reserve_category/small.json b/content/reserve_category/small.json new file mode 100644 index 0000000..884dc42 --- /dev/null +++ b/content/reserve_category/small.json @@ -0,0 +1,90 @@ +{ + "_links": { + "self": { + "href": "http:\/\/default\/admin\/structure\/reserve_category\/2?_format=hal_json" + }, + "type": { + "href": "http:\/\/drupal.org\/rest\/type\/reserve_category\/reserve_category" + }, + "http:\/\/drupal.org\/rest\/relation\/reserve_category\/reserve_category\/user_id": [ + { + "href": "http:\/\/default\/user\/1?_format=hal_json", + "lang": "en" + } + ] + }, + "id": [ + { + "value": 2 + } + ], + "uuid": [ + { + "value": "89a908f8-e90d-4536-a747-c411598fad89" + } + ], + "langcode": [ + { + "value": "en", + "lang": "en" + } + ], + "_embedded": { + "http:\/\/drupal.org\/rest\/relation\/reserve_category\/reserve_category\/user_id": [ + { + "_links": { + "self": { + "href": "http:\/\/default\/user\/1?_format=hal_json" + }, + "type": { + "href": "http:\/\/drupal.org\/rest\/type\/user\/user" + } + }, + "uuid": [ + { + "value": "1b16703e-48a1-4e8b-9611-3b1efbafb90d" + } + ], + "lang": "en" + } + ] + }, + "name": [ + { + "value": "Small" + } + ], + "status": [ + { + "value": true + } + ], + "created": [ + { + "value": "2018-01-10T20:41:23+00:00", + "format": "Y-m-d\\TH:i:sP" + } + ], + "changed": [ + { + "value": "2018-01-10T20:41:23+00:00", + "format": "Y-m-d\\TH:i:sP" + } + ], + "default_langcode": [ + { + "value": true, + "lang": "en" + } + ], + "reserve_setup_buffer": [ + { + "value": 0 + } + ], + "reserve_takedown_buffer": [ + { + "value": 0 + } + ] +} \ No newline at end of file diff --git a/content/reserve_category/video.json b/content/reserve_category/video.json new file mode 100644 index 0000000..5891c02 --- /dev/null +++ b/content/reserve_category/video.json @@ -0,0 +1,90 @@ +{ + "_links": { + "self": { + "href": "http:\/\/default\/admin\/structure\/reserve_category\/5?_format=hal_json" + }, + "type": { + "href": "http:\/\/drupal.org\/rest\/type\/reserve_category\/reserve_category" + }, + "http:\/\/drupal.org\/rest\/relation\/reserve_category\/reserve_category\/user_id": [ + { + "href": "http:\/\/default\/user\/1?_format=hal_json", + "lang": "en" + } + ] + }, + "id": [ + { + "value": 5 + } + ], + "uuid": [ + { + "value": "a5eea3e2-e717-4428-9d86-fe0681daf653" + } + ], + "langcode": [ + { + "value": "en", + "lang": "en" + } + ], + "_embedded": { + "http:\/\/drupal.org\/rest\/relation\/reserve_category\/reserve_category\/user_id": [ + { + "_links": { + "self": { + "href": "http:\/\/default\/user\/1?_format=hal_json" + }, + "type": { + "href": "http:\/\/drupal.org\/rest\/type\/user\/user" + } + }, + "uuid": [ + { + "value": "1b16703e-48a1-4e8b-9611-3b1efbafb90d" + } + ], + "lang": "en" + } + ] + }, + "name": [ + { + "value": "Video" + } + ], + "status": [ + { + "value": true + } + ], + "created": [ + { + "value": "2018-01-11T06:43:23+00:00", + "format": "Y-m-d\\TH:i:sP" + } + ], + "changed": [ + { + "value": "2018-01-11T06:43:23+00:00", + "format": "Y-m-d\\TH:i:sP" + } + ], + "default_langcode": [ + { + "value": true, + "lang": "en" + } + ], + "reserve_setup_buffer": [ + { + "value": 0 + } + ], + "reserve_takedown_buffer": [ + { + "value": 0 + } + ] +} \ No newline at end of file diff --git a/css/reserve-calendar.css b/css/reserve-calendar.css new file mode 100644 index 0000000..16fa02f --- /dev/null +++ b/css/reserve-calendar.css @@ -0,0 +1,259 @@ +/* ----------------------------- ROOM RESERVATIONS ---------------------------- */ + +#rooms-calendar .slink { + margin-right: 40px; +} + +.reservations-block ul { + list-style-image: url(images/bullet.gif); +} + +.reservations-block #reservations { + margin-bottom: 5px; +} + +#rooms-calendar .room-tabs { + margin: 0 0 0 7px; + padding: 0; +} + +#rooms-calendar ul li, +#rooms-calendar ul.menu li, +#rooms-calendar .item-list ul li, +#rooms-calendar li.leaf { + background: none; + padding: 0 0.75em 0.2em 0.75em; +} + +#rooms-calendar .room-tabs li { + float: left; + list-style: none; + margin: 0 0 -3px 0; + padding: 2px 3px; +} + +#rooms-calendar .room-tabs li a { + background: #dddddd; + border: 1px solid #999999; + border-top-left-radius: 7px; + border-top-right-radius: 7px; + display: block; + -moz-border-radius-topleft: 7px; + -moz-border-radius-topright: 7px; + padding: 3px 5px; + text-decoration: none; + -webkit-border-top-left-radius: 7px; + -webkit-border-top-right-radius: 7px; +} + +#rooms-calendar .room-tabs li a.active { + background: #ffffff; + border-bottom: 1px solid #ffffff; +} + +#rooms-calendar .panelContainer { + border: 1px solid #999; + clear: left; +} + +#rooms-calendar .panelContainer:after { + content: " "; + display: table; + clear: both; + min-height: 20px; +} + +#rooms-calendar .panel { + padding: 0 10px; +} + +#rooms-calendar #tabbedPanels .date { + clear: both; + margin: 0; + text-align: center; +} + +#rooms-calendar #tabbedPanels .hours { + color: #666666; + font-weight: bold; + margin-bottom: 5px; + text-align: center; +} + +#rooms-calendar #date-change, +#rooms-calendar .reserve-center { + text-align: center; +} + +#rooms-calendar .panel .gcolumns .grid-column { + float: left; +} + +#rooms-calendar div.grid-column { + max-width: 113px; +} + +#rooms-calendar .panel .gcolumns ul { + padding: 0 1px; +} + +#rooms-calendar .panel .gcolumns li { + border: 1px solid #dddddd; + font-size: 85%; + height: 1.33em; + line-height: 15px; + list-style-type: none; + /* margin: -1px auto 0; */ + padding: 2px 0; + text-align: center; +} + +/*#rooms-calendar .panel .gcolumns li.odd { + border-top: 1px solid #999999; +} +*/ +#rooms-calendar .panel .gcolumns li.room-info { + border: none; +} + +#rooms-calendar .panel .gcolumns li.room-info-heading { + padding-bottom: 5px; +} + +#rooms-calendar .panel .gcolumns li.room-info-footer { + padding-top: 5px; +} + +#rooms-calendar .panel .gcolumns .hours-column li { + width: 112px; +} + +#rooms-calendar .panel .gcolumns .room-column li { + width: 115px; +} + +#rooms-calendar .panel .gcolumns li.timeslot { + background-color: #fff; + *height: 1.33em; + *margin-bottom: 2px; +} + +#rooms-calendar .panel .gcolumns li.reservable { + background-color: #fff; +} + +#rooms-calendar .panel .gcolumns li.open { + background-color: #fff; + width: 112px !important; +} + +#rooms-calendar .panel .gcolumns li.booked { + background-color: #ddd; + /*width: 109px !important;*/ +} + +#rooms-calendar .gcolumns li.setup { + background-color: #eee; +} + +#rooms-calendar .panel .gcolumns li.closed { + background-color: #d1dfdf; + border: 1px solid #d1dfdf; +} + +#rooms-calendar .hide { + display: none; +} + +.page-room-reservations .form-item label { + display: inline; +} + +#txtmsg-fields { + display: none; +} + +#date-change { + margin-bottom:20px; +} +.clear {clear:both;} + +/* QTIP Overrides */ + +.qtip { + box-shadow: -2px 2px 8px rgba(0,0,0,0.3); +} + +.qtip canvas { + display: none; + width: 400px; +} +.qtip-link, .qtip-additional-element { + border-bottom: 1px dotted #999; +} + +.qtip h4 { + margin: 0px; + margin-top: 0.5em; + font-size: 12px; + color: #999; + font-family: 'AvenirNextLTW01-Regular', Arial; +} + +.qtip-tooltip { + font-family: 'AvenirNextLTW01-Regular', Arial; + font-size: 12px; + margin-bottom: 0.5em; + width: 400px; +} + +/*! Light tooltip style */ +.ui-tooltip-light .ui-tooltip-titlebar, +.ui-tooltip-light .ui-tooltip-content{ + border-color: transparent; + color: #333; +} + +.ui-tooltip-light .ui-tooltip-content { + background-color: white; + width: 270px !important; +} + +.ui-tooltip-light .ui-tooltip-titlebar { + width: 244px !important; +} + + +#ui-datepicker-div { + z-index:2 !important; +} + +#rooms-calendar ul li.reservable:hover, #rooms-calendar ul li.open:hover { + background-color:#FFF; +} + +#rooms-calendar .panel .gcolumns li.reservable { + text-align:center; + vertical-align:middle; + background-color:#fff; + border:1px solid #ccc; +} + +#rooms-calendar .panelContainer .grid-column li.highlighted { + background-color:#999; +} + +#rooms-calendar .reserve-group { + /*border: 1px solid blue;*/ + outline: 1px solid blue; + width: 111px; + position: relative; +} + +#rooms-calendar .panel .gcolumns .reserve-series li.booked { + background-color: lightgreen; +} + +.gcolumns a, .gcolumns a.link { + border-bottom: none !important; +} \ No newline at end of file diff --git a/css/reserve-calendar.css-bak b/css/reserve-calendar.css-bak new file mode 100644 index 0000000..60e3bd4 --- /dev/null +++ b/css/reserve-calendar.css-bak @@ -0,0 +1,252 @@ +/* ----------------------------- ROOM RESERVATIONS ---------------------------- */ + +#rooms-calendar .slink { + margin-right: 40px; +} + +.reservations-block ul { + list-style-image: url(images/bullet.gif); +} + +.reservations-block #reservations { + margin-bottom: 5px; +} + +#rooms-calendar .room-tabs { + margin: 0 0 0 7px; + padding: 0; +} + +#rooms-calendar ul li, +#rooms-calendar ul.menu li, +#rooms-calendar .item-list ul li, +#rooms-calendar li.leaf { + background: none; + padding: 0 0.75em 0.2em 0.75em; +} + +#rooms-calendar .room-tabs li { + float: left; + list-style: none; + margin: 0 0 -3px 0; + padding: 2px 3px; +} + +#rooms-calendar .room-tabs li a { + background: #dddddd; + border: 1px solid #999999; + border-top-left-radius: 7px; + border-top-right-radius: 7px; + display: block; + -moz-border-radius-topleft: 7px; + -moz-border-radius-topright: 7px; + padding: 3px 5px; + text-decoration: none; + -webkit-border-top-left-radius: 7px; + -webkit-border-top-right-radius: 7px; +} + +#rooms-calendar .room-tabs li a.active { + background: #ffffff; + border-bottom: 1px solid #ffffff; +} + +#rooms-calendar .panelContainer { + border: 1px solid #999; + clear: left; +} + +#rooms-calendar .panelContainer:after { + content: " "; + display: table; + clear: both; + min-height: 20px; +} + +#rooms-calendar .panel { + padding: 0 10px; +} + +#rooms-calendar #tabbedPanels .date { + clear: both; + margin: 0; + text-align: center; +} + +#rooms-calendar #tabbedPanels .hours { + color: #666666; + font-weight: bold; + margin-bottom: 5px; + text-align: center; +} + +#rooms-calendar #date-change, +#rooms-calendar .reserve-center { + text-align: center; +} + +#rooms-calendar .panel .gcolumns .grid-column { + float: left; +} + +#rooms-calendar div.grid-column { + max-width: 115px; +} + +#rooms-calendar .panel .gcolumns ul { + padding: 0 1px; +} + +#rooms-calendar .panel .gcolumns li { + border: 1px solid #dddddd; + font-size: 85%; + height: 1.33em; + line-height: 15px; + list-style-type: none; + margin: -1px auto 0; + padding: 2px 0; + text-align: center; +} + +/*#rooms-calendar .panel .gcolumns li.odd { + border-top: 1px solid #999999; +} +*/ +#rooms-calendar .panel .gcolumns li.room-info { + border: none; +} + +#rooms-calendar .panel .gcolumns li.room-info-heading { + padding-bottom: 5px; +} + +#rooms-calendar .panel .gcolumns li.room-info-footer { + padding-top: 5px; +} + +#rooms-calendar .panel .gcolumns .hours-column li { + width: 120px; +} + +#rooms-calendar .panel .gcolumns .room-column li { + width: 115px; +} + +#rooms-calendar .panel .gcolumns li.timeslot { + background-color: #fff; + *height: 1.33em; + *margin-bottom: 2px; +} + +#rooms-calendar .panel .gcolumns li.reservable { + background-color: #fff; +} + +#rooms-calendar .panel .gcolumns li.open { + background-color: #fff; +} + +#rooms-calendar .panel .gcolumns li.booked { + background-color: #ddd; +} + +#rooms-calendar .gcolumns li.setup { + background-color: #eee; +} + +#rooms-calendar .panel .gcolumns li.closed { + background-color: #d1dfdf; + border: 1px solid #d1dfdf; +} + +#rooms-calendar .hide { + display: none; +} + +.page-room-reservations .form-item label { + display: inline; +} + +#txtmsg-fields { + display: none; +} + +#date-change { + margin-bottom:20px; +} +.clear {clear:both;} + +/* QTIP Overrides */ + +.qtip { + box-shadow: -2px 2px 8px rgba(0,0,0,0.3); +} + +.qtip canvas { + display: none; + width: 400px; +} +.qtip-link, .qtip-additional-element { + border-bottom: 1px dotted #999; +} + +.qtip h4 { + margin: 0px; + margin-top: 0.5em; + font-size: 12px; + color: #999; + font-family: 'AvenirNextLTW01-Regular', Arial; +} + +.qtip-tooltip { + font-family: 'AvenirNextLTW01-Regular', Arial; + font-size: 12px; + margin-bottom: 0.5em; + width: 400px; +} + +/*! Light tooltip style */ +.ui-tooltip-light .ui-tooltip-titlebar, +.ui-tooltip-light .ui-tooltip-content{ + border-color: transparent; + color: #333; +} + +.ui-tooltip-light .ui-tooltip-content { + background-color: white; + width: 270px !important; +} + +.ui-tooltip-light .ui-tooltip-titlebar { + width: 244px !important; +} + + +#ui-datepicker-div { + z-index:2 !important; +} + +#rooms-calendar ul li.reservable:hover, #rooms-calendar ul li.open:hover { + background-color:#FFF; +} + +#rooms-calendar .panel .gcolumns li.reservable { + text-align:center; + vertical-align:middle; + background-color:#fff; + border:1px solid #ccc; +} + +#rooms-calendar .panelContainer .grid-column li.highlighted { + background-color:#999; +} + +#rooms-calendar .reserve-group { + border: 1px solid blue; + padding: 1px 0 1px 0 !important; + width: 122px !important; +} + +#rooms-calendar .panel .gcolumns .reserve-series li.booked { + background-color: lightgreen; +} diff --git a/images/arrow-icon.png b/images/arrow-icon.png new file mode 100644 index 0000000..18d4455 Binary files /dev/null and b/images/arrow-icon.png differ diff --git a/images/clear.png b/images/clear.png new file mode 100644 index 0000000..8070099 Binary files /dev/null and b/images/clear.png differ diff --git a/js/reserve.js b/js/reserve.js new file mode 100644 index 0000000..652f777 --- /dev/null +++ b/js/reserve.js @@ -0,0 +1,135 @@ +(function ($, Drupal) { + + Drupal.behaviors.reserve = { + attach: function (context, settings) { + // set default Category tab if one is set in URL anchor + var anchor = window.location.hash; + if (anchor) { + $('.room-tabs a.active').removeClass('active'); + $('.room-tabs li a[href=' + anchor + ']').addClass('active'); + $('.panel').hide(); + $(anchor).show(); + } + + // show the selected category panel + $('.room-tabs a').click(function () { + $this = $(this); + $('.panel').hide(); + $('.room-tabs a.active').removeClass('active'); + $this.addClass('active').blur(); + var panel = $this.attr('href'); + $(panel).fadeIn(250); + return false; + }); + + // change calendar date displayed + $('#edit-date').change(function () { + var datebits = $(this).val().split('-'); + var formatarr = drupalSettings.reserve.dateFormat.split('/'); + var dateobj = new Object(); + $.each(formatarr, function (index, value) { + dateobj[value] = datebits[index]; + }); + var val = dateobj.m + '/' + dateobj.d; + var newpath = '/reserve/' + drupalSettings.reserve.ebundle + '/calendar/' + val; + window.location.href = newpath; + }); + + // show form fields for text message confirmation and reminder + $('#edit-textmsg').each(function () { + if ($(this).attr('checked')) { + $('#txtmsg-fields').slideDown('fast'); + } + else { + $('#txtmsg-fields').slideUp('fast'); + } + }); + $('#edit-textmsg').click(function () { + if ($(this).attr('checked')) { + $('#txtmsg-fields').slideDown('fast'); + } + else { + $('#txtmsg-fields').slideUp('fast'); + } + }); + + var isMouseDown = false, isHighlighted; + var maxLength = drupalSettings.reserve.maxLength / 30; + $("#rooms-calendar .panel li.reservable") + + .mousedown(function () { + isMouseDown = true; + // original code used toggleClass; but addClass works better + $(this).addClass("highlighted"); + isHighlighted = $(this).hasClass("highlighted"); + return false; // prevent text selection + }) + + .mouseover(function () { + if (isMouseDown) { + $(this).addClass("highlighted", isHighlighted); + + // Limit the selection of cells equal to maxLength parameter + var limitselect = document.querySelectorAll('.highlighted').length - 1; + if (limitselect >= maxLength) { + isMouseDown = false; + } + + // Disable further selection when drag on lunch / booked time slot + $('.booked').mouseover(function () { + isMouseDown = false; + }); + $('.closed').mouseover(function () { + isMouseDown = false; + }); + + // Restrict horizontal selection + $('.grid-column').mouseleave(function () { + isMouseDown = false; + }); + } + }) + + .bind("selectstart", function () { + return false; + }) + + .mouseup(function () { + isMouseDown = false; + var link = $('li.highlighted:first a'); + var count = $('li.highlighted').length; + var href = $(link).attr('href'); + + // not sure why the count == 0 case is required; but it occurs 2nd+ times modal is opened + if (count == 0) return false; + if (count == 1) { + $('li.highlighted:first a').click(); + } + else { + var callback = 'reserve/ajax/reservation_add'; + $.ajax({ + async: false, + url: Drupal.url(callback), + data: { + count: count, + path: href + }, + //dataType: 'json', + success: function success(data) { + if (data) { + var $myDialog = $('
' + data + '
').appendTo('body'); + var options = { + title: 'Add Reservation', + width: 700, + }; + Drupal.dialog($myDialog, options).showModal(); + } + } + }); + } + + $("#rooms-calendar .panel li.reservable").removeClass("highlighted"); + }); + } + }; +})(jQuery, Drupal); diff --git a/reservation.inc b/reservation.inc new file mode 100644 index 0000000..0db3e7c --- /dev/null +++ b/reservation.inc @@ -0,0 +1,252 @@ +getPath(); + $path_args = explode('/', $current_path); + + // for case of std reservation add form - this should likely be blocked; but leave for development + if (count($path_args) <= 3) return; + + $length = 30; + // for modal form call + if ($path_args[2] == 'ajax') { + $path_args = explode('/', $_GET['path'] ); + $length = 30 * $_GET['count']; + }; + + //if (user_access('administer site configuration') && isset($_GET['edit']) && $_GET['edit'] == 'standard') { + // return; + //} + + // EDIT + // only allow editing: Group Name, Private, Duration (shorten or longer if available to extend) + // also, allow Cancelling + $edit = false; + if ($path_args[3] == 'edit') { + $edit = true; + $res = $form_state->getFormObject()->getEntity(); + $rid = $res->id(); + $eid = $res->reservable_id->getString(); + $entity_type = $res->reservable_content_type->getString(); + $series_id = $res->reservation_series_id->getString(); + $d = $res->reservation_date->getString(); + $yyyymmdd = date('Y-m-d', strtotime($d)); + $t = $res->reservation_time->getString(); + } + + // CREATE NEW + else { + $month = $path_args[3]; + $day = $path_args[4]; + $t = $path_args[5]; + $eid = $path_args[6]; + $ebundle = $path_args[7]; + $entity_type = ebundle_split($ebundle, 'type'); + // determine if this year or next year + $year = reserve_which_year($month, $day); + $yyyymmdd = date('Y-m-d', strtotime($year . '-' . $month . '-' . $day)); + $d = $yyyymmdd . ' 00:00:00'; + } + + $entity = entity_load($entity_type, $eid); + $bundle = $entity->bundle(); + $date = date('l, M d, Y', strtotime($d)); + $time = reserve_display_time($t); + $bundle_info = \Drupal::service("entity_type.bundle.info")->getAllBundleInfo(); + $bundleName = $bundle_info[$entity->getEntityTypeId()][$entity->bundle()]['label']; + + // set default Group Name as name of current user + $form['name']['widget'][0]['value']['#default_value'] = $form['name']['widget'][0]['value']['#default_value'] ? + $form['name']['widget'][0]['value']['#default_value'] : + \Drupal::currentUser()->getDisplayName(); + + // set values taken from URL + $form['reservation_date']['widget'][0]['value']['#default_value'] = DrupalDateTime::createFromTimestamp(strtotime($yyyymmdd)); + $form['reservation_time']['widget'][0]['value']['#default_value'] = $t; + $form['reservation_length']['widget']['#default_value'] = $form['reservation_length']['widget']['#default_value'] ? + $form['reservation_length']['widget']['#default_value'] : + $length; + $form['reservable_id']['widget'][0]['value']['#default_value'] = $entity->id(); + $form['reservable_content_type']['widget']['#default_value'] = $entity_type; + $form['reservation_ebundle']['widget'][0]['value']['#default_value'] = isset($form['reservation_ebundle']['widget'][0]['value']['#default_value']) ? + $form['reservation_ebundle']['widget'][0]['value']['#default_value'] : + $ebundle; + + if (!$GLOBALS['debug']) { + // hide fields that have been filled from URL + $form['reservation_date']['#access'] = false; + $form['reservation_time']['#access'] = false; + $form['user_id']['#access'] = false; + $form['reservable_id']['#access'] = false; + $form['reservable_content_type']['#access'] = false; + + // in prep for having better lockout of having slot taken by another user; let's also hide Length + $form['reservation_length']['#access'] = false; + + // hide other fields we don't want to show + $form['user_id']['#access'] = false; + $form['reservation_series_id']['#access'] = false; + $form['reservation_ebundle']['#access'] = false; + } + + // if we are editing; let's do some extra things: + // - disable Repeat options + // - (series) add msg that we are editing a series and link to edit just that entry + if ($edit) { + //$bundle = $entity->getType(); + $ebundle = $entity_type . '.' . $bundle; + + // Special handling for Series reservations + if ($series_id) { + $modal = ['attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => json_encode(['width' => 700]) + ]]; + // if we are in a Series, lets disable selectors so we can still show what type of series, but not allow changing + $form['reservation_repeat_type']['#disabled'] = TRUE; + $form['reservation_repeat_until']['#disabled'] = TRUE; + + if (isset($_GET['single'])) { + $series_link = Link::fromTextAndUrl(t('Click here'), Url::fromUri('internal:/reserve_reservation/' . $rid . '/edit', $modal))->toString(); + $message = t('NOTE: you are editing a SINGLE day in a SERIES of reservations. Any changes made here will impact only the reservation + for this day. %link if you want to edit the entire series.', array('%link' => $series_link)); + // relabel Delete + $form['actions']['delete']['#title'] = t('Cancel Reservation for This Day'); + } + else { + $single_link = Link::fromTextAndUrl('Click here', Url::fromUri('internal:/reserve_reservation/' . $rid . '/edit', + ['query' => ['single' => 1]] + $modal))->toString(); + $message = t('NOTE: you are editing a SERIES of reservations. Any changes made here will impact all reservations in this + series. %link if you only want to edit this specific day in this series.', array('%link' => $single_link)); + // remove single node delete and add Delete Series button + //unset($form['actions']['delete']); + /*$form['actions']['delete_series'] = array( + '#type' => 'submit', + '#value' => t('Cancel Entire Reservation Series'), + '#weight' => 20, + '#submit' => array('_reserve_series_delete'), + );*/ + $form['series'] = ['#type' => 'hidden', '#value' => true]; + $form['actions']['delete']['#title'] = t('Cancel Entire Reservation Series'); + + // not sure any other way to pass info from here to delete hook + $_SESSION['reserve_delete_type'] = 'series'; + } + + // add psuedo message for Series forms. Can't use std drupal messages as they do not work in modals + $form['message'] = [ + '#type' => 'markup', + '#weight' => -25, + '#markup' => ' +
+
', + ]; + } + else { + // but if not in Series, lets remove altogether + $form['reservation_repeat_type']['#access'] = FALSE; + $form['reservation_repeat_until']['#access'] = FALSE; + // and if not part of a series; let's change DELETE button + $form['actions']['delete']['#title'] = t('Cancel Reservation'); + } + } + + // this is required to post drupal_messages as this is not in modal tpl by default + $form['status_messages'] = [ + '#type' => 'status_messages', + '#weight' => -50, + ]; + + $lengthDisplay = $form['reservation_length']['widget']['#options'][$length]; + $form['reservation_heading'] = [ + '#type' => 'markup', + '#weight' => -20, + '#markup' => ' +

' . t('Reservation booking for') . ':

' . $bundleName . ': ' . $entity->label() . '
+
' . t('Date') . ': ' . $date . '
+
' . t('Time') . ': ' . $time . '
+
' . t('Length') . ': ' . $lengthDisplay . '
+
', + ]; + + // always redirect back to reservations calendar page + $form['ebundle'] = ['#type' => 'hidden', '#value' => $ebundle]; + $form['month'] = ['#type' => 'hidden', '#value' => date('m', strtotime($yyyymmdd))]; + $form['day'] = ['#type' => 'hidden', '#value' => date('d', strtotime($yyyymmdd))]; + $form['actions']['submit']['#submit'][] = 'reserve_return_to_reservations_page'; + + // limit valid lengths so we have no overlaps + // this really only applies for EDIT now as we pick length in UI for CREATE + $validlengths = reserve_valid_lengths($eid, $ebundle, $yyyymmdd, $t); + foreach ($validlengths as $length) { + if ($length['is_valid']) { + $lengths[] = $length['length']; + } + } + foreach ($form['reservation_length']['widget']['#options'] as $slot => &$option) { + if (!in_array($slot, $lengths)) { + unset($form['reservation_length']['widget']['#options'][$slot]); + } + } + + $form['reservation_repeat_until']['#states'] = array( + 'visible' => array( + ':input[name="reservation_repeat_type"]' => array('!value' => '0'), + ), + ); + + return; +} + +/* + * redirect Delete confirmation form back to Calendar + * there should be a pass arg info to this form like we do from edit form; but until i figure that out.. + */ +function reserve_form_reserve_reservation_delete_form_alter(array &$form, FormStateInterface $form_state) { + // Retrieve an array which contains the path pieces. + $refer_path = $_SERVER[HTTP_REFERER]; + $path_args = explode('/', $refer_path); + + $month = $path_args[6]; + $day = $path_args[7]; + $ebundle = $path_args[4]; + + $year = reserve_which_year($month, $day); + $yyyymmdd = date('Y-m-d', strtotime($year . '-' . $month . '-' . $day)); + + $form['ebundle'] = ['#type' => 'hidden', '#value' => $ebundle]; + $form['month'] = ['#type' => 'hidden', '#value' => date('m', strtotime($yyyymmdd))]; + $form['day'] = ['#type' => 'hidden', '#value' => date('d', strtotime($yyyymmdd))]; + + $form['actions']['submit']['#submit'][] = 'reserve_return_to_reservations_page'; +} + +/** + * Custom submit handler for login form. + */ +function reserve_return_to_reservations_page($form, FormStateInterface $form_state) { + $arguments = [ + 'ebundle' => $form_state->getValue('ebundle'), + 'selected_month' => $form_state->getValue('month'), + 'selected_day' => $form_state->getValue('day'), + ]; + $form_state->setRedirect('reserve.calendar', $arguments); +} \ No newline at end of file diff --git a/reserve.inc b/reserve.inc new file mode 100644 index 0000000..f3e2145 --- /dev/null +++ b/reserve.inc @@ -0,0 +1,1128 @@ +getSettings(); + $ids = array_filter($fconfig['categories']); + } + else { + $ids = Drupal::service('entity.query') + ->get('reserve_category') + ->condition('status', TRUE) + //->sort('reserve_display_order', 'ASC') + ->execute(); + } + + $cats = \Drupal::entityTypeManager()->getStorage('reserve_category')->loadMultiple($ids); + foreach ($cats as $cat) { + $categories[$cat->id()] = array( + 'id' => $cat->id(), + 'title' => $cat->label(), + 'prebuffer' => $cat->reserve_setup_buffer->getString() ? $cat->reserve_setup_buffer->getString() : 0, + 'postbuffer' => $cat->reserve_takedown_buffer->getString() ? $cat->reserve_takedown_buffer->getString() : 0, + 'minadvstd' => $cat->reserve_minadv_std->getString() ? $cat->reserve_minadv_std->getString() : 0, + 'minadvext' => $cat->reserve_minadv_ext->getString() ? $cat->reserve_minadv_ext->getString() : 0, + 'maxadvstd' => $cat->reserve_maxadv_std->getString() ? $cat->reserve_maxadv_std->getString() : 14, + 'maxadvext' => $cat->reserve_maxadv_ext->getString() ? $cat->reserve_maxadv_ext->getString() : 180, + ); + } + + return $categories; +} + +/** + * Retrieve all the reservable entities from the database of a certain bundle. + * + * + * @return array + * An array with each element representing a reservable entity. + */ +function reserve_entities($ebundle) { + $entity_type = ebundle_split($ebundle, 'type'); + $bundle = ebundle_split($ebundle, 'bundle'); + $query = Drupal::service('entity.query') + ->get($entity_type) + ->condition('status', TRUE); + + if ($entity_type != $bundle) { + $query->condition('type', $bundle); + } + + //->sort('reservations_display_order', 'ASC') + $ids = $query->execute(); + + $entities = \Drupal::entityTypeManager()->getStorage($entity_type)->loadMultiple($ids); + + return $entities; +} + +/** + * Determine the list of next 12 months. + * + * @return array + * Each element represents a month. + * + */ +function reserve_current_months() { + $advance_days = 365; + $found = false; + for ($x = 0; $x < $advance_days; $x++) { + if (date('m', mktime(0, 0, 0, date("m"), date("d") + $x, date("Y"))) != $found) { + $months[date('Y-m', mktime(0, 0, 0, date("m"), date("d") + $x, date("Y")))]['m'] = date('n', mktime(0, 0, 0, date("m"), date("d") + $x, date("Y"))); + $months[date('Y-m', mktime(0, 0, 0, date("m"), date("d") + $x, date("Y")))]['mm'] = date('m', mktime(0, 0, 0, date("m"), date("d") + $x, date("Y"))); + $months[date('Y-m', mktime(0, 0, 0, date("m"), date("d") + $x, date("Y")))]['y'] = date('Y', mktime(0, 0, 0, date("m"), date("d") + $x, date("Y"))); + $found = date('m', mktime(0, 0, 0, date("m"), date("d") + $x, date("Y"))); + } + } + + // Note: Month names are translated when they are displayed. + $names = array( + 'Unused', + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ); + + foreach ($months as $month) { + $item['display'] = $names[$month['m']] . ' ' . $month['y']; + $item['year'] = $month['y']; + $item['month'] = $month['m']; + $item['MM'] = $month['mm']; + $item['YYYY_MM'] = $month['y'] . '_' . $month['mm']; + $results[] = $item; + } + return $results; +} + +/** + * Returns default daily open hours for the month. + * + * @param int $year + * The year of the month being represented. + * @param int $month + * The year and month of the month being represented in format YYYY_MM. + * + * @return object + * An array detailing the DEFAULT open slots for each day of a given month + */ +function reserve_default_monthly_hours($year, $month) { + $mo_hours = array(); + + // Days in the month. + $days = date('t', mktime(0, 0, 0, $month, 1, $year)); + + $default_hours = \Drupal::config('reserve.default_hours')->get('data'); + if (!$default_hours) { + for ($x = 0; $x < $days; $x++) { + $mo_hours[] = 'D'; + $mo_hours[] = '9999'; + $mo_hours[] = '9999'; + $mo_hours[] = '9999'; + $mo_hours[] = '9999'; + } + } + else { + for ($x = 0; $x < $days; $x++) { + // Day of week. + $dow = date('w', mktime(0, 0, 0, $month, $x + 1, $year)); + $mo_hours[] = 'D'; + $mo_hours[] = $default_hours[$dow * 4]; + $mo_hours[] = $default_hours[($dow * 4) + 1]; + $mo_hours[] = $default_hours[($dow * 4) + 2]; + $mo_hours[] = $default_hours[($dow * 4) + 3]; + } + } + + return $mo_hours; +} + +/** + * Create reservations dates array. + * + * Create an array containing pertinent information about all the + * possible days for which a reservation can be made. + * + * 7.x-1.3 REV: + * - start of adding features on a per Category basis + * - for now let's just key this date's array by Cat ID + * + * @param int $selected_month + * The month of the day currently selected by the user + * @param int $selected_day + * The day of the month of the day currently selected by the user. + * + * @return array + * Information about each day for which a reservation can be made, including + * display name, day of the week, month name and number, day of the month, + * date in the format YYYY-MM-DD, whether the day is currently selected by + * the user, and whether the day is today. + */ +function reserve_dates($ebundle = null, $selected_month = NULL, $selected_day = NULL, $keyed = false) { + $config = \Drupal::config('reserve.settings'); + // Determine date information (month, day, year, etc.) for each of these days. + $categories = reserve_categories($ebundle); + $dates = array(); + foreach ($categories as $cat) { + $extended = \Drupal::currentUser()->hasPermission('add reservations extended'); + $advancedaysmax = $extended ? $cat['maxadvext'] : $cat['maxadvstd']; + $advancedaysmin = $extended ? $cat['minadvext'] : $cat['minadvstd']; + for ($j = $advancedaysmin; $j < $advancedaysmax; $j++) { + $day = array(); + $day['display'] = date("l, n/j", strtotime("now + " . $j . " days")); + $day['day-of-week'] = date("l", strtotime("now + " . $j . " days")); + $day['month'] = date("F", strtotime("now + " . $j . " days")); + $month_number = date("n", strtotime("now + " . $j . " days")); + $day['month-number'] = $month_number; + $sday = date("j", strtotime("now + " . $j . " days")); + $day['day'] = $sday; + $year = date("Y", strtotime("now + " . $j . " days")); + $day['year'] = $year; + // Determine the date selected by the user. If none selected, default to the first day. + if (($j == 0) && (!$selected_month) && (!$selected_day)) { + $day['selected'] = TRUE; + } + elseif (($selected_month == $month_number) && ($selected_day == $sday)) { + $day['selected'] = TRUE; + } + else { + $day['selected'] = FALSE; + } + // The date in YYYY-MM-DD format. + if ($month_number < 10) { + $month_number = str_pad($month_number, 2, '0', STR_PAD_LEFT); + } + if ($sday < 10) { + $sday = str_pad($sday, 2, '0', STR_PAD_LEFT); + } + $day['yyyymmdd'] = $year . "-" . $month_number . "-" . $sday; + $day['today'] = (($day['month-number'] == date('m')) && + ($day['day'] == date('j'))) ? TRUE : FALSE; + if ($keyed) { + $dates[$cat['id']][$day['yyyymmdd']] = $day; + } + else { + $dates[$cat['id']][] = $day; + } + } + } + return $dates; +} + +/** + * Create an array representing every half hour time slot in a single day. + * + * @param string $option + * If set to 'limited', only include time slots in the array that are + * later in the day than the current time minus the longest possible + * reservation length. + * + * @return array + * An array representing reservable time slots in a single day. + */ +function reserve_hours($option = NULL) { + $config = \Drupal::config('reserve.settings'); + $hours = array(); + $x = 0; + $y = 0; + while ($x <= 23) { + $hours_entry = array(); + $hour = ($x < 10) ? '0' . $x : $x; + if ($x == 0) { + $display_hour = 12; + } + elseif ($x <= 12) { + $display_hour = $x; + } + else { + $display_hour = $x - 12; + } + $minutes = ($y % 2) ? '30' : '00'; + $time = $hour . $minutes; + $ampm = ($y < 24) ? t('AM') : t('PM'); + if ($y == 0) { + // these shouldn't be wrapped in t() since we are just about to do a date() with them anyway + $display = 'Midnight'; + } + elseif ($y == 24) { + $display = 'Noon'; + } + else { + $display = $display_hour . ':' . $minutes . ' ' . $ampm; + } + + // convert display to 24:00 format if required + if ($config->get('reserve_hour_format')) { + $display = date('H:i', strtotime($display)); + } + + $class = ($y % 2) ? 'even' : 'odd'; + $hours_node_time = $display_hour . ':' . $minutes . $ampm; + $hours_entry['time'] = $time; + $hours_entry['display'] = $display; + $hours_entry['hours node time'] = $hours_node_time; + $hours_entry['class'] = $class; + $hours_entry['open'] = TRUE; + $hours[] = $hours_entry; + if ($y % 2) { + $x++; + } + $y++; + } + + // Only return time slots that are greater than the current time minus the maximum reservation length. + $extended = \Drupal::currentUser()->hasPermission('add reservations extended'); + if ($option == 'limited') { + $max_length = $extended ? $config->get('reservation_max_length_extended') : $config->get('reservation_max_length_standard'); + $margin_time = ($max_length / 60) * 100; + if ($max_length % 60) { + $margin_time += 30; + } + $str_current_time = date('H') . date('i'); + $int_current_time = intval($str_current_time); + $cutoff_time = $int_current_time - $margin_time; + $cutoff_time = ($cutoff_time < 0) ? 0 : $cutoff_time; + $limited_hours = array(); + foreach ($hours as $time_slot) { + $time_slot_time = intval($time_slot['time']); + if ($time_slot_time > $cutoff_time) { + $limited_hours[] = $time_slot; + } + } + return $limited_hours; + } + else { + return $hours; + } +} + +/** + * Create open hours array. + * + * Create an array of open hours information for the day in question. + + * + * D8 - add passing DATE parameter as is is used in functions like reserve_valid_lengths and only needs for the day + * D8 - actually, i don't see anywhere that full date range is required; so let's remove all the code for that + * + * @todo: this needs to be cached + * + * @return array + * An array representing information about the facility's open hours for that day, such + * as whether the facility is open that day, the number of open shifts, + * open and close hours, and a string that can be used to display the hours + * in a user friendly way. + */ +function reserve_facility_hours($date, $reset = FALSE) { + $monthly_hours = \Drupal::config('reserve.monthly_hours'); + static $building_hours; + if (!isset($building_hours[$date]) || $reset) { + $building_hours = array(); + + $month = date('Y_m', strtotime($date)); + $day = date('j', strtotime($date)); + $mo_hours = $monthly_hours->get($month); + + if (!$mo_hours) { + $m = intval(substr($month, 5)); + $year = substr($month, 0, 4); + $mo_hours = reserve_default_monthly_hours($year, $m); + } + + $start = ($day - 1) * 5; + $first_shift_open = $mo_hours[$start + 1]; + $first_shift_close = $mo_hours[$start + 2]; + $second_shift_open = $mo_hours[$start + 3]; + $second_shift_close = $mo_hours[$start + 4]; + if (($first_shift_open == '9999') && ($first_shift_close == '9999') && ($second_shift_open == '9999') && ($second_shift_close == '9999')) { + $open = FALSE; + } + else { + $open = TRUE; + } + if (($open) && ($first_shift_open == '0000') && ($first_shift_close == '2400')) { + $open_24_hours = TRUE; + } + else { + $open_24_hours = FALSE; + } + if (!$open) { + $shifts = 0; + } + elseif ($open_24_hours) { + $shifts = 1; + } + elseif (($open) && ($second_shift_open == '9999') && ($second_shift_close == '9999')) { + $shifts = 1; + } + else { + $shifts = 2; + } + $day_hours = array( + $first_shift_open, + $first_shift_close, + $second_shift_open, + $second_shift_close, + ); + $display = reserve_hours_display($day_hours); + $hours_data = array( + 'open' => $open, + 'open_24_hours' => $open_24_hours, + 'shifts' => $shifts, + 'first_shift_open' => $first_shift_open, + 'first_shift_close' => $first_shift_close, + 'second_shift_open' => $second_shift_open, + 'second_shift_close' => $second_shift_close, + 'display' => $display, + ); + $building_hours[$date] = $hours_data; + } + + return $building_hours[$date]; +} + + +/** + * Creates a string for displaying the open hours for a single day. + * + * @param array $day_hours + * An array that represents the openning and closing hours for two + * separate shifts in a single day + * + * @return string + * A string that can be used to display the hours for a single day, such as + * 'Open 24 Hours' or 'Noon - 6:00 PM'. + */ +function reserve_hours_display($day_hours) { + $first_shift_open = $day_hours[0]; + $first_shift_close = $day_hours[1]; + $second_shift_open = $day_hours[2]; + $second_shift_close = $day_hours[3]; + // Closed. + if (($first_shift_open == '9999') && + ($first_shift_close == '9999') && + ($second_shift_open == '9999') && + ($second_shift_close == '9999')) { + return 'Closed'; + } + // Open 24 hours. + if (($first_shift_open == '0000') && + ($first_shift_close == '2400')) { + return 'Open 24 Hours'; + } + // One shift. + if (($second_shift_open == '9999') && + ($second_shift_close == '9999')) { + $first_shift_open_display + = reserve_display_time($first_shift_open); + $first_shift_close_display + = reserve_display_time($first_shift_close); + return $first_shift_open_display . ' - ' . + $first_shift_close_display; + } + // Two shifts. + $first_shift_open_display + = reserve_display_time($first_shift_open); + $first_shift_close_display + = reserve_display_time($first_shift_close); + $second_shift_open_display + = reserve_display_time($second_shift_open); + $second_shift_close_display + = reserve_display_time($second_shift_close); + return $first_shift_open_display . ' - ' . + $first_shift_close_display . ' and ' . + $second_shift_open_display . ' - ' . + $second_shift_close_display; +} + +/** + * Create time slot array. + * + * Create an array with each element representing one of the 48 half hour time + * slots that make up a day. + * + * @return array + * An array with each element representing a half hour time slot. + */ +function reserve_times() { + $times = array(); + $hours = reserve_hours(); + foreach ($hours as $hour) { + $times[] = $hour['time']; + } + return $times; +} + +/** + * Return time in display format. + * + * This function returns the time in display format (Midnight, 12:30 AM, + * 1:00 AM, etc.) for any time slot of the day given in military time + * format (0000, 0030, 0100, etc.). + * + * @param string $military_time + * Time of day represented in four digit military time. + * + * @return string + * Time of day represented as HH:MM AM. + */ +function reserve_display_time($military_time) { + $hours = reserve_hours(); + $hours[] = array( + 'time' => '2400', + 'display' => 'Midnight', + ); + foreach ($hours as $hour) { + $time = $hour['time']; + if ($time == $military_time) { + return $hour['display']; + } + } + return ''; +} + +/** + * Determine closing times. + * + * Determines which half hour time slots represent the last ones before the + * building closes. + * + * @param string $yyyy_mmdd + * The date for which close times are being determine, in the format + * YYYY-MM-DD. + * + * @return array + * An array representing the time slots just before closing. The array can + * contain 0, 1, or 2 items. + */ +function reserve_close_times($yyyy_mmdd) { + $closing_times = array(); + $reserve_building_hours = reserve_facility_hours($yyyy_mmdd); + $building_hours_day = $reserve_building_hours; + // 24 hours. + if ($building_hours_day['open_24_hours']) { + $next_day = date('Y-m-d', strtotime("$yyyy_mmdd +1 days")); + $next_day_first_time_slot_open + = reserve_first_slot_open($next_day); + if (!$next_day_first_time_slot_open) { + $closing_times[] = '2330'; + } + return $closing_times; + } + // First shift ends at midnight. + if ($building_hours_day['first_shift_close'] === '2400') { + $next_day = date('Y-m-d', strtotime("$yyyy_mmdd +1 days")); + $next_day_first_time_slot_open + = reserve_first_slot_open($next_day); + if (!$next_day_first_time_slot_open) { + $closing_times[] = '2330'; + } + return $closing_times; + } + // First shift does not end at midnight. + $time = $building_hours_day['first_shift_close']; + $hours = reserve_hours(); + $time_found = FALSE; + while (!$time_found) { + $time_slot = array_pop($hours); + if ($time_slot['time'] == $time) { + $time_found = TRUE; + } + } + $time_slot = array_pop($hours); + $int_second_shift_close = intval($building_hours_day['second_shift_close']); + $closing_times[] = $time_slot['time']; + // Second shift ends at midnight. + if (($building_hours_day['shifts'] == 2) && + ($int_second_shift_close == 2400)) { + $next_day = date('Y-m-d', strtotime("$yyyy_mmdd +1 days")); + $next_day_first_time_slot_open + = reserve_first_slot_open($next_day); + if (!$next_day_first_time_slot_open) { + $closing_times[] = '2330'; + } + } + // Second shift does not end at midnight. + if (($building_hours_day['shifts'] == 2) && + ($int_second_shift_close < 2400)) { + $time = $building_hours_day['second_shift_close']; + $hours = reserve_hours(); + $time_found = FALSE; + while (!$time_found) { + $time_slot = array_pop($hours); + if ($time_slot['time'] == $time) { + $time_found = TRUE; + } + } + $time_slot = array_pop($hours); + $closing_times[] = $time_slot['time']; + } + return $closing_times; +} + +/** + * Determine if facility is open from midnight to 12:30 AM. + * + * Determines if the facility is open during the first half hour of the day, + * from midnight to 12:30 AM. This information is needed when determining if + * any particular half hour time slot is the last one before the building + * closes. + * + * @param string $yyyy_mmdd + * The date being examined, in the format YYYY-MM-DD. + * + * @return bool + * TRUE - The facility is open during the first half hour of the day. + * FALSE - The facility is not open during the first half hour of the day. + */ +function reserve_first_slot_open($yyyy_mmdd) { + $reserve_building_hours = reserve_facility_hours($yyyy_mmdd); + $building_hours_day = $reserve_building_hours; + if (!$building_hours_day['open']) { + return FALSE; + } + if ($building_hours_day['open_24_hours']) { + return TRUE; + } + if ($building_hours_day['first_shift_open'] == '0000') { + return TRUE; + } + return FALSE; +} + +/** + * + * NOTE - not currently used in D8 port + * + * Determine reservation start conflicts. + * + * Determines if a new reservation room, date and start time conflicts with a + * previously existing reservation. + * + * @param string $room + * The room that is being reserved. + * @param string $yyyy_mmdd + * The date of the start time for the reservation, in the format 'yyyy-mm-dd'. + * @param string $time + * The start time for the reservation, in military time. 9:00 AM is + * represented as '0900', and 9:00 PM is represented as '2100'. + * + * @return bool + * TRUE - A scheduling conflict was found. + * FALSE - A scheduling conflict was not found. + */ +function reserve_start_conflicts($room, $yyyy_mmdd, $time) { + $config = \Drupal::config('reserve.settings'); + + // Previous and next days. + $previous_day = date('Y-m-d', strtotime("$yyyy_mmdd -1 days")); + // Start times of other reservations that could conflict with this one. + $extended = \Drupal::currentUser()->hasPermission('add reservations extended'); + $max_length = $extended ? $config->get('reservation_max_length_extended') : $config->get('reservation_max_length_standard'); + $max_slots = $max_length / 30; + $search_items = array(); + for ($x = 0; $x < 8; $x++) { + $search_items = array( + 'date' => '1999-01-01', + 'start_time' => '9999', + 'length' => 999, + ); + } + $day = $yyyy_mmdd; + $hours = reserve_hours(); + $time_found = FALSE; + $time_slot = NULL; + while (!$time_found) { + $time_slot = array_pop($hours); + if ($time_slot['time'] == $time) { + $time_found = TRUE; + } + } + for ($x = 0; $x < $max_slots; $x++) { + if ($time_slot == NULL) { + $day = $previous_day; + $hours = reserve_hours(); + $time_slot = array_pop($hours); + } + $search_item['date'] = $day; + $search_item['start_time'] = $time_slot['time']; + $search_item['length'] = 30 * $x; + $search_items[] = $search_item; + $time_slot = array_pop($hours); + } + $sql = " + SELECT id FROM {room_reservations} + WHERE ( + (deleted = 'N' AND room = :room AND date = :date0 AND time = :time0 AND length > :length0) + OR + (deleted = 'N' AND room = :room AND date = :date1 AND time = :time1 AND length > :length1) + OR + (deleted = 'N' AND room = :room AND date = :date2 AND time = :time2 AND length > :length2) + OR + (deleted = 'N' AND room = :room AND date = :date3 AND time = :time3 AND length > :length3) + OR + (deleted = 'N' AND room = :room AND date = :date4 AND time = :time4 AND length > :length4) + OR + (deleted = 'N' AND room = :room AND date = :date5 AND time = :time5 AND length > :length5) + OR + (deleted = 'N' AND room = :room AND date = :date6 AND time = :time6 AND length > :length6) + OR + (deleted = 'N' AND room = :room AND date = :date7 AND time = :time7 AND length > :length7) + ) + "; + $conflicts_found = FALSE; + $conflicts_found = db_query($sql, array( + ':room' => $room, + ':date0' => $search_items[0]['date'], ':time0' => $search_items[0]['start_time'], ':length0' => $search_items[0]['length'], + ':date1' => $search_items[1]['date'], ':time1' => $search_items[1]['start_time'], ':length1' => $search_items[1]['length'], + ':date2' => $search_items[2]['date'], ':time2' => $search_items[2]['start_time'], ':length2' => $search_items[2]['length'], + ':date3' => $search_items[3]['date'], ':time3' => $search_items[3]['start_time'], ':length3' => $search_items[3]['length'], + ':date4' => $search_items[4]['date'], ':time4' => $search_items[4]['start_time'], ':length4' => $search_items[4]['length'], + ':date5' => $search_items[5]['date'], ':time5' => $search_items[5]['start_time'], ':length5' => $search_items[5]['length'], + ':date6' => $search_items[6]['date'], ':time6' => $search_items[6]['start_time'], ':length6' => $search_items[6]['length'], + ':date7' => $search_items[7]['date'], ':time7' => $search_items[7]['start_time'], ':length7' => $search_items[7]['length']) + )->rowCount(); + return $conflicts_found; +} + +/** + * Determine valid reservation lengths of time. + * + * Determines which lengths of time are valid for a reservation for a particular + * room starting at particular time. Valid lengths are limited by the + * following: + * (1) Previously scheduled reservations. + * (2) Building close times. + * (3) Last time slot of the day. Reservations possibly end 15 minutes before + * the building closes. + * + * @param string $room + * The room that is being reserved. + * @param string $yyyy_mmdd + * The date of the start time for the reservation, in the format 'yyyy-mm-dd'. + * @param string $time + * The start time for the reservation, in military time. + * 9:00 AM is represented as '0900', and 9:00 PM is represented as '2100'. + * @param int $id + * The id of the reservation being made. + * @param int $all + * By default we do not check the first time slot since it should be true or we wouldn't have been able to pick it on calendar + * but, for repeating reserverations we need to check all slots. + * + * @return array + * An array with an element for each possible reservation length of time, + * and an indicator showing whether that particular length is valid for the + * reservation being made. + */ +function reserve_valid_lengths($rid, $ebundle, $yyyy_mmdd, $time, $id = NULL, $all = FALSE) { + $config = \Drupal::config('reserve.settings'); + + // @todo: should make a small file with replacements for simple D7 functions like l, arg, etc + $current_path = \Drupal::service('path.current')->getPath(); + $path_args = explode('/', $current_path); + + // let's first ensure this is a valid RID + $rooms = reserve_entities($ebundle); + if (!isset($rooms[$rid])) { + return null; + } + + $extended = \Drupal::currentUser()->hasPermission('add reservations extended'); + $max_length = $extended ? + ($config->get('reservation_max_length_extended') ? $config->get('reservation_max_length_extended') : 120) : + ($config->get('reservation_max_length_standard') ? $config->get('reservation_max_length_standard') : 120); + $max_slots = $max_length / 30; + $valid_lengths = array(); + for ($x = 30; $x <= $max_length; $x += 30) { + $valid_lengths[] = array( + 'length' => $x, + 'is_valid' => TRUE, + ); + } + // Divide the maximum reservation length into 30 minute time slots and determine the start date and time of each of these slots. + $search_items = array(); + // Time slots for the first day. + $day = $yyyy_mmdd; + $hours = reserve_hours(); + $x = 0; + $include = FALSE; + foreach ($hours as $time_slot) { + if ($time_slot['time'] == $time) { + $include = TRUE; + } + if ($include) { + $search_item['date'] = $day; + $search_item['start_time'] = $time_slot['time']; + $search_items[] = $search_item; + $x++; + } + if ($x == $max_slots) { + $include = FALSE; + break; + } + } + + // Determine if the reservation at each possible length would conflict with another reservation. If so, set is_valid for that length to FALSE. + // The first time slot has already been validated and does not need to be checked again. + if ($all) { + $start = 0; + } + else { + $start = 1; + } + for ($x = $start; $x < $max_slots; $x++) { + $valid_length = $valid_lengths[$x]; + if ($valid_length['is_valid']) { + $search_item = $search_items[$x]; + $date = $search_item['date']; + $start_time = $search_item['start_time']; + + $conflicts_found = false; + + $query = Drupal::service('entity.query') + ->get('reserve_reservation') + ->condition('reservation_date', $date . '%', 'LIKE') + ->condition('reservation_time', $start_time) + ->condition('reservable_id', $rid) + ->accessCheck(FALSE); + //->sort('reserve_display_order', 'ASC'); + + // if editing a reservation (series) we need to not include any of the reservations in current series + $group = NULL; + if ($path_args[3] == 'edit') { + $eid = $path_args[2]; + $res = \Drupal::entityTypeManager()->getStorage('reserve_reservation')->load($eid); + + if ($sid = $res->reservation_series_id->getString()) { + $group = $query->orConditionGroup() + ->notExists('reservation_series_id') + ->condition('reservation_series_id', $sid, '<>'); + } + } + + if ($group) { + $ids = $query->condition($group)->execute(); + } + else { + $ids = $query->execute(); + } + $reservations = \Drupal::entityTypeManager()->getStorage('reserve_reservation')->loadMultiple($ids); + if (count($reservations)) { + $conflicts_found = true; + for ($y = $x; $y < $max_slots; $y++) { + $valid_lengths[$y]['is_valid'] = FALSE; + } + } + } + } + // need to limit these valid lengths by a prebuffer if it exists for the category this room is in + // but only if not permissions to do this + if (!\Drupal::currentUser()->hasPermission('book over buffer')) { + $categories = reserve_categories(); + $rooms = reserve_entities($ebundle); + $categoryField = reserve_get_ebundle_category_field($ebundle); + $category = $categories[$rooms[$rid]->get($categoryField)->getString()]; + $preslots = $category['prebuffer'] / 30; + if ($preslots) { + foreach ($valid_lengths as $index => $length) { + if ($length['is_valid'] == false) { + $last = $index; + break; + } + } + if (isset($last)) { + for ($x = $last - $preslots; $x <= $last; $x++) { + $valid_lengths[$x]['is_valid'] = false; + } + } + } + } + + // Determine if the reservation at each possible length would conflict with the hours that the building + // is closed. If so, set is_valid for that length to FALSE. The first time slot has already been + // validated and does not need to be checked again. + $building_hours_day = reserve_facility_hours($day); + for ($x = 0; $x < $max_slots; $x++) { + $valid_length = $valid_lengths[$x]; + if ($valid_length['is_valid']) { + $conflicts_found = FALSE; + $search_item = $search_items[$x]; + $date = $search_item['date']; + $start_time = $search_item['start_time']; + // If building is closed, set conflict flag. + if (!$building_hours_day['open']) { + $conflicts_found = TRUE; + } + $int_start_time = intval($start_time); + $int_first_shift_open = intval($building_hours_day['first_shift_open']); + $int_first_shift_close = intval($building_hours_day['first_shift_close']); + // One shift. + if ((!$building_hours_day['open_24_hours']) && ($building_hours_day['shifts'] == 1) && + (($int_start_time < $int_first_shift_open) || ($int_start_time >= $int_first_shift_close))) { + $conflicts_found = TRUE; + } + // Two shifts. + $int_second_shift_open = intval($building_hours_day['second_shift_open']); + $int_second_shift_close = intval($building_hours_day['second_shift_close']); + if ((!$building_hours_day['open_24_hours']) && + ($building_hours_day['shifts'] == 2) && + (($int_start_time < $int_first_shift_open) || + (($int_start_time >= $int_first_shift_close) && + ($int_start_time < $int_second_shift_open)) || + ($int_start_time >= $int_second_shift_close)) + ) { + $conflicts_found = TRUE; + } + if ($conflicts_found) { + for ($y = $x; $y < $max_slots; $y++) { + $valid_lengths[$y]['is_valid'] = FALSE; + } + } + } + } + + return $valid_lengths; +} + +/** + * Determine if the user has exceeded the maximum reservations per day. + * + * @global object $user + * Drupal user object. + * + * @param string $yyyy_mmdd + * The date for which maximum allowable reservations are being checked, in + * the format YYYY-MM-DD. + * + * @return bool + * TRUE - the maximum has been exceeded. + * FALSE - the maximum has not been exceeded. + */ +function reserve_daily_max_exceeded($yyyy_mmdd) { + global $user; + $config = \Drupal::config('reserve.settings'); + + $max = $config->get('reservations_per_day'); + if (!$max) { + return FALSE; + } + + $record_count = 0; + if ($user->uid) { + $ids = \Drupal::service('entity.query') + ->get('reserve_reservation') + ->condition('user_id', $user->id) + ->condition('reservation_date', 'value', $yyyy_mmdd . '%', 'like') + ->execute(); + $record_count = count($ids); + } + + if ($record_count < $max) { + return FALSE; + } + else { + return TRUE; + } +} + + +/** + * Find the current user's reservations for the next 14 days. + * + * @global object $user + * A Drupal user object. + * + * @return array + * An array with each element representing one reservation that the user has + * made. + */ +function reserve_user_reservations() { + $user_reservations = array(); + $all_hours = reserve_hours(); + $user = \Drupal::currentUser(); + if ($user->id()) { + $earliest_date = date('Y-m-d', strtotime(date('Y-m-d'))); + $latest_date = date('Y-m-d', strtotime("now +13 days")); + + $ids = \Drupal::service('entity.query') + ->get('reserve_reservation') + ->condition('user_id', $user->id()) + ->condition('reservation_date', $earliest_date, '>=') + ->condition('reservation_date', $latest_date, '<=') + ->sort('reservation_date', 'DESC') + ->sort('reservation_time', 'DESC') + ->execute(); + $results = \Drupal::entityTypeManager()->getStorage('reserve_reservation')->loadMultiple($ids); + + foreach ($results as $data) { + $reservation = array(); + $reservation['id'] = $data->id(); + $unix_timestamp = strtotime($data->reservation_date->getString()); + $reservation['date'] = date("l, n/j", $unix_timestamp); + $reservation['time'] = ''; + foreach ($all_hours as $time_slot) { + if ($time_slot['time'] == $data->reservation_time->getString()) { + $reservation['time'] = $time_slot['display']; + break; + } + } + $user_reservations[] = $reservation; + } + } + return $user_reservations; +} + +/* + * return TRUE/FALSE if a slot of the selected length is available to be booked + */ +function reserve_is_slot_free($rid, $ebundle, $yyyy_mmdd, $time, $length) { + $slots = reserve_valid_lengths($rid, $ebundle, $yyyy_mmdd, $time, NULL, true); + foreach ($slots as $slot) { + if ($slot['length'] == $length && $slot['is_valid']) { + return true; + } + } + return false; +} + +function reserve_yyyymmdd($month, $day) { + // determine if this year or next year + $yearnow = date('Y'); + $absdaynow = date('z'); + $absdaydefault = date('z', mktime(0, 0, 0, $month, $day, $yearnow)); + if ($absdaynow > $absdaydefault) { + $year = $yearnow + 1; + } + else { + $year = $yearnow; + } + return date('Y-m-d', strtotime($year . '-' . $month . '-' . $day)); +} + +/* + * List of bundles with Reserve Category field attached - i.e. reservable bundles + * + */ +function reserve_get_reserve_bundles() { + $fieldmap = \Drupal::entityManager()->getFieldMap(); + foreach ($fieldmap as $entity_type => $typedef) { + foreach ($typedef as $field) { + if ($field['type'] == 'reserve_category') { + foreach ($field['bundles'] as $bundle) { + $bundles[] = $entity_type . '.' . $bundle; + } + } + } + } + return $bundles; +} + +/* + * returns list of field names used as category fields in different bundles + * OR specific field name if $ebundle is provided (e.g. node.room) + */ +function reserve_category_fields($ebundle = NULL) { + $fieldmap = \Drupal::entityManager()->getFieldMap(); + $fields = []; + foreach ($fieldmap as $entity_type => $typedef) { + foreach ($typedef as $name => $field) { + if ($field['type'] == 'reserve_category') { + foreach ($field['bundles'] as $bundle) { + $fields[$entity_type . '.' . $bundle] = $name; + } + } + } + } + if ($ebundle) { + return $fields[$ebundle]; + } + else { + return $fields; + } +} + +function ebundle_split($ebundle, $part) { + $ebits = explode('.', $ebundle); + if ($part == 'type') return $ebits[0]; + if ($part == 'bundle') return $ebits[1]; +} + +/* + * return array of formatted eBundles + * e.g.: Room (node) + */ +function ebundles_formatted() { + $info = \Drupal::service("entity_type.bundle.info")->getAllBundleInfo(); + $ebundles = reserve_get_reserve_bundles(); + foreach ($ebundles as $ebundle) { + $type = ebundle_split($ebundle, 'type'); + $bundle = ebundle_split($ebundle, 'bundle'); + $result[$ebundle] = $info[$type][$bundle]['label'] . " ($type)"; + } + return $result; +} + +function reserve_which_year($month, $day) { + // determine if this year or next year + $yearnow = date('Y'); + $absdaynow = date('z'); + $absdaydefault = date('z', mktime(0, 0, 0, $month, $day, $yearnow)); + if ($absdaynow > $absdaydefault) { + $year = $yearnow + 1; + } + else { + $year = $yearnow; + } + + return $year; +} + +/* + * used for Allowed Values for Reservation.reservable_content_type field + */ +function reserve_site_entity_types() { + $types = \Drupal::entityManager()->getEntityTypeLabels(TRUE); + return $types['Content']; +} + +/* + * get Category field used on ebundle + */ +function reserve_get_ebundle_category_field($ebundle) { + $type = ebundle_split($ebundle, 'type'); + $bundle = ebundle_split($ebundle, 'bundle'); + $fields = \Drupal::service('entity_field.manager')->getFieldMapByFieldType('reserve_category'); + $fieldsType = $fields[$type]; + foreach ($fieldsType as $field => $fieldInfo) { + if (in_array($bundle, $fieldInfo['bundles'])) { + return $field; + } + } +} \ No newline at end of file diff --git a/reserve.info.yml b/reserve.info.yml new file mode 100644 index 0000000..f1e7606 --- /dev/null +++ b/reserve.info.yml @@ -0,0 +1,5 @@ +name: Reserve +type: module +description: Reservation system. +core: 8.x +package: Reserve diff --git a/reserve.libraries.yml b/reserve.libraries.yml new file mode 100644 index 0000000..9f27dec --- /dev/null +++ b/reserve.libraries.yml @@ -0,0 +1,11 @@ +reserve: + version: 1.x + css: + theme: + css/reserve-calendar.css: {} + js: + js/reserve.js: {} + dependencies: + - core/jquery + - core/drupalSettings + - core/drupal.dialog.ajax \ No newline at end of file diff --git a/reserve.links.action.yml b/reserve.links.action.yml new file mode 100644 index 0000000..38755c0 --- /dev/null +++ b/reserve.links.action.yml @@ -0,0 +1,10 @@ +entity.reserve_reservation.add_form: + route_name: entity.reserve_reservation.add_form + title: 'Add Reservation' + appears_on: + - entity.reserve_reservation.collection +entity.reserve_category.add_form: + route_name: entity.reserve_category.add_form + title: 'Add Reservation Category' + appears_on: + - entity.reserve_category.collection diff --git a/reserve.links.menu.yml b/reserve.links.menu.yml new file mode 100644 index 0000000..24128e9 --- /dev/null +++ b/reserve.links.menu.yml @@ -0,0 +1,38 @@ + +# Reservation menu items definition +entity.reserve_reservation.collection: + title: 'Reservations' + route_name: entity.reserve_reservation.collection + description: 'List reservations' + parent: system.admin_structure + +reserve_reservation.admin.structure.settings: + title: Reservation settings + description: 'Configure reservations' + route_name: reserve_reservation.settings + parent: entity.reserve_reservation.collection + +# Reservation Category menu items definition +entity.reserve_category.collection: + title: 'Reservation Categories' + route_name: entity.reserve_category.collection + description: 'List reservation categories' + parent: entity.reserve_reservation.collection + +reserve_category.admin.structure.settings: + title: Reservation Category settings + description: 'Configure reservation categories' + route_name: reserve_category.settings + parent: entity.reserve_category.collection + +reserve.admin.system.settings: + title: Reserve + description: 'Configure Reserve' + route_name: reserve.settings + parent: 'system.admin_config_system' +reserve.admin.system.settings.hours: + title: Hours + description: 'Configure Reserve Hours' + route_name: reserve.settings.hours + parent: 'reserve.admin.system.settings' + weight: -10 \ No newline at end of file diff --git a/reserve.links.task.yml b/reserve.links.task.yml new file mode 100644 index 0000000..539325d --- /dev/null +++ b/reserve.links.task.yml @@ -0,0 +1,81 @@ +# Reservation routing definition +reserve_reservation.settings_tab: + route_name: reserve_reservation.settings + title: 'Settings' + base_route: reserve_reservation.settings + +entity.reserve_reservation.canonical: + route_name: entity.reserve_reservation.canonical + base_route: entity.reserve_reservation.canonical + title: 'View' + +entity.reserve_reservation.edit_form: + route_name: entity.reserve_reservation.edit_form + base_route: entity.reserve_reservation.canonical + title: 'Edit' + +entity.reserve_reservation.delete_form: + route_name: entity.reserve_reservation.delete_form + base_route: entity.reserve_reservation.canonical + title: Delete + weight: 10 + +# Reservation Category routing definition +reserve_category.settings_tab: + route_name: reserve_category.settings + title: 'Settings' + base_route: reserve_category.settings + +entity.reserve_category.edit_form: + route_name: entity.reserve_category.edit_form + base_route: entity.reserve_category.canonical + title: 'Edit' +entity.reserve_category.canonical: + route_name: entity.reserve_category.canonical + base_route: entity.reserve_category.canonical + title: 'View' +entity.reserve_category.delete_form: + route_name: entity.reserve_category.delete_form + base_route: entity.reserve_category.canonical + title: Delete + weight: 10 + +# Reserve Settings +reserve.settings: + title: 'Settings' + route_name: reserve.settings + base_route: reserve.settings + +reserve.settings.hours: + title: 'Hours' + route_name: reserve.settings.hours + base_route: reserve.settings + +reserve.settings.display: + title: 'Display' + route_name: reserve.settings.display + base_route: reserve.settings + weight: 10 + +reserve.settings.hours.default: + title: 'Default Hours' + route_name: reserve.settings.hours + parent_id: reserve.settings.hours + +reserve.settings.hours.daily: + title: 'Daily Overrides' + route_name: reserve.settings.hours.daily + parent_id: reserve.settings.hours + +reserve.calendar: + title: 'Calendar' + route_name: reserve.calendar + base_route: reserve.calendar + +reserve.myreservation: + title: 'My Reservations' + route_name: view.reservations.page_2 + base_route: reserve.calendar + + + diff --git a/reserve.links.task.yml.bak b/reserve.links.task.yml.bak new file mode 100644 index 0000000..a4cdd53 --- /dev/null +++ b/reserve.links.task.yml.bak @@ -0,0 +1,71 @@ +# Reservation routing definition +reserve_reservation.settings_tab: + route_name: reserve_reservation.settings + title: 'Settings' + base_route: reserve_reservation.settings + +entity.reserve_reservation.canonical: + route_name: entity.reserve_reservation.canonical + base_route: entity.reserve_reservation.canonical + title: 'View' + +entity.reserve_reservation.edit_form: + route_name: entity.reserve_reservation.edit_form + base_route: entity.reserve_reservation.canonical + title: 'Edit' + +entity.reserve_reservation.delete_form: + route_name: entity.reserve_reservation.delete_form + base_route: entity.reserve_reservation.canonical + title: Delete + weight: 10 + +# Reservation Category routing definition +reserve_categories.settings_tab: + route_name: reserve_categories.settings + title: 'Settings' + base_route: reserve_categories.settings + +entity.reserve_category.edit_form: + route_name: entity.reserve_category.edit_form + base_route: entity.reserve_category.canonical + title: 'Edit' +entity.reserve_category.canonical: + route_name: entity.reserve_category.canonical + base_route: entity.reserve_category.canonical + title: 'View' +entity.reserve_category.delete_form: + route_name: entity.reserve_category.delete_form + base_route: entity.reserve_category.canonical + title: Delete + weight: 10 + +# Reserve Settings +reserve.settings: + title: 'Settings' + route_name: reserve.settings + base_route: reserve.settings + +reserve.settings.hours: + title: 'Hours' + route_name: reserve.settings.hours + base_route: reserve.settings + +reserve.settings.display: + title: 'Display' + route_name: reserve.settings.display + base_route: reserve.settings + weight: 10 + +reserve.settings.hours.default: + title: 'Default Hours' + route_name: reserve.settings.hours + parent_id: reserve.settings.hours + +reserve.settings.hours.daily: + title: 'Daily Overrides' + route_name: reserve.settings.hours.daily + parent_id: reserve.settings.hours + + + diff --git a/reserve.module b/reserve.module new file mode 100644 index 0000000..e5d1d91 --- /dev/null +++ b/reserve.module @@ -0,0 +1,113 @@ +' . t('Overview') . ''; + $output .= '

' . t('The Reserve module allows for the "booking" of entities. It provides a calendar for each bundle which is set up to be bookable. + From this calendar you can see what time slots have been booked as well as select a time slot to book. Many options are provided to make a very flexible + booking system.') . '

'; + $output .= '

' . t('Setup') . '

'; + $output .= '

' . t('A few things must be set up before you may use the booking system:') . '

+ +

' . t('The booking calendar will now be available at /reserve/{entitytype.bundle}/calendar. E.g. /reserve/node.room/calendar') . '

'; + + return $output; + + default: + } +} + +/** + * Implements hook_form_alter(). + */ +function reserve_form_alter(array &$form, FormStateInterface $form_state, $form_id) { + if ($form_state->getFormObject() instanceof BundleEntityFormBase) { + (new BundleFormAlter($form_state->getFormObject()->getEntity())) + ->formAlter($form, $form_state); + } + return; +} + + /** + * Implements hook_entity_insert(). + */ +function reserve_entity_insert(EntityInterface $entity) { + reserve_entity_type_save($entity); +} + +/** + * Implements hook_entity_update(). + */ +function reserve_entity_update(EntityInterface $entity) { + reserve_entity_type_save($entity); +} + +/** + * Helper to save group information. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity object. + */ +function reserve_entity_type_save(EntityInterface $entity) { + $bundle = $entity->id(); + $definition = \Drupal::entityTypeManager()->getDefinition($entity->getEntityTypeId()); + $entity_type_id = $definition->getBundleOf(); +} + +/** + * Implements hook_theme(). + */ +function reserve_theme($existing, $type, $theme, $path) { + return array( + 'calendar_template' => array( + 'variables' => array( + 'date_picker' => NULL, + 'arrow' => NULL, + 'date' => NULL, + 'calendar_text' => NULL, + 'dates' => NULL, + 'categories' => NULL, + 'calendar_header' => NULL, + 'reservation_instructions' => NULL, + 'building_hours_display' => NULL, + ), + ), + ); +} diff --git a/reserve.permissions.yml b/reserve.permissions.yml new file mode 100644 index 0000000..3b1fb95 --- /dev/null +++ b/reserve.permissions.yml @@ -0,0 +1,58 @@ +# Admin +administer reservations: + title: 'Administer Reservations' + description: 'Allow access to the administration form to configure Reservations.' + restrict access: true + +administer reservation categories: + title: 'Administer Reservation Categories' + description: 'Allow access to the administration form to configure Reservation Categories.' + restrict access: true + +# Reservations +add reservations: + title: 'Create new Reservations' + +add reservations extended: + title: 'Create new Reservations (extended)' + +edit reservations: + title: 'Edit Reservations' + +delete reservations: + title: 'Delete Reservations' + +view published reservations: + title: 'View published Reservations' + +view unpublished reservations: + title: 'View unpublished Reservations' + +book over reservation buffer: + title: 'Book over reservation buffer' + +access reservation overview: + title: 'Access the Reservation overview page' + +# Categories +add reservation categories: + title: 'Create new Reservation Categories' + +edit reservation categories: + title: 'Edit Reservation Categories' + +delete reservation categories: + title: 'Delete Reservation Categories' + +view published reservation categories: + title: 'View published Reservation Categories' + +view unpublished reservation categories: + title: 'View unpublished Reservation Categories' + +access reservation category overview: + title: 'Access the Reservation Category overview page' + +permission_callbacks: + - Drupal\reserve\Controller\ReservePermissions::permissions + diff --git a/reserve.routing.yml b/reserve.routing.yml new file mode 100644 index 0000000..ec8d41e --- /dev/null +++ b/reserve.routing.yml @@ -0,0 +1,61 @@ +# Route name can be used in several places; e.g. links, redirects, and local +# actions. + +reserve.settings: + path: '/admin/config/system/reserve' + defaults: + _form: '\Drupal\reserve\Form\ReserveSettingsForm' + _title: 'Reserve Settings' + requirements: + _permission: 'administer reservation categories' + +reserve.settings.hours: + path: '/admin/config/system/reserve/hours' + defaults: + _form: '\Drupal\reserve\Form\ReserveDefaultHoursForm' + _title: 'Reserve Settings (Default Hours)' + requirements: + _permission: 'administer reservation categories' + +reserve.settings.hours.daily: + path: '/admin/config/system/reserve/hours/daily/{passed_month}' + defaults: + _form: '\Drupal\reserve\Form\ReserveDailyHoursForm' + _title: 'Reserve Settings (Daily Overrides)' + passed_month: 'yyyy_mm' + requirements: + _permission: 'administer reservation categories' + passed_month: '20[0-9][0-9]_[0-1][0-9]' + +reserve.settings.display: + path: '/admin/config/system/reserve/diplay' + defaults: + _form: '\Drupal\reserve\Form\ReserveDisplayForm' + _title: 'Reserve Settings (Display)' + requirements: + _permission: 'administer reservation categories' + +reserve.calendar: + path: '/reserve/{ebundle}/calendar/{selected_month}/{selected_day}' + defaults: + _controller: '\Drupal\reserve\Controller\CalendarController::calendar' + _title_callback: '\Drupal\reserve\Controller\CalendarController::calendarTitle' + selected_month: '' + selected_day: '' + requirements: + _custom_access: '\Drupal\reserve\Controller\CalendarController::accessCalendarPage' + +reserve.reservation.add: + path: '/reserve_reservation/add/{month}/{day}/{time}/{id}/{ebundle}' + defaults: + _entity_form: 'reserve_reservation.default' + _title: 'Add Reservation' + requirements: + _permission: 'access content' + +reserve.reservation.add.callback: + path: '/reserve/ajax/reservation_add' + defaults: + _controller: '\Drupal\reserve\Controller\CalendarController::reservationAddModalCallback' + requirements: + _permission: 'access content' \ No newline at end of file diff --git a/reserve.schema.yml b/reserve.schema.yml new file mode 100644 index 0000000..4cf45b6 --- /dev/null +++ b/reserve.schema.yml @@ -0,0 +1,21 @@ +field.storage_settings.reserve_category_reference: + type: mapping + label: 'Reserve Category reference field storage settings' + mapping: + target_type: + type: string + label: 'Type of entity to reference' + +field.field_settings.reserve_category_reference: + type: mapping + label: 'Reserve Category reference field settings' + mapping: + handler: + type: string + label: 'Reference method' + handler_settings: + type: entity_reference_selection.[%parent.handler] + label: 'Reserve Category reference selection plugin settings' + access_override: + type: boolean + label: 'Reserve Cateegories' diff --git a/reserve.series.inc b/reserve.series.inc new file mode 100644 index 0000000..ecdb5a5 --- /dev/null +++ b/reserve.series.inc @@ -0,0 +1,192 @@ +saved)){ + return; + } + + $repeat_type = $entity->reservation_repeat_type->getString(); + + // if No Repeat, do nothing + if (!$repeat_type) { + return; + } + + $eid = $entity->id(); + $start = $entity->reservation_date->getString(); + $start_yyyy_mm_dd = date('Y-m-d', strtotime($start)); + $end = date('Y-m-d', strtotime($entity->reservation_repeat_until->getString())); + $time = $entity->reservation_time->getString(); + $length = $entity->reservation_length->getString(); + $rid = $entity->reservable_id->getString(); + $rtype = $entity->reservable_content_type->getString(); + $ebundle = $rtype . '.' . entity_load($rtype, $rid)->bundle(); + $day = date('l', strtotime($start)); + + $msg = ''; + switch ($repeat_type) { + // every day until.... + case 1: + $skip = '+1 day'; + $back = '-1 day'; + $msg = t('You have booked every day from %start until %end', array('%start' => $start_yyyy_mm_dd, '%end' => $end)); + break; + + // this day of the week until.. + case 2: + $skip = '+7 day'; + $back = '-7 day'; + $msg = t('You have booked every %day from %start until %end', array('%day' => $day, '%start' => $start_yyyy_mm_dd, '%end' => $end)); + break; + } + + // set NID as Series ID for both the primary entity and the repeat nodes + $entity->set('reservation_series_id', $eid); + + // then lets save the original reservation with Series ID but set SAVED so we skip the update hook + $entity->saved = true; + $entity->save(); + + // lets build the rest of the reservations in the series + $tmp = $entity->createDuplicate(); + $date = $start; + $failed = array(); + while (strtotime($date) <= strtotime($back, strtotime($end))) { + $date = date('Y-m-d', strtotime($skip, strtotime($date))); + + // must check to see if next booking is available + // the first one we don't check as we could not have picked it if it wasn't + if (reserve_is_slot_free($rid, $ebundle, $date, $time, $length)) { + $new = $tmp->createDuplicate(); + $new->set('reservation_date', $date); + $new->saved = true; + $new->save(); + } + else { + $failed[] = $date; + } + } + + // lets spit out some useful msgs + // first clear the msg stating we just created the reservation entity + drupal_get_messages('status'); + drupal_set_message(t('Your reservation series has been booked.')); + drupal_set_message($msg); + if (count($failed)) { + $dates = Markup::create('
' . implode('
', $failed)); + drupal_set_message(t('The following dates were not booked due to scheduling conflicts: %dates', array('%dates' => $dates)), 'warning'); + } +} + +/* + * to handle Series edits + * + * this is only run on the OTHER reservations in series triggered from submitting changes to one of the + * reservations in that series + * + * the changes to which ever reservation is being edited will be done in normal entity_save() + * + */ +function reserve_reserve_reservation_update($entity) { + // when we do a node_save below or when we insert from hook_insert above; it will hit this hook - so lets skip + if (isset($entity->saved)){ + return; + } + + $sid = $entity->reservation_series_id->getString(); + + // if not part of a Series or special single only url -> do nothing + if (!$sid || isset($_GET['single'])) { + return; + } + + // reservation details + $start = $entity->reservation_date->getString(); + $time = $entity->reservation_time->getString(); + $length = $entity->reservation_length->getString(); + $rid = $entity->reservable_id->getString(); + $rtype = $entity->reservable_content_type->getString(); + $ebundle = $rtype . '.' . entity_load($rtype, $rid)->bundle(); + $private = $entity->reservation_private->getString(); + + // grab all reservations in this series except the one being submitted + $ids = \Drupal::service('entity.query') + ->get('reserve_reservation') + ->condition('status', TRUE) + ->condition('reservation_series_id', $sid) + ->condition('id', $entity->id(), '!=') + ->execute(); + $results = \Drupal::entityTypeManager()->getStorage('reserve_reservation')->loadMultiple($ids); + + $failed = array(); + foreach ($results as $data) { + // for now, only allow changing Title, Privacy, Length + // first 2 are easy; but to change length we need to check if that period is available and if not delete entry from Series + $data->set('name', $entity->getName()); + $data->set('reservation_private', $private); + + $date = date('Y-m-d', strtotime($data->reservation_date->getString())); + if (reserve_is_slot_free($rid, $ebundle, $date, $time, $length)) { + $data->set('reservation_length', $length); + } + // if slot is not available; do not update the length of this reservation + // this is different than D7 version - in D7 we simply deleted the reservation; which seems silly. + else { + $failed[] = $date; + } + // update this reservation in our series + $data->saved = true; + $data->save(); + } + + // lets spit out some useful msgs + drupal_set_message(t('Your reservation series has been modified.')); + if (count($failed)) { + $dates = Markup::create('
' . implode('
', $failed)); + drupal_set_message(t('NOTE: The following dates did not have their length changed due to scheduling conflicts: %dates', array('%dates' => $dates)), 'warning'); + } +} + +/** + * + * Handle deleting other reservations in a series (selected reservation should already be handle through entity->delete() + * + * @param $entity + */ +function reserve_reserve_reservation_delete($entity) { + if($_SESSION['reserve_delete_type']) { + _reserve_series_delete($entity); + } + return; +} + +function _reserve_series_delete($entity) { + $sid = $entity->get('reservation_series_id')->getString(); + // grab all reservations in this series + $ids = \Drupal::service('entity.query') + ->get('reserve_reservation') + ->condition('reservation_series_id', $sid) + ->execute(); + $results = \Drupal::entityTypeManager()->getStorage('reserve_reservation')->loadMultiple($ids); + foreach ($results as $result) { + $result->delete(); + } + $title = $entity->get('name')->getString(); + drupal_set_message(t('The reservation series @title was deleted.', array('@title' => $title))); +} diff --git a/reserve_category.page.inc b/reserve_category.page.inc new file mode 100644 index 0000000..e31b9f6 --- /dev/null +++ b/reserve_category.page.inc @@ -0,0 +1,32 @@ +entity = $entity; + } + + /** + * This is a helper for og_ui_form_alter(). + * + * @param array $form + * The form variable. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state object. + */ + public function formAlter(array &$form, FormStateInterface $form_state) { + $this->prepare($form, $form_state); + $this->addReserveCategories($form, $form_state); + } + + /** + * Prepares object properties and adds the og details element. + * + * @param array $form + * The form variable. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state object. + */ + protected function prepare(array &$form, FormStateInterface $form_state) { + // Example: article. + $this->bundle = $this->entity->id(); + // Example: Article. + $this->bundleLabel = Unicode::lcfirst($this->entity->label()); + $this->definition = $this->entity->getEntityType(); + // Example: node. + $this->entityTypeId = $this->definition->getBundleOf(); + + $form['reserve'] = [ + '#type' => 'details', + '#title' => t('Reserve'), + '#collapsible' => TRUE, + '#group' => 'additional_settings', + '#description' => t('If any Reserve Categories are selected here; this bundle will be reservable.'), + ]; + } + + /** + * Adds the "is group?" checkbox. + */ + protected function addReserveCategories(array &$form, FormStateInterface $form_state) { + $options = array('big', 'small'); + $form['reserve']['reserve_categories'] = [ + '#type' => 'checkboxes', + '#title' => t('Categories'), + '#options' => array_combine($options, $options), + //'#default_value' => Og::isGroup($this->entityTypeId, $this->bundle), + + ]; + } + +} diff --git a/src/Controller/CalendarController.php b/src/Controller/CalendarController.php new file mode 100644 index 0000000..1724719 --- /dev/null +++ b/src/Controller/CalendarController.php @@ -0,0 +1,665 @@ +getBundleCalendar($ebundle, $selected_month, $selected_day); + } + } + + public function calendarTitle($ebundle) { + $bundle = ebundle_split($ebundle, 'bundle'); + return t('@bundle Calendar', array('@bundle' => ucwords($bundle))); + } + + private function getBundleCalendar($ebundle, $selected_month = null, $selected_day = null) { + $config = \Drupal::config('reserve.settings'); + // likely no need for this as not used anywhere else; but maybe for external modules + global $yyyy_mmdd; + + //$entity_type = ebundle_split($ebundle, 'type'); + //$bundle = ebundle_split($ebundle, 'bundle'); + $category_field = reserve_category_fields($ebundle); + + // dates are now keyed by Cat ID; but we only need one of these to build calendar but make sure its the one which starts earliest + $datesbycat = reserve_dates($ebundle, $selected_month, $selected_day); + $categories = reserve_categories($ebundle); + + // allow other modules to alter the categories + \Drupal::moduleHandler()->alter('reserve_categories', $categories); + + // if no Categories left; we should not bother with the rest of this + if (!count($categories)) { + drupal_set_message(t('There are no configured Reserve Categories. Please contact the System Administrator'), 'warning'); + return ''; + } + + $earliest = $this->reserve_earliest_category($categories); + $dates = $datesbycat[$earliest]; + + // Determine which day has been selected by the user. If the user has entered a url that specifies a day outside of the + // allowable reservation window, then set the current day as the selected day. + $yyyy_mmdd = $dates[0]['yyyymmdd']; + foreach ($dates as $day) { + if ($day['selected']) { + $yyyy_mmdd = $day['yyyymmdd']; + } + } + if ($yyyy_mmdd == $dates[0]['yyyymmdd']) { + $dates[0]['selected'] = TRUE; + $dates[0]['today'] = TRUE; + } + + // a bit hacky; but we need to store what the calendar is using for its date so we can use this in theme functions later + //$_SESSION['reservations_current_day'] = $yyyy_mmdd; + + // If the day being displayed is today, only display time slots that are later than the current time minus two hours. + $today_displayed = FALSE; + foreach ($dates as $day) { + if (($day['selected']) && $day['today']) { + $today_displayed = TRUE; + } + } + if ($today_displayed) { + $hours = reserve_hours('limited'); + } + else { + $hours = reserve_hours(); + } + + // Determine the open hours (display version) for the date selected by the user. + $building_hours_day = reserve_facility_hours($yyyy_mmdd); + $building_hours_display = $building_hours_day['display']; + + // For each time slot, determine if the rooms are open or closed. + $int_first_shift_open = intval($building_hours_day['first_shift_open']); + $int_first_shift_close = intval($building_hours_day['first_shift_close']); + $int_second_shift_open = intval($building_hours_day['second_shift_open']); + $int_second_shift_close = intval($building_hours_day['second_shift_close']); + $open_24 = $building_hours_day['open_24_hours']; + $x = 0; + $buffer = $config->get('show_before_after_hours') * 100; + foreach ($hours as $time_slot) { + $int_time_slot_time = intval($time_slot['time']); + if ($building_hours_day['open_24_hours']) { + $time_slot['open'] = TRUE; + } + elseif ((($int_time_slot_time >= $int_first_shift_open) && ($int_time_slot_time < $int_first_shift_close)) || + (($int_time_slot_time >= $int_second_shift_open) && ($int_time_slot_time < $int_second_shift_close))) { + $time_slot['open'] = TRUE; + } + else { + $time_slot['open'] = FALSE; + } + + // if not open ALL day let's limit display to start just before first open shift (or 2nd if only one used) + // this assume you must have 1st shift defined and possible 2nd (i.e. can't define only 2nd shift) + if (!$open_24 && $buffer != 300) { + if ($int_first_shift_open < 9999 && ($hours[$x]['time'] < $int_first_shift_open - $buffer)) { + unset($hours[$x]); + } + else { + $hours[$x] = $time_slot; + } + // and do the same for closing + if (isset($hours[$x])) { + if ($int_second_shift_close < 9999) { + if ($hours[$x]['time'] >= $int_second_shift_close + $buffer) { + unset($hours[$x]); + } + } + elseif ($int_first_shift_close < 9999) { + if ($hours[$x]['time'] >= $int_first_shift_close + $buffer) { + unset($hours[$x]); + } + } + } + } + else { + $hours[$x] = $time_slot; + } + $x++; + } + $times = reserve_times(); + $entities = reserve_entities($ebundle); + + // Initialize the $reservations array. + $reservations = array(); + foreach ($entities as $entity) { + $entity_name = $entity->label(); + foreach ($hours as $time_slot) { + $time = $time_slot['time']; + $reservations[$entity->id()][$time] = array( + 'time' => $time, + 'display' => $time_slot['display'], + 'class' => $time_slot['class'], + 'id' => '', + 'booked' => FALSE, + 'start' => FALSE, + 'end' => FALSE, + 'user' => '', + 'name' => '', + ); + } + } + + $results = array(); + $ids = \Drupal::service('entity.query') + ->get('reserve_reservation') + ->condition('status', TRUE) + ->condition('reservation_date', $yyyy_mmdd . '%', 'like') + ->condition('reservation_ebundle', $ebundle) + //->sort('reservation_time', 'DESC') + //->sort('reservations_display_order', 'ASC'); + ->accessCheck(FALSE) + ->execute(); + + $results = \Drupal::entityTypeManager()->getStorage('reserve_reservation')->loadMultiple($ids); + + foreach ($results as $data) { + $id = $data->id(); + $time = $data->reservation_time->getString(); + $rid = $data->reservable_id->getString(); + $name = $data->label(); + $user = $data->getOwner(); + $reservations[$rid][$time]['booked'] = TRUE; + $reservations[$rid][$time]['class'] .= ' booked'; + $reservations[$rid][$time]['name'] = $name; + $reservations[$rid][$time]['user_name'] = $user->getDisplayName(); + $reservations[$rid][$time]['id'] = $id; + $reservations[$rid][$time]['series_id'] = $data->reservation_series_id->getString() ? $data->reservation_series_id->getString() : ''; + $reservations[$rid][$time]['user'] = $user->id(); + $reservations[$rid][$time]['blocked'] = $data->reservation_private->getString() ? $data->reservation_private->getString() : FALSE; + + // add rest of slots as booked for the length of this reservation + $length = $data->reservation_length->getString();; + $time_slots = $length / 30; + $reservations[$rid][$time]['slots'] = $time_slots; + $remainder = $length % 30; + if ($remainder > 1) { + $time_slots++; + } + $key = array_search($time, $times); + $x = 2; + while ($x <= $time_slots) { + $key++; + $next_time = $times[$key]; + $reservations[$rid][$next_time]['booked'] = TRUE; + $reservations[$rid][$next_time]['class'] .= ' booked'; + $x++; + // reservation time slots can't go passed midnight; i.e. slot 47 + if ($key == 47) { + break; + } + } + + // add in pre/post buffer for setup/takedown (rev 7.x-1.3+) + // - if the slot is part of buffer we add "setup" to class + // - if we don't have admin rights; we also mark it as booked so no one can book in these slots + $category = $categories[$entities[$rid]->$category_field->getString()]; + $preslots = $category['prebuffer'] / 30; + $postslots = $category['postbuffer'] / 30; + $startkey = array_search($reservations[$rid][$time]['time'], $times); + $endkey = $startkey + $time_slots; + $k = $startkey - $preslots; + $bookover = \Drupal::currentUser()->hasPermission('book over reservation buffer'); + while ($k < $startkey) { + if (!$reservations[$rid][$times[$k]]['booked']) { + $reservations[$rid][$times[$k]]['class'] .= ' setup'; + } + $reservations[$rid][$times[$k]]['booked'] = $bookover ? $reservations[$rid][$times[$k]]['booked'] : true; + $k++; + } + $k = $endkey; + while ($k < $endkey + $postslots) { + if (!$reservations[$rid][$times[$k]]['booked']) { + $reservations[$rid][$times[$k]]['class'] .= ' setup'; + } + $reservations[$rid][$times[$k]]['booked'] = $bookover ? $reservations[$rid][$times[$k]]['booked'] : true; + $k++; + } + } + + $vars = array( + 'ebundle' => $ebundle, + 'dates' => $dates, + 'categories' => $categories, + 'reservations' => $reservations, + 'hours' => $hours, + 'rooms' => $entities, + 'user_reservations' => reserve_user_reservations(), + ); + + // @todo: should do something here with render array and tpl file + $variables = $this->calenderPage($vars); + $variables['#theme'] = 'calendar_template'; + $variables['#attached']['library'] = 'reserve/reserve'; + $variables['#building_hours_display'] = $building_hours_display; + + return $variables; + } + + /** + * Theme the reservation calendar page. + * + * @global object $user + * Drupal user object. + * @global type $base_url + * Application base url. + * + * @param array $dates + * An array containing information about all the possible days for which a + * reservtion can be made. + * @param array $categories + * An array of all the room categories. + * @param array $reservations + * An array representing all the reservations that have been made for the + * day that is being displayed on the calendar. + * @param array $hours + * An array representing every half hour time slot in a single day. + * @param string $building_hours_display + * A user friendly string of the open hours for the day being displayed + * on the calendar. + * @param array $rooms + * An array representing all of the ENTITIES that can be reserved. + * @param array $user_reservations + * An array representing the current user's reservations for the allowable + * reservation period. + * + * @return string + * The html for displaying the page. + */ + private function calenderPage($vars) { + /** + * @var $ebundle + * @var $dates + * @var $categories + * @var $reservations + * @var $hours + * @var $building_hours_display + * @var $rooms + * @var $user_reservations + */ + extract($vars); + + if (!count($rooms)) { + $this->messenger()->addError(t('You will need to first add entities which can be reserved.')); + throw new NotFoundHttpException(); + } + + global $base_url; + $user = \Drupal::currentUser(); + $extended = \Drupal::currentUser()->hasPermission('add reservations extended'); + $config = \Drupal::config('reserve.settings'); + $category_field = reserve_category_fields($ebundle); + $clearimg = ''; + $modal = ['attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => json_encode(['width' => 700]) + ]]; + + $entity_type = ebundle_split($ebundle, 'type'); + $bundle = ebundle_split($ebundle, 'bundle'); + + // to support minimum adv booking per category; let's get available dates per cat + $datespercat = reserve_dates($ebundle,null, null, true); + + // pass some variables to JS + $format = strtolower($config->get('date_format') ? $config->get('date_format') : 'y/m/d'); + $variables['#attached']['drupalSettings']['reserve']['dateFormat'] = $format; + $variables['#attached']['drupalSettings']['reserve']['ebundle'] = $ebundle; + $max_length = $extended ? + ($config->get('reservation_max_length_admin') ? $config->get('reservation_max_length_admin') : 120) : + ($config->get('reservation_max_length_standard') ? $config->get('reservation_max_length_standard') : 120); + $variables['#attached']['drupalSettings']['reserve']['maxLength'] = $max_length; + + // Determine which date has been selected by the user. + foreach ($dates as $day) { + if ($day['selected'] == TRUE) { + $day_of_week = $day['day-of-week']; + $month_number = $day['month-number']; + $month = $day['month']; + $xday = $day['day']; + $year = $day['year']; + $yyyymmdd = $day['yyyymmdd']; + } + } + + $variables['#calendar-text'] = $config->get('calendar_text'); + $variables['#reserve_room_instructions_text'] = $config->get('reserve_instructions') ? $config->get('reserve_instructions') : + t('To make a reservation, click on the desired time/day in the calendar below. You will be asked to login.'); + $variables['#arrow'] = base_path() . drupal_get_path('module', 'reserve') . '/images/arrow-icon.png'; + $variables['#date'] = format_date(strtotime($month . ' ' . $xday . ', ' . $year), 'custom', 'l, F d, Y'); + $variables['#date_picker'] = \Drupal::formBuilder()->getForm('Drupal\reserve\Form\CalendarDatePicker'); + + $field = reserve_category_fields($ebundle); + $fconfig = \Drupal\field\Entity\FieldConfig::loadByName($entity_type, $bundle, $field)->getSettings(); + $variables['#calendar_header'] = $fconfig['calendar_header']; + $variables['#reservation_instructions'] = $fconfig['reservation_instructions']; + + // Bundle name (or Entity Type name if no bundles) + $bundle_info = \Drupal::service("entity_type.bundle.info")->getAllBundleInfo(); + $bundle = $bundle_info[current($rooms)->getEntityTypeId()][current($rooms)->bundle()]['label']; + + // Reservation calendar grid: + // + // Each block on the grid is assigned one (or more) of the following classes: + // + // closed - the building is closed; + // booked - the building is open and the time slot has been reserved; + // open - the building is open, the time slot has not been reserved, but the user must login to reserve the time slot; + // reservable - the building is open, the time slot has not been reserved and the user is able to reserve the time slot. + // setup - buffer zones added before/after bookings to allow for setup/takedown (per category) + // + + // Panel container. + + // If the user is logged in, the class for each unbooked time slot is 'reservable'. If the user is not logged in, the class is 'open'. + // Only logged in users can click a block of time to open the reservation form. + $unbooked_class = ($user->id()) ? 'reservable' : 'open'; + + // Create a tab for each room category. Each tab contains a grid of time slots and rooms. + $i = 0; + foreach ($categories as $index => $category) { + $table = array(); + + // Category TABS + $categories[$index]['active'] = ($i == 0) ? 'active' : ''; + $categories[$index]['tag'] = strtolower(preg_replace('/[^a-zA-Z0-9-]+/', '-', $category['title'])); + + // Show the first tab and hide all others. + //// @todo: use JS to set active based on anchor + $categories[$index]['show'] = ($i == 0) ? 'show' : 'hide'; + + $i++; + $r = 0; $c = 0; + + // Date and building hours. + $flipped = $config->get('calendar_flipped'); + $orientclass = $flipped ? 'orient-horiz' : 'orient-vert'; + + $table[$r][$c] = '
  • ' . $bundle . '
  • ' . "\n"; + $r++; + + // Available hours column. + foreach ($hours as $time_slot) { + $time_display = ($time_slot['class'] == 'odd') ? t($time_slot['display']) : ""; + $table[$r][$c] = '
  • ' . $time_display . '
  • ' . "\n"; + $r++; + } + $table[$r][$c] = '' . "\n"; + $r++; + + // Column for each room in the category. + $rooms_per_category = 0; + foreach ($rooms as $room) { + $rid = $room->id(); + // Count the number of rooms in the selected category. + if ($room->$category_field->getString() == $category['id']) { + $rooms_per_category++; + } + $room_name = $room->label(); + $room_link = Link::fromTextAndUrl($room_name, Url::fromUri('internal:/' . $entity_type . '/' . $rid))->toString(); + + // use qtip if we have it + if (\Drupal::moduleHandler()->moduleExists('qtip')) { + $variables['#room_desc'] = $room->body->getString(); + $room_info = '' . $room_desc . '' . $room_link . ''; + } + else { + $room_info = $room_link; + } + + if ($room->$category_field->getString() == $category['id']) { + $c++; $r = 0; + + // Room name + $cell = '
  • ' . $room_info . '
  • '; + $r++; + + // Populate each time slot. + foreach ($hours as $time_slot) { + $r++; + $time = $time_slot['time']; + $open = $time_slot['open']; + + // lets use slot class from reservation if it is set + $slotclass = isset($reservations[$rid][$time_slot['time']]['class']) ? $reservations[$rid][$time_slot['time']]['class'] : $time_slot['class']; + + // to support min adv booking per cat; let's simply mark all slots as closed for dates not available to this user for this cat + if (!isset($datespercat[$category['id']][$yyyymmdd])) { + $open = false; + } + + // Determine if the building is open or closed for this time slot. + if ($open) { + $booked_class = ($reservations[$rid][$time]['booked']) ? '' : $unbooked_class; + } + else { + $booked_class = 'closed'; + } + // The time slot can be reserved by the current user. + $viewable_class = ''; + $widthclass = ''; + if ($booked_class == 'reservable' && $user->hasPermission('add reservations')) { + $link = Link::fromTextAndUrl( + Markup::create($clearimg), + Url::fromUri('internal:/reserve_reservation/add/' . + $month_number . '/' . $xday . '/' . $time_slot['time'] . '/' . $rid . '/' . $ebundle, $modal)) + ->toString(); + } + // The time slot can be reserved, but the user must login first. + elseif ($booked_class == 'open') { + $link = Link::fromTextAndUrl( + Markup::create($clearimg), + Url::fromUri('internal:/user/login?destination=/reserve_reservation/add/' . + $month_number . '/' . $xday . '/' . $time_slot['time'] . '/' . $rid . '/' . $ebundle, $modal)) + ->toString(); + } + elseif ($booked_class == 'closed') { + $link = ''; + } + else { + // if the slot has no ID this means it is the latter slots of the booking + $viewable_class = ''; + $link = ''; + + // The time slot has a reservation that can be edited by the current user. + if ($id = $reservations[$rid][$time]['id']) { + $reservation = entity_load('reserve_reservation', $id); + $viewable_class = $reservation->access('update') ? 'viewable' : ''; + if ($viewable_class == 'viewable') { + $options = array_merge_recursive($modal, array( + 'attributes' => array( + 'title' => $reservations[$rid][$time]['name'], + 'class' => 'booking-span'), + 'absolute' => TRUE, + )); + $link = Link::fromTextAndUrl( + $reservations[$rid][$time]['name'], + Url::fromUri('internal:/reserve_reservation/' . $id . '/edit', $options)) + ->toString(); + } + + // The time slot has a reservation that cannot be viewed by the current user. and we are NOT allowed to see the Title + else if (isset($reservations[$rid][$time]['blocked']) && $reservations[$rid][$time]['blocked']) { + $link = t('Booked'); + } + + // The time slot has a reservation that cannot be edited by the current user. but we are allowed to see the Title + else { + $link = $reservations[$rid][$time]['name']; + } + } + + $slots = isset($reservations[$rid][$time]['slots']) ? $reservations[$rid][$time]['slots'] : ''; + $widthclass = $slots ? 'colspan' . $reservations[$rid][$time]['slots'] : ''; + } + + // allow other modules to modify the $link + \Drupal::moduleHandler()->alter('reserve_reservations_link', $link, $reservations[$rid][$time]); + + // allow other modules to add a custom class to slots + $custom_class = ''; + \Drupal::moduleHandler()->alter('reserve_reservations_custom_class', $custom_class, $reservations[$rid][$time]); + + // add div wrapper to display better + $link = $link ? '
    ' . $link . '
    ' : ''; + + // we used book class to determine if linked or not; which we needed for pre/post slots as well as actual reservation slots + // but we don't want to show booked class now for the slots which are just buffer slots + if (stristr($slotclass, 'setup')) { + $booked_class = ''; + } + + if ($reservations[$rid][$time]['id']) { + $ingroup = true; + $numslots = $reservations[$rid][$time]['slots']; + if ($reservations[$rid][$time]['series_id']) { + $cell .= '
    '; + } + else { + $cell .= '
    '; + } + $numslots--; + } + + $cell .= '
  • ' . $link . '
  • ' . "\n"; + + if (isset($ingroup) && $ingroup) { + if ($numslots) { + $numslots--; + } + else { + $cell .= '
    '; + $ingroup = false; + } + } + } + + $table[$r][$c] = $cell; + + // Room name and capacity. + $r++; + $table[$r][$c] = '
  • ' . $room_info . '
  • ' . "\n"; + + } + } + + // remove extra table labels based on admin setting + $compressed = false; + if ($config->get('calendar_compressed_labels')) { + $compressed = true; + } + + // dump our table contents in std or flipped orientation + if ($flipped) { + $table = $this->reserve_transpose($table); + $m = $r; + $n = $c; + } + else { + $m = $c; + $n = $r; + } + + // @todo: this should be done as variables passed to twig + $table_markup = ''; + for ($x = 0; $x <= $m; $x++){ + if ($flipped && $compressed && ($x == 1 || $x == $m || $x == $m - 1)) continue; + $table_markup .= '
    + +
    + '; + } + + if ($rooms_per_category) { + $categories[$index]['table'] = Markup::create($table_markup); + } + else { + $nothing_to_book = t('There are no %bundles to book in this category. You will need to assign this Reserve Category to at least 1 %bundle.', + ['%bundles' => $bundle . 's', '%bundle' => $bundle]); + $categories[$index]['table'] = Markup::create('' . $nothing_to_book . '' . $table_markup); + } + } + + $variables['#categories'] = $categories; + + // if we have no bookable entities, lets put out helpful message + // @todo - possibly this could be done much earlier and then skip alot of the code above; but just processing empty arrays so likely not a big deal + + return $variables; + } + + private function reserve_transpose($array) { + array_unshift($array, null); + return call_user_func_array('array_map', $array); + } + + private function reserve_earliest_category($categories) { + $earliest = 1000; + $extended = \Drupal::currentUser()->hasPermission('add reservations extended'); + $catmin = $extended ? 'minadvext' : 'minadvstd'; + foreach ($categories as $cat) { + if ($cat[$catmin] < $earliest) { + $earliest = $cat[$catmin]; + $result = $cat['id']; + } + } + return $result; + } + + public function reservationAddModalCallback() { + $response = new jsonResponse(); + + $entity = ReserveReservation::create(); + $editForm = \Drupal::service('entity.form_builder')->getForm($entity, 'default'); + $form = \Drupal::service('renderer')->renderPlain($editForm); + + $response->setData($form); + + return $response; + } + + public function accessCalendarPage(AccountInterface $account, $ebundle) { + return AccessResult::allowedIf($account->hasPermission('access calendar for ' . $ebundle)); + } + +} diff --git a/src/Controller/ReservePermissions.php b/src/Controller/ReservePermissions.php new file mode 100644 index 0000000..d5e7e93 --- /dev/null +++ b/src/Controller/ReservePermissions.php @@ -0,0 +1,60 @@ +entityManager = $entity_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static($container->get('entity.manager')); + } + + /** + * Get permissions for Taxonomy Views Integrator. + * + * @return array + * Permissions array. + */ + public function permissions() { + $permissions = []; + + $ebundles = ebundles_formatted(); + + foreach ($ebundles as $ebundle => $bundle) { + $permissions += [ + 'access calendar for ' . $ebundle => [ + 'title' => $this->t('Allow access to the calendar for ' . $bundle), + ] + ]; + } + + return $permissions; + } +} \ No newline at end of file diff --git a/src/Entity/ReserveCategory.php b/src/Entity/ReserveCategory.php new file mode 100644 index 0000000..a8755cd --- /dev/null +++ b/src/Entity/ReserveCategory.php @@ -0,0 +1,222 @@ + \Drupal::currentUser()->id(), + ); + } + + /** + * {@inheritdoc} + */ + public function postCreate(EntityStorageInterface $storage) { + $this->newRevision = TRUE; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return $this->get('name')->value; + } + + /** + * {@inheritdoc} + */ + public function setName($name) { + $this->set('name', $name); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCreatedTime() { + return $this->get('created')->value; + } + + /** + * {@inheritdoc} + */ + public function setCreatedTime($timestamp) { + $this->set('created', $timestamp); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOwner() { + return $this->get('user_id')->entity; + } + + /** + * {@inheritdoc} + */ + public function getOwnerId() { + return $this->get('user_id')->target_id; + } + + /** + * {@inheritdoc} + */ + public function setOwnerId($uid) { + $this->set('user_id', $uid); + return $this; + } + + /** + * {@inheritdoc} + */ + public function setOwner(UserInterface $account) { + $this->set('user_id', $account->id()); + return $this; + } + + /** + * {@inheritdoc} + */ + public function isPublished() { + return (bool) $this->getEntityKey('status'); + } + + /** + * {@inheritdoc} + */ + public function setPublished($published) { + $this->set('status', $published ? TRUE : FALSE); + return $this; + } + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { + $fields = parent::baseFieldDefinitions($entity_type); + + $fields['user_id'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('Authored by')) + ->setDescription(t('The user ID of author of the Reservation Category entity.')) + ->setRevisionable(TRUE) + ->setSetting('target_type', 'user') + ->setSetting('handler', 'default') + ->setTranslatable(TRUE) + ->setDisplayOptions('view', array( + 'label' => 'hidden', + 'type' => 'author', + 'weight' => 0, + )) + ->setDisplayOptions('form', array( + 'type' => 'entity_reference_autocomplete', + 'weight' => 5, + 'settings' => array( + 'match_operator' => 'CONTAINS', + 'size' => '60', + 'autocomplete_type' => 'tags', + 'placeholder' => '', + ), + )) + ->setDisplayConfigurable('form', true) + ->setDisplayConfigurable('view', true); + + $fields['name'] = BaseFieldDefinition::create('string') + ->setLabel(t('Name')) + ->setDescription(t('The name of the Reservation Category entity.')) + ->setSettings(array( + 'max_length' => 50, + 'text_processing' => 0, + )) + ->setDefaultValue('') + ->setDisplayOptions('view', array( + 'label' => 'above', + 'type' => 'string', + 'weight' => -4, + )) + ->setDisplayOptions('form', array( + 'type' => 'string_textfield', + 'weight' => -4, + )) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['status'] = BaseFieldDefinition::create('boolean') + ->setLabel(t('Publishing status')) + ->setDescription(t('A boolean indicating whether the Reservation Category is published.')) + ->setDefaultValue(TRUE); + + $fields['created'] = BaseFieldDefinition::create('created') + ->setLabel(t('Created')) + ->setDescription(t('The time that the entity was created.')); + + $fields['changed'] = BaseFieldDefinition::create('changed') + ->setLabel(t('Changed')) + ->setDescription(t('The time that the entity was last edited.')); + + return $fields; + } + +} diff --git a/src/Entity/ReserveCategoryInterface.php b/src/Entity/ReserveCategoryInterface.php new file mode 100644 index 0000000..6ab1fe2 --- /dev/null +++ b/src/Entity/ReserveCategoryInterface.php @@ -0,0 +1,58 @@ + 'id', + 'title' => $this->t('Reservation Category'), + 'help' => $this->t('The Reservation Category ID.'), + ); + + return $data; + } + +} diff --git a/src/Entity/ReserveReservation.php b/src/Entity/ReserveReservation.php new file mode 100644 index 0000000..c1112a8 --- /dev/null +++ b/src/Entity/ReserveReservation.php @@ -0,0 +1,216 @@ + \Drupal::currentUser()->id(), + ); + } + + /** + * {@inheritdoc} + */ + public function getName() { + return $this->get('name')->value; + } + + /** + * {@inheritdoc} + */ + public function setName($name) { + $this->set('name', $name); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCreatedTime() { + return $this->get('created')->value; + } + + /** + * {@inheritdoc} + */ + public function setCreatedTime($timestamp) { + $this->set('created', $timestamp); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOwner() { + return $this->get('user_id')->entity; + } + + /** + * {@inheritdoc} + */ + public function getOwnerId() { + return $this->get('user_id')->target_id; + } + + /** + * {@inheritdoc} + */ + public function setOwnerId($uid) { + $this->set('user_id', $uid); + return $this; + } + + /** + * {@inheritdoc} + */ + public function setOwner(UserInterface $account) { + $this->set('user_id', $account->id()); + return $this; + } + + /** + * {@inheritdoc} + */ + public function isPublished() { + return (bool) $this->getEntityKey('status'); + } + + /** + * {@inheritdoc} + */ + public function setPublished($published) { + $this->set('status', $published ? TRUE : FALSE); + return $this; + } + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { + $fields = parent::baseFieldDefinitions($entity_type); + + $fields['user_id'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('Authored by')) + ->setDescription(t('The user ID of author of the Reservation entity.')) + ->setRevisionable(TRUE) + ->setSetting('target_type', 'user') + ->setSetting('handler', 'default') + ->setTranslatable(TRUE) + ->setDisplayOptions('view', array( + 'label' => 'hidden', + 'type' => 'author', + 'weight' => 0, + )) + ->setDisplayOptions('form', array( + 'type' => 'entity_reference_autocomplete', + 'weight' => 5, + 'settings' => array( + 'match_operator' => 'CONTAINS', + 'size' => '60', + 'autocomplete_type' => 'tags', + 'placeholder' => '', + ), + )) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['name'] = BaseFieldDefinition::create('string') + ->setLabel(t('Group Name')) + ->setDescription(t('The person or group making the reservation.')) + ->setSettings(array( + 'max_length' => 50, + 'text_processing' => 0, + )) + ->setDefaultValue('') + ->setDisplayOptions('view', array( + 'label' => 'above', + 'type' => 'string', + 'weight' => -4, + )) + ->setDisplayOptions('form', array( + 'type' => 'string_textfield', + 'weight' => -4, + )) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['status'] = BaseFieldDefinition::create('boolean') + ->setLabel(t('Publishing status')) + ->setDescription(t('A boolean indicating whether the Reservation is published.')) + ->setDefaultValue(TRUE); + + $fields['created'] = BaseFieldDefinition::create('created') + ->setLabel(t('Created')) + ->setDescription(t('The time that the entity was created.')); + + $fields['changed'] = BaseFieldDefinition::create('changed') + ->setLabel(t('Changed')) + ->setDescription(t('The time that the entity was last edited.')); + + return $fields; + } + + +} diff --git a/src/Entity/ReserveReservationInterface.php b/src/Entity/ReserveReservationInterface.php new file mode 100644 index 0000000..2aa0816 --- /dev/null +++ b/src/Entity/ReserveReservationInterface.php @@ -0,0 +1,77 @@ +getPath(); + $path_args = explode('/', $current_path); + + $form['date'] = [ + '#type' => 'date', + '#description' => $this->t('Click in box to select date.'), + ]; + + if (count($path_args) == 5) { + $date = date('Y') . '-' . $path_args[4] . '-' . $path_args[5]; + $form['date']['#default_value'] = $date; + } + else { + $form['date']['#default_value'] = date('Y-m-d'); + } + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + // Display result. + foreach ($form_state->getValues() as $key => $value) { + drupal_set_message($key . ': ' . $value); + } + } + +} diff --git a/src/Form/ReserveCategoryDeleteForm.php b/src/Form/ReserveCategoryDeleteForm.php new file mode 100644 index 0000000..bda6091 --- /dev/null +++ b/src/Form/ReserveCategoryDeleteForm.php @@ -0,0 +1,15 @@ +entity; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + $entity = &$this->entity; + + $status = parent::save($form, $form_state); + + switch ($status) { + case SAVED_NEW: + drupal_set_message($this->t('Created the %label Reservation Category.', [ + '%label' => $entity->label(), + ])); + break; + + default: + drupal_set_message($this->t('Saved the %label Reservation Category.', [ + '%label' => $entity->label(), + ])); + } + $form_state->setRedirect('entity.reserve_category.canonical', ['reserve_category' => $entity->id()]); + } + +} diff --git a/src/Form/ReserveCategorySettingsForm.php b/src/Form/ReserveCategorySettingsForm.php new file mode 100644 index 0000000..8aea982 --- /dev/null +++ b/src/Form/ReserveCategorySettingsForm.php @@ -0,0 +1,55 @@ +config('reserve.monthly_hours')->get($selected_month); + + // create a form to pick a month. + $month_options = array(); + $first = current($cur_months); + foreach ($cur_months as $cur_month) { + $yyyy_mm = $cur_month['YYYY_MM']; + $display = t($cur_month['display']); + $month_options[$yyyy_mm] = t($cur_month['display']); + } + $form['select_month'] = array( + '#type' => 'container', + '#weight' => -2000, + ); + $form['select_month']['month'] = array( + '#title' => t('Month'), + '#type' => 'select', + '#options' => $month_options, + '#default_value' => $selected_month, + ); + $form['select_month']['save'] = array( + '#type' => 'submit', + '#value' => t('Select a month'), + '#name' => 'select', + ); + + // Return a form to update the hours for each day of selected month. + $hours = reserve_hours(); + // Select box options. + $options = array(); + $options['9999'] = t('Select the time'); + $options['0000'] = t('Midnight - start of day'); + foreach ($hours as $hour) { + $time = $hour['time']; + $display = t($hour['display']); + if ($time != '0000') { + $options[$time] = $display; + } + } + $options['2400'] = t('Midnight - end of day'); + + $yyyy_mm = $selected_month; + $month = intval(substr($yyyy_mm, 5)); + $year = substr($yyyy_mm, 0, 4); + $month_display = date('F Y', mktime(0, 0, 0, $month, 1, $year)); + // Days in the month. + $days = date('t', mktime(0, 0, 0, $month, 1, $year)); + + // if we don't have Hours set for this Month, let's use default values + if (isset($monthly_hours)) { + $mo_hours = $monthly_hours; + } + else { + $mo_hours = reserve_default_monthly_hours($year, $month); + } + + // From Header + $form['#tree'] = TRUE; + $form['month_display'] = array( + '#prefix' => '
    ', + '#suffix' => '
    ', + '#markup' => $month_display, + '#weight' => -100, + ); + $form['note'] = array( + '#markup' => t('Asterisk (*) indicates that the hours have been changed from the default'), + '#weight' => -99, + ); + + for ($day = 0; $day < $days; $day++) { + // Day of week. + $dow = date('l', mktime(0, 0, 0, $month, $day + 1, $year)); + $changed_hours = ($mo_hours[($day * 5)] == 'O') ? '*' : ''; + $day_hours = array(); + $day_hours[] = $mo_hours[($day * 5) + 1]; + $day_hours[] = $mo_hours[($day * 5) + 2]; + $day_hours[] = $mo_hours[($day * 5) + 3]; + $day_hours[] = $mo_hours[($day * 5) + 4]; + $display_hours = reserve_hours_display($day_hours); + $title = ($day + 1) . ' ' . $dow . ' (' . $display_hours . ') ' . $changed_hours; + $form['day_' . $day] = array( + '#type' => 'details', + '#title' => $title, + '#open' => FALSE, + '#weight' => -9 + ($day * 2), + ); + $form['day_' . $day]['first_shift_open_' . $day] = array( + '#title' => t('First shift open'), + '#type' => 'select', + '#options' => $options, + '#default_value' => $mo_hours[($day * 5) + 1], + //'#weight' => -9 + ($day * 2) + 1, + ); + $form['day_' . $day]['first_shift_close_' . $day] = array( + '#title' => t('First shift close'), + '#type' => 'select', + '#options' => $options, + '#default_value' => $mo_hours[($day * 5) + 2], + //'#weight' => -9 + ($day * 10) + 2, + ); + $form['day_' . $day]['second_shift_open_' . $day] = array( + '#title' => t('Second shift open'), + '#type' => 'select', + '#options' => $options, + '#default_value' => $mo_hours[($day * 5) + 3], + //'#weight' => -90 + ($day * 10) + 3, + ); + $form['day_' . $day]['second_shift_close_' . $day] = array( + '#title' => t('Second shift close'), + '#type' => 'select', + '#options' => $options, + '#default_value' => $mo_hours[($day * 5) + 4], + //'#weight' => -90 + ($day * 10) + 4, + ); + } + $form['save'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + '#weight' => 400, + '#name' => 'save', + ); + $form['reset'] = array( + '#type' => 'submit', + '#value' => t('Reset to defaults'), + '#weight' => 401, + '#name' => 'reset', + ); + $form['month'] = array( + '#type' => 'value', + '#value' => $selected_month, + ); + + return $form; + } + + /** + * Form submission handler. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + $yyyy_mm = $form_state->getUserInput()['select_month']['month']; + $month = intval(substr($yyyy_mm, 5)); + $year = substr($yyyy_mm, 0, 4); + + if ($form_state->getTriggeringElement()['#name'] == 'select') { + $form_state->setRedirect('reserve.settings.hours.daily', array('passed_month' => $yyyy_mm)); + return; + } + + if ($form_state->getTriggeringElement()['#name'] == 'save') { + // Days in the month. + $days = date('t', mktime(0, 0, 0, $month, 1, $year)); + + $updated_mo_hours = array(); + $default_hours = \Drupal::config('reserve.default_hours')->get('data'); + for ($day = 0; $day < $days; $day++) { + // User entered hours for a single day. + $day_first_open = $values['day_' . $day]['first_shift_open_' . $day]; + $day_first_close = $values['day_' . $day]['first_shift_close_' . $day]; + $day_second_open = $values['day_' . $day]['second_shift_open_' . $day]; + $day_second_close = $values['day_' . $day]['second_shift_close_' . $day]; + // Default hours for the day of the week. + // Day of week. + $dow = date('w', mktime(0, 0, 0, $month, $day + 1, $year)); + $default_first_open = $default_hours[($dow * 4) + 0]; + $default_first_close = $default_hours[($dow * 4) + 1]; + $default_second_open = $default_hours[($dow * 4) + 2]; + $default_second_close = $default_hours[($dow * 4) + 3]; + // Compare user entered hours to default for the day of the week. + if (($day_first_open == $default_first_open) && + ($day_first_close == $default_first_close) && + ($day_second_open == $default_second_open) && + ($day_second_close == $default_second_close)) { + $day_is_default = TRUE; + } + else { + $day_is_default = FALSE; + } + // Update the monthly hours record. + $updated_mo_hours[($day * 5)] = ($day_is_default) ? 'D' : 'O'; + $updated_mo_hours[($day * 5) + 1] = $day_first_open; + $updated_mo_hours[($day * 5) + 2] = $day_first_close; + $updated_mo_hours[($day * 5) + 3] = $day_second_open; + $updated_mo_hours[($day * 5) + 4] = $day_second_close; + } + $confirmation = t('Daily overrides set for %month', array('%month' => date('F', mktime(0, 0, 0, $month, 10)))); + } + + if ($form_state->getTriggeringElement()['#name'] == 'reset') { + $updated_mo_hours = reserve_default_monthly_hours($year, $month); + $confirmation = t('Daily overrides cleared for %month', array('%month' => date('F', mktime(0, 0, 0, $month, 10)))); + } + + // save updates or defaults (reset) for this month + $this->config('reserve.monthly_hours') + ->set($yyyy_mm, $updated_mo_hours) + ->save(); + drupal_set_message($confirmation); + } +} diff --git a/src/Form/ReserveDefaultHoursForm.php b/src/Form/ReserveDefaultHoursForm.php new file mode 100644 index 0000000..a539aa1 --- /dev/null +++ b/src/Form/ReserveDefaultHoursForm.php @@ -0,0 +1,308 @@ +config('reserve.default_hours')->get('data'); + + $hours = reserve_hours(); + + // Select box options. + $options = array(); + $options['9999'] = t('Select the time'); + $options['0000'] = t('Midnight - start of day'); + foreach ($hours as $hour) { + $time = $hour['time']; + $display = t($hour['display']); + if ($time != '0000') { + $options[$time] = $display; + } + } + $options['2400'] = t('Midnight - end of day'); + // Get saved default hours. + if (!$default_hours) { + for ($x = 0; $x < 28; $x++) { + $default_hours[$x] = '9999'; + } + } + $days = array( + t('Sunday'), + t('Monday'), + t('Tuesday'), + t('Wednesday'), + t('Thursday'), + t('Friday'), + t('Saturday'), + ); + $form['#tree'] = TRUE; + for ($day = 0; $day < 7; $day++) { + $day_hours = array(); + $day_hours[] = $default_hours[($day * 4)]; + $day_hours[] = $default_hours[($day * 4) + 1]; + $day_hours[] = $default_hours[($day * 4) + 2]; + $day_hours[] = $default_hours[($day * 4) + 3]; + $display_hours = reserve_hours_display($day_hours); + $form['day_' . $day] = array( + '#type' => 'details', + '#title' => $days[$day] . ' (' . $display_hours . ')', + '#open' => FALSE, + '#weight' => -90 + ($day * 10), + ); + $form['day_' . $day]['first_shift_open_' . $day] = array( + '#title' => t('First shift open'), + '#type' => 'select', + '#options' => $options, + '#default_value' => $default_hours[($day * 4)], + '#weight' => -90 + ($day * 10) + 1, + ); + $form['day_' . $day]['first_shift_close_' . $day] = array( + '#title' => t('First shift close'), + '#type' => 'select', + '#options' => $options, + '#default_value' => $default_hours[($day * 4) + 1], + '#weight' => -90 + ($day * 10) + 2, + ); + $form['day_' . $day]['second_shift_open_' . $day] = array( + '#title' => t('Second shift open'), + '#type' => 'select', + '#options' => $options, + '#default_value' => $default_hours[($day * 4) + 2], + '#weight' => -90 + ($day * 10) + 3, + ); + $form['day_' . $day]['second_shift_close_' . $day] = array( + '#title' => t('Second shift close'), + '#type' => 'select', + '#options' => $options, + '#default_value' => $default_hours[($day * 4) + 3], + '#weight' => -90 + ($day * 10) + 4, + ); + } + $form['save'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + '#weight' => 100, + '#name' => 'save', + ); + $form['reset'] = array( + '#type' => 'submit', + '#value' => t('Reset to defaults'), + '#weight' => 101, + '#name' => 'reset', + ); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + $days = array( + t('Sunday'), + t('Monday'), + t('Tuesday'), + t('Wednesday'), + t('Thursday'), + t('Friday'), + t('Saturday'), + ); + if ($form_state->getTriggeringElement()['#name'] == 'save') { + for ($day = 0; $day < 7; $day++) { + $open = TRUE; + $second_shift = FALSE; + $first_shift_open = $values['day_' . $day]['first_shift_open_' . $day]; + $first_shift_close = $values['day_' . $day]['first_shift_close_' . $day]; + $second_shift_open = $values['day_' . $day]['second_shift_open_' . $day]; + $second_shift_close = $values['day_' . $day]['second_shift_close_' . $day]; + $int_first_shift_open = intval($values['day_' . $day]['first_shift_open_' . $day]); + $int_first_shift_close = intval($values['day_' . $day]['first_shift_close_' . $day]); + $int_second_shift_open = intval($values['day_' . $day]['second_shift_open_' . $day]); + $int_second_shift_close = intval($values['day_' . $day]['second_shift_close_' . $day]); + // Closed. + if (($int_first_shift_open == 9999) && ($int_first_shift_close == 9999) && ($int_second_shift_open == 9999) && ($int_second_shift_close == 9999)) { + $open = FALSE; + } + // First shift. + if ($open) { + if ($int_first_shift_open == 9999) { + $field = $form_state->getCompleteForm()['day_' . $day]['first_shift_open_' . $day]; + $message = t('%day - First shift open is required.', array('%day' => $days[$day])); + $form_state->setError($field, $message); + } + elseif ($int_first_shift_close == 9999) { + $field = $form_state->getCompleteForm()['day_' . $day]['first_shift_close_' . $day]; + $message = t('%day - First shift close is required.', array('%day' => $days[$day])); + $form_state->setError($field, $message); + } + elseif ($int_first_shift_open >= $int_first_shift_close) { + $field = $form_state->getCompleteForm()['day_' . $day]['first_shift_close_' . $day]; + $message = t('%day - First shift close must be later than first shift open.', array('%day' => $days[$day])); + $form_state->setError($field, $message); + } + } + // Second shift. + if ($open) { + if (($int_second_shift_open != 9999) || ($int_second_shift_close != 9999)) { + $second_shift = TRUE; + } + } + if ($second_shift) { + if (($int_first_shift_open == 9999) && ($int_first_shift_close == 9999)) { + $field = 'day_' . $day . '][second_shift_open_' . $day; + $field = $form_state->getCompleteForm()['day_' . $day]['second_shift_open_' . $day]; + $message = t('%day - Cannot have a second shift without a first shift.', array('%day' => $days[$day])); + $form_state->setError($field, $message); + } + elseif ($int_second_shift_open == 9999) { + $field = $form_state->getCompleteForm()['day_' . $day]['second_shift_open_' . $day]; + $message = t('%day - Second shift open is missing.', array('%day' => $days[$day])); + $form_state->setError($field, $message); + } + elseif ($int_second_shift_close == 9999) { + $field = $form_state->getCompleteForm()['day_' . $day]['second_shift_close_' . $day]; + $message = t('%day - Second shift close is missing.', array('%day' => $days[$day])); + $form_state->setError($field, $message); + } + elseif ($int_second_shift_open <= $int_first_shift_close) { + $field = $form_state->getCompleteForm()['day_' . $day]['second_shift_open_' . $day]; + $message = t('%day - Second shift open must be later than first shift close.', array('%day' => $days[$day])); + $form_state->setError($field, $message); + } + elseif ($int_second_shift_open >= $int_second_shift_close) { + $field = $form_state->getCompleteForm()['day_' . $day]['second_shift_close_' . $day]; + $message = t('%day - Second shift close must be later than second shift opten.', array('%day' => $days[$day])); + $form_state->setError($field, $message); + } + } + } + } + } + + /** + * Form submission handler. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + + $default_hours = array(); + if ($form_state->getTriggeringElement()['#name'] == 'save') { + for ($day = 0; $day < 7; $day++) { + $default_hours[] = $values['day_' . $day]['first_shift_open_' . $day]; + $default_hours[] = $values['day_' . $day]['first_shift_close_' . $day]; + $default_hours[] = $values['day_' . $day]['second_shift_open_' . $day]; + $default_hours[] = $values['day_' . $day]['second_shift_close_' . $day]; + } + $confirmation = t(RESERVE_SAVE_CONFIRMATION_MSG); + } + if ($form_state->getTriggeringElement()['#name'] == 'reset') { + for ($day = 0; $day < 7; $day++) { + $default_hours[] = '9999'; + $default_hours[] = '9999'; + $default_hours[] = '9999'; + $default_hours[] = '9999'; + } + $confirmation = t(RESERVE_RESET_CONFIRMATION_MSG); + } + + // Update monthly hours based on changes to defaults. + $monthly_hours = \Drupal::config('reserve.monthly_hours')->get('data'); + foreach ($monthly_hours as $yyyy_mm => $mo_hours) { + // this shouldnt be required; but just here until i figure out where monthly hours is being corrupted + if (!is_array($mo_hours)) { + unset($monthly_hours[$yyyy_mm]); + continue; + } + $month = intval(substr($yyyy_mm, 5)); + $year = substr($yyyy_mm, 0, 4); + // Days in the month. + $days = date('t', mktime(0, 0, 0, $month, 1, (int) $year)); + $updated_mo_hours = array(); + for ($day = 0; $day < $days; $day++) { + $default_ind = $mo_hours[($day * 5)]; + if ($default_ind == 'D') { + // Update monthly hours with default day of week hours. + // Day of the week. + $dow = date('w', mktime(0, 0, 0, $month, $day + 1, (int) $year)); + $updated_mo_hours[($day * 5)] = 'D'; + $updated_mo_hours[($day * 5) + 1] = $default_hours[($dow * 4)]; + $updated_mo_hours[($day * 5) + 2] = $default_hours[($dow * 4) + 1]; + $updated_mo_hours[($day * 5) + 3] = $default_hours[($dow * 4) + 2]; + $updated_mo_hours[($day * 5) + 4] = $default_hours[($dow * 4) + 3]; + } + elseif ($default_ind == 'O') { + // Leave monthly hours unchanged. + $updated_mo_hours[($day * 5)] = 'O'; + $updated_mo_hours[($day * 5) + 1] = $mo_hours[($day * 5) + 1]; + $updated_mo_hours[($day * 5) + 2] = $mo_hours[($day * 5) + 2]; + $updated_mo_hours[($day * 5) + 3] = $mo_hours[($day * 5) + 3]; + $updated_mo_hours[($day * 5) + 4] = $mo_hours[($day * 5) + 4]; + } + } + $monthly_hours[$yyyy_mm] = $updated_mo_hours; + } + + $this->config('reserve.default_hours') + ->set('data', $default_hours) + ->save(); + drupal_set_message($confirmation); + + // save updated monthly override hours + $this->config('reserve.monthly_hours') + ->set('data', $monthly_hours) + ->save(); + drupal_set_message(t('Daily overrides updated with new defaults.')); + } + +} diff --git a/src/Form/ReserveDisplayForm.php b/src/Form/ReserveDisplayForm.php new file mode 100644 index 0000000..647349e --- /dev/null +++ b/src/Form/ReserveDisplayForm.php @@ -0,0 +1,86 @@ +config('reserve.display'); + $form['ReserveDisplay']['#markup'] = 'Settings form for text displayed throughout the Reserve UI.'; + + $length_options = array( + '30' => '30', + '60' => '60', + '90' => '90', + '120' => '120', + '150' => '150', + '180' => '180', + '240' => '240', + '360' => '360', + '480' => '480', + '600' => '600', + '720' => '720', + ); + + return parent::buildForm($form, $form_state); + } + + /** + * Form submission handler. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + /*$values = $form_state->getValues(); + $this->config('reserve.category.hours.settings') + ->set('calendar_title', $values['calendar_title']) + ->set('advanced_booking_standard', $values['advanced_booking_standard']) + ->set('advanced_booking_admin', $values['advanced_booking_admin']) + ->save();*/ + } + +} diff --git a/src/Form/ReserveReservationDeleteForm.php b/src/Form/ReserveReservationDeleteForm.php new file mode 100644 index 0000000..362fcb7 --- /dev/null +++ b/src/Form/ReserveReservationDeleteForm.php @@ -0,0 +1,15 @@ +entity; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + $entity = &$this->entity; + + $status = parent::save($form, $form_state); + + switch ($status) { + case SAVED_NEW: + drupal_set_message($this->t('Created the %label Reservation.', [ + '%label' => $entity->label(), + ])); + break; + + default: + drupal_set_message($this->t('Saved the %label Reservation.', [ + '%label' => $entity->label(), + ])); + } + $form_state->setRedirect('entity.reserve_reservation.canonical', ['reserve_reservation' => $entity->id()]); + } + +} diff --git a/src/Form/ReserveReservationSettingsForm.php b/src/Form/ReserveReservationSettingsForm.php new file mode 100644 index 0000000..307358d --- /dev/null +++ b/src/Form/ReserveReservationSettingsForm.php @@ -0,0 +1,55 @@ +config('reserve.settings'); + $form['header']['#markup'] = 'Settings form for general Reserve settings. @TODO - make Category based'; + + $options = array( + '30' => '30', + '60' => '60', + '90' => '90', + '120' => '120', + '150' => '150', + '180' => '180', + '210' => '210', + '240' => '240', + '270' => '270', + '300' => '300', + ); + + $form['reservations_per_user'] = array( + '#title' => t('Open reservations per user'), + '#type' => 'textfield', + '#maxlength' => 2, + '#size' => 2, + '#description' => t('The maximum number of reservations that one particular user can have open at any time. Default is 4. Enter 0 to indicate + no limit.'), + '#default_value' => $config->get('reservations_per_user') ? $config->get('reservations_per_user') : 4, + ); + + $form['reservations_per_day'] = array( + '#title' => t('Reservations per day'), + '#type' => 'textfield', + '#maxlength' => 2, + '#size' => 2, + '#description' => t('The maximum number of reservations that one user can make for a single day. Default is 1. Enter 0 to indicate + no limit.'), + '#default_value' => $config->get('reservations_per_day') ? $config->get('reservations_per_day') : 1, + ); + + $form['reservation_max_length_standard'] = array( + '#title' => t('Maximum reservation length (standard)'), + '#type' => 'select', + '#options' => $options, + '#default_value' => $config->get('reservation_max_length_standard') ? $config->get('reservation_max_length_standard') : 120, + '#description' => t('The maximum amount of time (in minutes) which a reservation can be made by a user with "Create new reservations (extended)" + privilege. Default is 120.'), + ); + + $form['reservation_max_length_extended'] = array( + '#title' => t('Maximum reservation length (extended)'), + '#type' => 'select', + '#options' => $options, + '#default_value' => $config->get('reservation_max_length_extended') ? $config->get('reservation_max_length_extended') : 120, + '#description' => t('The maximum amount of time (in minutes) which a reservation can be made by a user with "Create new reservations (extended)" + privilege. Default is 120.'), + ); + + // @todo what is this for? + $form['reservation_end_early'] = array( + '#title' => t('End reservations 15 minutes before closing time.'), + '#type' => 'checkbox', + '#return_value' => 1, + '#default_value' => $config->get('reservation_end_early') ? $config->get('reservation_end_early') : 0, + '#description' => t('All reservations end 15 minutes before closing time.'), + ); + + $form['show_before_after_hours'] = array( + '#title' => t('Time to show on calendar before/after open slots'), + '#type' => 'select', + '#options' => array(0 => '0 hours', 1 => t('1 hour'), 2 => t('2 hours'), '3' => t('All day')), + '#default_value' => $config->get('show_before_after_hours') ? $config->get('advanced_booking_admin') : 3, + '#description' => t('The number of hours before the first and last open slots for the day that are shown on the calendar. The default is to display the entire day.'), + ); + + // @todo decide if worth porting this to D8 + $form['calendar_flipped'] = array( + '#title' => t('Flip orientation of calendar display.'), + '#type' => 'checkbox', + '#return_value' => 1, + '#default_value' => $config->get('calendar_flipped') ? $config->get('calendar_flipped') : 0, + '#description' => t('The default orientation of the calendar display has rooms listed along the top and hours along the left side. Selecting this checkbox will + flip this to display hours along the top and the rooms listed along the left side. This can be useful for categories with a very large number of rooms. NOTE: + custom CSS will most likely be required to allow all the hour slots to fit the width of your theme.'), + ); + + $form['calendar_compressed_labels'] = array( + '#title' => t('Use smaller labels on calendar display.'), + '#type' => 'checkbox', + '#return_value' => 1, + '#default_value' => $config->get('calendar_compressed_labels') ? $config->get('calendar_compressed_labels') : 0, + '#description' => t('The default display shows the reserve entity title on both sides of the calendar display. Selecting this option shows only the room title on one side.'), + ); + + $form['date_displays'] = array( + '#type' => 'details', + '#title' => t('Date and time display formats'), + '#open' => FALSE, + ); + + $form['date_displays']['date_format'] = array( + '#type' => 'textfield', + '#title' => t('Date format for the popup date picker to select calendar date.'), + '#description' => t('Enter format in the form "y/m/d", "m/d/y", etc.'), + '#default_value' => $config->get('date_format') ? $config->get('date_format') : 'y/m/d', + ); + + $form['date_displays']['hour_format'] = array( + '#type' => 'radios', + '#title' => t('Hour format for the calendar display.'), + '#description' => t('Select either 2:00 PM or 14:00 display format.'), + '#default_value' => $config->get('hour_format') ? $config->get('hour_format') : 0, + '#options' => array(0 => '1:00 PM', 1 => '13:00'), + ); + + return parent::buildForm($form, $form_state); + } + + /** + * Form submission handler. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + foreach ($values as $key => $value) { + if (stristr('form_', $key)) continue; + if (is_object($values[$key])) continue; + $this->config('reserve.settings')->set($key, $value); + } + $this->config('reserve.settings')->save(); + drupal_set_message(RESERVE_SAVE_CONFIRMATION_MSG); + } + +} diff --git a/src/Plugin/Field/FieldFormatter/ReserveCategoryFormatter.php b/src/Plugin/Field/FieldFormatter/ReserveCategoryFormatter.php new file mode 100644 index 0000000..7eb2f30 --- /dev/null +++ b/src/Plugin/Field/FieldFormatter/ReserveCategoryFormatter.php @@ -0,0 +1,49 @@ +getSettings(); + $summary[] = t('Displays the Reserve Category.'); + return $summary; + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $element = []; + + foreach ($items as $delta => $item) { + $category = \Drupal\reserve\Entity\ReserveCategory::load($item->cid); + // Render each element as markup. + $element[$delta] = [ + '#type' => 'markup', + '#markup' => $category->label(), + ]; + } + + return $element; + } + +} \ No newline at end of file diff --git a/src/Plugin/Field/FieldType/ReserveCategory.php b/src/Plugin/Field/FieldType/ReserveCategory.php new file mode 100644 index 0000000..d7772ae --- /dev/null +++ b/src/Plugin/Field/FieldType/ReserveCategory.php @@ -0,0 +1,116 @@ + array( + 'cid' => array( + 'type' => 'int', + 'not null' => TRUE, + ), + ), + 'indexes' => array( + 'cid' => array('cid'), + ), + ); + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + $value = $this->get('cid')->getValue(); + return $value === NULL || $value === ''; + } + + /** + * {@inheritdoc} + */ + public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { + $properties['cid'] = DataDefinition::create('integer') + ->setLabel(t('Category')); + + return $properties; + } + + /** + * {@inheritdoc} + */ + public function fieldSettingsForm(array $form, FormStateInterface $form_state) { + // get a list of all Reserve Categories + $ids = \Drupal::entityQuery('reserve_category')->sort('name', 'ASC')->execute(); + $categories = \Drupal\reserve\Entity\ReserveCategory::loadMultiple($ids); + $options = array(); + foreach ($categories as $cat) { + $options[$cat->id()] = $cat->label(); + } + + $element = []; + + $element['categories'] = [ + '#type' => 'checkboxes', + '#title' => t('Allowed categories'), + '#description' => t('Select which categories should be available for this bundle.'), + '#default_value' => $this->getSetting('categories'), + '#options' => $options, + ]; + + $element['calendar_header'] = [ + '#type' => 'textarea', + '#title' => t('Calendar header'), + '#description' => t('Optional header text to add above the calendar for this bundle.'), + '#default_value' => $this->getSetting('calendar_header') ? + $this->getSetting('calendar_header') : + t('You may set optional header text under the field settings for the Reserve Category field for this bundle.') + ]; + + $element['reservation_instructions'] = [ + '#type' => 'textarea', + '#title' => t('Reservation instructions'), + '#description' => t('Optional reservation instructions to add below the calendar for this bundle.'), + '#default_value' => $this->getSetting('reservation_instructions') ? + $this->getSetting('reservation_instructions') : + t('You may set optional reservation instructions under the field settings for the Reserve Category field for this bundle.') + ]; + + return $element; + } + + /** + * {@inheritdoc} + */ + public static function defaultFieldSettings() { + return [ + 'categories' => [], + 'calendar_header' => null, + 'reservation_instructions' => null, + ] + parent::defaultFieldSettings(); + } + +} \ No newline at end of file diff --git a/src/Plugin/Field/FieldWidget/ReserveCategorySelect.php b/src/Plugin/Field/FieldWidget/ReserveCategorySelect.php new file mode 100644 index 0000000..7644242 --- /dev/null +++ b/src/Plugin/Field/FieldWidget/ReserveCategorySelect.php @@ -0,0 +1,49 @@ +cid) ? $items[$delta]->cid : ''; + + // get a list of all Reserve Categories for this bundle + $set = $items->getSettings()['categories']; + + $ids = \Drupal::entityQuery('reserve_category')->sort('name', 'ASC')->execute(); + $categories = \Drupal\reserve\Entity\ReserveCategory::loadMultiple($ids); + $options = array(); + foreach ($categories as $key => $cat) { + if (!in_array($key, $set)) continue; + $options[$cat->id()] = $cat->label(); + } + + $element += [ + '#type' => 'select', + '#options' => $options, + '#default_value' => $cid, + ]; + + return array('cid' => $element); + } + +} \ No newline at end of file diff --git a/src/ReserveCategoryAccessControlHandler.php b/src/ReserveCategoryAccessControlHandler.php new file mode 100644 index 0000000..b5ac777 --- /dev/null +++ b/src/ReserveCategoryAccessControlHandler.php @@ -0,0 +1,47 @@ +isPublished()) { + return AccessResult::allowedIfHasPermission($account, 'view unpublished reservation categories'); + } + return AccessResult::allowedIfHasPermission($account, 'view published reservation categories'); + + case 'update': + return AccessResult::allowedIfHasPermission($account, 'edit reservation categories'); + + case 'delete': + return AccessResult::allowedIfHasPermission($account, 'delete reservation categories'); + } + + // Unknown operation, no opinion. + return AccessResult::neutral(); + } + + /** + * {@inheritdoc} + */ + protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { + return AccessResult::allowedIfHasPermission($account, 'add reservation categories'); + } + +} diff --git a/src/ReserveCategoryHtmlRouteProvider.php b/src/ReserveCategoryHtmlRouteProvider.php new file mode 100644 index 0000000..84b007f --- /dev/null +++ b/src/ReserveCategoryHtmlRouteProvider.php @@ -0,0 +1,85 @@ +id(); + + if ($collection_route = $this->getCollectionRoute($entity_type)) { + $collection->add("entity.{$entity_type_id}.collection", $collection_route); + } + + if ($settings_form_route = $this->getSettingsFormRoute($entity_type)) { + $collection->add("$entity_type_id.settings", $settings_form_route); + } + + return $collection; + } + + /** + * Gets the collection route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getCollectionRoute(EntityTypeInterface $entity_type) { + if ($entity_type->hasLinkTemplate('collection') && $entity_type->hasListBuilderClass()) { + $entity_type_id = $entity_type->id(); + $route = new Route($entity_type->getLinkTemplate('collection')); + $route + ->setDefaults([ + '_entity_list' => $entity_type_id, + '_title' => "{$entity_type->getLabel()} list", + ]) + ->setRequirement('_permission', 'access reservation category overview') + ->setOption('_admin_route', TRUE); + + return $route; + } + } + + /** + * Gets the settings form route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getSettingsFormRoute(EntityTypeInterface $entity_type) { + if (!$entity_type->getBundleEntityType()) { + $route = new Route("/admin/structure/{$entity_type->id()}/settings"); + $route + ->setDefaults([ + '_form' => 'Drupal\reserve\Form\ReserveCategorySettingsForm', + '_title' => "{$entity_type->getLabel()} settings", + ]) + ->setRequirement('_permission', $entity_type->getAdminPermission()) + ->setOption('_admin_route', TRUE); + + return $route; + } + } + +} diff --git a/src/ReserveCategoryListBuilder.php b/src/ReserveCategoryListBuilder.php new file mode 100644 index 0000000..4a8a917 --- /dev/null +++ b/src/ReserveCategoryListBuilder.php @@ -0,0 +1,45 @@ +t('Reservation Category ID'); + $header['name'] = $this->t('Name'); + return $header + parent::buildHeader(); + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + /* @var $entity \Drupal\reserve\Entity\ReserveCategory */ + $row['id'] = $entity->id(); + $row['name'] = $this->l( + $entity->label(), + new Url( + 'entity.reserve_category.edit_form', array( + 'reserve_category' => $entity->id(), + ) + ) + ); + return $row + parent::buildRow($entity); + } + +} diff --git a/src/ReserveCategoryTranslationHandler.php b/src/ReserveCategoryTranslationHandler.php new file mode 100644 index 0000000..ab430a7 --- /dev/null +++ b/src/ReserveCategoryTranslationHandler.php @@ -0,0 +1,14 @@ +isPublished()) { + return AccessResult::allowedIfHasPermission($account, 'view unpublished reservations'); + } + return AccessResult::allowedIfHasPermission($account, 'view published reservations'); + + case 'update': + return AccessResult::allowedIfHasPermission($account, 'edit reservations'); + + case 'delete': + return AccessResult::allowedIfHasPermission($account, 'delete reservations'); + } + + // Unknown operation, no opinion. + return AccessResult::neutral(); + } + + /** + * {@inheritdoc} + */ + protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { + return AccessResult::allowedIfHasPermission($account, 'add reservations'); + } + +} diff --git a/src/ReserveReservationHtmlRouteProvider.php b/src/ReserveReservationHtmlRouteProvider.php new file mode 100644 index 0000000..394e2fa --- /dev/null +++ b/src/ReserveReservationHtmlRouteProvider.php @@ -0,0 +1,85 @@ +id(); + + if ($collection_route = $this->getCollectionRoute($entity_type)) { + $collection->add("entity.{$entity_type_id}.collection", $collection_route); + } + + if ($settings_form_route = $this->getSettingsFormRoute($entity_type)) { + $collection->add("$entity_type_id.settings", $settings_form_route); + } + + return $collection; + } + + /** + * Gets the collection route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getCollectionRoute(EntityTypeInterface $entity_type) { + if ($entity_type->hasLinkTemplate('collection') && $entity_type->hasListBuilderClass()) { + $entity_type_id = $entity_type->id(); + $route = new Route($entity_type->getLinkTemplate('collection')); + $route + ->setDefaults([ + '_entity_list' => $entity_type_id, + '_title' => "{$entity_type->getLabel()} list", + ]) + ->setRequirement('_permission', 'access reservation overview') + ->setOption('_admin_route', TRUE); + + return $route; + } + } + + /** + * Gets the settings form route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getSettingsFormRoute(EntityTypeInterface $entity_type) { + if (!$entity_type->getBundleEntityType()) { + $route = new Route("/admin/structure/{$entity_type->id()}/settings"); + $route + ->setDefaults([ + '_form' => 'Drupal\reserve\Form\ReserveReservationSettingsForm', + '_title' => "{$entity_type->getLabel()} settings", + ]) + ->setRequirement('_permission', $entity_type->getAdminPermission()) + ->setOption('_admin_route', TRUE); + + return $route; + } + } + +} diff --git a/src/ReserveReservationListBuilder.php b/src/ReserveReservationListBuilder.php new file mode 100644 index 0000000..b2af230 --- /dev/null +++ b/src/ReserveReservationListBuilder.php @@ -0,0 +1,45 @@ +t('Reservation ID'); + $header['name'] = $this->t('Name'); + return $header + parent::buildHeader(); + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + /* @var $entity \Drupal\reserve\Entity\ReserveReservation */ + $row['id'] = $entity->id(); + $row['name'] = $this->l( + $entity->label(), + new Url( + 'entity.reserve_reservation.edit_form', array( + 'reserve_reservation' => $entity->id(), + ) + ) + ); + return $row + parent::buildRow($entity); + } + +} diff --git a/src/ReserveReservationTranslationHandler.php b/src/ReserveReservationTranslationHandler.php new file mode 100644 index 0000000..1bfa50d --- /dev/null +++ b/src/ReserveReservationTranslationHandler.php @@ -0,0 +1,14 @@ +user = $this->drupalCreateUser(['administer site configuration']); + $this->drupalLogin($this->user); + } + + /** + * Tests that the home page loads with a 200 response. + */ + public function testLoad() { + $this->drupalGet(Url::fromRoute('')); + $this->assertResponse(200); + } + +} diff --git a/templates/calendar-template.html.twig b/templates/calendar-template.html.twig new file mode 100644 index 0000000..1aefe0c --- /dev/null +++ b/templates/calendar-template.html.twig @@ -0,0 +1,37 @@ +
    +
    +
    +
    {{ calendar_header }}
    +
    +
    {{ reservation_instructions }}
    +
    +
    +

    {{ 'Reservation Calendar'|t }}

    +
    {{ date }}
    +
    {{ building_hours_display }}
    + +
    +
    {{ date_picker }}
    + + + + +
    + {% for cat1 in categories %} +
    +
    + {# $table from D7 #} + {{ cat1.table }} +
    +
    + {% endfor %} +
    + +
    +
    +
    + diff --git a/templates/reserve_category.html.twig b/templates/reserve_category.html.twig new file mode 100644 index 0000000..bed4617 --- /dev/null +++ b/templates/reserve_category.html.twig @@ -0,0 +1,22 @@ +{# +/** + * @file reserve_category.html.twig + * Default theme implementation to present Reservation Category data. + * + * This template is used when viewing Reservation Category pages. + * + * + * Available variables: + * - content: A list of content items. Use 'content' to print all content, or + * - attributes: HTML attributes for the container element. + * + * @see template_preprocess_reserve_category() + * + * @ingroup themeable + */ +#} + + {% if content %} + {{- content -}} + {% endif %} +
    diff --git a/templates/reserve_reservation.html.twig b/templates/reserve_reservation.html.twig new file mode 100644 index 0000000..44cd5e0 --- /dev/null +++ b/templates/reserve_reservation.html.twig @@ -0,0 +1,22 @@ +{# +/** + * @file reserve_reservation.html.twig + * Default theme implementation to present Reservation data. + * + * This template is used when viewing Reservation pages. + * + * + * Available variables: + * - content: A list of content items. Use 'content' to print all content, or + * - attributes: HTML attributes for the container element. + * + * @see template_preprocess_reserve_reservation() + * + * @ingroup themeable + */ +#} + + {% if content %} + {{- content -}} + {% endif %} +