Browse Source

initial commit

8.x-1.x
Peter Lindstrom 6 years ago
parent
commit
9675d1c530
  1. 14
      composer.json
  2. 63
      config/install/core.entity_form_display.reserve_category.reserve_category.default.yml
  3. 108
      config/install/core.entity_form_display.reserve_reservation.reserve_reservation.default.yml
  4. 72
      config/install/core.entity_view_display.reserve_category.reserve_category.default.yml
  5. 104
      config/install/core.entity_view_display.reserve_reservation.reserve_reservation.default.yml
  6. 22
      config/install/field.field.reserve_category.reserve_category.reserve_maxadv_ext.yml
  7. 22
      config/install/field.field.reserve_category.reserve_category.reserve_maxadv_std.yml
  8. 22
      config/install/field.field.reserve_category.reserve_category.reserve_minadv_ext.yml
  9. 22
      config/install/field.field.reserve_category.reserve_category.reserve_minadv_std.yml
  10. 22
      config/install/field.field.reserve_category.reserve_category.reserve_setup_buffer.yml
  11. 22
      config/install/field.field.reserve_category.reserve_category.reserve_takedown_buffer.yml
  12. 21
      config/install/field.field.reserve_reservation.reserve_reservation.reservable_content_type.yml
  13. 24
      config/install/field.field.reserve_reservation.reserve_reservation.reservable_id.yml
  14. 21
      config/install/field.field.reserve_reservation.reserve_reservation.reservation_date.yml
  15. 19
      config/install/field.field.reserve_reservation.reserve_reservation.reservation_ebundle.yml
  16. 20
      config/install/field.field.reserve_reservation.reserve_reservation.reservation_length.yml
  17. 23
      config/install/field.field.reserve_reservation.reserve_reservation.reservation_private.yml
  18. 22
      config/install/field.field.reserve_reservation.reserve_reservation.reservation_repeat_type.yml
  19. 20
      config/install/field.field.reserve_reservation.reserve_reservation.reservation_repeat_until.yml
  20. 23
      config/install/field.field.reserve_reservation.reserve_reservation.reservation_series_id.yml
  21. 19
      config/install/field.field.reserve_reservation.reserve_reservation.reservation_time.yml
  22. 34
      config/install/field.storage.reserve_category.reserve_maxadv_ext.yml
  23. 34
      config/install/field.storage.reserve_category.reserve_maxadv_std.yml
  24. 34
      config/install/field.storage.reserve_category.reserve_minadv_ext.yml
  25. 34
      config/install/field.storage.reserve_category.reserve_minadv_std.yml
  26. 35
      config/install/field.storage.reserve_category.reserve_setup_buffer.yml
  27. 35
      config/install/field.storage.reserve_category.reserve_takedown_buffer.yml
  28. 19
      config/install/field.storage.reserve_reservation.reservable_content_type.yml
  29. 19
      config/install/field.storage.reserve_reservation.reservable_id.yml
  30. 19
      config/install/field.storage.reserve_reservation.reservation_date.yml
  31. 20
      config/install/field.storage.reserve_reservation.reservation_ebundle.yml
  32. 49
      config/install/field.storage.reserve_reservation.reservation_length.yml
  33. 17
      config/install/field.storage.reserve_reservation.reservation_private.yml
  34. 29
      config/install/field.storage.reserve_reservation.reservation_repeat_type.yml
  35. 19
      config/install/field.storage.reserve_reservation.reservation_repeat_until.yml
  36. 19
      config/install/field.storage.reserve_reservation.reservation_series_id.yml
  37. 20
      config/install/field.storage.reserve_reservation.reservation_time.yml
  38. 0
      config/install/reserve.hours.yml
  39. 3
      config/install/reserve.settings.yml
  40. 1161
      config/install/views.view.reservations.yml
  41. 13
      config/schema/reserve.schema.yml
  42. 90
      content/reserve_category/digital.json
  43. 90
      content/reserve_category/instant.json
  44. 90
      content/reserve_category/large.json
  45. 90
      content/reserve_category/small.json
  46. 90
      content/reserve_category/video.json
  47. 259
      css/reserve-calendar.css
  48. 252
      css/reserve-calendar.css-bak
  49. BIN
      images/arrow-icon.png
  50. BIN
      images/clear.png
  51. 135
      js/reserve.js
  52. 252
      reservation.inc
  53. 1128
      reserve.inc
  54. 5
      reserve.info.yml
  55. 11
      reserve.libraries.yml
  56. 10
      reserve.links.action.yml
  57. 38
      reserve.links.menu.yml
  58. 81
      reserve.links.task.yml
  59. 71
      reserve.links.task.yml.bak
  60. 113
      reserve.module
  61. 58
      reserve.permissions.yml
  62. 61
      reserve.routing.yml
  63. 21
      reserve.schema.yml
  64. 192
      reserve.series.inc
  65. 32
      reserve_category.page.inc
  66. 32
      reserve_reservation.page.inc
  67. 113
      src/BundleFormAlter.php
  68. 665
      src/Controller/CalendarController.php
  69. 60
      src/Controller/ReservePermissions.php
  70. 222
      src/Entity/ReserveCategory.php
  71. 58
      src/Entity/ReserveCategoryInterface.php
  72. 28
      src/Entity/ReserveCategoryViewsData.php
  73. 216
      src/Entity/ReserveReservation.php
  74. 77
      src/Entity/ReserveReservationInterface.php
  75. 14
      src/Entity/ReserveReservationViewsData.php
  76. 56
      src/Form/CalendarDatePicker.php
  77. 15
      src/Form/ReserveCategoryDeleteForm.php
  78. 50
      src/Form/ReserveCategoryForm.php
  79. 55
      src/Form/ReserveCategorySettingsForm.php
  80. 250
      src/Form/ReserveDailyHoursForm.php
  81. 308
      src/Form/ReserveDefaultHoursForm.php
  82. 86
      src/Form/ReserveDisplayForm.php
  83. 15
      src/Form/ReserveReservationDeleteForm.php
  84. 50
      src/Form/ReserveReservationForm.php
  85. 55
      src/Form/ReserveReservationSettingsForm.php
  86. 183
      src/Form/ReserveSettingsForm.php
  87. 49
      src/Plugin/Field/FieldFormatter/ReserveCategoryFormatter.php
  88. 116
      src/Plugin/Field/FieldType/ReserveCategory.php
  89. 49
      src/Plugin/Field/FieldWidget/ReserveCategorySelect.php
  90. 47
      src/ReserveCategoryAccessControlHandler.php
  91. 85
      src/ReserveCategoryHtmlRouteProvider.php
  92. 45
      src/ReserveCategoryListBuilder.php
  93. 14
      src/ReserveCategoryTranslationHandler.php
  94. 47
      src/ReserveReservationAccessControlHandler.php
  95. 85
      src/ReserveReservationHtmlRouteProvider.php
  96. 45
      src/ReserveReservationListBuilder.php
  97. 14
      src/ReserveReservationTranslationHandler.php
  98. 46
      src/Tests/LoadTest.php
  99. 37
      templates/calendar-template.html.twig
  100. 22
      templates/reserve_category.html.twig
  101. Some files were not shown because too many files have changed in this diff Show More

14
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": { }
}

63
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

108
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

72
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

104
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

22
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

22
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

22
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

22
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

22
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

22
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

21
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

24
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

21
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

19
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

20
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

23
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

22
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

20
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

23
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

19
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

34
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

34
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

34
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

34
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

35
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

35
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

19
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

19
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

19
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

20
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

49
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

17
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

29
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

19
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

19
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

20
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

0
config/install/reserve.hours.yml

3
config/install/reserve.settings.yml

@ -0,0 +1,3 @@
hour_format: 0
max_length_standard: 120
max_length_admin: 180

1161
config/install/views.view.reservations.yml

File diff suppressed because it is too large Load Diff

13
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'

90
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
}
]
}

90
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
}
]
}

90
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
}
]
}

90
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
}
]
}

90
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
}
]
}

259
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;
}

252
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;
}

BIN
images/arrow-icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
images/clear.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

135
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 = $('<div>' + data + '</div>').appendTo('body');
var options = {
title: 'Add Reservation',
width: 700,
};
Drupal.dialog($myDialog, options).showModal();
}
}
});
}
$("#rooms-calendar .panel li.reservable").removeClass("highlighted");
});
}
};
})(jQuery, Drupal);

252
reservation.inc

@ -0,0 +1,252 @@
<?php
/**
* alters for Reservation form
*/
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Link;
use Drupal\Core\Url;
function reserve_form_reserve_reservation_form_alter(array &$form, FormStateInterface $form_state) {
// params either passed in on url - CREATE
// or pulled from node - EDIT
$_SESSION['reserve_delete_type'] = null;
// Retrieve an array which contains the path pieces.
$current_path = \Drupal::service('path.current')->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' => '
<div class="messages__wrapper layout-container">
<div role="contentinfo" aria-label="Warning message" class="messages messages--warning">' .
$message .
'</div></div>',
];
}
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' => '
<h2 class="reservation-h2">' . t('Reservation booking for') . ':</h2><div><strong>' . $bundleName . ': ' . $entity->label() . '</strong></div>
<div><strong>' . t('Date') . ': ' . $date . '</strong></div>
<div><strong>' . t('Time') . ': ' . $time . '</strong></div>
<div><strong>' . t('Length') . ': ' . $lengthDisplay . '</strong></div>
<br>',
];
// 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);
}

1128
reserve.inc

File diff suppressed because it is too large Load Diff

5
reserve.info.yml

@ -0,0 +1,5 @@
name: Reserve
type: module
description: Reservation system.
core: 8.x
package: Reserve

11
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

10
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

38
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

81
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

71
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

113
reserve.module

@ -0,0 +1,113 @@
<?php
/**
* @file
* Contains reserve.module..
*
* @todo: fix reserve cat field with cardinality of 1
*
*/
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Entity\BundleEntityFormBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\reserve\BundleFormAlter;
define ('RESERVE_SAVE_CONFIRMATION_MSG', t('Configuration settings have been saved'));
define ('RESERVE_RESET_CONFIRMATION_MSG', t('Configuration settings have been reset to their default values'));
module_load_include('inc', 'reserve', 'reserve');
module_load_include('inc', 'reserve', 'reserve.series');
module_load_include('inc', 'reserve', 'reservation');
$GLOBALS['debug'] = false;
/**
* Implements hook_help().
*/
function reserve_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help for the reserve module.
case 'help.page.reserve':
$output = '';
$output .= '<h3>' . t('Overview') . '</h3>';
$output .= '<p>' . 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.') . '</p>';
$output .= '<h3>' . t('Setup') . '</h3>';
$output .= '<p>' . t('A few things must be set up before you may use the booking system:') . '</p>
<ul>
<li>' . t('Create the content bundle which is to be reservable. E.g. Room') . '</li>
<li>' . t('Create Reservation Categories as required. E.g. Large, Small. Set required values for each category.') . '</li>
<li>' . 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.') . '</li>
<li>' . t('Review the settings under Reserve Settings and modify as required.') . '</li>
<li>' . t('Set required permissions.') . '</li>
<li>' . t('Finally, define the default times that entities are reservable. You may also set any daily overrides that may be required (optional).') . '</li>
</ul>
<p>' . t('The booking calendar will now be available at /reserve/{entitytype.bundle}/calendar. E.g. /reserve/node.room/calendar') . '</p>';
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,
),
),
);
}

58
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

61
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'

21
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'

192
reserve.series.inc

@ -0,0 +1,192 @@
<?php
/**
* Reservation CRUD for supporting Series
*/
use Drupal\Core\Render\Markup;
/**
* Create REPEATING reservations
*
* NOTE: first reservation has aready been saved at this point - we are just saving the others in series
* BUT - we do need to update the original entity with the Series ID
*
* @param mixed $node
*/
function reserve_reserve_reservation_insert($entity) {
// when we do a node_save below; it will hit this hook; so if this routine just did the save lets bail
if (isset($entity->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('<br>' . implode('<br>', $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('<br>' . implode('<br>', $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)));
}

32
reserve_category.page.inc

@ -0,0 +1,32 @@
<?php
/**
* @file
* Contains reserve_category.page.inc.
*
* Page callback for reservation categories.
*/
use Drupal\Core\Render\Element;
use Drupal\Core\Link;
use Drupal\Core\Url;
/**
* Prepares variables for Reservation Category templates.
*
* Default template: reserve_category.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An associative array containing the user information and any
* - attributes: HTML attributes for the containing element.
*/
function template_preprocess_reserve_category(array &$variables) {
// Fetch ReserveCategory Entity Object.
$reserve_category = $variables['elements']['#reserve_category'];
// Helpful $content variable for templates.
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
}

32
reserve_reservation.page.inc

@ -0,0 +1,32 @@
<?php
/**
* @file
* Contains reserve_reservation.page.inc.
*
* Page callback for reservations.
*/
use Drupal\Core\Render\Element;
use Drupal\Core\Link;
use Drupal\Core\Url;
/**
* Prepares variables for Reservation templates.
*
* Default template: reserve_reservation.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An associative array containing the user information and any
* - attributes: HTML attributes for the containing element.
*/
function template_preprocess_reserve_reservation(array &$variables) {
// Fetch ReserveReservation Entity Object.
$reserve_reservation = $variables['elements']['#reserve_reservation'];
// Helpful $content variable for templates.
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
}

113
src/BundleFormAlter.php

@ -0,0 +1,113 @@
<?php
namespace Drupal\reserve;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\field\Entity\FieldConfig;
/**
* Helper for reserve_form_alter().
*/
class BundleFormAlter {
/**
* Entity type definition.
*
* @var \Drupal\Core\Entity\ContentEntityTypeInterface
*/
protected $definition;
/**
* The entity bundle.
*
* @var string
*/
protected $bundle;
/**
* The bundle label.
*
* @var string
*/
protected $bundleLabel;
/**
* The entity type ID.
*
* @var string
*/
protected $entityTypeId;
/**
* The form entity which has been used for populating form element defaults.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $entity;
/**
* Construct a BundleFormAlter object.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity object.
*/
public function __construct(EntityInterface $entity) {
$this->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),
];
}
}

665
src/Controller/CalendarController.php

@ -0,0 +1,665 @@
<?php
namespace Drupal\reserve\Controller;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\Core\Render\Markup;
use Symfony\Component\HttpFoundation\JsonResponse;
use Drupal\reserve\Entity\ReserveReservation;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;
/**
* Provides route responses for the Example module.
*/
class CalendarController extends ControllerBase {
/**
* Returns a simple page.
*
* Param: $ebundle, e.g. node.room
*
* @return array
* A simple renderable array.
*/
public function calendar($ebundle, $selected_month = null, $selected_day = null) {
$ebundles = reserve_get_reserve_bundles();
if(!in_array($ebundle, $ebundles)) {
throw new NotFoundHttpException();
}
else {
return $this->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 = '<img src="' . base_path() . drupal_get_path('module', 'reserve') . '/images/clear.png" />';
$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] = '<li class="room-info">' . $bundle . '</li>' . "\n";
$r++;
// Available hours column.
foreach ($hours as $time_slot) {
$time_display = ($time_slot['class'] == 'odd') ? t($time_slot['display']) : "";
$table[$r][$c] = '<li class="' . $time_slot['class'] . ' timeslot" time="' . $time_slot['time'] . '">' . $time_display . '</li>' . "\n";
$r++;
}
$table[$r][$c] = '<li class="room-info room-info-footer">' . $bundle . '</li>' . "\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 = '<span class="qtip-link"><span class="qtip-tooltip">' . $room_desc . '</span>' . $room_link . '</span>';
}
else {
$room_info = $room_link;
}
if ($room->$category_field->getString() == $category['id']) {
$c++; $r = 0;
// Room name
$cell = '<li class="room-info">' . $room_info . '</li>';
$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 ? '<div class="booking-span">' . $link . '</div>' : '';
// 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 .= '<div class="reserve-group reserve-series">';
}
else {
$cell .= '<div class="reserve-group">';
}
$numslots--;
}
$cell .= '<li class="' . $slotclass . ' ' . $booked_class . ' ' . $custom_class . ' ' .
$viewable_class . ' ' . $widthclass . '">' . $link . '</li>' . "\n";
if (isset($ingroup) && $ingroup) {
if ($numslots) {
$numslots--;
}
else {
$cell .= '</div>';
$ingroup = false;
}
}
}
$table[$r][$c] = $cell;
// Room name and capacity.
$r++;
$table[$r][$c] = '<li class="room-info">' . $room_info . '</li>' . "\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 .= '<div class="grid-column hours-column">
<ul>
';
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 .= '</ul>
</div>
';
}
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('<strong>' . $nothing_to_book . '</strong>' . $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));
}
}

60
src/Controller/ReservePermissions.php

@ -0,0 +1,60 @@
<?php
namespace Drupal\reserve\Controller;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
class ReservePermissions implements ContainerInjectionInterface {
use StringTranslationTrait;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a TaxonomyViewsIntegratorPermissions instance.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->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;
}
}

222
src/Entity/ReserveCategory.php

@ -0,0 +1,222 @@
<?php
namespace Drupal\reserve\Entity;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\user\UserInterface;
/**
* Defines the Reservation Category entity.
*
* @ingroup reserve
*
* @ContentEntityType(
* id = "reserve_category",
* label = @Translation("Reservation Category"),
* handlers = {
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\reserve\ReserveCategoryListBuilder",
* "views_data" = "Drupal\reserve\Entity\ReserveCategoryViewsData",
* "translation" = "Drupal\reserve\ReserveCategoryTranslationHandler",
*
* "form" = {
* "default" = "Drupal\reserve\Form\ReserveCategoryForm",
* "add" = "Drupal\reserve\Form\ReserveCategoryForm",
* "edit" = "Drupal\reserve\Form\ReserveCategoryForm",
* "delete" = "Drupal\reserve\Form\ReserveCategoryDeleteForm",
* },
* "access" = "Drupal\reserve\ReserveCategoryAccessControlHandler",
* "route_provider" = {
* "html" = "Drupal\reserve\ReserveCategoryHtmlRouteProvider",
* },
* },
* base_table = "reserve_category",
* data_table = "reserve_category_field_data",
* translatable = TRUE,
* admin_permission = "administer reservation categories",
* entity_keys = {
* "id" = "id",
* "label" = "name",
* "uuid" = "uuid",
* "uid" = "user_id",
* "langcode" = "langcode",
* "status" = "status",
* },
* links = {
* "canonical" = "/admin/structure/reserve_category/{reserve_category}",
* "add-form" = "/admin/structure/reserve_category/add",
* "edit-form" = "/admin/structure/reserve_category/{reserve_category}/edit",
* "delete-form" = "/admin/structure/reserve_category/{reserve_category}/delete",
* "collection" = "/admin/structure/reserve_category",
* },
* field_ui_base_route = "reserve_category.settings"
* )
*/
class ReserveCategory extends ContentEntityBase implements ReserveCategoryInterface {
use EntityChangedTrait;
/**
* {@inheritdoc}
*/
public static function preCreate(EntityStorageInterface $storage_controller, array &$values) {
parent::preCreate($storage_controller, $values);
$values += array(
'user_id' => \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;
}
}

58
src/Entity/ReserveCategoryInterface.php

@ -0,0 +1,58 @@
<?php
namespace Drupal\reserve\Entity;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\user\EntityOwnerInterface;
/**
* Provides an interface for defining reservation categories.
*
* @ingroup reserve
*/
interface ReserveCategoryInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface {
// Add get/set methods for your configuration properties here.
/**
* Gets the Reservation Category name.
*
* @return string
* Name of the Reservation Category.
*/
public function getName();
/**
* Sets the Reservation Category name.
*
* @param string $name
* The Reservation Category name.
*
* @return \Drupal\reserve\Entity\ReserveCategoryInterface
* The called Reservation Category entity.
*/
public function setName($name);
/**
* Returns the Reservation Category published status indicator.
*
* Unpublished Reservation Category are only visible to restricted users.
*
* @return bool
* TRUE if the Reservation Category is published.
*/
public function isPublished();
/**
* Sets the published status of a Reservation Category.
*
* @param bool $published
* TRUE to set this Reservation Category to published, FALSE to set it to unpublished.
*
* @return \Drupal\reserve\Entity\ReserveCategoryInterface
* The called Reservation Category entity.
*/
public function setPublished($published);
}

28
src/Entity/ReserveCategoryViewsData.php

@ -0,0 +1,28 @@
<?php
namespace Drupal\reserve\Entity;
use Drupal\views\EntityViewsData;
use Drupal\views\EntityViewsDataInterface;
/**
* Provides Views data for reservation categories.
*/
class ReserveCategoryViewsData extends EntityViewsData implements EntityViewsDataInterface {
/**
* {@inheritdoc}
*/
public function getViewsData() {
$data = parent::getViewsData();
$data['reserve_category']['table']['base'] = array(
'field' => 'id',
'title' => $this->t('Reservation Category'),
'help' => $this->t('The Reservation Category ID.'),
);
return $data;
}
}

216
src/Entity/ReserveReservation.php

@ -0,0 +1,216 @@
<?php
namespace Drupal\reserve\Entity;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\user\UserInterface;
/**
* Defines the Reservation entity.
*
* @ingroup reserve
*
* @ContentEntityType(
* id = "reserve_reservation",
* label = @Translation("Reservation"),
* handlers = {
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\reserve\ReserveReservationListBuilder",
* "views_data" = "Drupal\reserve\Entity\ReserveReservationViewsData",
* "translation" = "Drupal\reserve\ReserveReservationTranslationHandler",
*
* "form" = {
* "default" = "Drupal\reserve\Form\ReserveReservationForm",
* "add" = "Drupal\reserve\Form\ReserveReservationForm",
* "edit" = "Drupal\reserve\Form\ReserveReservationForm",
* "delete" = "Drupal\reserve\Form\ReserveReservationDeleteForm",
* },
* "access" = "Drupal\reserve\ReserveReservationAccessControlHandler",
* "route_provider" = {
* "html" = "Drupal\reserve\ReserveReservationHtmlRouteProvider",
* },
* },
* base_table = "reserve_reservation",
* data_table = "reserve_reservation_field_data",
* translatable = TRUE,
* admin_permission = "administer reservations",
* entity_keys = {
* "id" = "id",
* "label" = "name",
* "uuid" = "uuid",
* "uid" = "user_id",
* "langcode" = "langcode",
* "status" = "status",
* },
* links = {
* "canonical" = "/reserve_reservation/{reserve_reservation}",
* "add-form" = "/reserve_reservation/add",
* "edit-form" = "/reserve_reservation/{reserve_reservation}/edit",
* "delete-form" = "/reserve_reservation/{reserve_reservation}/delete",
* "collection" = "/admin/structure/reservations/reserve_reservation",
* },
* field_ui_base_route = "reserve_reservation.settings"
* )
*/
class ReserveReservation extends ContentEntityBase implements ReserveReservationInterface {
use EntityChangedTrait;
/**
* {@inheritdoc}
*/
public static function preCreate(EntityStorageInterface $storage_controller, array &$values) {
parent::preCreate($storage_controller, $values);
$values += array(
'user_id' => \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;
}
}

77
src/Entity/ReserveReservationInterface.php

@ -0,0 +1,77 @@
<?php
namespace Drupal\reserve\Entity;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\user\EntityOwnerInterface;
/**
* Provides an interface for defining reservations.
*
* @ingroup reserve
*/
interface ReserveReservationInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface {
// Add get/set methods for your configuration properties here.
/**
* Gets the Reservation name.
*
* @return string
* Name of the Reservation.
*/
public function getName();
/**
* Sets the Reservation name.
*
* @param string $name
* The Reservation name.
*
* @return \Drupal\reserve\Entity\ReserveReservationInterface
* The called Reservation entity.
*/
public function setName($name);
/**
* Gets the Reservation creation timestamp.
*
* @return int
* Creation timestamp of the Reservation.
*/
public function getCreatedTime();
/**
* Sets the Reservation creation timestamp.
*
* @param int $timestamp
* The Reservation creation timestamp.
*
* @return \Drupal\reserve\Entity\ReserveReservationInterface
* The called Reservation entity.
*/
public function setCreatedTime($timestamp);
/**
* Returns the Reservation published status indicator.
*
* Unpublished Reservation are only visible to restricted users.
*
* @return bool
* TRUE if the Reservation is published.
*/
public function isPublished();
/**
* Sets the published status of a Reservation.
*
* @param bool $published
* TRUE to set this Reservation to published, FALSE to set it to unpublished.
*
* @return \Drupal\reserve\Entity\ReserveReservationInterface
* The called Reservation entity.
*/
public function setPublished($published);
}

14
src/Entity/ReserveReservationViewsData.php

@ -0,0 +1,14 @@
<?php
namespace Drupal\reserve\Entity;
use Drupal\views\EntityViewsData;
use Drupal\views\EntityViewsDataInterface;
/**
* Provides Views data for reservations.
*/
class ReserveReservationViewsData extends EntityViewsData implements EntityViewsDataInterface {
}

56
src/Form/CalendarDatePicker.php

@ -0,0 +1,56 @@
<?php
namespace Drupal\reserve\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Datetime\DrupalDateTime;
/**
* Class CalendarDatePicker.
*/
class CalendarDatePicker extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'calendarDatePicker';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Retrieve an array which contains the path pieces.
$current_path = \Drupal::service('path.current')->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);
}
}
}

15
src/Form/ReserveCategoryDeleteForm.php

@ -0,0 +1,15 @@
<?php
namespace Drupal\reserve\Form;
use Drupal\Core\Entity\ContentEntityDeleteForm;
/**
* Provides a form for deleting reservation categories.
*
* @ingroup reserve
*/
class ReserveCategoryDeleteForm extends ContentEntityDeleteForm {
}

50
src/Form/ReserveCategoryForm.php

@ -0,0 +1,50 @@
<?php
namespace Drupal\reserve\Form;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
/**
* Form controller for Reservation Category edit forms.
*
* @ingroup reserve
*/
class ReserveCategoryForm extends ContentEntityForm {
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
/* @var $entity \Drupal\reserve\Entity\ReserveCategory */
$form = parent::buildForm($form, $form_state);
$entity = $this->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()]);
}
}

55
src/Form/ReserveCategorySettingsForm.php

@ -0,0 +1,55 @@
<?php
namespace Drupal\reserve\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Class ReserveCategorySettingsForm.
*
* @package Drupal\reserve\Form
*
* @ingroup reserve
*/
class ReserveCategorySettingsForm extends FormBase {
/**
* Returns a unique string identifying the form.
*
* @return string
* The unique string identifying the form.
*/
public function getFormId() {
return 'ReserveCategory_settings';
}
/**
* 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) {
// Empty implementation of the abstract submit class.
}
/**
* Defines the settings form for reservation categories.
*
* @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.
*
* @return array
* Form definition array.
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['ReserveCategory_settings']['#markup'] = 'Settings form for reservation categories. Manage field settings here.';
return $form;
}
}

250
src/Form/ReserveDailyHoursForm.php

@ -0,0 +1,250 @@
<?php
namespace Drupal\reserve\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
module_load_include('inc', 'reserve', 'reserve.admin');
/**
* Class ReserveCategorySettingsForm.
*
* @package Drupal\reserve\Form
*
* @ingroup reserve
*/
class ReserveDailyHoursForm extends ConfigFormBase {
/**
* Returns a unique string identifying the form.
*
* @return string
* The unique string identifying the form.
*/
public function getFormId() {
return 'ReserveDailyHours';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return [
'reserve.monthly_hours',
];
}
/**
* Form constructor for the Hours / Daily Hours configuration page.
*
*/
public function buildForm(array $form, FormStateInterface $form_state, $passed_month = null) {
$cur_months = reserve_current_months();
$selected_month = ($passed_month == 'yyyy_mm') ? date('Y_m') : $passed_month;
$monthly_hours = $this->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' => '<br /><b>',
'#suffix' => '</b><br />',
'#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);
}
}

308
src/Form/ReserveDefaultHoursForm.php

@ -0,0 +1,308 @@
<?php
namespace Drupal\reserve\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
module_load_include('inc', 'reserve', 'reserve.admin');
/**
* Class ReserveCategorySettingsForm.
*
* @package Drupal\reserve\Form
*
* @ingroup reserve
*/
class ReserveDefaultHoursForm extends ConfigFormBase {
/**
* Returns a unique string identifying the form.
*
* @return string
* The unique string identifying the form.
*/
public function getFormId() {
return 'ReserveDefaultHours';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return [
'reserve.default_hours',
'reserve.monthly_hours',
];
}
/**
* Defines the settings form for reservation categories.
*
* @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.
*
* @return array
* Form definition array.
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$default_hours = $this->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.'));
}
}

86
src/Form/ReserveDisplayForm.php

@ -0,0 +1,86 @@
<?php
namespace Drupal\reserve\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Class ReserveCategorySettingsForm.
*
* @package Drupal\reserve\Form
*
* @ingroup reserve
*/
class ReserveDisplayForm extends ConfigFormBase {
/**
* Returns a unique string identifying the form.
*
* @return string
* The unique string identifying the form.
*/
public function getFormId() {
return 'ReserveDisplaySettings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return [
'reserve.display',
];
}
/**
* Defines the settings form for reservation categories.
*
* @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.
*
* @return array
* Form definition array.
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->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();*/
}
}

15
src/Form/ReserveReservationDeleteForm.php

@ -0,0 +1,15 @@
<?php
namespace Drupal\reserve\Form;
use Drupal\Core\Entity\ContentEntityDeleteForm;
/**
* Provides a form for deleting reservations.
*
* @ingroup reserve
*/
class ReserveReservationDeleteForm extends ContentEntityDeleteForm {
}

50
src/Form/ReserveReservationForm.php

@ -0,0 +1,50 @@
<?php
namespace Drupal\reserve\Form;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
/**
* Form controller for Reservation edit forms.
*
* @ingroup reserve
*/
class ReserveReservationForm extends ContentEntityForm {
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
/* @var $entity \Drupal\reserve\Entity\ReserveReservation */
$form = parent::buildForm($form, $form_state);
$entity = $this->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()]);
}
}

55
src/Form/ReserveReservationSettingsForm.php

@ -0,0 +1,55 @@
<?php
namespace Drupal\reserve\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Class ReserveReservationSettingsForm.
*
* @package Drupal\reserve\Form
*
* @ingroup reserve
*/
class ReserveReservationSettingsForm extends FormBase {
/**
* Returns a unique string identifying the form.
*
* @return string
* The unique string identifying the form.
*/
public function getFormId() {
return 'ReserveReservation_settings';
}
/**
* 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) {
// Empty implementation of the abstract submit class.
}
/**
* Defines the settings form for reservations.
*
* @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.
*
* @return array
* Form definition array.
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['ReserveReservation_settings']['#markup'] = 'Settings form for reservations. Manage field settings here.';
return $form;
}
}

183
src/Form/ReserveSettingsForm.php

@ -0,0 +1,183 @@
<?php
namespace Drupal\reserve\Form;
use Drupal\Core\Form\ConfigFormBase;
//use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Class ReserveCategorySettingsForm.
*
* @package Drupal\reserve\Form
*
* @ingroup reserve
*/
class ReserveSettingsForm extends ConfigFormBase {
/**
* Returns a unique string identifying the form.
*
* @return string
* The unique string identifying the form.
*/
public function getFormId() {
return 'ReserveSettings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return [
'reserve.settings',
];
}
/**
* Defines the settings form for reservation categories.
*
* @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.
*
* @return array
* Form definition array.
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->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);
}
}

49
src/Plugin/Field/FieldFormatter/ReserveCategoryFormatter.php

@ -0,0 +1,49 @@
<?php
namespace Drupal\reserve\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Plugin implementation of the 'Random_default' formatter.
*
* @FieldFormatter(
* id = "reserve_category_formatter",
* label = @Translation("Default"),
* field_types = {
* "reserve_category"
* }
* )
*/
class ReserveCategoryFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$settings = $this->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;
}
}

116
src/Plugin/Field/FieldType/ReserveCategory.php

@ -0,0 +1,116 @@
<?php
namespace Drupal\reserve\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
/**
* Class ReserveCategory for Category selection field type
*
* @FieldType(
* id = "reserve_category",
* label = @Translation("Reserve Category"),
* description = @Translation("A selection field to assign which Reserve Category this entity is grouped under."),
* default_widget = "reserve_category_select",
* default_formatter = "reserve_category_formatter",
* category = @Translation("Reference"),
* cardinality = 1,
* )
*
*/
class ReserveCategory extends FieldItemBase implements FieldItemInterface {
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
return array(
'columns' => 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();
}
}

49
src/Plugin/Field/FieldWidget/ReserveCategorySelect.php

@ -0,0 +1,49 @@
<?php
namespace Drupal\reserve\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'reserve_category_select' widget.
*
* @FieldWidget(
* id = "reserve_category_select",
* module = "reserve",
* label = @Translation("Select list"),
* field_types = {
* "reserve_category"
* }
* )
*/
class ReserveCategorySelect extends WidgetBase {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$cid = isset($items[$delta]->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);
}
}

47
src/ReserveCategoryAccessControlHandler.php

@ -0,0 +1,47 @@
<?php
namespace Drupal\reserve;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResult;
/**
* Access controller for the Reservation Category entity.
*
* @see \Drupal\reserve\Entity\ReserveCategory.
*/
class ReserveCategoryAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
/** @var \Drupal\reserve\Entity\ReserveCategoryInterface $entity */
switch ($operation) {
case 'view':
if (!$entity->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');
}
}

85
src/ReserveCategoryHtmlRouteProvider.php

@ -0,0 +1,85 @@
<?php
namespace Drupal\reserve;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
use Symfony\Component\Routing\Route;
/**
* Provides routes for reservation categories.
*
* @see Drupal\Core\Entity\Routing\AdminHtmlRouteProvider
* @see Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider
*/
class ReserveCategoryHtmlRouteProvider extends AdminHtmlRouteProvider {
/**
* {@inheritdoc}
*/
public function getRoutes(EntityTypeInterface $entity_type) {
$collection = parent::getRoutes($entity_type);
$entity_type_id = $entity_type->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;
}
}
}

45
src/ReserveCategoryListBuilder.php

@ -0,0 +1,45 @@
<?php
namespace Drupal\reserve;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityListBuilder;
use Drupal\Core\Routing\LinkGeneratorTrait;
use Drupal\Core\Url;
/**
* Defines a class to build a listing of reservation categories.
*
* @ingroup reserve
*/
class ReserveCategoryListBuilder extends EntityListBuilder {
use LinkGeneratorTrait;
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['id'] = $this->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);
}
}

14
src/ReserveCategoryTranslationHandler.php

@ -0,0 +1,14 @@
<?php
namespace Drupal\reserve;
use Drupal\content_translation\ContentTranslationHandler;
/**
* Defines the translation handler for reserve_category.
*/
class ReserveCategoryTranslationHandler extends ContentTranslationHandler {
// Override here the needed methods from ContentTranslationHandler.
}

47
src/ReserveReservationAccessControlHandler.php

@ -0,0 +1,47 @@
<?php
namespace Drupal\reserve;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResult;
/**
* Access controller for the Reservation entity.
*
* @see \Drupal\reserve\Entity\ReserveReservation.
*/
class ReserveReservationAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
/** @var \Drupal\reserve\Entity\ReserveReservationInterface $entity */
switch ($operation) {
case 'view':
if (!$entity->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');
}
}

85
src/ReserveReservationHtmlRouteProvider.php

@ -0,0 +1,85 @@
<?php
namespace Drupal\reserve;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
use Symfony\Component\Routing\Route;
/**
* Provides routes for reservations.
*
* @see Drupal\Core\Entity\Routing\AdminHtmlRouteProvider
* @see Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider
*/
class ReserveReservationHtmlRouteProvider extends AdminHtmlRouteProvider {
/**
* {@inheritdoc}
*/
public function getRoutes(EntityTypeInterface $entity_type) {
$collection = parent::getRoutes($entity_type);
$entity_type_id = $entity_type->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;
}
}
}

45
src/ReserveReservationListBuilder.php

@ -0,0 +1,45 @@
<?php
namespace Drupal\reserve;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityListBuilder;
use Drupal\Core\Routing\LinkGeneratorTrait;
use Drupal\Core\Url;
/**
* Defines a class to build a listing of reservations.
*
* @ingroup reserve
*/
class ReserveReservationListBuilder extends EntityListBuilder {
use LinkGeneratorTrait;
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['id'] = $this->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);
}
}

14
src/ReserveReservationTranslationHandler.php

@ -0,0 +1,14 @@
<?php
namespace Drupal\reserve;
use Drupal\content_translation\ContentTranslationHandler;
/**
* Defines the translation handler for reserve_reservation.
*/
class ReserveReservationTranslationHandler extends ContentTranslationHandler {
// Override here the needed methods from ContentTranslationHandler.
}

46
src/Tests/LoadTest.php

@ -0,0 +1,46 @@
<?php
namespace Drupal\reserve\Tests;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
/**
* Simple test to ensure that main page loads with module enabled.
*
* @group reserve
*/
class LoadTest extends WebTestBase{
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['reserve'];
/**
* A user with permission to administer site configuration.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->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('<front>'));
$this->assertResponse(200);
}
}

37
templates/calendar-template.html.twig

@ -0,0 +1,37 @@
<div id="rooms-calendar">
<div id="tabbedPanels">
<div id="info">
<div>{{ calendar_header }}</div>
<hr>
<div id="start">{{ reservation_instructions }}</div>
</div>
<br>
<h3 class="date">{{ 'Reservation Calendar'|t }}</h3>
<div class="hours">{{ date }} </div>
<div class="hours">{{ building_hours_display }}</div>
<div class="reserve-center"><img id="arrow" src="{{ arrow }}" /></div>
<div id="date-change">{{ date_picker }}</div>
<!-- Tabs -->
<ul class="room-tabs">
{% for cat in categories %}
<li><a class="{{ cat.active }}" href="#{{ cat.tag }}">{{ cat.title }}</a></li>
{% endfor %}
</ul>
<div class="panelContainer">
{% for cat1 in categories %}
<div id="{{ cat1.tag }}" class="panel {{ cat1.show }}">
<div class="gcolumns {{ orientclass }}">
{# $table from D7 #}
{{ cat1.table }}
</div>
</div>
{% endfor %}
</div>
<div class="clear"></div>
</div>
</div>

22
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
*/
#}
<div{{ attributes.addClass('reserve_category') }}>
{% if content %}
{{- content -}}
{% endif %}
</div>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save