From 9675d1c530abc8fee86d59b6e955e4c82cc53d0b Mon Sep 17 00:00:00 2001 From: Peter Lindstrom Date: Thu, 11 Apr 2019 14:51:22 -0400 Subject: [PATCH] initial commit --- composer.json | 14 + ...erve_category.reserve_category.default.yml | 63 + ...eservation.reserve_reservation.default.yml | 108 ++ ...erve_category.reserve_category.default.yml | 72 + ...eservation.reserve_reservation.default.yml | 104 ++ ...ry.reserve_category.reserve_maxadv_ext.yml | 22 + ...ry.reserve_category.reserve_maxadv_std.yml | 22 + ...ry.reserve_category.reserve_minadv_ext.yml | 22 + ...ry.reserve_category.reserve_minadv_std.yml | 22 + ....reserve_category.reserve_setup_buffer.yml | 22 + ...serve_category.reserve_takedown_buffer.yml | 22 + ...ve_reservation.reservable_content_type.yml | 21 + ...tion.reserve_reservation.reservable_id.yml | 24 + ...n.reserve_reservation.reservation_date.yml | 21 + ...eserve_reservation.reservation_ebundle.yml | 19 + ...reserve_reservation.reservation_length.yml | 20 + ...eserve_reservation.reservation_private.yml | 23 + ...ve_reservation.reservation_repeat_type.yml | 22 + ...e_reservation.reservation_repeat_until.yml | 20 + ...erve_reservation.reservation_series_id.yml | 23 + ...n.reserve_reservation.reservation_time.yml | 19 + ...ge.reserve_category.reserve_maxadv_ext.yml | 34 + ...ge.reserve_category.reserve_maxadv_std.yml | 34 + ...ge.reserve_category.reserve_minadv_ext.yml | 34 + ...ge.reserve_category.reserve_minadv_std.yml | 34 + ....reserve_category.reserve_setup_buffer.yml | 35 + ...serve_category.reserve_takedown_buffer.yml | 35 + ...ve_reservation.reservable_content_type.yml | 19 + ...rage.reserve_reservation.reservable_id.yml | 19 + ...e.reserve_reservation.reservation_date.yml | 19 + ...eserve_reservation.reservation_ebundle.yml | 20 + ...reserve_reservation.reservation_length.yml | 49 + ...eserve_reservation.reservation_private.yml | 17 + ...ve_reservation.reservation_repeat_type.yml | 29 + ...e_reservation.reservation_repeat_until.yml | 19 + ...erve_reservation.reservation_series_id.yml | 19 + ...e.reserve_reservation.reservation_time.yml | 20 + config/install/reserve.hours.yml | 0 config/install/reserve.settings.yml | 3 + config/install/views.view.reservations.yml | 1161 +++++++++++++++++ config/schema/reserve.schema.yml | 13 + content/reserve_category/digital.json | 90 ++ content/reserve_category/instant.json | 90 ++ content/reserve_category/large.json | 90 ++ content/reserve_category/small.json | 90 ++ content/reserve_category/video.json | 90 ++ css/reserve-calendar.css | 259 ++++ css/reserve-calendar.css-bak | 252 ++++ images/arrow-icon.png | Bin 0 -> 9042 bytes images/clear.png | Bin 0 -> 2827 bytes js/reserve.js | 135 ++ reservation.inc | 252 ++++ reserve.inc | 1128 ++++++++++++++++ reserve.info.yml | 5 + reserve.libraries.yml | 11 + reserve.links.action.yml | 10 + reserve.links.menu.yml | 38 + reserve.links.task.yml | 81 ++ reserve.links.task.yml.bak | 71 + reserve.module | 113 ++ reserve.permissions.yml | 58 + reserve.routing.yml | 61 + reserve.schema.yml | 21 + reserve.series.inc | 192 +++ reserve_category.page.inc | 32 + reserve_reservation.page.inc | 32 + src/BundleFormAlter.php | 113 ++ src/Controller/CalendarController.php | 665 ++++++++++ src/Controller/ReservePermissions.php | 60 + src/Entity/ReserveCategory.php | 222 ++++ src/Entity/ReserveCategoryInterface.php | 58 + src/Entity/ReserveCategoryViewsData.php | 28 + src/Entity/ReserveReservation.php | 216 +++ src/Entity/ReserveReservationInterface.php | 77 ++ src/Entity/ReserveReservationViewsData.php | 14 + src/Form/CalendarDatePicker.php | 56 + src/Form/ReserveCategoryDeleteForm.php | 15 + src/Form/ReserveCategoryForm.php | 50 + src/Form/ReserveCategorySettingsForm.php | 55 + src/Form/ReserveDailyHoursForm.php | 250 ++++ src/Form/ReserveDefaultHoursForm.php | 308 +++++ src/Form/ReserveDisplayForm.php | 86 ++ src/Form/ReserveReservationDeleteForm.php | 15 + src/Form/ReserveReservationForm.php | 50 + src/Form/ReserveReservationSettingsForm.php | 55 + src/Form/ReserveSettingsForm.php | 183 +++ .../ReserveCategoryFormatter.php | 49 + .../Field/FieldType/ReserveCategory.php | 116 ++ .../FieldWidget/ReserveCategorySelect.php | 49 + src/ReserveCategoryAccessControlHandler.php | 47 + src/ReserveCategoryHtmlRouteProvider.php | 85 ++ src/ReserveCategoryListBuilder.php | 45 + src/ReserveCategoryTranslationHandler.php | 14 + ...ReserveReservationAccessControlHandler.php | 47 + src/ReserveReservationHtmlRouteProvider.php | 85 ++ src/ReserveReservationListBuilder.php | 45 + src/ReserveReservationTranslationHandler.php | 14 + src/Tests/LoadTest.php | 46 + templates/calendar-template.html.twig | 37 + templates/reserve_category.html.twig | 22 + templates/reserve_reservation.html.twig | 22 + 101 files changed, 8818 insertions(+) create mode 100644 composer.json create mode 100644 config/install/core.entity_form_display.reserve_category.reserve_category.default.yml create mode 100644 config/install/core.entity_form_display.reserve_reservation.reserve_reservation.default.yml create mode 100644 config/install/core.entity_view_display.reserve_category.reserve_category.default.yml create mode 100644 config/install/core.entity_view_display.reserve_reservation.reserve_reservation.default.yml create mode 100644 config/install/field.field.reserve_category.reserve_category.reserve_maxadv_ext.yml create mode 100644 config/install/field.field.reserve_category.reserve_category.reserve_maxadv_std.yml create mode 100644 config/install/field.field.reserve_category.reserve_category.reserve_minadv_ext.yml create mode 100644 config/install/field.field.reserve_category.reserve_category.reserve_minadv_std.yml create mode 100644 config/install/field.field.reserve_category.reserve_category.reserve_setup_buffer.yml create mode 100644 config/install/field.field.reserve_category.reserve_category.reserve_takedown_buffer.yml create mode 100644 config/install/field.field.reserve_reservation.reserve_reservation.reservable_content_type.yml create mode 100644 config/install/field.field.reserve_reservation.reserve_reservation.reservable_id.yml create mode 100644 config/install/field.field.reserve_reservation.reserve_reservation.reservation_date.yml create mode 100644 config/install/field.field.reserve_reservation.reserve_reservation.reservation_ebundle.yml create mode 100644 config/install/field.field.reserve_reservation.reserve_reservation.reservation_length.yml create mode 100644 config/install/field.field.reserve_reservation.reserve_reservation.reservation_private.yml create mode 100644 config/install/field.field.reserve_reservation.reserve_reservation.reservation_repeat_type.yml create mode 100644 config/install/field.field.reserve_reservation.reserve_reservation.reservation_repeat_until.yml create mode 100644 config/install/field.field.reserve_reservation.reserve_reservation.reservation_series_id.yml create mode 100644 config/install/field.field.reserve_reservation.reserve_reservation.reservation_time.yml create mode 100644 config/install/field.storage.reserve_category.reserve_maxadv_ext.yml create mode 100644 config/install/field.storage.reserve_category.reserve_maxadv_std.yml create mode 100644 config/install/field.storage.reserve_category.reserve_minadv_ext.yml create mode 100644 config/install/field.storage.reserve_category.reserve_minadv_std.yml create mode 100644 config/install/field.storage.reserve_category.reserve_setup_buffer.yml create mode 100644 config/install/field.storage.reserve_category.reserve_takedown_buffer.yml create mode 100644 config/install/field.storage.reserve_reservation.reservable_content_type.yml create mode 100644 config/install/field.storage.reserve_reservation.reservable_id.yml create mode 100644 config/install/field.storage.reserve_reservation.reservation_date.yml create mode 100644 config/install/field.storage.reserve_reservation.reservation_ebundle.yml create mode 100644 config/install/field.storage.reserve_reservation.reservation_length.yml create mode 100644 config/install/field.storage.reserve_reservation.reservation_private.yml create mode 100644 config/install/field.storage.reserve_reservation.reservation_repeat_type.yml create mode 100644 config/install/field.storage.reserve_reservation.reservation_repeat_until.yml create mode 100644 config/install/field.storage.reserve_reservation.reservation_series_id.yml create mode 100644 config/install/field.storage.reserve_reservation.reservation_time.yml create mode 100644 config/install/reserve.hours.yml create mode 100644 config/install/reserve.settings.yml create mode 100644 config/install/views.view.reservations.yml create mode 100644 config/schema/reserve.schema.yml create mode 100644 content/reserve_category/digital.json create mode 100644 content/reserve_category/instant.json create mode 100644 content/reserve_category/large.json create mode 100644 content/reserve_category/small.json create mode 100644 content/reserve_category/video.json create mode 100644 css/reserve-calendar.css create mode 100644 css/reserve-calendar.css-bak create mode 100644 images/arrow-icon.png create mode 100644 images/clear.png create mode 100644 js/reserve.js create mode 100644 reservation.inc create mode 100644 reserve.inc create mode 100644 reserve.info.yml create mode 100644 reserve.libraries.yml create mode 100644 reserve.links.action.yml create mode 100644 reserve.links.menu.yml create mode 100644 reserve.links.task.yml create mode 100644 reserve.links.task.yml.bak create mode 100644 reserve.module create mode 100644 reserve.permissions.yml create mode 100644 reserve.routing.yml create mode 100644 reserve.schema.yml create mode 100644 reserve.series.inc create mode 100644 reserve_category.page.inc create mode 100644 reserve_reservation.page.inc create mode 100644 src/BundleFormAlter.php create mode 100644 src/Controller/CalendarController.php create mode 100644 src/Controller/ReservePermissions.php create mode 100644 src/Entity/ReserveCategory.php create mode 100644 src/Entity/ReserveCategoryInterface.php create mode 100644 src/Entity/ReserveCategoryViewsData.php create mode 100644 src/Entity/ReserveReservation.php create mode 100644 src/Entity/ReserveReservationInterface.php create mode 100644 src/Entity/ReserveReservationViewsData.php create mode 100644 src/Form/CalendarDatePicker.php create mode 100644 src/Form/ReserveCategoryDeleteForm.php create mode 100644 src/Form/ReserveCategoryForm.php create mode 100644 src/Form/ReserveCategorySettingsForm.php create mode 100644 src/Form/ReserveDailyHoursForm.php create mode 100644 src/Form/ReserveDefaultHoursForm.php create mode 100644 src/Form/ReserveDisplayForm.php create mode 100644 src/Form/ReserveReservationDeleteForm.php create mode 100644 src/Form/ReserveReservationForm.php create mode 100644 src/Form/ReserveReservationSettingsForm.php create mode 100644 src/Form/ReserveSettingsForm.php create mode 100644 src/Plugin/Field/FieldFormatter/ReserveCategoryFormatter.php create mode 100644 src/Plugin/Field/FieldType/ReserveCategory.php create mode 100644 src/Plugin/Field/FieldWidget/ReserveCategorySelect.php create mode 100644 src/ReserveCategoryAccessControlHandler.php create mode 100644 src/ReserveCategoryHtmlRouteProvider.php create mode 100644 src/ReserveCategoryListBuilder.php create mode 100644 src/ReserveCategoryTranslationHandler.php create mode 100644 src/ReserveReservationAccessControlHandler.php create mode 100644 src/ReserveReservationHtmlRouteProvider.php create mode 100644 src/ReserveReservationListBuilder.php create mode 100644 src/ReserveReservationTranslationHandler.php create mode 100644 src/Tests/LoadTest.php create mode 100644 templates/calendar-template.html.twig create mode 100644 templates/reserve_category.html.twig create mode 100644 templates/reserve_reservation.html.twig 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 0000000000000000000000000000000000000000..18d44551f9fbef7d1679d9eef91249269c11d495 GIT binary patch literal 9042 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 literal 0 HcmV?d00001 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('Create the content bundle which is to be reservable. E.g. Room') . '
  • +
  • ' . t('Create Reservation Categories as required. E.g. Large, Small. Set required values for each category.') . '
  • +
  • ' . t('Add the Reservation Category field type to the bundle which you want to make reservable. Select which Reservation Categories + will apply to this bundle.') . '
  • +
  • ' . t('Review the settings under Reserve Settings and modify as required.') . '
  • +
  • ' . t('Set required permissions.') . '
  • +
  • ' . t('Finally, define the default times that entities are reservable. You may also set any daily overrides that may be required (optional).') . '
  • +
+

' . 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 .= '
    +
      + '; + for ($y = 0; $y <= $n; $y++) { + if (!$flipped && $compressed && ($y == 1 || $y == $n || $y == $n - 1)) continue; + if (isset($table[$y][$x])) { + $table_markup .= $table[$y][$x]; + } + } + $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 %} +