diff --git a/web/profiles/custom/yalesites_profile/composer.json b/web/profiles/custom/yalesites_profile/composer.json
index 7128dde846..192465d0e6 100644
--- a/web/profiles/custom/yalesites_profile/composer.json
+++ b/web/profiles/custom/yalesites_profile/composer.json
@@ -106,7 +106,7 @@
"laminas/laminas-escaper": "2.12",
"northernco/ckeditor5-anchor-drupal": "0.4.0",
"yalesites-org/ai_engine": "1.2.4",
- "yalesites-org/atomic": "1.36.0",
+ "yalesites-org/atomic": "1.38.0",
"yalesites-org/yale_cas": "1.0.4"
},
"minimum-stability": "dev",
diff --git a/web/profiles/custom/yalesites_profile/config/sync/ai_engine_chat.settings.yml b/web/profiles/custom/yalesites_profile/config/sync/ai_engine_chat.settings.yml
deleted file mode 100644
index c678f9ba23..0000000000
--- a/web/profiles/custom/yalesites_profile/config/sync/ai_engine_chat.settings.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-_core:
- default_config_hash: 2UNzD8lgl2QZFwoEWDqXPOjnX-1xrj2WOUPOyV36X4w
-enable: false
-azure_base_url: ''
-initial_questions: { }
-disclaimer: ''
-footer: ''
diff --git a/web/profiles/custom/yalesites_profile/config/sync/ai_engine_metadata.settings.yml b/web/profiles/custom/yalesites_profile/config/sync/ai_engine_metadata.settings.yml
deleted file mode 100644
index d48968cc55..0000000000
--- a/web/profiles/custom/yalesites_profile/config/sync/ai_engine_metadata.settings.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-_core:
- default_config_hash: _M8zK1JzYowovWvVZ0BNgS8MXJ_VkFi1KuUjskwJ2h8
-enable: false
diff --git a/web/profiles/custom/yalesites_profile/config/sync/config_ignore.settings.yml b/web/profiles/custom/yalesites_profile/config/sync/config_ignore.settings.yml
index 814e7b3bd3..007ac237c4 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/config_ignore.settings.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/config_ignore.settings.yml
@@ -8,3 +8,4 @@ ignored_config_entities:
- ys_themes.theme_settings
- 'ys_localist*'
- 'ai_engine*'
+ - 'ys_servicenow*'
diff --git a/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.block_content.content_spotlight.default.yml b/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.block_content.content_spotlight.default.yml
index d967b1cae3..c41eaf9021 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.block_content.content_spotlight.default.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.block_content.content_spotlight.default.yml
@@ -140,7 +140,7 @@ content:
hide_help: '1'
hide_guidelines: '1'
maxlength:
- maxlength_js: 500
+ maxlength_js: 600
maxlength_js_label: 'Content limited to @limit characters, remaining: @remaining'
maxlength_js_enforce: true
info:
diff --git a/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.block_content.content_spotlight_portrait.default.yml b/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.block_content.content_spotlight_portrait.default.yml
index e06663272f..fcc3f2d1d2 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.block_content.content_spotlight_portrait.default.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.block_content.content_spotlight_portrait.default.yml
@@ -136,7 +136,7 @@ content:
hide_help: '1'
hide_guidelines: '1'
maxlength:
- maxlength_js: 500
+ maxlength_js: 650
maxlength_js_label: 'Content limited to @limit characters, remaining: @remaining'
maxlength_js_enforce: true
info:
diff --git a/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.block_content.image_banner.default.yml b/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.block_content.image_banner.default.yml
index a3492304ad..d328a37a37 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.block_content.image_banner.default.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.block_content.image_banner.default.yml
@@ -5,6 +5,7 @@ dependencies:
config:
- block_content.type.image_banner
- field.field.block_content.image_banner.field_media
+ - field.field.block_content.image_banner.field_style_variation
module:
- media_library
- media_library_edit
@@ -18,10 +19,18 @@ content:
weight: 1
region: content
settings:
- media_types: { }
+ media_types:
+ - image
+ - background_video
third_party_settings:
media_library_edit:
show_edit: '1'
+ field_style_variation:
+ type: options_select
+ weight: 11
+ region: content
+ settings: { }
+ third_party_settings: { }
info:
type: string_textfield
weight: 0
diff --git a/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.node.profile.default.yml b/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.node.profile.default.yml
index f6e3a6e54c..c5aecefb5b 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.node.profile.default.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/core.entity_form_display.node.profile.default.yml
@@ -15,6 +15,7 @@ dependencies:
- field.field.node.profile.field_media
- field.field.node.profile.field_metatags
- field.field.node.profile.field_position
+ - field.field.node.profile.field_pronouns
- field.field.node.profile.field_subtitle
- field.field.node.profile.field_tags
- field.field.node.profile.field_teaser_media
@@ -107,6 +108,7 @@ third_party_settings:
- field_first_name
- field_last_name
- field_honorific_prefix
+ - field_pronouns
- field_position
- field_subtitle
- field_department
@@ -164,7 +166,7 @@ content:
third_party_settings: { }
field_department:
type: string_textfield
- weight: 33
+ weight: 34
region: content
settings:
size: 60
@@ -235,6 +237,14 @@ content:
use_details: true
third_party_settings: { }
field_position:
+ type: string_textfield
+ weight: 32
+ region: content
+ settings:
+ size: 60
+ placeholder: ''
+ third_party_settings: { }
+ field_pronouns:
type: string_textfield
weight: 31
region: content
@@ -244,7 +254,7 @@ content:
third_party_settings: { }
field_subtitle:
type: text_textfield
- weight: 32
+ weight: 33
region: content
settings:
size: 60
diff --git a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.block_content.image_banner.default.yml b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.block_content.image_banner.default.yml
index e7362d3b0c..733e1b8063 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.block_content.image_banner.default.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.block_content.image_banner.default.yml
@@ -5,6 +5,9 @@ dependencies:
config:
- block_content.type.image_banner
- field.field.block_content.image_banner.field_media
+ - field.field.block_content.image_banner.field_style_variation
+ module:
+ - options
id: block_content.image_banner.default
targetEntityType: block_content
bundle: image_banner
@@ -14,10 +17,17 @@ content:
type: entity_reference_entity_view
label: hidden
settings:
- view_mode: banner_16_5
+ view_mode: default
link: false
third_party_settings: { }
weight: 0
region: content
+ field_style_variation:
+ type: list_key
+ label: hidden
+ settings: { }
+ third_party_settings: { }
+ weight: 5
+ region: content
hidden:
search_api_excerpt: true
diff --git a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.card.yml b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.card.yml
index 344f6fc635..7db4d41818 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.card.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.card.yml
@@ -16,6 +16,7 @@ dependencies:
- field.field.node.profile.field_media
- field.field.node.profile.field_metatags
- field.field.node.profile.field_position
+ - field.field.node.profile.field_pronouns
- field.field.node.profile.field_subtitle
- field.field.node.profile.field_tags
- field.field.node.profile.field_teaser_media
@@ -128,6 +129,7 @@ hidden:
field_last_name: true
field_login_required: true
field_metatags: true
+ field_pronouns: true
field_telephone: true
layout_builder__layout: true
links: true
diff --git a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.condensed.yml b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.condensed.yml
index 1ad780139e..258573abef 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.condensed.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.condensed.yml
@@ -16,6 +16,7 @@ dependencies:
- field.field.node.profile.field_media
- field.field.node.profile.field_metatags
- field.field.node.profile.field_position
+ - field.field.node.profile.field_pronouns
- field.field.node.profile.field_subtitle
- field.field.node.profile.field_tags
- field.field.node.profile.field_teaser_media
@@ -47,6 +48,11 @@ targetEntityType: node
bundle: profile
mode: condensed
content:
+ content_moderation_control:
+ settings: { }
+ third_party_settings: { }
+ weight: -20
+ region: content
field_external_source:
type: link_separate
label: hidden
@@ -78,6 +84,7 @@ hidden:
field_login_required: true
field_media: true
field_metatags: true
+ field_pronouns: true
field_subtitle: true
field_tags: true
field_teaser_media: true
@@ -87,3 +94,4 @@ hidden:
layout_builder__layout: true
links: true
search_api_excerpt: true
+ workflow_buttons: true
diff --git a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.default.yml b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.default.yml
index 77771730b1..8b0cdfa720 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.default.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.default.yml
@@ -15,6 +15,7 @@ dependencies:
- field.field.node.profile.field_media
- field.field.node.profile.field_metatags
- field.field.node.profile.field_position
+ - field.field.node.profile.field_pronouns
- field.field.node.profile.field_subtitle
- field.field.node.profile.field_tags
- field.field.node.profile.field_teaser_media
@@ -323,6 +324,14 @@ content:
third_party_settings: { }
weight: 107
region: content
+ field_pronouns:
+ type: string
+ label: above
+ settings:
+ link_to_entity: false
+ third_party_settings: { }
+ weight: 119
+ region: content
field_subtitle:
type: text_default
label: above
diff --git a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.directory.yml b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.directory.yml
index 7ea14769c7..8ea2fb6955 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.directory.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.directory.yml
@@ -16,6 +16,7 @@ dependencies:
- field.field.node.profile.field_media
- field.field.node.profile.field_metatags
- field.field.node.profile.field_position
+ - field.field.node.profile.field_pronouns
- field.field.node.profile.field_subtitle
- field.field.node.profile.field_tags
- field.field.node.profile.field_teaser_media
@@ -27,6 +28,7 @@ dependencies:
module:
- layout_builder
- layout_builder_restrictions
+ - link
- text
- user
third_party_settings:
@@ -47,6 +49,11 @@ targetEntityType: node
bundle: profile
mode: directory
content:
+ content_moderation_control:
+ settings: { }
+ third_party_settings: { }
+ weight: -20
+ region: content
field_affiliation:
type: entity_reference_label
label: hidden
@@ -130,9 +137,11 @@ hidden:
field_last_name: true
field_login_required: true
field_metatags: true
+ field_pronouns: true
field_tags: true
field_teaser_text: true
field_teaser_title: true
layout_builder__layout: true
links: true
search_api_excerpt: true
+ workflow_buttons: true
diff --git a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.list_item.yml b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.list_item.yml
index b797a59601..9ad2bca6fb 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.list_item.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.list_item.yml
@@ -16,6 +16,7 @@ dependencies:
- field.field.node.profile.field_media
- field.field.node.profile.field_metatags
- field.field.node.profile.field_position
+ - field.field.node.profile.field_pronouns
- field.field.node.profile.field_subtitle
- field.field.node.profile.field_tags
- field.field.node.profile.field_teaser_media
@@ -128,6 +129,7 @@ hidden:
field_last_name: true
field_login_required: true
field_metatags: true
+ field_pronouns: true
field_telephone: true
layout_builder__layout: true
links: true
diff --git a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.search_result.yml b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.search_result.yml
index 8ca5f5fdef..c4154d40db 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.search_result.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.search_result.yml
@@ -16,6 +16,7 @@ dependencies:
- field.field.node.profile.field_media
- field.field.node.profile.field_metatags
- field.field.node.profile.field_position
+ - field.field.node.profile.field_pronouns
- field.field.node.profile.field_subtitle
- field.field.node.profile.field_tags
- field.field.node.profile.field_teaser_media
@@ -26,6 +27,7 @@ dependencies:
- node.type.profile
module:
- layout_builder
+ - link
- text
- user
third_party_settings:
@@ -37,6 +39,11 @@ targetEntityType: node
bundle: profile
mode: search_result
content:
+ content_moderation_control:
+ settings: { }
+ third_party_settings: { }
+ weight: -20
+ region: content
field_external_source:
type: link_separate
label: hidden
@@ -73,6 +80,7 @@ hidden:
field_media: true
field_metatags: true
field_position: true
+ field_pronouns: true
field_subtitle: true
field_tags: true
field_teaser_media: true
@@ -80,3 +88,4 @@ hidden:
field_telephone: true
layout_builder__layout: true
links: true
+ workflow_buttons: true
diff --git a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.single.yml b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.single.yml
index 518a1cb1fa..6c7a65ee20 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.single.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.single.yml
@@ -16,6 +16,7 @@ dependencies:
- field.field.node.profile.field_media
- field.field.node.profile.field_metatags
- field.field.node.profile.field_position
+ - field.field.node.profile.field_pronouns
- field.field.node.profile.field_subtitle
- field.field.node.profile.field_tags
- field.field.node.profile.field_teaser_media
@@ -163,6 +164,11 @@ targetEntityType: node
bundle: profile
mode: single
content:
+ content_moderation_control:
+ settings: { }
+ third_party_settings: { }
+ weight: -20
+ region: content
field_address:
type: text_default
label: hidden
@@ -288,7 +294,9 @@ content:
hidden:
field_login_required: true
field_metatags: true
+ field_pronouns: true
field_tags: true
layout_builder__layout: true
links: true
search_api_excerpt: true
+ workflow_buttons: true
diff --git a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.teaser.yml b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.teaser.yml
index eabe3d2873..3a21a64a51 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.teaser.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/core.entity_view_display.node.profile.teaser.yml
@@ -16,6 +16,7 @@ dependencies:
- field.field.node.profile.field_media
- field.field.node.profile.field_metatags
- field.field.node.profile.field_position
+ - field.field.node.profile.field_pronouns
- field.field.node.profile.field_subtitle
- field.field.node.profile.field_tags
- field.field.node.profile.field_teaser_media
@@ -31,6 +32,11 @@ targetEntityType: node
bundle: profile
mode: teaser
content:
+ content_moderation_control:
+ settings: { }
+ third_party_settings: { }
+ weight: -20
+ region: content
links:
settings: { }
third_party_settings: { }
@@ -49,6 +55,7 @@ hidden:
field_media: true
field_metatags: true
field_position: true
+ field_pronouns: true
field_subtitle: true
field_tags: true
field_teaser_media: true
@@ -57,3 +64,4 @@ hidden:
field_telephone: true
layout_builder__layout: true
search_api_excerpt: true
+ workflow_buttons: true
diff --git a/web/profiles/custom/yalesites_profile/config/sync/core.extension.yml b/web/profiles/custom/yalesites_profile/config/sync/core.extension.yml
index 9cd2218d72..86a2966cd4 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/core.extension.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/core.extension.yml
@@ -148,6 +148,7 @@ module:
ys_localist: 0
ys_mail: 0
ys_node_access: 0
+ ys_servicenow: 0
ys_starterkit: 0
ys_toolbar: 0
ys_views_basic: 0
diff --git a/web/profiles/custom/yalesites_profile/config/sync/field.field.block_content.image_banner.field_media.yml b/web/profiles/custom/yalesites_profile/config/sync/field.field.block_content.image_banner.field_media.yml
index ed5e599347..7431310e3a 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/field.field.block_content.image_banner.field_media.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/field.field.block_content.image_banner.field_media.yml
@@ -27,5 +27,5 @@ settings:
field: _none
direction: ASC
auto_create: true
- auto_create_bundle: background_video
+ auto_create_bundle: image
field_type: entity_reference
diff --git a/web/profiles/custom/yalesites_profile/config/sync/field.field.block_content.image_banner.field_style_variation.yml b/web/profiles/custom/yalesites_profile/config/sync/field.field.block_content.image_banner.field_style_variation.yml
new file mode 100644
index 0000000000..f95053e5cc
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/config/sync/field.field.block_content.image_banner.field_style_variation.yml
@@ -0,0 +1,21 @@
+uuid: 746c6e8d-b767-4afe-bc09-8bf928f39ae4
+langcode: en
+status: true
+dependencies:
+ config:
+ - block_content.type.image_banner
+ - field.storage.block_content.field_style_variation
+ module:
+ - options
+id: block_content.image_banner.field_style_variation
+field_name: field_style_variation
+entity_type: block_content
+bundle: image_banner
+label: 'Media Size'
+description: ''
+required: true
+translatable: false
+default_value: { }
+default_value_callback: ys_themes_default_value_function
+settings: { }
+field_type: list_string
diff --git a/web/profiles/custom/yalesites_profile/config/sync/field.field.node.profile.field_pronouns.yml b/web/profiles/custom/yalesites_profile/config/sync/field.field.node.profile.field_pronouns.yml
new file mode 100644
index 0000000000..751f6fef47
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/config/sync/field.field.node.profile.field_pronouns.yml
@@ -0,0 +1,19 @@
+uuid: 47bd0833-5f70-4c13-a2d9-25b0f01dcfff
+langcode: en
+status: true
+dependencies:
+ config:
+ - field.storage.node.field_pronouns
+ - node.type.profile
+id: node.profile.field_pronouns
+field_name: field_pronouns
+entity_type: node
+bundle: profile
+label: Pronouns
+description: ''
+required: false
+translatable: false
+default_value: { }
+default_value_callback: ''
+settings: { }
+field_type: string
diff --git a/web/profiles/custom/yalesites_profile/config/sync/field.storage.node.field_pronouns.yml b/web/profiles/custom/yalesites_profile/config/sync/field.storage.node.field_pronouns.yml
new file mode 100644
index 0000000000..dd884e313a
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/config/sync/field.storage.node.field_pronouns.yml
@@ -0,0 +1,21 @@
+uuid: 78b31208-ff3a-404a-ba52-8fba4c418766
+langcode: en
+status: true
+dependencies:
+ module:
+ - node
+id: node.field_pronouns
+field_name: field_pronouns
+entity_type: node
+type: string
+settings:
+ max_length: 255
+ case_sensitive: false
+ is_ascii: false
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: { }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/web/profiles/custom/yalesites_profile/config/sync/ys_themes.component_overrides.yml b/web/profiles/custom/yalesites_profile/config/sync/ys_themes.component_overrides.yml
index d9d548e0bd..aa97e20d67 100644
--- a/web/profiles/custom/yalesites_profile/config/sync/ys_themes.component_overrides.yml
+++ b/web/profiles/custom/yalesites_profile/config/sync/ys_themes.component_overrides.yml
@@ -254,3 +254,9 @@ video:
left: Left
center: Center
default: left
+image_banner:
+ field_style_variation:
+ values:
+ tall: Tall
+ short: Short
+ default: tall
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_core/src/Plugin/Action/ModeratedPublish.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_core/src/Plugin/Action/ModeratedPublish.php
index 65cb92234d..039c6f77a9 100644
--- a/web/profiles/custom/yalesites_profile/modules/custom/ys_core/src/Plugin/Action/ModeratedPublish.php
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_core/src/Plugin/Action/ModeratedPublish.php
@@ -27,7 +27,7 @@ public function execute($entity = NULL) {
/**
* {@inheritdoc}
*/
- public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
$key = $object->getEntityType()->getKey('published');
/** @var \Drupal\Core\Entity\EntityInterface $object */
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_core/src/Plugin/Action/ModeratedUnpublish.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_core/src/Plugin/Action/ModeratedUnpublish.php
index 20325818bb..460b10bbc5 100644
--- a/web/profiles/custom/yalesites_profile/modules/custom/ys_core/src/Plugin/Action/ModeratedUnpublish.php
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_core/src/Plugin/Action/ModeratedUnpublish.php
@@ -27,7 +27,7 @@ public function execute($entity = NULL) {
/**
* {@inheritdoc}
*/
- public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
$key = $object->getEntityType()->getKey('published');
/** @var \Drupal\Core\Entity\EntityInterface $object */
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_layouts/src/Plugin/Block/ProfileMetaBlock.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_layouts/src/Plugin/Block/ProfileMetaBlock.php
index 31e97bde96..e8cc91ff9c 100644
--- a/web/profiles/custom/yalesites_profile/modules/custom/ys_layouts/src/Plugin/Block/ProfileMetaBlock.php
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_layouts/src/Plugin/Block/ProfileMetaBlock.php
@@ -101,6 +101,7 @@ public function build() {
$position = NULL;
$subtitle = NULL;
$department = NULL;
+ $pronouns = NULL;
$mediaId = NULL;
$request = $this->requestStack->getCurrentRequest();
@@ -125,6 +126,7 @@ public function build() {
$position = $node->get('field_position')->getValue()[0]['value'] ?? NULL;
$subtitle = $node->get('field_subtitle')->getValue()[0]['value'] ?? NULL;
$department = $node->get('field_department')->getValue()[0]['value'] ?? NULL;
+ $pronouns = $node->get('field_pronouns')->getValue()[0]['value'] ?? NULL;
$mediaId = $node->get('field_media')->getValue()[0]['target_id'] ?? NULL;
}
@@ -134,10 +136,12 @@ public function build() {
'#profile_meta__title_line' => $position,
'#profile_meta__subtitle_line' => $subtitle,
'#profile_meta__department' => $department,
+ '#profile_meta__pronouns' => $pronouns,
'#media_id' => $mediaId,
'#profile_meta__image_orientation' => $this->configuration['image_orientation'] ?? 'portrait',
'#profile_meta__image_style' => $this->configuration['image_style'] ?? 'inline',
'#profile_meta__image_alignment' => $this->configuration['image_alignment'] ?? 'left',
+
];
}
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_layouts/src/Service/LayoutUpdater.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_layouts/src/Service/LayoutUpdater.php
index 2bcaf6660d..a75694c344 100644
--- a/web/profiles/custom/yalesites_profile/modules/custom/ys_layouts/src/Service/LayoutUpdater.php
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_layouts/src/Service/LayoutUpdater.php
@@ -2,7 +2,6 @@
namespace Drupal\ys_layouts\Service;
-use Drupal\block_content\Entity\BlockContent;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityFieldManager;
@@ -10,6 +9,7 @@
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\block_content\Entity\BlockContent;
use Drupal\node\NodeInterface;
use Psr\Log\LoggerInterface;
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_layouts/ys_layouts.module b/web/profiles/custom/yalesites_profile/modules/custom/ys_layouts/ys_layouts.module
index ade2e3546d..b029a58559 100644
--- a/web/profiles/custom/yalesites_profile/modules/custom/ys_layouts/ys_layouts.module
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_layouts/ys_layouts.module
@@ -57,6 +57,7 @@ function ys_layouts_theme($existing, $type, $theme, $path): array {
'profile_meta__title_line' => NULL,
'profile_meta__subtitle_line' => NULL,
'profile_meta__department' => NULL,
+ 'profile_meta__pronouns' => NULL,
'media_id' => NULL,
'profile_meta__image_orientation' => NULL,
'profile_meta__image_style' => NULL,
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_localist/src/MetaFieldsManager.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_localist/src/MetaFieldsManager.php
index 7df6cb1ae2..5b2cfa4dc6 100644
--- a/web/profiles/custom/yalesites_profile/modules/custom/ys_localist/src/MetaFieldsManager.php
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_localist/src/MetaFieldsManager.php
@@ -2,11 +2,11 @@
namespace Drupal\ys_localist;
-use Drupal\calendar_link\Twig\CalendarLinkTwigExtension;
use Drupal\Core\Datetime\DateFormatter;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
+use Drupal\calendar_link\Twig\CalendarLinkTwigExtension;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_localist/src/Plugin/migrate_plus/data_parser/LocalistJson.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_localist/src/Plugin/migrate_plus/data_parser/LocalistJson.php
index c1d7ca7ab7..094fd64fdc 100644
--- a/web/profiles/custom/yalesites_profile/modules/custom/ys_localist/src/Plugin/migrate_plus/data_parser/LocalistJson.php
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_localist/src/Plugin/migrate_plus/data_parser/LocalistJson.php
@@ -49,7 +49,7 @@ protected function getSourceData(string $url): array {
// If json_decode() has returned NULL, it might be that the data isn't
// valid utf8 see http://php.net/manual/en/function.json-decode.php#86997.
if (is_null($source_data)) {
- $utf8response = utf8_encode($response);
+ $utf8response = mb_convert_encoding($response, 'UTF-8', 'ISO-8859-1');
$source_data = json_decode($utf8response, TRUE, 512, JSON_THROW_ON_ERROR);
}
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/README.md b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/README.md
new file mode 100644
index 0000000000..e560f49ab6
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/README.md
@@ -0,0 +1,40 @@
+# YaleSites ServiceNow
+
+This is a YaleSites integration to allow ServiceNow knowledge base articles to import into the platform. The ultimate goal of this is to allow it to then go through our AI pipeline so that you can ask questions that will consider the articles synced.
+
+## Features
+
+Features include:
+
+- Key-based authentication
+- Block-based syncing
+
+## Usage
+
+1. Install the module
+2. Create a new key in the keys module with the ServiceNow endpoint username and password credentials (see pantheon secrets below)
+3. Visit the ServiceNow Settings under Configuration
+4. Enable the module
+5. Select the key you created from the drop down
+6. Enter the endpoint URL you were given by the ServiceNow Team
+ 1. Ensure that the following fields are present in the JSON output:
+ 1. number: The KB article number
+ 2. short_description: The title of the article
+ 3. text: The body of the article
+ 4. workflow_state: The state of the article (Published, etc)
+7. Save
+8. Upon reload, you'll notice a Sync button; click this button to do a manual sync
+
+The service once turned on will attempt to sync hourly.
+
+## Pantheon Secrets
+
+Pantheon secrets can be used with their Drupal module to interact with the keys module. To do this you'd want to add a pantheon secret at the site level first with empty data:
+
+`terminus secrets:site:set --scope web,user servicenow_auth ""`
+
+From there, you can then specify the multidev specific information. The key must ultimately be a JSON payload of the following:
+
+`terminus secrets:site:set . servicenow_auth '{"username":"username","password":"password"}'`
+
+Then simply sync pantheon secrets in the keys configuration to bring in the key; remember that there is a time delay on when that becomes available for sync.
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/migrations/servicenow_knowledge_base_article_block.yml b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/migrations/servicenow_knowledge_base_article_block.yml
new file mode 100644
index 0000000000..20d4459b56
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/migrations/servicenow_knowledge_base_article_block.yml
@@ -0,0 +1,49 @@
+id: servicenow_knowledge_base_article_block
+label: 'ServiceNow knowledge base article blocks'
+source:
+ plugin: url
+ data_fetcher_plugin: http
+ data_parser_plugin: json
+ headers:
+ Accept: 'application/json; charset=utf-8'
+ Content-Type: application/json
+ authentication:
+ plugin: servicenow_auth
+ track_changes: true
+ urls:
+ callback: ys_servicenow_url_endpoint
+ item_selector: result
+ fields:
+ - name: servicenow_number
+ label: 'ServiceNow number'
+ selector: number
+ - name: servicenow_title
+ label: 'ServiceNow title'
+ selector: short_description
+ - name: servicenow_text
+ label: 'ServiceNow text'
+ selector: text
+ ids:
+ servicenow_number:
+ type: string
+process:
+ field_text/value: servicenow_text
+ field_text/format:
+ plugin: default_value
+ default_value: basic_html
+ info: servicenow_title
+ reusable:
+ plugin: default_value
+ default_value: 0
+destination:
+ plugin: 'entity:block_content'
+ default_bundle: text
+ overwrite_properties:
+ - field_text
+ - info
+dependencies:
+ enforced:
+ module:
+ - migrate_plus
+ - migrate_tools
+ - layout_builder
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/migrations/servicenow_knowledge_base_articles.yml b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/migrations/servicenow_knowledge_base_articles.yml
new file mode 100644
index 0000000000..ebc6d2053e
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/migrations/servicenow_knowledge_base_articles.yml
@@ -0,0 +1,59 @@
+id: servicenow_knowledge_base_articles
+label: 'ServiceNow knowledge base articles'
+source:
+ plugin: url
+ data_fetcher_plugin: http
+ data_parser_plugin: json
+ headers:
+ Accept: 'application/json; charset=utf-8'
+ Content-Type: application/json
+ authentication:
+ plugin: servicenow_auth
+ track_changes: true
+ urls:
+ callback: ys_servicenow_url_endpoint
+ item_selector: result
+ fields:
+ - name: servicenow_number
+ label: 'ServiceNow number'
+ selector: number
+ - name: servicenow_title
+ label: 'ServiceNow title'
+ selector: short_description
+ - name: servicenow_text
+ label: 'ServiceNow text'
+ selector: text
+ - name: servicenow_workflow_state
+ label: 'ServiceNow workflow state'
+ selector: workflow_state
+ ids:
+ servicenow_number:
+ type: string
+process:
+ title: servicenow_title
+ block_id:
+ plugin: migration_lookup
+ migration: servicenow_knowledge_base_article_block
+ source: servicenow_number
+ layout_builder__layout:
+ source: servicenow_title
+ plugin: layout_builder_sections
+ moderation_state:
+ plugin: callback
+ callable: ys_servicenow_moderation_state_transformation
+ source: servicenow_workflow_state
+destination:
+ plugin: 'entity:node'
+ default_bundle: page
+ overwrite_properties:
+ - title
+ - moderation_state
+migration_dependencies:
+ required:
+ - servicenow_knowledge_base_article_block
+dependencies:
+ enforced:
+ module:
+ - migrate_plus
+ - migrate_tools
+ - layout_builder
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/BasicAuthWithKeys.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/BasicAuthWithKeys.php
new file mode 100644
index 0000000000..3e3d72d3ad
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/BasicAuthWithKeys.php
@@ -0,0 +1,108 @@
+keyId = $key_id;
+ $this->configuration = $configuration;
+ }
+
+ /**
+ * Get the authentication options.
+ *
+ * @return array
+ * The authentication options.
+ */
+ public function getAuthenticationOptions() {
+ if (!$this->keyId) {
+ throw new \Exception("Key not set");
+ }
+ $key = $this->getKey($this->keyId);
+ $key_object = $this->getKeyValues($key);
+ return [
+ 'auth' => [
+ $key_object->username,
+ $key_object->password,
+ ],
+ ];
+ }
+
+ /**
+ * Given a key ID, return the key object.
+ *
+ * @param string $key_id
+ * The key ID.
+ *
+ * @return \Drupal\key\KeyInterface
+ * The key object
+ */
+ protected function getKey($key_id) {
+ $key = \Drupal::service('key.repository')->getKey($key_id);
+
+ if (!$key) {
+ throw new \Exception("Key '$key_id' not found");
+ }
+
+ return $key;
+ }
+
+ /**
+ * Given a key, return the key values.
+ *
+ * @param \Drupal\key\KeyInterface $key
+ * The key object.
+ *
+ * @return object
+ * The key values.
+ */
+ protected function getKeyValues($key) {
+ $json_key = $key->getKeyValue();
+
+ if (!$json_key) {
+ throw new \Exception("Key has no value");
+ }
+
+ $decoded_object = json_decode($json_key);
+
+ if (!$decoded_object) {
+ throw new \Exception("Key value is not valid JSON. Could you have accidentally used single quotes vs double?");
+ }
+
+ return $decoded_object;
+ }
+
+}
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/Controller/RunMigrations.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/Controller/RunMigrations.php
new file mode 100644
index 0000000000..33bf45b569
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/Controller/RunMigrations.php
@@ -0,0 +1,83 @@
+servicenowConfig = $config_factory->get('ys_servicenow.settings');
+ $this->servicenowManager = $servicenow_manager;
+ $this->messenger = $messenger;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('config.factory'),
+ $container->get('ys_servicenow.manager'),
+ $container->get('messenger'),
+ );
+
+ }
+
+ /**
+ * Runs all ServiceNow migrations.
+ */
+ public function runAllMigrations() {
+ if ($this->servicenowConfig->get('enable_servicenow_sync')) {
+ $this->messenger->addMessage('Running ServiceNow migrations...');
+ $this->servicenowManager->runAllMigrations();
+ $this->messenger->addMessage('ServiceNow migrations complete.');
+ }
+ else {
+ $this->messenger->addMessage('ServiceNow sync is disabled. No sync was performed.');
+ }
+
+ $redirectUrl = Url::fromRoute('ys_servicenow.settings')->toString();
+ $response = new RedirectResponse($redirectUrl);
+ return $response;
+ }
+
+}
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/Form/ServiceNowSettings.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/Form/ServiceNowSettings.php
new file mode 100644
index 0000000000..3a81b8d2df
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/Form/ServiceNowSettings.php
@@ -0,0 +1,170 @@
+entityTypeManager = $entity_type_manager;
+ $this->servicenowManager = $servicenow_manager;
+ $this->currentUserSession = $current_user_session;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('config.factory'),
+ $container->get('entity_type.manager'),
+ $container->get('ys_servicenow.manager'),
+ $container->get('current_user'),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'ys_servicenow_settings';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getEditableConfigNames() {
+ return ['ys_servicenow.settings'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state) {
+ $config = $this->config('ys_servicenow.settings');
+
+ $allowSecretItems = function_exists('ys_core_allow_secret_items') ? ys_core_allow_secret_items($this->currentUserSession) : FALSE;
+
+ if (
+ $config->get('enable_servicenow_sync') &&
+ $config->get('servicenow_endpoint') &&
+ $config->get('servicenow_auth_key')
+ ) {
+ $form['sync_now_button'] = [
+ '#type' => 'markup',
+ '#markup' => 'Sync now',
+ ];
+ }
+
+ $form['enable_servicenow_sync'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Enable ServiceNow sync'),
+ '#description' => $this->t('Once enabled, ServiceNow data will sync knowledge base articles roughly every hour.'),
+ '#default_value' => $config->get('enable_servicenow_sync') ?: FALSE,
+ '#disabled' => !$allowSecretItems,
+ ];
+
+ $form['servicenow_endpoint'] = [
+ '#type' => 'textarea',
+ '#title' => $this->t('ServiceNow Endpoint'),
+ '#default_value' => $config->get('servicenow_endpoint') ?: '',
+ ];
+
+ $form['servicenow_auth_key'] = [
+ '#type' => 'key_select',
+ '#title' => $this->t('ServiceNow Authentication Credentials'),
+ '#default_value' => $config->get('servicenow_auth_key') ?: '',
+ ];
+
+ return parent::buildForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, FormStateInterface $form_state) {
+ $enabled = $form_state->getValue('enable_servicenow_sync');
+ if ($enabled) {
+ $requiredFields = [
+ 'servicenow_auth_key',
+ 'servicenow_endpoint',
+ ];
+
+ foreach ($requiredFields as $field) {
+ if (!$form_state->getValue($field)) {
+ $form_state->setErrorByName(
+ $field,
+ $this->t("%required_field is required.", ['%required_field' => $form_state->getCompleteForm()[$field]['#title']->__toString()])
+ );
+ }
+ }
+
+ }
+
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+
+ $this->configFactory->getEditable('ys_servicenow.settings')
+ // Set the submitted configuration setting.
+ ->set('enable_servicenow_sync', $form_state->getValue('enable_servicenow_sync'))
+ ->set('servicenow_auth_key', $form_state->getValue('servicenow_auth_key'))
+ ->set('servicenow_endpoint', $form_state->getValue('servicenow_endpoint'))
+ ->save();
+
+ parent::submitForm($form, $form_state);
+ }
+
+}
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/MetaFieldsManager.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/MetaFieldsManager.php
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/Plugin/migrate/process/LayoutBuilderSections.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/Plugin/migrate/process/LayoutBuilderSections.php
new file mode 100644
index 0000000000..a361b5b327
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/Plugin/migrate/process/LayoutBuilderSections.php
@@ -0,0 +1,84 @@
+getStorage('block_content')->getQuery();
+ $entityQuery->condition('info', $value)
+ ->accessCheck(FALSE);
+ $ids = $entityQuery->execute();
+
+ $block_content = NULL;
+ if (!empty($ids)) {
+ $block_content = $entityTypeManager->getStorage('block_content')->load(reset($ids));
+ }
+
+ if (is_null($block_content)) {
+ \Drupal::messenger()->addError("Could not load " . $value . ' ???', 'status', TRUE);
+ return NULL;
+ }
+
+ $config = [
+ 'id' => 'inline_block:text',
+ 'label' => $block_content->label(),
+ 'provider' => 'layout_builder',
+ 'label_display' => FALSE,
+ 'view_mode' => 'full',
+ 'block_revision_id' => $block_content->getRevisionId(),
+ 'block_serialized' => serialize($block_content),
+ 'context_mapping' => [],
+ ];
+
+ $components[] = new SectionComponent($generator->generate(), 'content', $config);
+
+ // If you were doing multiple sections, you'd want this to be an array
+ // somehow. @TODO figure out how to do that ;)
+ // PARAMS: $layout_id, $layout_settings, $components.
+ $sections = new Section('layout_onecol', [], $components);
+
+ return $sections;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function multiple() {
+ // Perhaps if multiple() returned TRUE this would help allow
+ // multiple Sections. ;)
+ return FALSE;
+ }
+
+}
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/Plugin/migrate_plus/authentication/ServiceNowAuth.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/Plugin/migrate_plus/authentication/ServiceNowAuth.php
new file mode 100644
index 0000000000..b4b4f1d530
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/Plugin/migrate_plus/authentication/ServiceNowAuth.php
@@ -0,0 +1,31 @@
+get('servicenow_auth_key');
+ $basicAuthWithKeys = new BasicAuthWithKeys($servicenow_config, $servicenow_key_id);
+ return $basicAuthWithKeys->getAuthenticationOptions();
+ }
+
+}
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/ServiceNowManager.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/ServiceNowManager.php
new file mode 100644
index 0000000000..c2fe83ef3a
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/src/ServiceNowManager.php
@@ -0,0 +1,203 @@
+servicenowConfig = $config_factory->get('ys_servicenow.settings');
+ $this->httpClient = $http_client;
+ $this->entityTypeManager = $entity_type_manager;
+ $this->migrationManager = $migration_manager;
+ $this->moduleHandler = $module_handler;
+ $this->time = $time;
+ $this->messenger = $messenger;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('config.factory'),
+ $container->get('http_client'),
+ $container->get('entity_type.manager'),
+ $container->get('plugin.manager.migration'),
+ $container->get('module_handler'),
+ $container->get('date.formatter'),
+ $container->get('messenger'),
+ );
+
+ }
+
+ /**
+ * Runs specific migration.
+ *
+ * @param string $migration
+ * The migration ID.
+ */
+ public function runMigration($migration) {
+ // Loop over the list of the migrations and check if they require
+ // execution.
+ // Prevent non-existent migrations from breaking cron.
+ $migrationInstance = $this->migrationManager->createInstance($migration);
+ if ($migrationInstance) {
+ // Check if the migration status is IDLE, if not, make it so.
+ $status = $migrationInstance->getStatus();
+ if ($status !== MigrationInterface::STATUS_IDLE) {
+ $migrationInstance->setStatus(MigrationInterface::STATUS_IDLE);
+ }
+
+ /*
+ * @todo Possibly implement the following flags, if needed.
+ * Runs migration with the --update flag.
+ * $migration_update = $migration->getIdMap();
+ * $migration_update->prepareUpdate();
+ * Runs migration with the --sync flag.
+ * The problem here is if editor adds layout builder, this will wipe those
+ * changes out before recreating. So, this not be a good idea.
+ * $migrationInstance->set('syncSource', TRUE);
+ */
+
+ // We choose to use syncSource since we want to recreate the block for
+ // update.
+ /*if ($migration === 'servicenow_knowledge_base_article_block') {*/
+ /* $migrationInstance->set('syncSource', TRUE);*/
+ /* $migration_update = $migrationInstance->getIdMap();*/
+ /* $migration_update->prepareUpdate();*/
+ /*}*/
+ $message = new MigrateMessage();
+ $executable = new MigrateExecutable($migrationInstance, $message);
+ $executable->import();
+
+ /* If using migrate_plus module, update the migrate_last_imported value
+ * for the migration.
+ */
+
+ if ($this->moduleHandler->moduleExists('migrate_plus')) {
+ $migrate_last_imported_store = $this->keyValue('migrate_last_imported');
+ $migrate_last_imported_store->set($migrationInstance->id(), round($this->time->getCurrentMicroTime() * 1000));
+ }
+ }
+ }
+
+ /**
+ * Gets the migration status such as number of items imported.
+ *
+ * @return int
+ * For now, just the number of items imported.
+ */
+ public function getMigrationStatus($migration_id) {
+ $migration = $this->migrationManager->createInstance($migration_id);
+ $map = $migration->getIdMap();
+ $imported = $map->importedCount();
+ return $imported;
+ }
+
+ /**
+ * Runs all Localist migrations.
+ *
+ * @return array
+ * Array of status of all migrations run.
+ */
+ public function runAllMigrations() {
+ foreach (self::SERVICENOW_MIGRATIONS as $migration) {
+ $this->runMigration($migration);
+ $messageData[$migration] = [
+ 'imported' => $this->getMigrationStatus($migration),
+ ];
+ }
+
+ return $messageData;
+ }
+
+}
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.info.yml b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.info.yml
new file mode 100644
index 0000000000..7a3d69928d
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.info.yml
@@ -0,0 +1,10 @@
+name: 'YS ServiceNow'
+type: module
+description: 'Provides integration with the ServiceNow knowledge base platform'
+package: YaleSites
+core_version_requirement: ^10
+dependencies:
+ - drupal:migrate
+ - migrate_plus:migrate_plus
+ - migrate_tools:migrate_tools
+ - drupal:key
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.links.menu.yml b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.links.menu.yml
new file mode 100644
index 0000000000..c97fc72830
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.links.menu.yml
@@ -0,0 +1,6 @@
+ys_servicenow.settings:
+ title: 'ServiceNow settings'
+ description: 'Configure ServiceNow sync settings'
+ route_name: ys_servicenow.settings
+ parent: system.admin_config_services
+ weight: 0
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.module b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.module
new file mode 100644
index 0000000000..5609e3bc6a
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.module
@@ -0,0 +1,56 @@
+get('enable_servicenow_sync')) {
+
+ $state = \Drupal::state();
+
+ $next_execution_time = $state->get("servicenow_migrations", 0);
+ $current_time = \Drupal::time()->getRequestTime();
+ if ($current_time > $next_execution_time) {
+ $state->set("servicenow_migrations", $current_time + 3600);
+ $servicenowManager = \Drupal::service('ys_servicenow.manager');
+ $servicenowManager->runAllMigrations();
+ }
+ }
+}
+
+/**
+ * Provide the URL endpoint for migrations in servicenow from config.
+ */
+function ys_servicenow_url_endpoint(MigrationInterface $migration): array {
+ // Get the servicenow configuration.
+ $servicenow_config = \Drupal::config('ys_servicenow.settings');
+ // Get the endpoint type from the migration.
+ $endpoint = $servicenow_config->get('servicenow_endpoint');
+
+ if ($endpoint) {
+ return [$endpoint];
+ }
+
+ return [];
+}
+
+/**
+ * Any state other than "Published" is unpublished.
+ */
+function ys_servicenow_moderation_state_transformation($value) {
+ $unpublished_state = 'archive';
+
+ $published_states = [
+ 'Published' => 'published',
+ ];
+
+ return $published_states[$value] ?? $unpublished_state;
+}
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.permissions.yml b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.permissions.yml
new file mode 100644
index 0000000000..f4935e13c7
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.permissions.yml
@@ -0,0 +1,4 @@
+# Permission to manage YaleSites ServiceNow.
+yalesites manage servicenow:
+ title: 'Manage YaleSites ServiceNow'
+ description: 'Set and change YaleSites ServiceNow settings.'
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.routing.yml b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.routing.yml
new file mode 100644
index 0000000000..36c48a1363
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.routing.yml
@@ -0,0 +1,14 @@
+ys_servicenow.settings:
+ path: '/admin/yalesites/servicenow'
+ defaults:
+ _form: '\Drupal\ys_servicenow\Form\ServiceNowSettings'
+ _title: 'ServiceNow Settings'
+ requirements:
+ _permission: 'yalesites manage servicenow'
+ys_servicenow.run_migrations:
+ path: '/admin/yalesites/servicenow/sync'
+ defaults:
+ _controller: '\Drupal\ys_servicenow\Controller\RunMigrations::runAllMigrations'
+ _title: 'Import ServiceNow events now'
+ requirements:
+ _permission: 'yalesites manage servicenow'
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.services.yml b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.services.yml
new file mode 100644
index 0000000000..a14c27b508
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_servicenow/ys_servicenow.services.yml
@@ -0,0 +1,5 @@
+services:
+ # Service for interacting with ServiceNow sync and configuration.
+ ys_servicenow.manager:
+ class: Drupal\ys_servicenow\ServiceNowManager
+ arguments: ['@config.factory', '@http_client', '@entity_type.manager', '@plugin.manager.migration', '@module_handler', '@datetime.time', '@messenger']
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_themes/ys_themes.module b/web/profiles/custom/yalesites_profile/modules/custom/ys_themes/ys_themes.module
index d675ad1140..af8277d0fc 100644
--- a/web/profiles/custom/yalesites_profile/modules/custom/ys_themes/ys_themes.module
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_themes/ys_themes.module
@@ -17,15 +17,13 @@ use Drupal\field\Entity\FieldStorageConfig;
* The field definition.
* @param \Drupal\Core\Entity\ContentEntityInterface|null $entity
* The entity being created, if applicable.
- * @param bool $cacheable
- * Boolean indicating if the results are cache-able.
*
* @return array
* An array of possible key and value options.
*
* @see options_allowed_values()
*/
-function ys_themes_allowed_values_function(FieldStorageConfig $definition, ContentEntityInterface $entity = NULL, $cacheable) {
+function ys_themes_allowed_values_function(FieldStorageConfig $definition, ?ContentEntityInterface $entity = NULL) {
$options = [];
$config = \Drupal::config('ys_themes.component_overrides');
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/assets/js/views-basic.js b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/assets/js/views-basic.js
index c6a8580b13..8dbcc3a121 100644
--- a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/assets/js/views-basic.js
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/assets/js/views-basic.js
@@ -75,6 +75,28 @@
displayElement.addEventListener('change', updateLimitElement);
updateLimitElement();
}
+
+ // Unified selectors to handle both cases
+ const entityTypesSelector = 'input[name="settings[block_form][group_user_selection][entity_and_view_mode][entity_types]"], input[name="block_form[group_user_selection][entity_and_view_mode][entity_types]"]';
+ const viewModeSelector = 'input[name="settings[block_form][group_user_selection][entity_and_view_mode][view_mode]"], input[name="block_form[group_user_selection][entity_and_view_mode][view_mode]"]';
+ const eventTimePeriod = document.querySelector('#edit-event-time-period');
+
+ const entityTypes = document.querySelectorAll(entityTypesSelector);
+ const viewModes = document.querySelectorAll(viewModeSelector);
+
+ // Function to handle visibility based on conditions
+ function updateVisibility() {
+ const entityType = Array.from(entityTypes).find(input => input.checked)?.value;
+ const viewMode = Array.from(viewModes).find(input => input.checked)?.value;
+
+ if (eventTimePeriod) {
+ eventTimePeriod.style.display = (entityType === 'event' && viewMode === 'calendar') ? 'none' : '';
+ }
+ }
+
+ entityTypes.forEach(input => input.addEventListener('change', updateVisibility));
+ viewModes.forEach(input => input.addEventListener('change', updateVisibility));
+ updateVisibility();
},
};
})(Drupal);
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Controller/EventsCalendarController.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Controller/EventsCalendarController.php
new file mode 100644
index 0000000000..e43e8968db
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Controller/EventsCalendarController.php
@@ -0,0 +1,66 @@
+get('ys_views_basic.events_calendar'),
+ );
+ }
+
+ /**
+ * Builds the response.
+ */
+ public function __invoke(Request $request): AjaxResponse {
+ $response = new AjaxResponse();
+
+ if (!$request->request->has('calendar_id') || !$request->request->has('month') || !$request->request->has('year')) {
+ return $response;
+ }
+
+ // Calendar wrapper that needs to be updated.
+ $calendar_id = $request->request->get('calendar_id');
+ $month = $request->request->get('month');
+ $year = $request->request->get('year');
+
+ $events_calendar = $this->eventsCalendar
+ ->getCalendar($month, $year);
+
+ $calendar = [
+ '#theme' => 'views_basic_events_calendar',
+ '#month_data' => $events_calendar,
+ '#cache' => [
+ 'tags' => ['node_list:event'],
+ ],
+ ];
+
+ $response->addCommand(new ReplaceCommand($calendar_id, $calendar));
+
+ return $response;
+ }
+
+}
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Plugin/Field/FieldFormatter/ViewsBasicDefaultFormatter.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Plugin/Field/FieldFormatter/ViewsBasicDefaultFormatter.php
index 3428c952e7..56ffeb9444 100644
--- a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Plugin/Field/FieldFormatter/ViewsBasicDefaultFormatter.php
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Plugin/Field/FieldFormatter/ViewsBasicDefaultFormatter.php
@@ -6,7 +6,7 @@
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\Core\Render\Renderer;
+use Drupal\ys_views_basic\Service\EventsCalendarInterface;
use Drupal\ys_views_basic\ViewsBasicManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -28,14 +28,14 @@ class ViewsBasicDefaultFormatter extends FormatterBase implements ContainerFacto
*
* @var \Drupal\ys_views_basic\ViewsBasicManager
*/
- protected $viewsBasicManager;
+ protected ViewsBasicManager $viewsBasicManager;
/**
- * The renderer service.
+ * The Events Calendar service.
*
- * @var \Drupal\Core\Render\Renderer
+ * @var \Drupal\ys_views_basic\Service\EventsCalendarInterface
*/
- protected $rendererService;
+ protected EventsCalendarInterface $eventsCalendar;
/**
* Constructs an views basic default formatter object.
@@ -54,10 +54,10 @@ class ViewsBasicDefaultFormatter extends FormatterBase implements ContainerFacto
* The view mode.
* @param array $third_party_settings
* Any third party settings.
- * @param \Drupal\ys_views_basic\Plugin\ViewsBasicManager $viewsBasicManager
+ * @param \Drupal\ys_views_basic\ViewsBasicManager $viewsBasicManager
* The views basic manager service.
- * @param \Drupal\Core\Render\Renderer $renderer_service
- * Drupal Core renderer service.
+ * @param \Drupal\ys_views_basic\Service\EventsCalendarInterface $eventsCalendar
+ * The Events Calendar service.
*/
public function __construct(
string $plugin_id,
@@ -68,7 +68,7 @@ public function __construct(
string $view_mode,
array $third_party_settings,
ViewsBasicManager $viewsBasicManager,
- Renderer $renderer_service,
+ EventsCalendarInterface $eventsCalendar,
) {
parent::__construct(
$plugin_id,
@@ -78,20 +78,15 @@ public function __construct(
$label,
$view_mode,
$third_party_settings,
- $this->rendererService = $renderer_service,
);
$this->viewsBasicManager = $viewsBasicManager;
+ $this->eventsCalendar = $eventsCalendar;
}
/**
* {@inheritdoc}
*/
- public static function create(
- ContainerInterface $container,
- array $configuration,
- $plugin_id,
- $plugin_definition,
- ) {
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$plugin_id,
$plugin_definition,
@@ -101,33 +96,57 @@ public static function create(
$configuration['view_mode'],
$configuration['third_party_settings'],
$container->get('ys_views_basic.views_basic_manager'),
- $container->get('renderer'),
+ $container->get('ys_views_basic.events_calendar')
);
}
/**
* Define how the field type is showed.
*/
- public function viewElements(FieldItemListInterface $items, $langcode) {
+ public function viewElements(FieldItemListInterface $items, $langcode): array {
$elements = [];
+
foreach ($items as $delta => $item) {
+ // Get decoded parameters.
+ $paramsDecoded = json_decode($item->getValue()['params'], TRUE);
+
+ if ($paramsDecoded['filters']['types'][0] === 'event' && $paramsDecoded['view_mode'] === 'calendar') {
+ // Calculate the remaining time until the end of the current month.
+ $now = new \DateTime();
+ $end_of_month = new \DateTime('last day of this month 23:59:59');
+ $remaining_time_in_seconds = $end_of_month->getTimestamp() - $now->getTimestamp();
- $view = $this->viewsBasicManager->getView('rendered', $item->getValue()['params']);
+ $events_calendar = $this->eventsCalendar
+ ->getCalendar(date('m'), date('Y'));
- $elements[$delta] = [
- '#theme' => 'views_basic_formatter_default',
- '#view' => $view,
- // Extract exposed filters from the view and place them separately.
- // This is necessary because we are conditionally displaying
- // specific exposed filters based on field configuration.
- // By placing the exposed filters outside of the view rendering
- // context, we ensure that they do not get re-rendered
- // when AJAX operations are performed on the view,
- // allowing for better control over which filters are displayed
- // and maintaining the expected user interface behavior.
- '#exposed' => $view['#view']->exposed_widgets,
- ];
+ $elements[$delta] = [
+ '#theme' => 'views_basic_events_calendar',
+ '#month_data' => $events_calendar,
+ '#cache' => [
+ 'tags' => ['node_list:event'],
+ // Set max-age to the remaining time until the end of the month.
+ 'max-age' => $remaining_time_in_seconds,
+ 'contexts' => ['timezone'],
+ ],
+ ];
+ }
+ else {
+ $view = $this->viewsBasicManager->getView('rendered', $item->getValue()['params']);
+ $elements[$delta] = [
+ '#theme' => 'views_basic_formatter_default',
+ '#view' => $view,
+ // Extract exposed filters from the view and place them separately.
+ // This is necessary because we are conditionally displaying
+ // specific exposed filters based on field configuration.
+ // By placing the exposed filters outside of the view rendering
+ // context, we ensure that they do not get re-rendered
+ // when AJAX operations are performed on the view,
+ // allowing for better control over which filters are displayed
+ // and maintaining the expected user interface behavior.
+ '#exposed' => $view['#view']->exposed_widgets,
+ ];
+ }
}
return $elements;
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Plugin/Field/FieldWidget/ViewsBasicDefaultWidget.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Plugin/Field/FieldWidget/ViewsBasicDefaultWidget.php
index 468fbe71df..7680c5f2dc 100644
--- a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Plugin/Field/FieldWidget/ViewsBasicDefaultWidget.php
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Plugin/Field/FieldWidget/ViewsBasicDefaultWidget.php
@@ -219,6 +219,11 @@ public function formElement(
'#suffix' => '',
];
+ // Define the state for when the view mode is 'calendar' once.
+ $calendarViewInvisibleState = [
+ $formSelectors['view_mode_input_selector'] => ['value' => 'calendar'],
+ ];
+
$fieldOptionValue = ($items[$delta]->params) ? $this->viewsBasicManager->getDefaultParamValue('field_options', $items[$delta]->params) : [];
$fieldOptionDefaultValue = $fieldOptionValue ?? ['show_thumbnail' => 'show_thumbnail'];
$isNewForm = str_contains($formState->getCompleteForm()['#id'], 'layout-builder-add-block');
@@ -234,6 +239,7 @@ public function formElement(
'#title' => $this->t('Field Display Options'),
'#tree' => TRUE,
'#default_value' => ($isNewForm && empty($fieldOptionValue)) ? ['show_thumbnail'] : $fieldOptionDefaultValue,
+ '#states' => ['invisible' => $calendarViewInvisibleState],
'show_thumbnail' => [
'#states' => [
'visible' => [
@@ -256,14 +262,9 @@ public function formElement(
'#title' => $this->t('Exposed Filter Options'),
'#tree' => TRUE,
'#default_value' => ($items[$delta]->params) ? $this->viewsBasicManager->getDefaultParamValue('exposed_filter_options', $items[$delta]->params) : [],
+ '#states' => ['invisible' => $calendarViewInvisibleState],
'show_year_filter' => [
- '#states' => [
- 'visible' => [
- $formSelectors['entity_types_ajax'] => [
- 'value' => 'post',
- ],
- ],
- ],
+ '#states' => ['visible' => [$formSelectors['entity_types_ajax'] => ['value' => 'post']]],
],
];
@@ -273,9 +274,8 @@ public function formElement(
'#description' => $this->t("Enter a custom label for the Category Filter. This label will be displayed to users as the filter's name. If left blank, the default label Category will be used."),
'#default_value' => ($items[$delta]->params) ? $this->viewsBasicManager->getDefaultParamValue('category_filter_label', $items[$delta]->params) : NULL,
'#states' => [
- 'visible' => [
- $formSelectors['show_category_filter_selector'] => ['checked' => TRUE],
- ],
+ 'visible' => [$formSelectors['show_category_filter_selector'] => ['checked' => TRUE]],
+ 'invisible' => $calendarViewInvisibleState,
],
];
@@ -291,9 +291,8 @@ public function formElement(
'#prefix' => '',
'#suffix' => '
',
'#states' => [
- 'visible' => [
- $formSelectors['show_category_filter_selector'] => ['checked' => TRUE],
- ],
+ 'visible' => [$formSelectors['show_category_filter_selector'] => ['checked' => TRUE]],
+ 'invisible' => $calendarViewInvisibleState,
],
];
@@ -306,6 +305,7 @@ public function formElement(
'#tags' => TRUE,
'#target_type' => 'taxonomy_term',
'#default_value' => ($items[$delta]->params) ? $this->viewsBasicManager->getDefaultParamValue('terms_include', $items[$delta]->params) : [],
+ '#states' => ['invisible' => $calendarViewInvisibleState],
];
$form['group_user_selection']['filter_and_sort']['terms_exclude'] = [
@@ -317,6 +317,7 @@ public function formElement(
'#tags' => TRUE,
'#target_type' => 'taxonomy_term',
'#default_value' => ($items[$delta]->params) ? $this->viewsBasicManager->getDefaultParamValue('terms_exclude', $items[$delta]->params) : [],
+ '#states' => ['invisible' => $calendarViewInvisibleState],
];
$form['group_user_selection']['filter_and_sort']['term_operator'] = [
@@ -333,7 +334,7 @@ public function formElement(
'term-operator-item',
],
],
-
+ '#states' => ['invisible' => $calendarViewInvisibleState],
];
// Gets the view mode options based on Ajax callbacks or initial load.
@@ -349,6 +350,7 @@ public function formElement(
'#validated' => 'true',
'#prefix' => '',
'#suffix' => '
',
+ '#states' => ['invisible' => $calendarViewInvisibleState],
];
$form['group_user_selection']['entity_specific']['event_time_period'] = [
@@ -360,13 +362,8 @@ public function formElement(
'all' => $this->t('All Events') . '',
],
'#default_value' => ($items[$delta]->params) ? $this->viewsBasicManager->getDefaultParamValue('event_time_period', $items[$delta]->params) : 'future',
- '#states' => [
- 'visible' => [
- $formSelectors['entity_types_ajax'] => [
- 'value' => 'event',
- ],
- ],
- ],
+ '#prefix' => '',
+ '#suffix' => '
',
];
$displayValue = ($items[$delta]->params) ? $this->viewsBasicManager->getDefaultParamValue('display', $items[$delta]->params) : 'all';
@@ -381,6 +378,7 @@ public function formElement(
'limit' => $this->t('Limit to'),
'pager' => $this->t('Pagination after'),
],
+ '#states' => ['invisible' => $calendarViewInvisibleState],
];
$limitTitle = $this->t('Items');
@@ -401,6 +399,7 @@ public function formElement(
'#required' => TRUE,
'#prefix' => '',
'#suffix' => '
',
+ '#states' => ['invisible' => $calendarViewInvisibleState],
];
$form['group_user_selection']['options']['offset'] = [
@@ -412,6 +411,7 @@ public function formElement(
'#attributes' => [
'placeholder' => 0,
],
+ '#states' => ['invisible' => $calendarViewInvisibleState],
];
$element['group_params']['params'] = [
@@ -424,6 +424,7 @@ public function formElement(
'views-basic--params',
],
],
+ '#states' => ['invisible' => $calendarViewInvisibleState],
];
$form['#attached']['library'][] = 'ys_views_basic/ys_views_basic';
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Plugin/views/filter/PostYearFilter.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Plugin/views/filter/PostYearFilter.php
index bb9dc2b371..5c9b2f9abc 100644
--- a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Plugin/views/filter/PostYearFilter.php
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Plugin/views/filter/PostYearFilter.php
@@ -56,7 +56,7 @@ public static function create(ContainerInterface $container, array $configuratio
/**
* {@inheritdoc}
*/
- public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
+ public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$options = NULL) {
parent::init($view, $display, $options);
$this->valueTitle = $this->t('Post Year Filter');
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Service/EventsCalendar.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Service/EventsCalendar.php
new file mode 100644
index 0000000000..9f54e55849
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Service/EventsCalendar.php
@@ -0,0 +1,280 @@
+entityTypeManager = $entity_type_manager;
+ $this->aliasManager = $alias_manager;
+ $this->nodeStorage = $entity_type_manager->getStorage('node');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container): static {
+ return new static(
+ $container->get('entity_type.manager'),
+ $container->get('path_alias.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCalendar(string $month, string $year): array {
+ // Create a date object for the first day of the given month and year.
+ $firstDayOfMonth = new DrupalDateTime("$year-$month-01");
+ $totalDaysInMonth = (int) $firstDayOfMonth->format('t');
+ $startDayOfWeek = (int) $firstDayOfMonth->format('w');
+ $lastDayOfMonth = new DrupalDateTime("$year-$month-$totalDaysInMonth");
+ $endDayOfWeek = (int) $lastDayOfMonth->format('w');
+
+ $paddingStart = $startDayOfWeek;
+ $paddingEnd = 6 - $endDayOfWeek;
+ $totalCells = $totalDaysInMonth + $paddingStart + $paddingEnd;
+ $totalRows = (int) ceil($totalCells / 7);
+ $calendarRows = [];
+
+ // Calculate the previous month and year.
+ $previousMonthDate = clone $firstDayOfMonth;
+ $previousMonthDate->modify('-1 month');
+ $daysInPreviousMonth = (int) $previousMonthDate->format('t');
+ $previousMonth = $previousMonthDate->format('m');
+ $previousYear = $previousMonthDate->format('Y');
+
+ // Calculate the next month and year.
+ $nextMonthDate = clone $lastDayOfMonth;
+ $nextMonthDate->modify('+1 month');
+ $nextMonth = $nextMonthDate->format('m');
+ $nextYear = $nextMonthDate->format('Y');
+
+ // Load all events for the given month and year.
+ $monthlyEvents = $this->loadMonthlyEvents($month, $year);
+
+ $currentDay = 1;
+
+ for ($row = 0; $row < $totalRows; $row++) {
+ $calendarRows[$row] = [];
+ for ($cell = 0; $cell < 7; $cell++) {
+ if ($row == 0 && $cell < $paddingStart) {
+ // Fill in days from the previous month.
+ $day = $daysInPreviousMonth - ($paddingStart - $cell - 1);
+ $calendarRows[$row][] = $this->createCalendarCell($day, $previousMonth, $previousYear, $monthlyEvents);
+ }
+ elseif ($row == $totalRows - 1 && $cell > $endDayOfWeek) {
+ // Fill in days from the next month.
+ $day = $cell - $endDayOfWeek;
+ $calendarRows[$row][] = $this->createCalendarCell($day, $nextMonth, $nextYear, $monthlyEvents);
+ }
+ else {
+ // Normal date cell within the current month.
+ $calendarRows[$row][] = $this->createCalendarCell($currentDay, $month, $year, $monthlyEvents);
+ $currentDay++;
+ }
+ }
+ }
+
+ return $calendarRows;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createCalendarCell(int $day, string $month, string $year, array $events): array {
+ return [
+ 'date' => [
+ 'day' => str_pad($day, 2, '0', STR_PAD_LEFT),
+ 'month' => $month,
+ 'year' => $year,
+ ],
+ 'events' => $this->getEvents($day, $month, $year, $events),
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getEvents(int $day, string $month, string $year, array $events): array {
+ $startDate = new DrupalDateTime("$year-$month-$day 00:00:00");
+ $endDate = new DrupalDateTime("$year-$month-$day 23:59:59");
+
+ $startTimestamp = $startDate->getTimestamp();
+ $endTimestamp = $endDate->getTimestamp();
+
+ $events_data = [];
+ foreach ($events as $event) {
+ if (!$event->get('field_event_date')->isEmpty()) {
+ // Handle recurrence rules if present.
+ if ($event->field_event_date?->rrule) {
+
+ /** @var \Drupal\smart_date_recur\Entity\SmartDateRule $rule */
+ $rule = $this->entityTypeManager->getStorage('smart_date_rule')
+ ->load($event->field_event_date->rrule);
+
+ if ($rule instanceof SmartDateRule) {
+ // Iterate over the stored instances to find occurrences for the
+ // current day.
+ foreach ($rule->getStoredInstances() as $instance) {
+ $instanceStartTimestamp = $instance['value'];
+ $instanceEndTimestamp = $instance['end_value'];
+
+ // Check if the instance overlaps with the current day.
+ if ($instanceStartTimestamp <= $endTimestamp && $instanceEndTimestamp >= $startTimestamp) {
+ $time = $this->isAllDay($instanceStartTimestamp, $instanceEndTimestamp)
+ ? $this->t('All Day')
+ : $this->t('@start to @end', [
+ '@start' => date('g:iA', $instanceStartTimestamp),
+ '@end' => date('g:iA', $instanceEndTimestamp),
+ ]);
+
+ $events_data[] = $this->createEventArray($event, $time, $instanceStartTimestamp);
+ }
+ }
+ }
+ }
+ else {
+ // Iterate through the nodes to extract event details.
+ foreach ($event->get('field_event_date')->getValue() as $eventDate) {
+ $eventStartTimestamp = $eventDate['value'];
+ $eventEndTimestamp = $eventDate['end_value'];
+
+ // Check if the event overlaps with the current day.
+ if ($eventStartTimestamp <= $endTimestamp && $eventEndTimestamp >= $startTimestamp) {
+ if (date('Y-m-d', $eventStartTimestamp) !== date('Y-m-d', $eventEndTimestamp)) {
+ $time = $this->t('Multi-day Event');
+ }
+ else {
+ $time = $this->isAllDay($eventStartTimestamp, $eventEndTimestamp)
+ ? $this->t('All Day')
+ : $this->t('@start to @end', [
+ '@start' => date('g:iA', $eventStartTimestamp),
+ '@end' => date('g:iA', $eventEndTimestamp),
+ ]);
+ }
+
+ // Add event to the list if it overlaps with the current day.
+ $events_data[] = $this->createEventArray($event, $time, $eventStartTimestamp);
+ }
+ }
+ }
+ }
+ }
+
+ // Sort events by the start timestamp.
+ usort($events_data, function ($a, $b) {
+ return $a['timestamp'] <=> $b['timestamp'];
+ });
+
+ return $events_data;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createEventArray($node, string $time, int $timestamp): array {
+ // Extract the event's categories.
+ $categories = implode(' | ', array_map(function ($term) {
+ return $term->label();
+ }, $node->get('field_category')->referencedEntities()));
+
+ // Extract the event's tags.
+ $tags = array_map(fn($term) => $term->label(), $node->get('field_tags')->referencedEntities());
+
+ // Build and return the event array.
+ return [
+ 'category' => $categories,
+ 'title' => $node->label(),
+ 'url' => $this->aliasManager->getAliasByPath('/node/' . $node->id()),
+ 'time' => $time,
+ 'type' => $tags,
+ 'timestamp' => $timestamp,
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isAllDay(int $start_ts, int $end_ts, ?string $timezone = NULL): bool {
+ if ($timezone) {
+ $default_tz = date_default_timezone_get();
+ date_default_timezone_set($timezone);
+ }
+
+ $temp_start = date('H:i', $start_ts);
+ $temp_end = date('H:i', $end_ts);
+
+ if ($timezone) {
+ date_default_timezone_set($default_tz);
+ }
+
+ return $temp_start == '00:00' && $temp_end == '23:59';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadMonthlyEvents(string $month, string $year): array {
+ $startDate = new DrupalDateTime("$year-$month-01 00:00:00");
+ $endDate = clone $startDate;
+ $endDate->modify('last day of this month 23:59:59');
+
+ // Query to fetch event nodes that overlap with the given month.
+ $query = $this->nodeStorage->getQuery()
+ ->accessCheck()
+ ->condition('type', 'event')
+ ->condition('status', 1)
+ ->condition('field_event_date.value', $endDate->getTimestamp(), '<=')
+ ->condition('field_event_date.end_value', $startDate->getTimestamp(), '>=');
+
+ $nids = $query->execute();
+ return $this->nodeStorage->loadMultiple($nids);
+ }
+
+}
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Service/EventsCalendarInterface.php b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Service/EventsCalendarInterface.php
new file mode 100644
index 0000000000..4c46404786
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/src/Service/EventsCalendarInterface.php
@@ -0,0 +1,102 @@
+ '/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/assets/icons/display-type-condensed.svg',
'img_alt' => 'Icon showing 3 generic list items one on top of the other with no images on the items.',
],
+ 'calendar' => [
+ 'label' => 'Calendar',
+ 'img' => '/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/assets/icons/content-type-event.svg',
+ 'img_alt' => 'Calendar',
+ ],
],
'sort_by' => [
'field_event_date:DESC' => 'Event Date - newer first',
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/templates/views-basic-events-calendar.html.twig b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/templates/views-basic-events-calendar.html.twig
new file mode 100644
index 0000000000..7d089c2a19
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/templates/views-basic-events-calendar.html.twig
@@ -0,0 +1,5 @@
+{{ attach_library('atomic/calendar') }}
+
+{% include "@organisms/calendar/yds-calendar.twig" with {
+ month:month_data
+} %}
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/ys_views_basic.module b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/ys_views_basic.module
index a58c13d946..70ad89fb59 100644
--- a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/ys_views_basic.module
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/ys_views_basic.module
@@ -32,6 +32,11 @@ function ys_views_basic_theme($existing, $type, $theme, $path): array {
'contentType' => NULL,
],
],
+ 'views_basic_events_calendar' => [
+ 'variables' => [
+ 'month_data' => [],
+ ],
+ ],
];
}
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/ys_views_basic.routing.yml b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/ys_views_basic.routing.yml
new file mode 100644
index 0000000000..818b81880f
--- /dev/null
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/ys_views_basic.routing.yml
@@ -0,0 +1,7 @@
+ys_views_basic.events_calendar:
+ path: '/events-calendar'
+ defaults:
+ _title: 'Events Calendar'
+ _controller: '\Drupal\ys_views_basic\Controller\EventsCalendarController'
+ requirements:
+ _permission: 'access content'
diff --git a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/ys_views_basic.services.yml b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/ys_views_basic.services.yml
index 4039bf8a2f..0a37a9f47d 100644
--- a/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/ys_views_basic.services.yml
+++ b/web/profiles/custom/yalesites_profile/modules/custom/ys_views_basic/ys_views_basic.services.yml
@@ -3,3 +3,7 @@ services:
ys_views_basic.views_basic_manager:
class: Drupal\ys_views_basic\ViewsBasicManager
arguments: ['@entity_type.manager', '@entity_display.repository']
+
+ ys_views_basic.events_calendar:
+ class: Drupal\ys_views_basic\Service\EventsCalendar
+ arguments: ['@entity_type.manager', '@path_alias.manager']