diff --git a/.docksal/commands/phpunit b/.docksal/commands/phpunit
index a2e2e90f..2513a903 100644
--- a/.docksal/commands/phpunit
+++ b/.docksal/commands/phpunit
@@ -11,7 +11,6 @@
# $DOCROOT - name of the docroot folder
# $DOCKER_RUNNING - (string) "true" or "false"
-args="$@"
path="/var/www"
phpunit_xml_path="$PROJECT_ROOT/phpunit.xml"
@@ -27,4 +26,21 @@ if [[ ! -f "$phpunit_path" ]]; then
exit 1
fi
-fin exec "php $path/${phpunit_path} -c $path ${args} $path/$DOCROOT/modules/custom"
\ No newline at end of file
+
+COVERAGE=false
+args=""
+while [ "$1" != "" ]; do
+ case $1 in
+ --open-coverage ) COVERAGE=true
+ ;;
+ *) args="$args $1"
+ ;;
+ esac
+ shift
+done
+
+fin exec "XDEBUG_MODE=coverage $path/${phpunit_path} -c $path ${args} $path/$DOCROOT/modules/custom --coverage-html $path/$DOCROOT/sites/simpletest/test-coverage"
+
+if $COVERAGE; then
+ fin utils/open-link "http://ghi-site.docksal.site/sites/simpletest/test-coverage/dashboard.html"
+fi
\ No newline at end of file
diff --git a/.docksal/commands/post-deploy b/.docksal/commands/post-deploy
index d7e0de7e..cbcb433f 100644
--- a/.docksal/commands/post-deploy
+++ b/.docksal/commands/post-deploy
@@ -65,6 +65,9 @@ if $IMPORT_BACKUP; then
fi
fi
+# Make sure that the code base is up to date.
+composer install
+
echo "Set maintenance mode to on"
drush sset system.maintenance_mode 1 -y
diff --git a/.docksal/docksal.yml b/.docksal/docksal.yml
index 328ab3f1..dc94ed86 100644
--- a/.docksal/docksal.yml
+++ b/.docksal/docksal.yml
@@ -9,6 +9,8 @@ services:
- HID_CLIENT_SECRET
- GRAPHQL_KEY
- COMPOSER_MEMORY_LIMIT=-1
+ - XDEBUG_ENABLED=1
+ - XDEBUG_MODE=off
browser:
hostname: browser
diff --git a/composer.json b/composer.json
index b5eccad8..a7ace90f 100644
--- a/composer.json
+++ b/composer.json
@@ -58,13 +58,14 @@
"drupal/core-recommended": "^10",
"drupal/csp": "^1.16",
"drupal/ctools": "^4.0",
- "drupal/custom_field": "^2.0@RC",
+ "drupal/custom_field": "^3",
"drupal/diff": "^1.1",
"drupal/double_field": "^4.0",
"drupal/editor_advanced_link": "^2.0",
"drupal/entity_browser": "^2.9",
"drupal/entity_browser_table": "^1.4",
"drupal/entity_usage": "^2.0@beta",
+ "drupal/field_config_cardinality": "^3.0",
"drupal/field_group": "^3.4",
"drupal/gin": "^3.0@RC",
"drupal/google_tag": "^1.4",
diff --git a/composer.lock b/composer.lock
index bdefa7d0..04746783 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "12569a8c6834a7ab5add06011793900c",
+ "content-hash": "26e93d38d1c5ff6c15abae3101f2879d",
"packages": [
{
"name": "asm89/stack-cors",
@@ -3512,30 +3512,30 @@
},
{
"name": "drupal/custom_field",
- "version": "2.0.0-rc6",
+ "version": "3.0.0-rc2",
"source": {
"type": "git",
"url": "https://git.drupalcode.org/project/custom_field.git",
- "reference": "2.0.0-rc6"
+ "reference": "3.0.0-rc2"
},
"dist": {
"type": "zip",
- "url": "https://ftp.drupal.org/files/projects/custom_field-2.0.0-rc6.zip",
- "reference": "2.0.0-rc6",
- "shasum": "53db8135daee8c9273006de3ca208ba9de4a74f0"
+ "url": "https://ftp.drupal.org/files/projects/custom_field-3.0.0-rc2.zip",
+ "reference": "3.0.0-rc2",
+ "shasum": "0a7e27a71a20a9483bb92845625f0827bce9f354"
},
"require": {
- "drupal/core": "^9.4 || ^10.1"
+ "drupal/core": "^10.2"
},
"require-dev": {
"drupal/feeds": "3.x-dev",
- "drupal/linkit": "^6"
+ "drupal/linkit": "^6.1"
},
"type": "drupal-module",
"extra": {
"drupal": {
- "version": "2.0.0-rc6",
- "datestamp": "1703275144",
+ "version": "3.0.0-rc2",
+ "datestamp": "1713762104",
"security-coverage": {
"status": "not-covered",
"message": "RC releases are not covered by Drupal security advisories."
@@ -4046,6 +4046,82 @@
"issues": "http://drupal.org/project/issues/entity_usage"
}
},
+ {
+ "name": "drupal/field_config_cardinality",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://git.drupalcode.org/project/field_config_cardinality.git",
+ "reference": "3.0.1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://ftp.drupal.org/files/projects/field_config_cardinality-3.0.1.zip",
+ "reference": "3.0.1",
+ "shasum": "47a2d933514464bea620617eb2ec34988b60ce37"
+ },
+ "require": {
+ "drupal/core": "^9 || ^10"
+ },
+ "type": "drupal-module",
+ "extra": {
+ "drupal": {
+ "version": "3.0.1",
+ "datestamp": "1680866025",
+ "security-coverage": {
+ "status": "covered",
+ "message": "Covered by Drupal's security advisory policy"
+ }
+ }
+ },
+ "notification-url": "https://packages.drupal.org/8/downloads",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "ayalon",
+ "homepage": "https://www.drupal.org/user/419226"
+ },
+ {
+ "name": "brittany.huntzberry",
+ "homepage": "https://www.drupal.org/user/3552960"
+ },
+ {
+ "name": "dealancer",
+ "homepage": "https://www.drupal.org/user/243418"
+ },
+ {
+ "name": "devkinetic",
+ "homepage": "https://www.drupal.org/user/114708"
+ },
+ {
+ "name": "e.bogatyrev",
+ "homepage": "https://www.drupal.org/user/2760527"
+ },
+ {
+ "name": "eloivaque",
+ "homepage": "https://www.drupal.org/user/605038"
+ },
+ {
+ "name": "Naveen Kumar Reddy",
+ "homepage": "https://www.drupal.org/user/3693535"
+ },
+ {
+ "name": "Shruthi Shetty M",
+ "homepage": "https://www.drupal.org/user/3580970"
+ },
+ {
+ "name": "stefan.korn",
+ "homepage": "https://www.drupal.org/user/1942204"
+ }
+ ],
+ "description": "Allow apply cardinality on Field Config (Cardinality Instance)",
+ "homepage": "https://www.drupal.org/project/field_config_cardinality",
+ "support": {
+ "source": "https://git.drupalcode.org/project/field_config_cardinality"
+ }
+ },
{
"name": "drupal/field_group",
"version": "3.4.0",
@@ -16651,7 +16727,6 @@
"stability-flags": {
"drupal/bigint": 20,
"drupal/bulk_update_fields": 15,
- "drupal/custom_field": 5,
"drupal/entity_usage": 10,
"drupal/gin": 5,
"drupal/imageapi_optimize_binaries": 15,
diff --git a/composer.patches.json b/composer.patches.json
index 2f132011..a10f7440 100644
--- a/composer.patches.json
+++ b/composer.patches.json
@@ -1,7 +1,8 @@
{
"patches": {
"drupal/core": {
- "https://www.drupal.org/project/drupal/issues/2544110": "./patches/2544110-2024-02-15.patch",
+ "Issue #2544110: XSS attribute filtering is inconsistent and strips valid attributes": "./patches/2544110-2024-02-15.patch",
+ "Issue #2329253: Allow the ChangedItem to skip updating the entity's changed timestamp when synchronizing (f.e. when migrating)": "https://www.drupal.org/files/issues/2024-04-18/2329253-127-10.2.5.patch",
"Issue #3392572: strnatcasecmp(): Passing null to parameter #2 in LayoutPluginManager": "https://www.drupal.org/files/issues/2023-10-25/drupal-strnatcasecmp_string_only-3392572-10.patch",
"Issue #3413079: Cannot read properties of null (reading 'nodeType') on node.page.body": "https://www.drupal.org/files/issues/2024-01-08/3413079-9.patch",
"Issue #3274635: [upstream] Use CKEditor 5's native
and UX": "https://www.drupal.org/files/issues/2024-03-20/ckeditor5_custom_patch_file.patch",
@@ -25,6 +26,10 @@
"drupal/gin": {
"Issue #3342513: Ajax behaviour on entity edit form action buttons can break": "https://www.drupal.org/files/issues/2024-02-02/3342513-entity-edit-form-update-ajax-ids-12.patch"
},
+ "drupal/layout_paragraphs": {
+ "Issue #3265794: Modal form actions broken if subform has validation errors or certain AJAX callbacks": "https://git.drupalcode.org/project/layout_paragraphs/-/commit/9e6ce29756e7a44d1644f77a9606d3b598f67b5d.patch",
+ "Issue #3442062: Make selector less greedy, only look at the form actions": "https://git.drupalcode.org/project/layout_paragraphs/-/merge_requests/160.patch"
+ },
"drupal/maintenance200": {
"Issue #3288424: Automated Drupal 10 compatibility fixes": "./patches/maintenance200-drupal-10-compatibility.patch"
},
@@ -32,7 +37,7 @@
"Issue #3345123: entityViewModeAlter throws an exception in php 8.2": "https://www.drupal.org/files/issues/2023-03-01/paragraphs_viewmode-remove-context-parameter-3345123-2.patch"
},
"drupal/user_expire": {
- "Allow the notification email to be customised": "https://git.drupalcode.org/project/user_expire/-/merge_requests/5.patch",
+ "Issue #2836514: Disable or customize expiration email": "https://git.drupalcode.org/project/user_expire/-/merge_requests/5.patch",
"Issue #2855005: Reset expiration when user is reactivated": "https://www.drupal.org/files/issues/2022-11-22/2855005-13.patch"
}
}
diff --git a/config/core.entity_form_display.paragraph.achievement_list.default.yml b/config/core.entity_form_display.paragraph.achievement_list.default.yml
index c085d6b8..2c0e0baa 100755
--- a/config/core.entity_form_display.paragraph.achievement_list.default.yml
+++ b/config/core.entity_form_display.paragraph.achievement_list.default.yml
@@ -7,28 +7,84 @@ dependencies:
- field.field.paragraph.achievement_list.field_text
- paragraphs.paragraphs_type.achievement_list
module:
+ - field_group
- paragraphs
- text
+third_party_settings:
+ field_group:
+ group_tabs:
+ children:
+ - group_achievements
+ - group_caption
+ label: Tabs
+ region: content
+ parent_name: ''
+ weight: 0
+ format_type: tabs
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ direction: horizontal
+ width_breakpoint: 640
+ group_achievements:
+ children:
+ - field_achievements
+ label: Achievements
+ region: content
+ parent_name: group_tabs
+ weight: 6
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: open
+ description: ''
+ required_fields: true
+ group_caption:
+ children:
+ - field_text
+ label: Caption
+ region: content
+ parent_name: group_tabs
+ weight: 7
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: closed
+ description: ''
+ required_fields: true
id: paragraph.achievement_list.default
targetEntityType: paragraph
bundle: achievement_list
mode: default
content:
field_achievements:
- type: entity_reference_paragraphs
- weight: 0
+ type: paragraphs
+ weight: 1
region: content
settings:
title: Paragraph
title_plural: Paragraphs
edit_mode: open
+ closed_mode: summary
+ autocollapse: none
+ closed_mode_threshold: 0
add_mode: dropdown
form_display_mode: default
- default_paragraph_type: ''
+ default_paragraph_type: achievement
+ features:
+ add_above: '0'
+ collapse_edit_all: collapse_edit_all
+ convert: '0'
+ duplicate: duplicate
third_party_settings: { }
field_text:
type: text_textarea
- weight: 1
+ weight: 2
region: content
settings:
rows: 5
@@ -36,13 +92,13 @@ content:
third_party_settings: { }
status:
type: boolean_checkbox
- weight: 2
+ weight: 3
region: content
settings:
display_label: true
third_party_settings: { }
translation:
- weight: 3
+ weight: 4
region: content
settings: { }
third_party_settings: { }
diff --git a/config/core.entity_form_display.paragraph.article_card_list.default.yml b/config/core.entity_form_display.paragraph.article_card_list.default.yml
index d5888b23..9cb656c3 100755
--- a/config/core.entity_form_display.paragraph.article_card_list.default.yml
+++ b/config/core.entity_form_display.paragraph.article_card_list.default.yml
@@ -3,34 +3,92 @@ langcode: en
status: true
dependencies:
config:
+ - entity_browser.browser.articles
- field.field.paragraph.article_card_list.field_articles
- field.field.paragraph.article_card_list.field_emphasize_first_row
- paragraphs.paragraphs_type.article_card_list
+ module:
+ - field_group
+ - ncms_ui
+third_party_settings:
+ field_group:
+ group_tabs:
+ children:
+ - group_articles
+ - group_settings
+ label: Tabs
+ region: content
+ parent_name: ''
+ weight: 0
+ format_type: tabs
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ direction: horizontal
+ width_breakpoint: 640
+ group_articles:
+ children:
+ - field_articles
+ label: Articles
+ region: content
+ parent_name: group_tabs
+ weight: 6
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: open
+ description: ''
+ required_fields: true
+ group_settings:
+ children:
+ - field_emphasize_first_row
+ label: Settings
+ region: content
+ parent_name: group_tabs
+ weight: 7
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: closed
+ description: ''
+ required_fields: true
id: paragraph.article_card_list.default
targetEntityType: paragraph
bundle: article_card_list
mode: default
content:
field_articles:
- type: entity_reference_autocomplete
- weight: 1
+ type: entity_reference_browser_table_widget_ncms
+ weight: 2
region: content
settings:
- match_operator: CONTAINS
- match_limit: 10
- size: 60
- placeholder: ''
+ open: false
+ entity_browser: articles
+ field_widget_display: label
+ field_widget_edit: '1'
+ field_widget_remove: '1'
+ selection_mode: selection_append
+ field_widget_replace: 0
+ additional_fields:
+ options:
+ status: 0
+ field_widget_display_settings: { }
third_party_settings: { }
field_emphasize_first_row:
type: boolean_checkbox
- weight: 0
+ weight: 2
region: content
settings:
display_label: true
third_party_settings: { }
status:
type: boolean_checkbox
- weight: 2
+ weight: 3
region: content
settings:
display_label: true
diff --git a/config/core.entity_form_display.paragraph.article_list.default.yml b/config/core.entity_form_display.paragraph.article_list.default.yml
index 28b933fb..12cdb53e 100755
--- a/config/core.entity_form_display.paragraph.article_list.default.yml
+++ b/config/core.entity_form_display.paragraph.article_list.default.yml
@@ -7,7 +7,40 @@ dependencies:
- field.field.paragraph.article_list.field_title
- paragraphs.paragraphs_type.article_list
module:
+ - field_group
- link
+third_party_settings:
+ field_group:
+ group_tabs:
+ children:
+ - group_article_list
+ label: Tabs
+ region: content
+ parent_name: ''
+ weight: 0
+ format_type: tabs
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ direction: horizontal
+ width_breakpoint: 640
+ group_article_list:
+ children:
+ - field_title
+ - field_links
+ label: 'Article list'
+ region: content
+ parent_name: group_tabs
+ weight: 6
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: open
+ description: ''
+ required_fields: true
id: paragraph.article_list.default
targetEntityType: paragraph
bundle: article_list
@@ -15,7 +48,7 @@ mode: default
content:
field_links:
type: link_default
- weight: 1
+ weight: 8
region: content
settings:
placeholder_url: ''
@@ -23,7 +56,7 @@ content:
third_party_settings: { }
field_title:
type: string_textfield
- weight: 0
+ weight: 7
region: content
settings:
size: 60
@@ -31,7 +64,7 @@ content:
third_party_settings: { }
status:
type: boolean_checkbox
- weight: 2
+ weight: 3
region: content
settings:
display_label: true
diff --git a/config/core.entity_form_display.paragraph.bottom_figure_row.default.yml b/config/core.entity_form_display.paragraph.bottom_figure_row.default.yml
index 76ac6b89..4bf1c113 100755
--- a/config/core.entity_form_display.paragraph.bottom_figure_row.default.yml
+++ b/config/core.entity_form_display.paragraph.bottom_figure_row.default.yml
@@ -3,7 +3,6 @@ langcode: en
status: true
dependencies:
config:
- - field.field.paragraph.bottom_figure_row.field_bottom_figures
- field.field.paragraph.bottom_figure_row.field_dataset
- field.field.paragraph.bottom_figure_row.field_figures
- paragraphs.paragraphs_type.bottom_figure_row
@@ -94,5 +93,4 @@ content:
third_party_settings: { }
hidden:
created: true
- field_bottom_figures: true
translation: true
diff --git a/config/core.entity_form_display.paragraph.document_chapter.default.yml b/config/core.entity_form_display.paragraph.document_chapter.default.yml
index e62db610..68f46b8f 100644
--- a/config/core.entity_form_display.paragraph.document_chapter.default.yml
+++ b/config/core.entity_form_display.paragraph.document_chapter.default.yml
@@ -51,7 +51,6 @@ third_party_settings:
- group_title
- field_summary
- field_tags
- - status
- field_hide_from_navigation
label: 'Chapter properties'
region: content
@@ -149,7 +148,7 @@ content:
third_party_settings: { }
status:
type: boolean_checkbox
- weight: 7
+ weight: 1
region: content
settings:
display_label: true
diff --git a/config/core.entity_form_display.paragraph.facts_and_figures.default.yml b/config/core.entity_form_display.paragraph.facts_and_figures.default.yml
index d279f3f8..b136b1da 100755
--- a/config/core.entity_form_display.paragraph.facts_and_figures.default.yml
+++ b/config/core.entity_form_display.paragraph.facts_and_figures.default.yml
@@ -7,27 +7,68 @@ dependencies:
- field.field.paragraph.facts_and_figures.field_title
- paragraphs.paragraphs_type.facts_and_figures
module:
+ - field_group
- paragraphs
+third_party_settings:
+ field_group:
+ group_tabs:
+ children:
+ - group_facts_and_figures
+ label: Tabs
+ region: content
+ parent_name: ''
+ weight: 0
+ format_type: tabs
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ direction: horizontal
+ width_breakpoint: 640
+ group_facts_and_figures:
+ children:
+ - field_title
+ - field_paragraphs
+ label: 'Facts and figures'
+ region: content
+ parent_name: group_tabs
+ weight: 6
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: open
+ description: ''
+ required_fields: true
id: paragraph.facts_and_figures.default
targetEntityType: paragraph
bundle: facts_and_figures
mode: default
content:
field_paragraphs:
- type: entity_reference_paragraphs
- weight: 1
+ type: paragraphs
+ weight: 2
region: content
settings:
title: 'Fact and figure'
title_plural: 'Facts and figures'
- edit_mode: preview
+ edit_mode: closed
+ closed_mode: preview
+ autocollapse: none
+ closed_mode_threshold: 0
add_mode: button
form_display_mode: default
default_paragraph_type: image_with_text
+ features:
+ add_above: '0'
+ collapse_edit_all: collapse_edit_all
+ convert: '0'
+ duplicate: duplicate
third_party_settings: { }
field_title:
type: string_textfield
- weight: 0
+ weight: 1
region: content
settings:
size: 60
@@ -35,7 +76,7 @@ content:
third_party_settings: { }
status:
type: boolean_checkbox
- weight: 2
+ weight: 3
region: content
settings:
display_label: true
diff --git a/config/core.entity_form_display.paragraph.further_reading.default.yml b/config/core.entity_form_display.paragraph.further_reading.default.yml
index 3382f94a..de343120 100755
--- a/config/core.entity_form_display.paragraph.further_reading.default.yml
+++ b/config/core.entity_form_display.paragraph.further_reading.default.yml
@@ -6,7 +6,39 @@ dependencies:
- field.field.paragraph.further_reading.field_links
- paragraphs.paragraphs_type.further_reading
module:
+ - field_group
- gho_fields
+third_party_settings:
+ field_group:
+ group_tabs:
+ children:
+ - group_links
+ label: Tabs
+ region: content
+ parent_name: ''
+ weight: 0
+ format_type: tabs
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ direction: horizontal
+ width_breakpoint: 640
+ group_links:
+ children:
+ - field_links
+ label: Links
+ region: content
+ parent_name: group_tabs
+ weight: 5
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: open
+ description: ''
+ required_fields: true
id: paragraph.further_reading.default
targetEntityType: paragraph
bundle: further_reading
@@ -14,7 +46,7 @@ mode: default
content:
field_links:
type: gho_further_reading_link
- weight: 0
+ weight: 1
region: content
settings:
placeholder_url: 'https://example.com'
@@ -22,7 +54,7 @@ content:
third_party_settings: { }
status:
type: boolean_checkbox
- weight: 1
+ weight: 2
region: content
settings:
display_label: true
diff --git a/config/core.entity_form_display.paragraph.interactive_content.default.yml b/config/core.entity_form_display.paragraph.interactive_content.default.yml
index 952496a8..90ca4278 100755
--- a/config/core.entity_form_display.paragraph.interactive_content.default.yml
+++ b/config/core.entity_form_display.paragraph.interactive_content.default.yml
@@ -13,9 +13,92 @@ dependencies:
- field.field.paragraph.interactive_content.field_type
- paragraphs.paragraphs_type.interactive_content
module:
+ - field_group
- gho_fields
- link
- media_library
+third_party_settings:
+ field_group:
+ group_tabs:
+ children:
+ - group_interactive_content
+ - group_fallback_image
+ - group_source
+ - group_settings
+ label: Tabs
+ region: content
+ parent_name: ''
+ weight: 0
+ format_type: tabs
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ direction: horizontal
+ width_breakpoint: 640
+ group_interactive_content:
+ children:
+ - field_title
+ - field_link
+ - field_embed_code
+ label: 'Interactive content'
+ region: content
+ parent_name: group_tabs
+ weight: 12
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: open
+ description: ''
+ required_fields: true
+ group_source:
+ children:
+ - field_dataset
+ label: Source
+ region: content
+ parent_name: group_tabs
+ weight: 14
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: closed
+ description: ''
+ required_fields: true
+ group_fallback_image:
+ children:
+ - field_show_interactive_content
+ - field_image
+ label: 'Fallback image'
+ region: content
+ parent_name: group_tabs
+ weight: 13
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: closed
+ description: ''
+ required_fields: true
+ group_settings:
+ children:
+ - field_full_width
+ label: 'Additional settings'
+ region: content
+ parent_name: group_tabs
+ weight: 15
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: closed
+ description: ''
+ required_fields: true
id: paragraph.interactive_content.default
targetEntityType: paragraph
bundle: interactive_content
@@ -23,7 +106,7 @@ mode: default
content:
field_dataset:
type: gho_dataset_link
- weight: 6
+ weight: 14
region: content
settings:
placeholder_url: 'Ex: https://humdata.org/dataset'
@@ -39,21 +122,21 @@ content:
third_party_settings: { }
field_full_width:
type: boolean_checkbox
- weight: 0
+ weight: 15
region: content
settings:
display_label: true
third_party_settings: { }
field_image:
type: media_library_widget
- weight: 3
+ weight: 6
region: content
settings:
media_types: { }
third_party_settings: { }
field_link:
type: link_default
- weight: 2
+ weight: 3
region: content
settings:
placeholder_url: 'Ex: https://datawrapper.de/content-page'
@@ -68,7 +151,7 @@ content:
third_party_settings: { }
field_title:
type: string_textfield
- weight: 1
+ weight: 2
region: content
settings:
size: 60
@@ -76,7 +159,7 @@ content:
third_party_settings: { }
status:
type: boolean_checkbox
- weight: 7
+ weight: 1
region: content
settings:
display_label: true
diff --git a/config/core.entity_form_display.paragraph.text.default.yml b/config/core.entity_form_display.paragraph.text.default.yml
index 25c405ff..5947d89e 100755
--- a/config/core.entity_form_display.paragraph.text.default.yml
+++ b/config/core.entity_form_display.paragraph.text.default.yml
@@ -8,7 +8,40 @@ dependencies:
- paragraphs.paragraphs_type.text
module:
- allowed_formats
+ - field_group
- text
+third_party_settings:
+ field_group:
+ group_tabs:
+ children:
+ - group_content
+ label: Tabs
+ region: content
+ parent_name: ''
+ weight: 0
+ format_type: tabs
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ direction: horizontal
+ width_breakpoint: 640
+ group_content:
+ children:
+ - field_text
+ - field_footnotes
+ label: Content
+ region: content
+ parent_name: group_tabs
+ weight: 6
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: open
+ description: ''
+ required_fields: true
id: paragraph.text.default
targetEntityType: paragraph
bundle: text
@@ -16,7 +49,7 @@ mode: default
content:
field_footnotes:
type: text_textarea
- weight: 1
+ weight: 2
region: content
settings:
rows: 5
@@ -27,7 +60,7 @@ content:
hide_guidelines: '1'
field_text:
type: text_textarea
- weight: 0
+ weight: 1
region: content
settings:
rows: 5
@@ -38,7 +71,7 @@ content:
hide_guidelines: '1'
status:
type: boolean_checkbox
- weight: 2
+ weight: 3
region: content
settings:
display_label: true
diff --git a/config/core.entity_form_display.paragraph.top_figures.default.yml b/config/core.entity_form_display.paragraph.top_figures.default.yml
new file mode 100644
index 00000000..91cca9e8
--- /dev/null
+++ b/config/core.entity_form_display.paragraph.top_figures.default.yml
@@ -0,0 +1,95 @@
+uuid: 382ed4e3-c4ba-4ba5-8d7f-12572773dbba
+langcode: en
+status: true
+dependencies:
+ config:
+ - field.field.paragraph.top_figures.field_dataset
+ - field.field.paragraph.top_figures.field_figures
+ - paragraphs.paragraphs_type.top_figures
+ module:
+ - custom_field
+ - field_group
+ - gho_fields
+third_party_settings:
+ field_group:
+ group_tabs:
+ children:
+ - group_figures
+ - group_source
+ label: Tabs
+ region: content
+ parent_name: ''
+ weight: 0
+ format_type: tabs
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ direction: horizontal
+ width_breakpoint: 640
+ group_figures:
+ children:
+ - field_figures
+ label: Figures
+ region: content
+ parent_name: group_tabs
+ weight: 3
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: open
+ description: ''
+ required_fields: true
+ group_source:
+ children:
+ - field_dataset
+ label: Source
+ region: content
+ parent_name: group_tabs
+ weight: 4
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: closed
+ description: ''
+ required_fields: true
+id: paragraph.top_figures.default
+targetEntityType: paragraph
+bundle: top_figures
+mode: default
+content:
+ field_dataset:
+ type: gho_dataset_link
+ weight: 1
+ region: content
+ settings:
+ placeholder_url: 'https://example.com/link-to-data'
+ placeholder_title: 'Ex: Office for the Coordination of Humanitarian Affairs'
+ third_party_settings: { }
+ field_figures:
+ type: custom_flex
+ weight: 2
+ region: content
+ settings:
+ label: false
+ wrapper: div
+ open: true
+ columns:
+ label: auto
+ value: auto
+ footnote: auto
+ breakpoint: ''
+ third_party_settings: { }
+ status:
+ type: boolean_checkbox
+ weight: 2
+ region: content
+ settings:
+ display_label: true
+ third_party_settings: { }
+hidden:
+ created: true
diff --git a/config/core.entity_form_display.paragraph.top_figures_small.default.yml b/config/core.entity_form_display.paragraph.top_figures_small.default.yml
new file mode 100644
index 00000000..b1e57d9d
--- /dev/null
+++ b/config/core.entity_form_display.paragraph.top_figures_small.default.yml
@@ -0,0 +1,95 @@
+uuid: dd76a23b-79b9-4342-82df-f28128fb1780
+langcode: en
+status: true
+dependencies:
+ config:
+ - field.field.paragraph.top_figures_small.field_dataset
+ - field.field.paragraph.top_figures_small.field_figures
+ - paragraphs.paragraphs_type.top_figures_small
+ module:
+ - custom_field
+ - field_group
+ - gho_fields
+third_party_settings:
+ field_group:
+ group_tabs:
+ children:
+ - group_figures
+ - group_source
+ label: Tabs
+ region: content
+ parent_name: ''
+ weight: 0
+ format_type: tabs
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ direction: horizontal
+ width_breakpoint: 640
+ group_figures:
+ children:
+ - field_figures
+ label: Figures
+ region: content
+ parent_name: group_tabs
+ weight: 5
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: open
+ description: ''
+ required_fields: true
+ group_source:
+ children:
+ - field_dataset
+ label: Source
+ region: content
+ parent_name: group_tabs
+ weight: 6
+ format_type: tab
+ format_settings:
+ classes: ''
+ show_empty_fields: false
+ id: ''
+ formatter: closed
+ description: ''
+ required_fields: true
+id: paragraph.top_figures_small.default
+targetEntityType: paragraph
+bundle: top_figures_small
+mode: default
+content:
+ field_dataset:
+ type: gho_dataset_link
+ weight: 3
+ region: content
+ settings:
+ placeholder_url: 'https://example.com/link-to-data'
+ placeholder_title: 'Ex: Office for the Coordination of Humanitarian Affairs'
+ third_party_settings: { }
+ field_figures:
+ type: custom_flex
+ weight: 1
+ region: content
+ settings:
+ label: false
+ wrapper: div
+ open: true
+ columns:
+ label: auto
+ value: auto
+ footnote: auto
+ breakpoint: ''
+ third_party_settings: { }
+ status:
+ type: boolean_checkbox
+ weight: 2
+ region: content
+ settings:
+ display_label: true
+ third_party_settings: { }
+hidden:
+ created: true
diff --git a/config/core.entity_view_display.paragraph.article_card_list.default.yml b/config/core.entity_view_display.paragraph.article_card_list.default.yml
index 8cb07106..05a6612c 100755
--- a/config/core.entity_view_display.paragraph.article_card_list.default.yml
+++ b/config/core.entity_view_display.paragraph.article_card_list.default.yml
@@ -20,14 +20,5 @@ content:
third_party_settings: { }
weight: 0
region: content
- field_emphasize_first_row:
- type: boolean
- label: hidden
- settings:
- format: custom
- format_custom_false: default
- format_custom_true: show-2col
- third_party_settings: { }
- weight: 1
- region: content
-hidden: { }
+hidden:
+ field_emphasize_first_row: true
diff --git a/config/core.entity_view_display.paragraph.bottom_figure_row.default.yml b/config/core.entity_view_display.paragraph.bottom_figure_row.default.yml
index 14a618f9..907cdc4d 100755
--- a/config/core.entity_view_display.paragraph.bottom_figure_row.default.yml
+++ b/config/core.entity_view_display.paragraph.bottom_figure_row.default.yml
@@ -3,7 +3,6 @@ langcode: en
status: true
dependencies:
config:
- - field.field.paragraph.bottom_figure_row.field_bottom_figures
- field.field.paragraph.bottom_figure_row.field_dataset
- field.field.paragraph.bottom_figure_row.field_figures
- paragraphs.paragraphs_type.bottom_figure_row
@@ -29,5 +28,4 @@ content:
third_party_settings: { }
weight: 0
region: content
-hidden:
- field_bottom_figures: true
+hidden: { }
diff --git a/config/core.entity_view_display.paragraph.bottom_figure_row.top_figures.yml b/config/core.entity_view_display.paragraph.bottom_figure_row.top_figures.yml
index 5bb705e9..6465e2fb 100755
--- a/config/core.entity_view_display.paragraph.bottom_figure_row.top_figures.yml
+++ b/config/core.entity_view_display.paragraph.bottom_figure_row.top_figures.yml
@@ -4,7 +4,6 @@ status: true
dependencies:
config:
- core.entity_view_mode.paragraph.top_figures
- - field.field.paragraph.bottom_figure_row.field_bottom_figures
- field.field.paragraph.bottom_figure_row.field_dataset
- field.field.paragraph.bottom_figure_row.field_figures
- paragraphs.paragraphs_type.bottom_figure_row
@@ -29,5 +28,4 @@ content:
weight: 0
region: content
hidden:
- field_bottom_figures: true
field_dataset: true
diff --git a/config/core.entity_view_display.paragraph.top_figures.default.yml b/config/core.entity_view_display.paragraph.top_figures.default.yml
new file mode 100644
index 00000000..2872922b
--- /dev/null
+++ b/config/core.entity_view_display.paragraph.top_figures.default.yml
@@ -0,0 +1,31 @@
+uuid: 9aacccd4-0a89-4c5f-8a45-f9f19e47cd49
+langcode: en
+status: true
+dependencies:
+ config:
+ - field.field.paragraph.top_figures.field_dataset
+ - field.field.paragraph.top_figures.field_figures
+ - paragraphs.paragraphs_type.top_figures
+ module:
+ - gho_fields
+id: paragraph.top_figures.default
+targetEntityType: paragraph
+bundle: top_figures
+mode: default
+content:
+ field_dataset:
+ type: gho_dataset_link
+ label: hidden
+ settings: { }
+ third_party_settings: { }
+ weight: 1
+ region: content
+ field_figures:
+ type: gho_figures
+ label: hidden
+ settings:
+ format: large
+ third_party_settings: { }
+ weight: 0
+ region: content
+hidden: { }
diff --git a/config/core.entity_view_display.paragraph.top_figures_small.default.yml b/config/core.entity_view_display.paragraph.top_figures_small.default.yml
new file mode 100644
index 00000000..6d1236c7
--- /dev/null
+++ b/config/core.entity_view_display.paragraph.top_figures_small.default.yml
@@ -0,0 +1,31 @@
+uuid: 21c9311d-5370-495e-bf19-2a75a324e4f2
+langcode: en
+status: true
+dependencies:
+ config:
+ - field.field.paragraph.top_figures_small.field_dataset
+ - field.field.paragraph.top_figures_small.field_figures
+ - paragraphs.paragraphs_type.top_figures_small
+ module:
+ - gho_fields
+id: paragraph.top_figures_small.default
+targetEntityType: paragraph
+bundle: top_figures_small
+mode: default
+content:
+ field_dataset:
+ type: gho_dataset_link
+ label: hidden
+ settings: { }
+ third_party_settings: { }
+ weight: 1
+ region: content
+ field_figures:
+ type: gho_figures
+ label: hidden
+ settings:
+ format: small
+ third_party_settings: { }
+ weight: 0
+ region: content
+hidden: { }
diff --git a/config/core.extension.yml b/config/core.extension.yml
index ec08109c..b3d78eeb 100755
--- a/config/core.extension.yml
+++ b/config/core.extension.yml
@@ -33,6 +33,7 @@ module:
entity_browser_table: 0
entity_reference_revisions: 0
field: 0
+ field_config_cardinality: 0
field_group: 0
field_ui: 0
file: 0
@@ -71,6 +72,7 @@ module:
monitoring: 0
mysql: 0
ncms_graphql: 0
+ ncms_paragraphs: 0
ncms_publisher: 0
ncms_ui: 0
node: 0
diff --git a/config/field.field.node.article.field_paragraphs.yml b/config/field.field.node.article.field_paragraphs.yml
index 5ac43a0d..f2bdea68 100755
--- a/config/field.field.node.article.field_paragraphs.yml
+++ b/config/field.field.node.article.field_paragraphs.yml
@@ -6,9 +6,18 @@ dependencies:
- field.storage.node.field_paragraphs
- node.type.article
- paragraphs.paragraphs_type.achievement
- - paragraphs.paragraphs_type.article_card_list
+ - paragraphs.paragraphs_type.document_articles
+ - paragraphs.paragraphs_type.document_chapter
module:
- entity_reference_revisions
+ - field_config_cardinality
+third_party_settings:
+ field_config_cardinality:
+ cardinality_config: '-1'
+ cardinality_label_config: false
+ unlimited_not_required: ''
+ limited_not_required: ''
+ limited_required: ''
id: node.article.field_paragraphs
field_name: field_paragraphs
entity_type: node
@@ -24,70 +33,74 @@ settings:
handler_settings:
target_bundles:
achievement: achievement
- article_card_list: article_card_list
+ document_articles: document_articles
+ document_chapter: document_chapter
negate: 1
target_bundles_drag_drop:
achievement:
- weight: 22
+ weight: -35
enabled: true
achievement_list:
- weight: 21
+ weight: -36
enabled: false
article_card_list:
- weight: 23
- enabled: true
+ weight: -34
+ enabled: false
article_list:
- weight: 24
+ weight: -33
enabled: false
bottom_figure_row:
- weight: 8
+ weight: -43
enabled: false
document_articles:
- weight: 28
- enabled: false
+ weight: -31
+ enabled: true
document_chapter:
- weight: 29
- enabled: false
+ weight: -29
+ enabled: true
download_button:
- weight: 26
+ weight: -32
enabled: false
facts_and_figures:
- weight: 9
+ weight: -42
enabled: false
further_reading:
- weight: 28
+ weight: -30
enabled: false
heading:
- weight: 29
+ weight: -28
enabled: false
image_with_text:
- weight: 12
+ weight: -38
enabled: false
infographic:
- weight: 36
+ weight: -26
enabled: false
interactive_content:
- weight: 31
+ weight: -27
enabled: false
interactive_content_2_columns:
- weight: 39
+ weight: -25
enabled: false
- layout:
- weight: 8
- enabled: true
photo_gallery:
- weight: 11
+ weight: -39
enabled: false
separator:
- weight: 15
+ weight: -37
enabled: false
story:
- weight: 10
+ weight: -41
enabled: false
sub_article:
- weight: 39
+ weight: -24
enabled: false
text:
- weight: 10
+ weight: -40
+ enabled: false
+ top_figures:
+ weight: -45
+ enabled: false
+ top_figures_small:
+ weight: -44
enabled: false
field_type: entity_reference_revisions
diff --git a/config/field.field.paragraph.article_card_list.field_emphasize_first_row.yml b/config/field.field.paragraph.article_card_list.field_emphasize_first_row.yml
index 83a7d585..9c109a95 100755
--- a/config/field.field.paragraph.article_card_list.field_emphasize_first_row.yml
+++ b/config/field.field.paragraph.article_card_list.field_emphasize_first_row.yml
@@ -5,14 +5,23 @@ dependencies:
config:
- field.storage.paragraph.field_emphasize_first_row
- paragraphs.paragraphs_type.article_card_list
+ module:
+ - field_config_cardinality
+third_party_settings:
+ field_config_cardinality:
+ cardinality_config: '1'
+ cardinality_label_config: false
+ unlimited_not_required: ''
+ limited_not_required: ''
+ limited_required: ''
id: paragraph.article_card_list.field_emphasize_first_row
field_name: field_emphasize_first_row
entity_type: paragraph
bundle: article_card_list
label: 'Show first two items in 2-column layout?'
-description: ''
+description: 'Check this to display the first 2 articles bigger than the following.'
required: false
-translatable: false
+translatable: true
default_value:
-
value: 0
diff --git a/config/field.field.paragraph.bottom_figure_row.field_bottom_figures.yml b/config/field.field.paragraph.bottom_figure_row.field_bottom_figures.yml
deleted file mode 100755
index 041a3a1e..00000000
--- a/config/field.field.paragraph.bottom_figure_row.field_bottom_figures.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-uuid: 25815140-0e56-4211-9be6-f89af6c289a8
-langcode: en
-status: true
-dependencies:
- config:
- - field.storage.paragraph.field_bottom_figures
- - paragraphs.paragraphs_type.bottom_figure_row
- module:
- - double_field
-id: paragraph.bottom_figure_row.field_bottom_figures
-field_name: field_bottom_figures
-entity_type: paragraph
-bundle: bottom_figure_row
-label: 'Figures (Deprecated)'
-description: ''
-required: false
-translatable: true
-default_value: { }
-default_value_callback: ''
-settings:
- first:
- label: Label
- list: false
- allowed_values: { }
- max: null
- min: null
- required: true
- on_label: 'On'
- off_label: 'Off'
- second:
- label: Value
- list: false
- allowed_values: { }
- max: null
- min: null
- required: true
- on_label: 'On'
- off_label: 'Off'
-field_type: double_field
diff --git a/config/field.field.paragraph.interactive_content.field_full_width.yml b/config/field.field.paragraph.interactive_content.field_full_width.yml
index 896c58ef..13d039ff 100755
--- a/config/field.field.paragraph.interactive_content.field_full_width.yml
+++ b/config/field.field.paragraph.interactive_content.field_full_width.yml
@@ -5,12 +5,21 @@ dependencies:
config:
- field.storage.paragraph.field_full_width
- paragraphs.paragraphs_type.interactive_content
+ module:
+ - field_config_cardinality
+third_party_settings:
+ field_config_cardinality:
+ cardinality_config: '1'
+ cardinality_label_config: false
+ unlimited_not_required: ''
+ limited_not_required: ''
+ limited_required: ''
id: paragraph.interactive_content.field_full_width
field_name: field_full_width
entity_type: paragraph
bundle: interactive_content
label: 'Display at full width?'
-description: ''
+description: 'If checked, the interactive content will be shown at full-width. Otherwise it will be restricted to same width as the surrounding text content.'
required: false
translatable: true
default_value:
diff --git a/config/field.field.paragraph.interactive_content.field_image.yml b/config/field.field.paragraph.interactive_content.field_image.yml
index e0f1b3e6..ee2f6169 100755
--- a/config/field.field.paragraph.interactive_content.field_image.yml
+++ b/config/field.field.paragraph.interactive_content.field_image.yml
@@ -6,11 +6,20 @@ dependencies:
- field.storage.paragraph.field_image
- media.type.image
- paragraphs.paragraphs_type.interactive_content
+ module:
+ - field_config_cardinality
+third_party_settings:
+ field_config_cardinality:
+ cardinality_config: '1'
+ cardinality_label_config: false
+ unlimited_not_required: ''
+ limited_not_required: ''
+ limited_required: ''
id: paragraph.interactive_content.field_image
field_name: field_image
entity_type: paragraph
bundle: interactive_content
-label: Thumbnail
+label: 'Fallback image'
description: "Image representing the content. When clicked it will redirect to the interactive content's page."
required: false
translatable: true
@@ -23,6 +32,7 @@ settings:
image: image
sort:
field: _none
+ direction: ASC
auto_create: false
auto_create_bundle: ''
field_type: entity_reference
diff --git a/config/field.field.paragraph.top_figures.field_dataset.yml b/config/field.field.paragraph.top_figures.field_dataset.yml
new file mode 100644
index 00000000..e0624529
--- /dev/null
+++ b/config/field.field.paragraph.top_figures.field_dataset.yml
@@ -0,0 +1,31 @@
+uuid: 45ec8d00-c54d-4a33-89bf-b77225c91b7d
+langcode: en
+status: true
+dependencies:
+ config:
+ - field.storage.paragraph.field_dataset
+ - paragraphs.paragraphs_type.top_figures
+ module:
+ - field_config_cardinality
+ - link
+third_party_settings:
+ field_config_cardinality:
+ cardinality_config: '1'
+ cardinality_label_config: false
+ unlimited_not_required: ''
+ limited_not_required: ''
+ limited_required: ''
+id: paragraph.top_figures.field_dataset
+field_name: field_dataset
+entity_type: paragraph
+bundle: top_figures
+label: Source
+description: ''
+required: false
+translatable: false
+default_value: { }
+default_value_callback: ''
+settings:
+ title: 2
+ link_type: 16
+field_type: link
diff --git a/config/field.field.paragraph.top_figures.field_figures.yml b/config/field.field.paragraph.top_figures.field_figures.yml
new file mode 100644
index 00000000..cc8a9ea4
--- /dev/null
+++ b/config/field.field.paragraph.top_figures.field_figures.yml
@@ -0,0 +1,78 @@
+uuid: ef933458-7a8c-4ae0-8ff9-8060b5c446de
+langcode: en
+status: true
+dependencies:
+ config:
+ - field.storage.paragraph.field_figures
+ - paragraphs.paragraphs_type.top_figures
+ module:
+ - custom_field
+ - field_config_cardinality
+third_party_settings:
+ field_config_cardinality:
+ cardinality_config: '3'
+ cardinality_label_config: false
+ unlimited_not_required: ''
+ limited_not_required: ''
+ limited_required: ''
+id: paragraph.top_figures.field_figures
+field_name: field_figures
+entity_type: paragraph
+bundle: top_figures
+label: 'Top figures'
+description: 'You can add up to 3 top figures to this paragraph'
+required: false
+translatable: false
+default_value: { }
+default_value_callback: ''
+settings:
+ field_settings:
+ label:
+ type: text
+ weight: 0
+ check_empty: false
+ widget_settings:
+ label: Label
+ settings:
+ description: ''
+ description_display: after
+ size: 30
+ placeholder: 'Ex: Population'
+ maxlength: 255
+ maxlength_js: false
+ prefix: ''
+ suffix: ''
+ required: false
+ value:
+ type: text
+ weight: 0
+ check_empty: false
+ widget_settings:
+ label: Value
+ settings:
+ description: ''
+ description_display: after
+ size: 30
+ placeholder: 'Ex: 10.4 million'
+ maxlength: 255
+ maxlength_js: false
+ prefix: ''
+ suffix: ''
+ required: false
+ footnote:
+ type: text
+ weight: 0
+ check_empty: false
+ widget_settings:
+ label: Footnote
+ settings:
+ description: ''
+ description_display: after
+ size: 30
+ placeholder: ''
+ maxlength: 255
+ maxlength_js: false
+ prefix: ''
+ suffix: ''
+ required: false
+field_type: custom
diff --git a/config/field.field.paragraph.top_figures_small.field_dataset.yml b/config/field.field.paragraph.top_figures_small.field_dataset.yml
new file mode 100644
index 00000000..504d73c4
--- /dev/null
+++ b/config/field.field.paragraph.top_figures_small.field_dataset.yml
@@ -0,0 +1,31 @@
+uuid: 65ea473d-f3d1-4de7-aa82-26fbea17572d
+langcode: en
+status: true
+dependencies:
+ config:
+ - field.storage.paragraph.field_dataset
+ - paragraphs.paragraphs_type.top_figures_small
+ module:
+ - field_config_cardinality
+ - link
+third_party_settings:
+ field_config_cardinality:
+ cardinality_config: '1'
+ cardinality_label_config: false
+ unlimited_not_required: ''
+ limited_not_required: ''
+ limited_required: ''
+id: paragraph.top_figures_small.field_dataset
+field_name: field_dataset
+entity_type: paragraph
+bundle: top_figures_small
+label: Source
+description: ''
+required: false
+translatable: false
+default_value: { }
+default_value_callback: ''
+settings:
+ title: 2
+ link_type: 16
+field_type: link
diff --git a/config/field.field.paragraph.top_figures_small.field_figures.yml b/config/field.field.paragraph.top_figures_small.field_figures.yml
new file mode 100644
index 00000000..57147e84
--- /dev/null
+++ b/config/field.field.paragraph.top_figures_small.field_figures.yml
@@ -0,0 +1,78 @@
+uuid: 94a317bf-feba-4285-b5c5-a43281ab9bcb
+langcode: en
+status: true
+dependencies:
+ config:
+ - field.storage.paragraph.field_figures
+ - paragraphs.paragraphs_type.top_figures_small
+ module:
+ - custom_field
+ - field_config_cardinality
+third_party_settings:
+ field_config_cardinality:
+ cardinality_config: '6'
+ cardinality_label_config: false
+ unlimited_not_required: ''
+ limited_not_required: ''
+ limited_required: ''
+id: paragraph.top_figures_small.field_figures
+field_name: field_figures
+entity_type: paragraph
+bundle: top_figures_small
+label: Figures
+description: 'You can add up to 6 figures to this paragraph'
+required: false
+translatable: false
+default_value: { }
+default_value_callback: ''
+settings:
+ field_settings:
+ label:
+ type: text
+ weight: 0
+ check_empty: false
+ widget_settings:
+ label: Label
+ settings:
+ description: ''
+ description_display: after
+ size: 30
+ placeholder: 'Ex: Population'
+ maxlength: 255
+ maxlength_js: false
+ prefix: ''
+ suffix: ''
+ required: false
+ value:
+ type: text
+ weight: 0
+ check_empty: false
+ widget_settings:
+ label: Value
+ settings:
+ description: ''
+ description_display: after
+ size: 30
+ placeholder: 'Ex: 10.4 million'
+ maxlength: 255
+ maxlength_js: false
+ prefix: ''
+ suffix: ''
+ required: false
+ footnote:
+ type: text
+ weight: 0
+ check_empty: false
+ widget_settings:
+ label: Footnote
+ settings:
+ description: ''
+ description_display: after
+ size: 30
+ placeholder: ''
+ maxlength: 255
+ maxlength_js: false
+ prefix: ''
+ suffix: ''
+ required: false
+field_type: custom
diff --git a/config/field.storage.paragraph.field_bottom_figures.yml b/config/field.storage.paragraph.field_bottom_figures.yml
deleted file mode 100755
index 8b1628a1..00000000
--- a/config/field.storage.paragraph.field_bottom_figures.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-uuid: b3bc588b-4076-4280-ab42-663b687b7571
-langcode: en
-status: true
-dependencies:
- module:
- - double_field
- - paragraphs
-id: paragraph.field_bottom_figures
-field_name: field_bottom_figures
-entity_type: paragraph
-type: double_field
-settings:
- storage:
- first:
- type: string
- maxlength: 255
- precision: 10
- scale: 2
- datetime_type: datetime
- second:
- type: string
- maxlength: 255
- precision: 10
- scale: 2
- datetime_type: datetime
-module: double_field
-locked: false
-cardinality: -1
-translatable: true
-indexes: { }
-persist_with_no_fields: false
-custom_storage: false
diff --git a/config/paragraphs.paragraphs_type.achievement_list.yml b/config/paragraphs.paragraphs_type.achievement_list.yml
index 3a6e5020..0ba79a8e 100755
--- a/config/paragraphs.paragraphs_type.achievement_list.yml
+++ b/config/paragraphs.paragraphs_type.achievement_list.yml
@@ -3,16 +3,17 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
+ - ncms_paragraphs
- ncms_publisher
+third_party_settings:
+ ncms_paragraphs:
+ category: list
+ disabled: false
id: achievement_list
label: 'Achievement list'
icon_uuid: null
icon_default: null
description: 'A list of Achievements with an optional caption.'
behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
promoted_behavior:
enabled: true
diff --git a/config/paragraphs.paragraphs_type.article_card_list.yml b/config/paragraphs.paragraphs_type.article_card_list.yml
index 8a6d5059..24513da5 100755
--- a/config/paragraphs.paragraphs_type.article_card_list.yml
+++ b/config/paragraphs.paragraphs_type.article_card_list.yml
@@ -3,16 +3,17 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
+ - ncms_paragraphs
- ncms_publisher
+third_party_settings:
+ ncms_paragraphs:
+ category: content
+ disabled: true
id: article_card_list
label: 'Article Card List'
icon_uuid: null
icon_default: null
description: 'A list of Articles rendered as cards with thumbnails.'
behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
promoted_behavior:
enabled: true
diff --git a/config/paragraphs.paragraphs_type.article_list.yml b/config/paragraphs.paragraphs_type.article_list.yml
index 576e365f..a0487dfa 100755
--- a/config/paragraphs.paragraphs_type.article_list.yml
+++ b/config/paragraphs.paragraphs_type.article_list.yml
@@ -3,16 +3,17 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
+ - ncms_paragraphs
- ncms_publisher
+third_party_settings:
+ ncms_paragraphs:
+ category: list
+ disabled: false
id: article_list
label: 'Article list'
icon_uuid: null
icon_default: null
description: 'A list of links to articles with an optional title.'
behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
promoted_behavior:
enabled: true
diff --git a/config/paragraphs.paragraphs_type.bottom_figure_row.yml b/config/paragraphs.paragraphs_type.bottom_figure_row.yml
index facb9f2d..71e92179 100755
--- a/config/paragraphs.paragraphs_type.bottom_figure_row.yml
+++ b/config/paragraphs.paragraphs_type.bottom_figure_row.yml
@@ -3,18 +3,19 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
+ - ncms_paragraphs
- ncms_publisher
- paragraphs_viewmode
+third_party_settings:
+ ncms_paragraphs:
+ category: content
+ disabled: true
id: bottom_figure_row
-label: 'Bottom figure row'
+label: 'Bottom figure row (deprecated)'
icon_uuid: null
icon_default: null
description: 'A row of textual figures with custom labels.'
behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
paragraphs_viewmode_behavior:
enabled: true
override_mode: default
diff --git a/config/paragraphs.paragraphs_type.download_button.yml b/config/paragraphs.paragraphs_type.download_button.yml
index fdba0d77..ec547c5f 100755
--- a/config/paragraphs.paragraphs_type.download_button.yml
+++ b/config/paragraphs.paragraphs_type.download_button.yml
@@ -3,13 +3,14 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
+ - ncms_paragraphs
+third_party_settings:
+ ncms_paragraphs:
+ category: misc
+ disabled: false
id: download_button
label: 'Download button'
icon_uuid: null
icon_default: null
description: 'A button that points to a document somewhere on the internet.'
-behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
+behavior_plugins: { }
diff --git a/config/paragraphs.paragraphs_type.facts_and_figures.yml b/config/paragraphs.paragraphs_type.facts_and_figures.yml
index a0f79857..5b1e480f 100755
--- a/config/paragraphs.paragraphs_type.facts_and_figures.yml
+++ b/config/paragraphs.paragraphs_type.facts_and_figures.yml
@@ -3,16 +3,17 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
+ - ncms_paragraphs
- ncms_publisher
+third_party_settings:
+ ncms_paragraphs:
+ category: list
+ disabled: false
id: facts_and_figures
label: 'Facts and figures'
icon_uuid: null
icon_default: null
description: 'A list of facts and figures composed of images with a text.'
behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
promoted_behavior:
enabled: true
diff --git a/config/paragraphs.paragraphs_type.further_reading.yml b/config/paragraphs.paragraphs_type.further_reading.yml
index fdc2d6e6..722c207e 100755
--- a/config/paragraphs.paragraphs_type.further_reading.yml
+++ b/config/paragraphs.paragraphs_type.further_reading.yml
@@ -3,16 +3,17 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
+ - ncms_paragraphs
- ncms_publisher
+third_party_settings:
+ ncms_paragraphs:
+ category: misc
+ disabled: false
id: further_reading
label: 'Further reading'
icon_uuid: null
icon_default: null
description: 'A list of links to futher information related to the topics of the article.'
behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
promoted_behavior:
enabled: true
diff --git a/config/paragraphs.paragraphs_type.heading.yml b/config/paragraphs.paragraphs_type.heading.yml
index 385be458..adda1c16 100755
--- a/config/paragraphs.paragraphs_type.heading.yml
+++ b/config/paragraphs.paragraphs_type.heading.yml
@@ -3,13 +3,14 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
+ - ncms_paragraphs
+third_party_settings:
+ ncms_paragraphs:
+ category: content
+ disabled: false
id: heading
label: Heading
icon_uuid: null
icon_default: null
description: 'A generic heading.'
-behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
+behavior_plugins: { }
diff --git a/config/paragraphs.paragraphs_type.image_with_text.yml b/config/paragraphs.paragraphs_type.image_with_text.yml
index 4635d01f..c059aeb8 100755
--- a/config/paragraphs.paragraphs_type.image_with_text.yml
+++ b/config/paragraphs.paragraphs_type.image_with_text.yml
@@ -3,13 +3,14 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
+ - ncms_paragraphs
+third_party_settings:
+ ncms_paragraphs:
+ category: media
+ disabled: false
id: image_with_text
label: 'Image with text'
icon_uuid: null
icon_default: null
description: 'An image with a text.'
-behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
+behavior_plugins: { }
diff --git a/config/paragraphs.paragraphs_type.infographic.yml b/config/paragraphs.paragraphs_type.infographic.yml
index 2bca4b61..6e460b54 100644
--- a/config/paragraphs.paragraphs_type.infographic.yml
+++ b/config/paragraphs.paragraphs_type.infographic.yml
@@ -3,13 +3,14 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
+ - ncms_paragraphs
+third_party_settings:
+ ncms_paragraphs:
+ category: media
+ disabled: false
id: infographic
label: Infographic
icon_uuid: null
icon_default: null
description: 'An infographic media'
-behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
+behavior_plugins: { }
diff --git a/config/paragraphs.paragraphs_type.interactive_content.yml b/config/paragraphs.paragraphs_type.interactive_content.yml
index fd57a7db..86f956a2 100755
--- a/config/paragraphs.paragraphs_type.interactive_content.yml
+++ b/config/paragraphs.paragraphs_type.interactive_content.yml
@@ -3,16 +3,17 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
+ - ncms_paragraphs
- ncms_publisher
+third_party_settings:
+ ncms_paragraphs:
+ category: widget
+ disabled: false
id: interactive_content
label: 'Interactive content'
icon_uuid: null
icon_default: null
description: 'Interactive content like an infographic, a map or a table.'
behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
promoted_behavior:
enabled: true
diff --git a/config/paragraphs.paragraphs_type.interactive_content_2_columns.yml b/config/paragraphs.paragraphs_type.interactive_content_2_columns.yml
index cafdf715..9b16f6f5 100644
--- a/config/paragraphs.paragraphs_type.interactive_content_2_columns.yml
+++ b/config/paragraphs.paragraphs_type.interactive_content_2_columns.yml
@@ -4,6 +4,11 @@ status: true
dependencies:
module:
- layout_paragraphs
+ - ncms_paragraphs
+third_party_settings:
+ ncms_paragraphs:
+ category: widget
+ disabled: false
id: interactive_content_2_columns
label: 'Interactive content (2 columns)'
icon_uuid: null
diff --git a/config/paragraphs.paragraphs_type.photo_gallery.yml b/config/paragraphs.paragraphs_type.photo_gallery.yml
index ff3eff6a..74af0d56 100755
--- a/config/paragraphs.paragraphs_type.photo_gallery.yml
+++ b/config/paragraphs.paragraphs_type.photo_gallery.yml
@@ -3,16 +3,14 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
- - ncms_publisher
+ - ncms_paragraphs
+third_party_settings:
+ ncms_paragraphs:
+ category: media
+ disabled: false
id: photo_gallery
label: 'Photo Gallery'
icon_uuid: null
icon_default: null
description: 'A photo gallery with up to 4 images.'
-behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
- promoted_behavior:
- enabled: true
+behavior_plugins: { }
diff --git a/config/paragraphs.paragraphs_type.separator.yml b/config/paragraphs.paragraphs_type.separator.yml
index a061d9cf..f31bb184 100755
--- a/config/paragraphs.paragraphs_type.separator.yml
+++ b/config/paragraphs.paragraphs_type.separator.yml
@@ -3,13 +3,14 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
+ - ncms_paragraphs
+third_party_settings:
+ ncms_paragraphs:
+ category: misc
+ disabled: false
id: separator
label: Separator
icon_uuid: null
icon_default: null
description: 'A horizontal rule separating two sections of content.'
-behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
+behavior_plugins: { }
diff --git a/config/paragraphs.paragraphs_type.story.yml b/config/paragraphs.paragraphs_type.story.yml
index de9909c9..8aeaec41 100755
--- a/config/paragraphs.paragraphs_type.story.yml
+++ b/config/paragraphs.paragraphs_type.story.yml
@@ -3,16 +3,14 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
- - ncms_publisher
+ - ncms_paragraphs
+third_party_settings:
+ ncms_paragraphs:
+ category: embed
+ disabled: false
id: story
label: Story
icon_uuid: null
icon_default: null
description: 'A story (ex: a story from the field).'
-behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
- promoted_behavior:
- enabled: true
+behavior_plugins: { }
diff --git a/config/paragraphs.paragraphs_type.sub_article.yml b/config/paragraphs.paragraphs_type.sub_article.yml
index b3d57b2e..4bcd8fb3 100755
--- a/config/paragraphs.paragraphs_type.sub_article.yml
+++ b/config/paragraphs.paragraphs_type.sub_article.yml
@@ -3,16 +3,14 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
- - ncms_publisher
+ - ncms_paragraphs
+third_party_settings:
+ ncms_paragraphs:
+ category: embed
+ disabled: false
id: sub_article
label: Sub-article
icon_uuid: null
icon_default: null
description: 'A paragraph showing a sub-article.'
-behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
- promoted_behavior:
- enabled: true
+behavior_plugins: { }
diff --git a/config/paragraphs.paragraphs_type.text.yml b/config/paragraphs.paragraphs_type.text.yml
index 7235869a..a78311af 100755
--- a/config/paragraphs.paragraphs_type.text.yml
+++ b/config/paragraphs.paragraphs_type.text.yml
@@ -3,16 +3,17 @@ langcode: en
status: true
dependencies:
module:
- - layout_paragraphs
+ - ncms_paragraphs
- ncms_publisher
+third_party_settings:
+ ncms_paragraphs:
+ category: content
+ disabled: false
id: text
label: Text
icon_uuid: null
icon_default: null
description: 'A text with a rich editor to allow basic formatting like bold, italic, list and links.'
behavior_plugins:
- layout_paragraphs:
- enabled: false
- available_layouts: { }
promoted_behavior:
enabled: true
diff --git a/config/paragraphs.paragraphs_type.top_figures.yml b/config/paragraphs.paragraphs_type.top_figures.yml
new file mode 100644
index 00000000..61ea04e1
--- /dev/null
+++ b/config/paragraphs.paragraphs_type.top_figures.yml
@@ -0,0 +1,19 @@
+uuid: 42ca3952-bcb5-40ed-89ec-39ff9ef0a4f0
+langcode: en
+status: true
+dependencies:
+ module:
+ - ncms_paragraphs
+ - ncms_publisher
+third_party_settings:
+ ncms_paragraphs:
+ category: content
+ disabled: false
+id: top_figures
+label: 'Top figures'
+icon_uuid: null
+icon_default: null
+description: 'A set of label/value pairs to show top figures'
+behavior_plugins:
+ promoted_behavior:
+ enabled: true
diff --git a/config/paragraphs.paragraphs_type.top_figures_small.yml b/config/paragraphs.paragraphs_type.top_figures_small.yml
new file mode 100644
index 00000000..c164ea2b
--- /dev/null
+++ b/config/paragraphs.paragraphs_type.top_figures_small.yml
@@ -0,0 +1,19 @@
+uuid: 00352a35-f75b-43bb-aa8a-8dfb622949dc
+langcode: en
+status: true
+dependencies:
+ module:
+ - ncms_paragraphs
+ - ncms_publisher
+third_party_settings:
+ ncms_paragraphs:
+ category: content
+ disabled: false
+id: top_figures_small
+label: 'Top figures (small)'
+icon_uuid: null
+icon_default: null
+description: 'A set of label/value pairs to show top figures in a less prominent way'
+behavior_plugins:
+ promoted_behavior:
+ enabled: true
diff --git a/config/views.view.trash_nodes.yml b/config/views.view.trash_nodes.yml
index 483aad6b..02c81010 100644
--- a/config/views.view.trash_nodes.yml
+++ b/config/views.view.trash_nodes.yml
@@ -719,6 +719,7 @@ display:
- 'languages:language_interface'
- url
- url.query_args
+ - user
- 'user.node_grants:view'
- user.permissions
tags:
@@ -763,6 +764,7 @@ display:
- 'languages:language_interface'
- url
- url.query_args
+ - user
- 'user.node_grants:view'
- user.permissions
tags:
diff --git a/html/modules/custom/gho_fields/gho_fields.deploy.php b/html/modules/custom/gho_fields/gho_fields.deploy.php
index 0403c990..771a1227 100644
--- a/html/modules/custom/gho_fields/gho_fields.deploy.php
+++ b/html/modules/custom/gho_fields/gho_fields.deploy.php
@@ -5,6 +5,9 @@
* Post update functions for GHO Fields.
*/
+use Drupal\Core\Field\FieldItemInterface;
+use Drupal\paragraphs\ParagraphInterface;
+
/**
* Copy data from old fields to new fields.
*/
@@ -79,3 +82,125 @@ function gho_fields_deploy_migrate_bottom_figure_rows() {
}
}
}
+
+/**
+ * Migrate bottom figure row paragraph types to the new paragraph types.
+ */
+function gho_fields_deploy_migrate_bottom_figure_rows_paragraphs_to_top_figures() {
+ $entity_type_manager = \Drupal::entityTypeManager();
+ $storage = $entity_type_manager->getStorage('paragraph');
+
+ /** @var \Drupal\paragraphs\ParagraphInterface[] $paragraphs */
+ $paragraphs = $storage->loadByProperties([
+ 'type' => ['bottom_figure_row'],
+ ]);
+ foreach ($paragraphs as $paragraph) {
+ if (!$paragraph->hasField('field_figures') || $paragraph->get('field_figures')->isEmpty()) {
+ $paragraph->delete();
+ continue;
+ }
+
+ $view_mode = $paragraph->getBehaviorSetting('paragraphs_viewmode_behavior', 'view_mode');
+ $target_bundle = $view_mode == 'top_figures' ? 'top_figures' : 'top_figures_small';
+
+ $field_config = $entity_type_manager
+ ->getStorage('field_config')
+ ->load($paragraph->getEntityTypeId() . '.' . $target_bundle . '.field_figures');
+ $max_figures = $field_config->getThirdPartySetting('field_config_cardinality', 'cardinality_config');
+ foreach ($paragraph->getTranslationLanguages() as $language) {
+ if ($language->isDefault()) {
+ continue;
+ }
+ if (!$paragraph->hasTranslation($language->getId())) {
+ continue;
+ }
+
+ $translated_paragraph = $paragraph->getTranslation($language->getId());
+ $figures = $translated_paragraph->get('field_figures');
+ $figures->filter(function (FieldItemInterface $figure) use ($language) {
+ return $figure->getLangcode() == $language->getId();
+ });
+
+ if (!$figures || $figures->isEmpty()) {
+ $translated_paragraph->delete();
+ continue;
+ }
+
+ if ($figures->count() > $max_figures) {
+ continue;
+ }
+
+ /** @var \Drupal\paragraphs\ParagraphInterface $top_figures_paragraph */
+ $top_figures_paragraph = ghi_fields_create_top_figures_from_paragraph($translated_paragraph, $target_bundle);
+ if ($top_figures_paragraph) {
+ $translated_paragraph->delete();
+ }
+ }
+
+ $figures = $paragraph->get('field_figures');
+ $figures->filter(function (FieldItemInterface $figure) use ($paragraph) {
+ return $figure->getLangcode() == $paragraph->language()->getId();
+ });
+ if ($figures->count() > $max_figures) {
+ continue;
+ }
+
+ $top_figures_paragraph = ghi_fields_create_top_figures_from_paragraph($paragraph, $target_bundle);
+ if ($top_figures_paragraph) {
+ $paragraph->delete();
+ }
+ }
+}
+
+/**
+ * Create a top figures paragraph from a bottom_figure_row paragraph.
+ *
+ * @param \Drupal\paragraphs\ParagraphInterface $paragraph
+ * The original bottom_figure_row paragraph.
+ * @param string $bundle
+ * The new paragraph type, either top_figures or top_figures_small.
+ *
+ * @return \Drupal\paragraphs\ParagraphInterface
+ * The newly created paragraph.
+ */
+function ghi_fields_create_top_figures_from_paragraph(ParagraphInterface $paragraph, $bundle) {
+ if ($paragraph->bundle() != 'bottom_figure_row') {
+ return NULL;
+ }
+ $entity_type_manager = \Drupal::entityTypeManager();
+ $storage = $entity_type_manager->getStorage('paragraph');
+ /** @var \Drupal\paragraphs\ParagraphInterface $top_figures_paragraph */
+ $top_figures_paragraph = $storage->create([
+ 'type' => $bundle,
+ 'status' => $paragraph->isPublished(),
+ 'parent_id' => $paragraph->get('parent_id')->value,
+ 'parent_type' => $paragraph->get('parent_type')->value,
+ 'parent_field_name' => $paragraph->get('parent_field_name')->value,
+ 'field_figures' => $paragraph->get('field_figures')->getValue(),
+ 'field_dataset' => $paragraph->get('field_dataset')->getValue(),
+ ]);
+ $top_figures_paragraph->setBehaviorSettings('promoted_behavior', $paragraph->getAllBehaviorSettings()['promoted_behavior'] ?? []);
+ $top_figures_paragraph->setBehaviorSettings('ncms_paragraphs', [
+ 'replaces' => $paragraph->uuid(),
+ ]);
+ $top_figures_paragraph->setSyncing(TRUE);
+ $top_figures_paragraph->save();
+
+ $parent_field_name = $paragraph->get('parent_field_name')->value;
+ $parent = $paragraph->getParentEntity();
+ $values = $parent->get($parent_field_name)->getValue();
+
+ foreach ($values as &$item) {
+ if ($item['target_id'] != $paragraph->id()) {
+ continue;
+ }
+ $item['target_id'] = $top_figures_paragraph->id();
+ $item['target_revision_id'] = $top_figures_paragraph->getRevisionId();
+ }
+ $parent->get($parent_field_name)->setValue($values);
+ $parent->setNewRevision(FALSE);
+ $parent->setSyncing(TRUE);
+ $parent->save();
+
+ return $top_figures_paragraph;
+}
diff --git a/html/modules/custom/gho_figures/README.md b/html/modules/custom/gho_figures/README.md
deleted file mode 100644
index cb4618bc..00000000
--- a/html/modules/custom/gho_figures/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-HPC Content Module - Figures Module
-=============================================
-
-This module provides a form (/admin/content/needs-and-requirements/import) to
-import a spreadsheet with the "needs and requirements" figures (also known as
-"top figures").
-
-This requires the "Needs and requirements" vocabulary.
diff --git a/html/modules/custom/gho_figures/composer.json b/html/modules/custom/gho_figures/composer.json
deleted file mode 100644
index eba9bf6b..00000000
--- a/html/modules/custom/gho_figures/composer.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "name": "unocha/gho_figures",
- "description": "HPC Content Module Figures Import.",
- "type": "drupal-module",
- "authors": [
- {
- "name": "UNOCHA"
- }
- ],
- "keywords": [
- "Drupal",
- "Spreadsheet",
- "Excel"
- ],
- "require": {
- "phpoffice/phpspreadsheet": "^1.14.1"
- },
- "minimum-stability": "dev",
- "prefer-stable": true
-}
diff --git a/html/modules/custom/gho_figures/gho_figures.info.yml b/html/modules/custom/gho_figures/gho_figures.info.yml
deleted file mode 100644
index ce1cd223..00000000
--- a/html/modules/custom/gho_figures/gho_figures.info.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-type: module
-name: GHO Figures
-description: 'DEPRECATED Figures import handling (ex: needs and requirements).'
-package: gho
-core_version_requirement: ^9.3 || ^10
diff --git a/html/modules/custom/gho_figures/gho_figures.links.action.yml b/html/modules/custom/gho_figures/gho_figures.links.action.yml
deleted file mode 100644
index 270a8b2f..00000000
--- a/html/modules/custom/gho_figures/gho_figures.links.action.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-gho_figures.import_needs_and_requirements:
- title: 'Import needs and requirements figures'
- route_name: gho_figures.import_needs_and_requirements
- appears_on:
- - view.needs_and_requirements.page_needs_and_requirements
diff --git a/html/modules/custom/gho_figures/gho_figures.module b/html/modules/custom/gho_figures/gho_figures.module
deleted file mode 100644
index 1976e0aa..00000000
--- a/html/modules/custom/gho_figures/gho_figures.module
+++ /dev/null
@@ -1,6 +0,0 @@
- [
- 'mandatory' => TRUE,
- 'field' => 'name',
- 'label' => '',
- ],
- 'people in need' => [
- 'mandatory' => TRUE,
- 'field' => 'field_people_in_need',
- 'label' => 'People in need',
- 'preprocess' => [
- '\Drupal\gho_figures\Form\GhoFiguresImportNeedsAndRequirementsFiguresForm::preprocessNumber',
- ],
- 'table_display' => [
- '\Drupal\gho_figures\Form\GhoFiguresImportNeedsAndRequirementsFiguresForm::displayNumber',
- ],
- ],
- 'people targeted' => [
- 'mandatory' => TRUE,
- 'field' => 'field_people_targeted',
- 'label' => 'People targeted',
- 'preprocess' => [
- '\Drupal\gho_figures\Form\GhoFiguresImportNeedsAndRequirementsFiguresForm::preprocessNumber',
- ],
- 'table_display' => [
- '\Drupal\gho_figures\Form\GhoFiguresImportNeedsAndRequirementsFiguresForm::displayNumber',
- ],
- ],
- 'requirements (us$)' => [
- 'mandatory' => TRUE,
- 'field' => 'field_requirements',
- 'label' => 'Requirements (US$)',
- 'preprocess' => [
- '\Drupal\gho_figures\Form\GhoFiguresImportNeedsAndRequirementsFiguresForm::preprocessNumber',
- ],
- 'table_display' => [
- '\Drupal\gho_figures\Form\GhoFiguresImportNeedsAndRequirementsFiguresForm::displayNumber',
- ],
- ],
- ];
-
- /**
- * Database connection.
- *
- * @var \Drupal\Core\Database\Connection
- */
- protected $database;
-
- /**
- * File system service.
- *
- * @var \Drupal\Core\File\FileSystemInterface
- */
- protected $fileSystem;
-
- /**
- * Configuration Factory.
- *
- * @var \Drupal\Core\Config\ConfigFactory
- */
- protected $configFactory;
-
- /**
- * Entity type manager service.
- *
- * @var \Drupal\Core\Entity\EntityTypeManagerInterface
- */
- protected $entityTypeManager;
-
- /**
- * Messenger service.
- *
- * @var \Drupal\Core\Messenger\MessengerInterface
- */
- protected $messenger;
-
- /**
- * Class constructor.
- *
- * @param \Drupal\Core\Database\Connection $database
- * Database connection.
- * @param \Drupal\Core\File\FileSystemInterface $file_system
- * File system service.
- * @param \Drupal\Core\Config\ConfigFactory $config_factory
- * Config factory.
- * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
- * Entity type manager service.
- * @param \Drupal\Core\Messenger\MessengerInterface $messenger
- * Messenger service.
- */
- public function __construct(Connection $database, FileSystemInterface $file_system, ConfigFactory $config_factory, EntityTypeManagerInterface $entity_type_manager, MessengerInterface $messenger) {
- $this->database = $database;
- $this->fileSystem = $file_system;
- $this->configFactory = $config_factory;
- $this->entityTypeManager = $entity_type_manager;
- $this->messenger = $messenger;
- }
-
- /**
- * {@inheritdoc}
- */
- public static function create(ContainerInterface $container) {
- return new static(
- $container->get('database'),
- $container->get('file_system'),
- $container->get('config.factory'),
- $container->get('entity_type.manager'),
- $container->get('messenger')
- );
- }
-
- /**
- * Get a setting for the gho_figures module.
- *
- * @param string $setting
- * Setting name.
- * @param mixed $default
- * Default value for the setting.
- *
- * @return mixed
- * Value for the setting.
- */
- public function getSetting($setting, $default = NULL) {
- static $settings;
- if (!isset($settings)) {
- $settings = $this->configFactory->get('gho_figures.settings');
- }
- return $settings->get($setting) ?? $default;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getFormId() {
- return 'gho_figures_import_needs_and_requirements_figures_form';
- }
-
- /**
- * {@inheritdoc}
- */
- public function buildForm(array $form, FormStateInterface $form_state) {
- $form['actions'] = [
- '#type' => 'actions',
- // Ensure it's at the bottom of the list.
- '#weight' => 10,
- ];
-
- if ($form_state->has('step') && $form_state->get('step') == 2) {
- return self::buildFormStepTwo($form, $form_state);
- }
- else {
- return self::buildFormStepOne($form, $form_state);
- }
- }
-
- /**
- * Build the first step of the form with the file upload.
- *
- * @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
- * Modified form.
- */
- public function buildFormStepOne(array $form, FormStateInterface $form_state) {
- $form_state->set('step', 1);
-
- $form['description'] = [
- '#type' => 'item',
- '#title' => $this->t('Step 1: File Upload
'),
- ];
-
- $form['tips'] = [
- '#type' => 'item',
- '#markup' => $this->t('
- Tips
-
- - Make sure the spreadsheet contains those 4 columns:
-
- - "Name" column (with overviews and appeals; assumed to be the first column)
- - People in need
- - People targeted
- - Requirements (US$)
-
-
- - Make sure all the figures are non formatted numbers (ex: 1200000 not 1.2 million)
- - Use 0, -1, - or TBC to indicate a missing value
-
- '),
- ];
-
- $form['file'] = [
- '#type' => 'managed_file',
- '#title' => $this->t('Spreadsheet file'),
- '#description' => $this->t('Spreadsheet file with the figures. Accepted formats are: xls, xlsx, ods, csv.'),
- '#required' => TRUE,
- '#default_value' => $form_state->getValue('file'),
- '#upload_location' => 'temporary://figures-import',
- '#upload_validators' => [
- 'file_validate_extensions' => ['xls xlsx ods csv'],
- ],
- ];
-
- // Proceed to step 2.
- $form['actions']['next'] = [
- '#type' => 'submit',
- '#button_type' => 'primary',
- '#value' => $this->t('Next'),
- '#submit' => ['::submitFormStepOne'],
- '#validate' => ['::validateFormStepOne'],
- ];
-
- return $form;
- }
-
- /**
- * Validate the first step of the form.
- *
- * Here we also parse the spreadsheet file and extract the figures.
- *
- * @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 validateFormStepOne(array &$form, FormStateInterface $form_state) {
- $fids = $form_state->getValue('file');
- if (!empty($fids)) {
- // Load the file object and get its real path as that's what
- // PHPSpreadsheet expects.
- $file = $this->entityTypeManager->getStorage('file')->load(reset($fids));
- $path = $this->fileSystem->realpath($file->getFileUri());
-
- // Maximum number of rows that can be parsed to limit memory consumption.
- // In our case, there will be probably be less than 100 rows.
- $max_rows = $this->getSetting('max_rows', 9999);
-
- // Extract the figures from the spreadsheet.
- $figures = static::parseSpreadsheet($path, $max_rows);
- if (empty($figures['figures']) && !empty($figures['errors'])) {
- $form_state->setErrorByName('file', $this->t("Unable to extract figures from the spreadsheet: \n@errors.", [
- '@errors' => static::formatList($figures['errors']),
- ]));
- }
- else {
- $form_state->set('figures', $figures);
- }
- }
- }
-
- /**
- * Submit the first step of the form.
- *
- * @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 submitFormStepOne(array &$form, FormStateInterface $form_state) {
- $form_state
- ->set('first_step_values', ['file' => $form_state->getValue('file')])
- ->set('step', 2)
- ->setRebuild(TRUE);
- }
-
- /**
- * Build the second step of the form with the confirmation.
- *
- * @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
- * Modified form.
- */
- public function buildFormStepTwo(array $form, FormStateInterface $form_state) {
- $form_state->set('step', 2);
-
- $form['description'] = [
- '#type' => 'item',
- '#title' => $this->t('Step 2: Confirmation
'),
- ];
-
- // Display the table with the list of figures to import.
- $figures = $form_state->get('figures');
- if (!empty($figures['figures'])) {
- // Maximum number of figures to show.
- $count = count($figures['figures']);
- $headers = static::getTableHeaders($figures['columns']);
- $rows = static::getTableRows($headers, $figures['figures']);
-
- $form['figure-list'] = [
- '#type' => 'details',
- '#title' => $this->formatPlural($count,
- '@count figure to import',
- '@count figures to import'
- ),
- 'table' => [
- '#type' => 'table',
- '#header' => $headers,
- '#rows' => $rows,
- ],
- ];
- }
-
- // Display the errors detected while parsing the spreadsheet.
- if (!empty($figures['errors'])) {
- $count = count($figures['errors']);
-
- $form['error-list'] = [
- '#type' => 'details',
- '#title' => $this->formatPlural($count,
- '@count parsing error',
- '@count parsing errors'
- ),
- 'list' => [
- '#theme' => 'item_list',
- '#list_type' => 'ul',
- '#items' => $figures['errors'],
- ],
- ];
- }
-
- // Button to go back to the step 1.
- $form['actions']['back'] = [
- '#type' => 'submit',
- '#value' => $this->t('Back'),
- '#submit' => ['::cancelFormStepTwo'],
- // Prevent validation errors as we are going back and the values from
- // the step 2 should be ignored then.
- '#limit_validation_errors' => [],
- ];
-
- // Submit the form, actually replacing the figures.
- // We default to base form submit and validation callbacks.
- $form['actions']['submit'] = [
- '#type' => 'submit',
- '#button_type' => 'primary',
- '#value' => $this->t('Import figures'),
- ];
-
- return $form;
- }
-
- /**
- * Get the figures table headers to display in the import confirmation step.
- *
- * @param array $columns
- * Array of Header columns with the names as keys.
- *
- * @return array
- * List of table headers.
- */
- public static function getTableHeaders(array $columns) {
- $headers = [];
-
- foreach (static::$columns as $name => $definition) {
- if (isset($columns[$name])) {
- $headers[$name] = $definition['label'];
- }
- }
-
- return $headers;
- }
-
- /**
- * Get the figures table rows to display in the import confirmation step.
- *
- * @param array $headers
- * List of header column names.
- * @param array $figures
- * List of figures data extracted from the spreadsheet.
- *
- * @return array
- * Table rows. Each row contains cells for each column. Each cell can be
- * either a string or a render array.
- */
- public static function getTableRows(array $headers, array $figures) {
- $rows = [];
- foreach ($figures as $data) {
- $row = [];
- foreach ($headers as $name => $label) {
- if (isset(static::$columns[$name]['table_display'])) {
- $row[$name] = static::call(static::$columns[$name]['table_display'] + [
- 'name' => $name,
- ], $data);
- }
- else {
- $row[$name] = $data[$name] ?? '';
- }
- }
- $rows[] = $row;
- }
- return $rows;
- }
-
- /**
- * Return to the first step.
- *
- * @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 cancelFormStepTwo(array &$form, FormStateInterface $form_state) {
- $form_state
- ->setValues($form_state->get('first_step_values'))
- ->set('step', 1)
- ->setRebuild(TRUE);
- }
-
- /**
- * Validate the form (after step 2).
- *
- * @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 validateForm(array &$form, FormStateInterface $form_state) {
- // Nothing to validate.
- }
-
- /**
- * Submit the form (after step 2).
- *
- * Generate the batch to update the "needs and requirements" figures.
- *
- * @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) {
- $batch_size = $this->getSetting('batch_size', 50);
-
- // We will (optionally) delete the old figures and create the new ones
- // in batches as that takes time.
- $figures = $form_state->get('figures');
- if (!empty($figures['figures'])) {
- $operations = [];
-
- // We log the parsing errors (if any) during the batch process because
- // otherwise it slows down too much the parsing. Also it makes more sense
- // to log them when actually proceeding with the import.
- $operations[] = [
- __CLASS__ . '::logParsingInfo',
- [$figures['path'], $figures['errors']],
- ];
-
- // Extract the name of the figures to update/create.
- $names = array_map(function ($figure) {
- return $figure['name'];
- }, $figures['figures']);
-
- // Create batch steps to delete figure terms not present in the list.
- $records = $this->entityTypeManager->getStorage('taxonomy_term')->getQuery()
- ->condition('vid', 'needs_and_requirements')
- ->condition('name', $names, 'NOT IN')
- ->accessCheck(FALSE)
- ->execute();
-
- if (!empty($records)) {
- foreach (array_chunk($records, $batch_size) as $ids) {
- $operations[] = [__CLASS__ . '::deleteFigureTerms', [$ids]];
- }
- }
-
- // Create batch steps to create the new figure terms.
- foreach (array_chunk($figures['figures'], $batch_size) as $data) {
- $operations[] = [__CLASS__ . '::createFigureTerms', [$data]];
- }
-
- $batch = [
- 'title' => $this->t('Importing figures...'),
- 'operations' => $operations,
- 'finished' => __CLASS__ . '::batchFinished',
- ];
- batch_set($batch);
- }
- else {
- $this->messenger->addWarning($this->t('No figures to import. Existing figures will not be updated.'));
- }
- }
-
- /**
- * Log the parsing errors.
- *
- * @param string $path
- * The path of the spreadsheet.
- * @param array $errors
- * The parsing errors.
- * @param array $context
- * The batch context.
- */
- public static function logParsingInfo($path, array $errors, array &$context) {
- // Log the filename to help make sense of the parsing errors.
- static::log(new FormattableMarkup('Parsed spreadsheet: @path', [
- '@path' => $path,
- ]), 'info');
-
- // We log the errors as notices because they don't impact the whole site.
- foreach ($errors as $error) {
- static::log($error, 'notice');
- }
-
- // Set a message for the current batch and update the progress status.
- $count = count($errors);
- $context['message'] = \Drupal::translation()->formatPlural($count,
- 'Logged @count parsing error.',
- 'Logged @count parsing errors.'
- );
- $context['results']['logged'][] = $count;
- }
-
- /**
- * Delete the "needs and requirements" figure terms for the given ids.
- *
- * Note: we don't actually delete the terms but wipe out their data and
- * unpublish them so that we can preserve any references to them in article
- * nodes for example, in case they are updated later on.
- *
- * @param array $ids
- * Term ids.
- * @param array $context
- * The batch context.
- */
- public static function deleteFigureTerms(array $ids, array &$context) {
- $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
-
- // Unpublish the terms that are not used.
- $terms = $storage->loadMultiple($ids);
- foreach ($terms as $term) {
- // Mark as unpublished.
- $term->status = 0;
- // Wipe out the values of the figures for the term.
- foreach (static::$columns as $field => $definition) {
- // The term name is a mandatory field that cannot be empty. It's also
- // the field we use when matching figures before creating a new one.
- if ($field !== 'name' && isset($definition['field'])) {
- $term->set($definition['field'], NULL);
- }
- }
- $term->save();
- }
-
- // Set a message for the current batch and update the progress status.
- $count = count($ids);
- $context['message'] = \Drupal::translation()->formatPlural($count,
- 'Deleted @count old figure.',
- 'Deleted @count old figures.'
- );
- $context['results']['deleted'][] = $count;
- }
-
- /**
- * Create new "needs and requirements" figure terms.
- *
- * Note: if a term already exists, we simply update it.
- *
- * @param array $figures
- * Figure data.
- * @param array $context
- * The batch context.
- */
- public static function createFigureTerms(array $figures, array &$context) {
- $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
-
- // Create a new term for each figure.
- foreach ($figures as $figure) {
- // Check if a term with the same name already exists and re-use it.
- $terms = $storage->loadByProperties([
- 'vid' => 'needs_and_requirements',
- 'name' => $figure['name'],
- ]);
-
- if (!empty($terms)) {
- $term = reset($terms);
- }
- else {
- $term = $storage->create(['vid' => 'needs_and_requirements']);
- }
-
- // Update the term fields.
- static::updateFigureTerm($term, $figure);
- // Mark as published.
- $term->status = 1;
- $term->save();
- }
-
- // Set a message for the current batch and update the progress status.
- $count = count($figures);
- $context['message'] = \Drupal::translation()->formatPlural($count,
- 'Created/updated @count new figure.',
- 'Created/updated @count new figures.'
- );
- $context['results']['created'][] = $count;
- }
-
- /**
- * Update a "needs and requirements" figure term's fields with new data.
- *
- * @param \Drupal\taxonomy\Entity\Term $term
- * Node to update.
- * @param array $data
- * New figure data.
- *
- * @todo review if we should get the node fields via ::getFields() and
- * reset all the fields for which there is no corresponding data.
- */
- public static function updateFigureTerm(Term $term, array $data) {
- foreach (static::$columns as $name => $definition) {
- if (!isset($definition['field'])) {
- continue;
- }
- $field = $definition['field'];
- $value = NULL;
- if (isset($data[$name])) {
- $value = $data[$name];
-
- if (isset($definition['process'])) {
- $value = static::call($definition['process'] + ['name' => $name], $value);
- }
- }
- $term->set($field, $value);
- }
- }
-
- /**
- * Display message after the batch import is finished.
- *
- * @param bool $success
- * Whether the batch process succeeeded or not.
- * @param array $results
- * Batch results.
- * @param array $operations
- * List of batch operations.
- */
- public static function batchFinished($success, array $results, array $operations) {
- if ($success) {
- if (!empty($results['deleted'])) {
- \Drupal::messenger()->addStatus(t('Deleted %deleted old figures.', [
- '%deleted' => array_sum($results['deleted']),
- ]));
- }
- if (!empty($results['created'])) {
- \Drupal::messenger()->addStatus(t('Created/updated %created figures.', [
- '%created' => array_sum($results['created']),
- ]));
- }
- }
- else {
- // @todo Show a more useful error message?
- \Drupal::messenger()->addError(t('No figures to import. Old figures were not deleted.'));
- }
- }
-
- /**
- * Get a taxonomy term ID. Create a new term if necessary.
- *
- * @param string $vocabulary
- * Taxonomy vocabulary.
- * @param string|array $name
- * Taxonomy term name or array of names.
- *
- * @return int|null
- * Id of the first term, newly created if doesn't already exists.
- */
- public static function getTermId($vocabulary, $name) {
- $multiple = is_array($name);
-
- $names = array_filter(array_map('trim', $multiple ? $name : [$name]));
-
- if (empty($names)) {
- return NULL;
- }
-
- $results = [];
- foreach ($names as $name) {
- $term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
-
- // Sanitize and truncate the term if necessary.
- $words = preg_split('/' . Unicode::PREG_CLASS_WORD_BOUNDARY . '/u', $name, -1, PREG_SPLIT_NO_EMPTY);
- if (count($words) > 10) {
- $name = implode(' ', array_slice($words, 0, 10)) . ' ...';
- }
-
- // Get any existing taxonomy term matching the given term name.
- $terms = $term_storage->loadByProperties([
- 'vid' => $vocabulary,
- 'name' => $name,
- ]);
-
- // Get the first existing term or create one.
- if (!empty($terms)) {
- $term = reset($terms);
- }
- else {
- $term = $term_storage->create([
- 'vid' => $vocabulary,
- 'name' => $name,
- ]);
- $term->save();
- }
- $results[] = $term->id();
- }
-
- return $multiple ? $results : reset($results);
- }
-
- /**
- * Extract figures from a spreadsheet.
- *
- * @param string $path
- * File path.
- * @param int $max_rows
- * Maximum number rows to parse.
- *
- * @return array
- * Associative array with the spreadsheet file path, the header columns,
- * figure list and potential parsing errors.
- */
- public static function parseSpreadsheet($path, $max_rows) {
- $columns = [];
- $figures = [];
- $errors = [];
-
- // We wrap this code in a try...catch because PHPSpreadsheet can throw
- // various exceptions when parsing a spreadsheet.
- try {
- // Get the worksheet to work with (pun intended).
- $sheet = static::getWorksheet($path);
-
- // Get the row to which will stop the parsing.
- $max_rows = min($sheet->getHighestDataRow(), $max_rows);
-
- // Parse the sheet, extracting figure data.
- $header_row_found = FALSE;
- foreach ($sheet->getRowIterator(1, $max_rows) as $row) {
- // Parse the row to see if it's the header one.
- if ($header_row_found === FALSE) {
- $data = static::parseHeaderRow($sheet, $row);
- // There are errors if the header row was found but some mandatory
- // columns are missing. In that case we abort the parsing.
- if (!empty($data['errors'])) {
- $errors = array_merge($errors, $data['errors']);
- break;
- }
- // Otherwise if the header row was found, we store the columns and
- // make sure we can start parsing the data. If not, we continue
- // looking for it.
- elseif (!empty($data['columns'])) {
- $columns = $data['columns'];
- $header_row_found = TRUE;
- }
- }
- // Parse a figure data row.
- else {
- $data = static::parseDataRow($columns, $sheet, $row);
- if (!empty($data['data'])) {
- // Log the errors only for "useful" rows with some data.
- if (!empty($data['errors'])) {
- $errors = array_merge($errors, $data['errors']);
- }
- // If there is an email, merge the data with the figure entry with
- // the same email address if any, otherwise create a new entry if
- // the data is "valid", meaning, it has all the mandatory fields.
- if (!empty($data['data']['name'])) {
- $figures[$data['data']['name']] = $data['data'];
- }
- }
- }
- }
- }
- catch (\Exception $exception) {
- $errors[] = $exception->getMessage();
- }
-
- return [
- 'path' => $path,
- 'columns' => $columns,
- 'figures' => $figures,
- 'errors' => $errors,
- ];
- }
-
- /**
- * Load a spreadsheet and return its first worksheet.
- *
- * @param string $path
- * File path.
- *
- * @return \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
- * First worksheet.
- */
- public static function getWorksheet($path) {
- if (!file_exists($path)) {
- throw new \Exception("The spreadsheet file doesn't exist.");
- }
-
- // Get the spreadsheet type.
- $filetype = IOFactory::identify($path);
-
- // Create the spreadsheet reader.
- $reader = IOFactory::createReader($filetype);
-
- // Start reading the file.
- $spreadsheet = $reader->load($path);
-
- // We only deal with the first sheet.
- return $spreadsheet->getActiveSheet();
- }
-
- /**
- * Parse a row, attempting to determine if its the header row.
- *
- * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $sheet
- * Spreadsheet worksheet.
- * @param \PhpOffice\PhpSpreadsheet\Worksheet\Row $row
- * Spreadsheet row.
- *
- * @return array
- * Empty array if the row is not the header row, otherwise, return an
- * associative array with the found columns which is an associative array
- * with the column name (header) as value and the column letter as value.
- * If the row is the header one but some mandatory columns are missing, the
- * returnning array will have an `errors` key with the list of errors.
- */
- public static function parseHeaderRow(Worksheet $sheet, Row $row) {
- $columns = [];
- foreach ($row->getCellIterator() as $index => $cell) {
- $value = mb_strtolower(static::getCellValue($sheet, $cell->getCoordinate()));
- // Fix malformed column names...
- $value = trim(preg_replace('/\s+/u', ' ', $value));
- if (isset($value, static::$columns[$value]) && !isset($columns[$value])) {
- $columns[$value] = $cell->getColumn();
- }
- // If the first column doesn't have a name we assume it is the "name"
- // column with the overviews and appeals.
- elseif ($index === 'A') {
- $columns['name'] = $cell->getColumn();
- }
- }
-
- if (count($columns) < 2) {
- return [];
- }
-
- // Validate mandatory columns.
- $errors = [];
- foreach (static::$columns as $name => $definition) {
- if (!static::checkMandatoryField($name, $definition, $columns)) {
- // We use TranslatableMarkup so that the error can be displayed
- // translated in the confirmation step.
- $errors[] = new TranslatableMarkup('Missing @column column.', [
- '@column' => $name,
- ]);
- }
- }
-
- return [
- 'columns' => $columns,
- 'errors' => $errors,
- ];
- }
-
- /**
- * Parse a row with figure data.
- *
- * @todo Check if we need to use getCalculatedValue() instead of getValue().
- *
- * @param array $columns
- * Associative array of header columns with their associated column index.
- * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $sheet
- * Spreadsheet worksheet.
- * @param \PhpOffice\PhpSpreadsheet\Worksheet\Row $row
- * Spreadsheet row.
- *
- * @return array
- * Associative array with the following keys:
- * - data: associative array mapping the column names to their values,
- * - errors: array with a list of parsing errors,
- * - valid: a flag to indicate that the data contains all the mandatory
- * fields.
- */
- public static function parseDataRow(array $columns, Worksheet $sheet, Row $row) {
- $index = $row->getRowIndex();
- $data = [];
- $errors = [];
- $valid = TRUE;
-
- // Get the data from the row foreach column with a recognized header.
- foreach ($columns as $name => $column) {
- $data[$name] = static::getCellValue($sheet, $column . $index);
- }
-
- // Skip if there no valid cell beside the name one as it's probably
- // not a data row.
- $skip = count(array_filter($data, function ($value, $name) {
- return $name !== 'name' && $value !== '';
- }, ARRAY_FILTER_USE_BOTH)) === 0;
-
- if ($skip) {
- return [];
- }
-
- // Process the row's data.
- foreach (static::$columns as $name => $definition) {
- if (isset($definition['preprocess'])) {
- if (!static::call($definition['preprocess'] + ['name' => $name], $data)) {
- $errors[$name] = new TranslatableMarkup('Invalid @column on row @row.', [
- '@column' => $name,
- '@row' => $index,
- ]);
- }
- }
- }
-
- // Check mandatory fields. This is not done in the loop above because
- // the data may change during preprocessing.
- foreach (static::$columns as $name => $definition) {
- if (!static::checkMandatoryField($name, $definition, $data)) {
- // No need to add different error messages for the same field, for
- // example if the field data was found invalid during preprocessing.
- if (!isset($errors[$name])) {
- $errors[$name] = new TranslatableMarkup('Missing @column on row @row.', [
- '@column' => $name,
- '@row' => $index,
- ]);
- }
- $valid = FALSE;
- }
- }
-
- return [
- 'data' => $data,
- 'errors' => array_values($errors),
- 'valid' => $valid,
- ];
- }
-
- /**
- * Get a cell value.
- *
- * This extracts the value of a cell. If the cell is merged with other cells
- * we extract the combine value for the whole merge range.
- *
- * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $sheet
- * Spreadsheet worksheet.
- * @param string $reference
- * Cell reference (ex: A1).
- *
- * @return string
- * Extracted value, defaulting to an empty string.
- */
- public static function getCellValue(Worksheet $sheet, $reference) {
- static $references;
- static $values;
-
- if (!isset($references, $values)) {
- [$references, $values] = static::extractMergedCells($sheet);
- }
-
- if (isset($references[$reference])) {
- return $values[$references[$reference]];
- }
- elseif ($sheet->getCellCollection()->has($reference)) {
- return trim($sheet->getCellCollection()->get($reference)->getValue());
- }
- return '';
- }
-
- /**
- * Extract the values for the merged cells.
- *
- * We store the merged cells references and the merge range values so that
- * we don't have to parse the merge ranges every time we try to get a cell
- * value. This speeds tremendously the spreadsheet parsing at the cost of
- * increased memory usage.
- *
- * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $sheet
- * Worksheet from which to extract the merged cells data.
- *
- * @return array
- * Array containing 2 elements: an associative array of the references of
- * the cells included in merge ranges mapped to to the range reference, and
- * an associative array mapping range references to their values.
- */
- public static function extractMergedCells(Worksheet $sheet) {
- $references = [];
- $values = [];
-
- foreach ($sheet->getMergeCells() as $range) {
- // Extract all the merged cell references and store their mapping to
- // the merge range. We don't copy directly the merge range value to
- // reduce memory usage.
- foreach (Coordinate::extractAllCellReferencesInRange($range) as $index => $reference) {
- // The first cell of the range is supposed to contain the value of the
- // range.
- // @see \PhpOffice\PhpSpreadsheet\Cell\Cell::isMergeRangeValueCell()
- if ($index === 0) {
- if ($sheet->getCellCollection()->has($reference)) {
- $values[$range] = trim($sheet->getCellCollection()->get($reference)->getValue());
- }
- else {
- $values[$range] = '';
- }
- }
- $references[$reference] = $range;
- }
- }
- return [$references, $values];
- }
-
- /**
- * Preprocess the duty station country field.
- *
- * This extracts the countries from the `country` field or from the
- * `duty station country` field if the former is empty.
- *
- * @param string $name
- * Field name.
- * @param array $data
- * Row data.
- *
- * @return bool
- * FALSE if the data was invalid, TRUE otherwise.
- */
- public static function preprocessNumber($name, array &$data) {
- if (isset($data[$name])) {
- $value = $data[$name];
- if ($value <= 0 || $value === '-' || strtoupper($value) === 'TBC') {
- $value = 0;
- }
- else {
- $options = ['options' => ['min_range' => 0]];
- $value = filter_var($data[$name], FILTER_VALIDATE_INT, $options);
- }
- // Remove the value if it's not a positive integer so that the row
- // will be removed and an error displayed.
- if ($value === FALSE) {
- unset($data[$name]);
- return FALSE;
- }
- else {
- $data[$name] = $value;
- return TRUE;
- }
- }
- return FALSE;
- }
-
- /**
- * Get a render array/string to display a formatted number (ex: 1.2 million).
- *
- * @param string $name
- * Field name.
- * @param array $data
- * Figure data.
- *
- * @return array|string
- * Render array or empty string.
- */
- public static function displayNumber($name, array $data) {
- if (!isset($data[$name])) {
- return '';
- }
- if ($data[$name] == 0) {
- return 'TBC';
- }
- // @todo use the same formatter as the one used to render the figures in
- // the frontend (ex: 1.2 million).
- return number_format($data[$name]);
- }
-
- /**
- * Helper method to display a list of items as a HTML list.
- *
- * @param array $items
- * List of strings.
- *
- * @return \Drupal\Component\Render\FormattableMarkup
- * FormattableMarkup object containing the HTML list that can be passed
- * as placeholder replacement to `t()`.
- */
- public static function formatList(array $items) {
- $html = '- ' . implode('
- ', $items) . '
';
- return new FormattableMarkup($html, []);
- }
-
- /**
- * Check of the given data contains the mandatory field data.
- *
- * @param string $name
- * Column name.
- * @param array $definition
- * Column definition.
- * @param array $data
- * Field data.
- *
- * @return bool
- * Whether the field is present or not.
- */
- public static function checkMandatoryField($name, array $definition, array $data) {
- return empty($definition['mandatory']) || isset($data[$name]);
- }
-
- /**
- * Call a function with the given arguments and data.
- *
- * @param array $arguments
- * Array with the callable function/method as first element and with the
- * rest as parameters to pass to the callable.
- * @param mixed $data
- * Additional data to pass to the callable. It is passed by reference and
- * may be modified by the callable.
- *
- * @return mixed
- * The result of the call.
- */
- public static function call(array $arguments, &$data) {
- $callable = array_shift($arguments);
- $arguments[] = &$data;
- return call_user_func_array($callable, array_values($arguments));
- }
-
- /**
- * Log a message.
- *
- * @param mixed $message
- * String-ish value. If the message is an instance of
- * \Drupal\Core\StringTranslation\TranslatableMarkup then we build a non
- * translated message as the logs are for internal information and it
- * doesn't make sense to have them in the display language of the current
- * user.
- * @param string $level
- * Log level.
- */
- public static function log($message, $level = 'info') {
- if ($message instanceof TranslatableMarkup) {
- $message = new FormattableMarkup($message->getUntranslatedString(), $message->getArguments());
- }
- \Drupal::logger('gho-figures-import')->log($level, $message);
- }
-
-}
diff --git a/html/modules/custom/gho_figures/src/config/install/gho_figures.settings.yml b/html/modules/custom/gho_figures/src/config/install/gho_figures.settings.yml
deleted file mode 100644
index 69987793..00000000
--- a/html/modules/custom/gho_figures/src/config/install/gho_figures.settings.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-# Maximum number of rows to parse in the spreadsheet.
-max_rows: 9999
-# Number of figures to delete/create per batch.
-batch_size: 50
diff --git a/html/modules/custom/ncms_graphql/ncms_graphql.info.yml b/html/modules/custom/ncms_graphql/ncms_graphql.info.yml
index dd38f9b0..ce6ca0ca 100644
--- a/html/modules/custom/ncms_graphql/ncms_graphql.info.yml
+++ b/html/modules/custom/ncms_graphql/ncms_graphql.info.yml
@@ -5,4 +5,5 @@ package: HPC Content Module
dependencies:
- graphql:graphql
- node:node
+ - ncms_paragraphs:ncms_paragraphs
core_version_requirement: ^9.3 || ^10
diff --git a/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/DataProducer/EntityConfiguration.php b/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/DataProducer/EntityConfiguration.php
index 59b5943b..c4752612 100644
--- a/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/DataProducer/EntityConfiguration.php
+++ b/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/DataProducer/EntityConfiguration.php
@@ -124,6 +124,9 @@ public function resolve(EntityInterface $entity, RefinableCacheableDependencyInt
];
}
}
+ if ($original_uuid = $entity->getBehaviorSetting('ncms_paragraphs', 'replaces')) {
+ $config['replaces'] = $original_uuid;
+ }
}
return Yaml::encode($this->mapObjectsToString($config));
});
diff --git a/html/modules/custom/ncms_paragraphs/config/schema/ncms_paragraphs.schema.yml b/html/modules/custom/ncms_paragraphs/config/schema/ncms_paragraphs.schema.yml
new file mode 100644
index 00000000..cc7d890f
--- /dev/null
+++ b/html/modules/custom/ncms_paragraphs/config/schema/ncms_paragraphs.schema.yml
@@ -0,0 +1,13 @@
+paragraphs.paragraphs_type.*.third_party.ncms_paragraphs:
+ type: config_entity
+ label: "Paragraph settings"
+ mapping:
+ category:
+ type: text
+ label: "Category"
+ disabled:
+ type: boolean
+ label: "Disabled"
+ replaces:
+ type: text
+ label: "Replaces (UUID)"
\ No newline at end of file
diff --git a/html/modules/custom/ncms_paragraphs/css/component_menu.css b/html/modules/custom/ncms_paragraphs/css/component_menu.css
new file mode 100644
index 00000000..b0157667
--- /dev/null
+++ b/html/modules/custom/ncms_paragraphs/css/component_menu.css
@@ -0,0 +1,16 @@
+.lpb-component-list {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+.lpb-component-list__group--menu {
+ --vertical-tabs-menu-width: 10rem;
+ min-width: 40rem;
+}
+.lpb-component-list .paragraph-category-links ul {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+.lpb-component-list .paragraph-category-links ul li {
+ list-style-type: none;
+}
\ No newline at end of file
diff --git a/html/modules/custom/ncms_ui/css/layout_paragraphs.css b/html/modules/custom/ncms_paragraphs/css/layout_paragraphs.css
similarity index 100%
rename from html/modules/custom/ncms_ui/css/layout_paragraphs.css
rename to html/modules/custom/ncms_paragraphs/css/layout_paragraphs.css
diff --git a/html/modules/custom/ncms_paragraphs/css/paragraph_edit_form.css b/html/modules/custom/ncms_paragraphs/css/paragraph_edit_form.css
new file mode 100644
index 00000000..fbb8f329
--- /dev/null
+++ b/html/modules/custom/ncms_paragraphs/css/paragraph_edit_form.css
@@ -0,0 +1,41 @@
+/* Move the status checkbox into the bottom pane of the modal. */
+form.layout-paragraphs-component-form > .field--name-status {
+ position: absolute;
+ bottom: 0;
+ z-index: 10;
+}
+
+form.layout-paragraphs-component-form table.field-multiple-table tbody td.tabledrag-cell {
+ vertical-align: middle;
+}
+
+/* Give the tab content some space. */
+form.layout-paragraphs-component-form .horizontal-tabs-panes {
+ margin-top: 1rem;
+}
+
+/* Adjustments for some multi value fields that. */
+form.layout-paragraphs-component-form .details-wrapper > .form-wrapper > div > .form-item--multiple .tabledrag-toggle-weight-wrapper {
+ margin-top: -2rem;
+}
+form.layout-paragraphs-component-form .details-wrapper > .form-wrapper > div > .form-item--multiple > .field-actions {
+ align-items: center;
+}
+form.layout-paragraphs-component-form .details-wrapper > .form-wrapper > div > .form-item--multiple > .field-actions > em.placeholder {
+ padding-left: 0.2rem;
+}
+
+form.layout-paragraphs-component-form .field--type-custom[data-drupal-selector='edit-field-figures-wrapper'] table.field-multiple-table tbody tr td:nth-child(2) {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+form.layout-paragraphs-component-form .field--type-custom[data-drupal-selector='edit-field-figures-wrapper'] table.field-multiple-table tbody tr td:nth-child(2) input.button {
+ height: fit-content;
+}
+form.layout-paragraphs-component-form .field--type-custom[data-drupal-selector='edit-field-figures-wrapper'] table.field-multiple-table th.tabledrag-hide {
+ display: none;
+}
+form.layout-paragraphs-component-form .field--type-custom[data-drupal-selector='edit-field-figures-wrapper'] table.field-multiple-table th.tabledrag-hide[style*="none"] ~ th {
+ display: none;
+}
\ No newline at end of file
diff --git a/html/modules/custom/ncms_paragraphs/ncms_paragraphs.info.yml b/html/modules/custom/ncms_paragraphs/ncms_paragraphs.info.yml
new file mode 100644
index 00000000..62141ba2
--- /dev/null
+++ b/html/modules/custom/ncms_paragraphs/ncms_paragraphs.info.yml
@@ -0,0 +1,8 @@
+name: Better paragraph support for the HPC Content Module
+type: module
+description: 'Provides entity classes for paragraphs.'
+package: HPC Content Module
+core_version_requirement: ^9.3 || ^10
+dependencies:
+ - paragraphs:paragraphs
+ - layout_paragraphs:layout_paragraphs
\ No newline at end of file
diff --git a/html/modules/custom/ncms_paragraphs/ncms_paragraphs.libraries.yml b/html/modules/custom/ncms_paragraphs/ncms_paragraphs.libraries.yml
new file mode 100644
index 00000000..b1272413
--- /dev/null
+++ b/html/modules/custom/ncms_paragraphs/ncms_paragraphs.libraries.yml
@@ -0,0 +1,12 @@
+paragraph_edit_form:
+ css:
+ component:
+ css/paragraph_edit_form.css: {}
+layout_paragraphs:
+ css:
+ component:
+ css/layout_paragraphs.css: {}
+component_menu:
+ css:
+ component:
+ css/component_menu.css: {}
\ No newline at end of file
diff --git a/html/modules/custom/ncms_paragraphs/ncms_paragraphs.module b/html/modules/custom/ncms_paragraphs/ncms_paragraphs.module
new file mode 100644
index 00000000..83736d52
--- /dev/null
+++ b/html/modules/custom/ncms_paragraphs/ncms_paragraphs.module
@@ -0,0 +1,121 @@
+getFormObject();
+ $paragraph = $form_object->getParagraph();
+
+ if ($paragraph instanceof NcmsParagraphInterface) {
+ $paragraph->entityFormAlter($form, $form_state);
+ }
+
+ // Don't trigger "Required" messages when canceling the add/edit form.
+ $form['actions']['cancel']['#limit_validation_errors'] = [];
+
+ // Add our library to improve the display.
+ $form['#attached']['library'][] = 'ncms_paragraphs/paragraph_edit_form';
+
+ // Move the behavior plugin from a stand-alone fieldset into existing field
+ // groups.
+ if (!empty($form['#fieldgroups']) && !empty($form['behavior_plugins'])) {
+
+ $form['behavior_plugins']['#parents'] = ['behavior_plugins'];
+ $form['behavior_plugins'] = $form_object->behaviorPluginsForm($form['behavior_plugins'], $form_state, $form);
+ unset($form['behavior_plugins']['#process']);
+
+ $group_name = 'group_settings';
+ if (empty($form['#fieldgroups'][$group_name])) {
+ $group = clone end($form['#fieldgroups']);
+ $group->children = ['behavior_plugins'];
+ $group->format_settings['formatter'] = 'close';
+ $group->weight = $group->weight + 1;
+ $group->group_name = $group_name;
+ $form['#fieldgroups'][$group->group_name] = $group;
+ }
+ else {
+ $form['#fieldgroups'][$group_name]->children[] = 'behavior_plugins';
+ }
+ $form['#fieldgroups'][$group_name]->label = t('Additional settings');
+ $form['#group_children']['behavior_plugins'] = $group_name;
+ }
+
+ if (!empty($form['field_dataset'])) {
+ // Hide the fieldset.
+ $form['field_dataset']['widget'][0]['#type'] = 'container';
+ }
+
+}
+
+/**
+ * Implements hook_preprocess_paragraph().
+ */
+function ncms_paragraphs_preprocess_paragraph(&$variables) {
+ $paragraph = $variables['paragraph'];
+ if ($paragraph instanceof NcmsParagraphInterface) {
+ $paragraph->preprocess($variables);
+ }
+}
+
+/**
+ * Implements hook_preprocess_layout_paragraphs_builder().
+ */
+function ncms_paragraphs_preprocess_layout_paragraphs_builder(&$variables) {
+ $variables['#attached']['library'][] = 'ncms_paragraphs/layout_paragraphs';
+}
+
+/**
+ * Implements hook_preprocess_layout_paragraphs_builder_component_menu().
+ */
+function ncms_paragraphs_preprocess_layout_paragraphs_builder_component_menu(&$variables) {
+ /** @var \Drupal\ncms_paragraphs\ComponentMenu $component_menu */
+ $component_menu = \Drupal::service('ncms_paragraphs.component_menu');
+ $component_menu->preprocessTemplate($variables);
+}
+
+/**
+ * Implements hook_theme_registry_alter().
+ */
+function ncms_paragraphs_theme_registry_alter(&$theme_registry) {
+ // Override layout-paragraphs-builder-component-menu.html.twig to support
+ // the changes in \Drupal\ncms_paragraphs\ComponentMenu::preprocessTemplate.
+ $module_path = \Drupal::service('extension.path.resolver')->getPath('module', 'ncms_paragraphs');
+ $theme_registry['layout_paragraphs_builder_component_menu']['theme path'] = $module_path;
+ $theme_registry['layout_paragraphs_builder_component_menu']['path'] = $module_path . '/templates';
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Implements our third-party settings into the paragraphs type form.
+ */
+function ncms_paragraphs_form_paragraphs_type_edit_form_alter(&$form, FormStateInterface $form_state) {
+ /** @var \Drupal\ncms_paragraphs\ParagraphsTypeSettings $paragraph_type_settings */
+ $paragraph_type_settings = \Drupal::service('ncms_paragraphs.paragraph_type_settings');
+ $paragraph_type_settings->paragraphsTypeFormAlter($form, $form_state);
+}
diff --git a/html/modules/custom/ncms_paragraphs/ncms_paragraphs.services.yml b/html/modules/custom/ncms_paragraphs/ncms_paragraphs.services.yml
new file mode 100644
index 00000000..8913ed66
--- /dev/null
+++ b/html/modules/custom/ncms_paragraphs/ncms_paragraphs.services.yml
@@ -0,0 +1,7 @@
+services:
+ ncms_paragraphs.component_menu:
+ class: Drupal\ncms_paragraphs\ComponentMenu
+ arguments: ['@entity_type.manager', '@ncms_paragraphs.paragraph_type_settings']
+ ncms_paragraphs.paragraph_type_settings:
+ class: Drupal\ncms_paragraphs\ParagraphsTypeSettings
+ arguments: []
\ No newline at end of file
diff --git a/html/modules/custom/ncms_paragraphs/src/ComponentMenu.php b/html/modules/custom/ncms_paragraphs/src/ComponentMenu.php
new file mode 100644
index 00000000..a5a82925
--- /dev/null
+++ b/html/modules/custom/ncms_paragraphs/src/ComponentMenu.php
@@ -0,0 +1,156 @@
+entityTypeManager = $entityTypeManager;
+ $this->paragraphsTypeSettings = $paragraph_type_settings;
+ }
+
+ /**
+ * Preprocess the layout paragraphs component menu template.
+ *
+ * This turns the 2-category list into a custom list with multiple
+ * categories using verticala tabs.
+ *
+ * @param array $variables
+ * The template variables to alter.
+ *
+ * @see ncms_paragraphs_theme_registry_alter()
+ */
+ public function preprocessTemplate(&$variables) {
+ $items = array_merge($variables['types']['content'], $variables['types']['layout']);
+ uasort($items, function ($a, $b) {
+ return SortArray::sortByKeyString($a, $b, 'label');
+ });
+
+ // Prepare a pseudo form where we can attach vertical tabs.
+ $form_state = new FormState();
+ $complete_form = [];
+ $form = [];
+ $form['category_header'] = [
+ '#type' => 'html_tag',
+ '#tag' => 'h5',
+ '#value' => $this->t('Choose a paragraph type from the following categories:'),
+ ];
+ $form['tabs'] = [
+ '#type' => 'vertical_tabs',
+ '#parents' => ['tabs'],
+ ];
+
+ $category_ids = array_keys($this->paragraphsTypeSettings->getCategoryOptions());
+ $categories = [];
+ foreach ($items as $item) {
+ /** @var \Drupal\paragraphs\Entity\ParagraphsType $entity */
+ $entity = $this->entityTypeManager->getStorage('paragraphs_type')->load($item['id']);
+ $settings = $entity->getThirdPartySettings('ncms_paragraphs');
+ if (!empty($settings['disabled'])) {
+ continue;
+ }
+
+ $category = $settings['category'] ?? NULL;
+ if (!$category) {
+ continue;
+ }
+ $category_label = $this->paragraphsTypeSettings->getCategoryLabel($category) ?? $this->t('Unknown');
+
+ $category_build = $categories[$category] ?? [
+ '#title' => $category_label,
+ 'links' => [
+ '#theme' => 'links',
+ '#links' => [],
+ ],
+ ];
+ $category_build['links']['#links'][] = [
+ 'title' => (string) $item['label'],
+ 'url' => $item['url_object'],
+ 'attributes' => [
+ 'class' => ['use-ajax'],
+ 'data-dialog-type' => 'dialog',
+ 'data-dialog-renderer' => 'off_canvas',
+ ],
+ ];
+ $categories[$category] = $category_build;
+ }
+
+ $form['paragraph_categories'] = array_combine($category_ids, array_map(function ($category_id) use ($categories) {
+ return $categories[$category_id];
+ }, $category_ids));
+ $form['paragraph_categories']['#tree'] = TRUE;
+
+ // Let the tab element set itself up.
+ VerticalTabs::processVerticalTabs($form['tabs'], $form_state, $complete_form);
+ RenderElement::processGroup($form['tabs']['group'], $form_state, $complete_form);
+
+ // Default tab is the first one. We have to set #value instead of the
+ // #default_value, because this is not a real form and the normal form
+ // processing doesn't work.
+ $form['tabs']['tabs__active_tab']['#value'] = reset($categories);
+
+ // Now go over the block categories, add some required properties and
+ // run the process callback.
+ $form['paragraph_categories']['#parents'] = ['paragraph_categories'];
+ foreach (Element::children($form['paragraph_categories']) as $element_key) {
+ $form['paragraph_categories'][$element_key]['#type'] = 'details';
+ $form['paragraph_categories'][$element_key]['#group'] = 'tabs';
+ $form['paragraph_categories'][$element_key]['#id'] = Html::getId($element_key);
+ $form['paragraph_categories'][$element_key]['#parents'] = [
+ 'paragraph_categories',
+ $element_key,
+ ];
+ $form['paragraph_categories'][$element_key]['#attributes'] = [
+ 'class' => [
+ 'paragraph-category-links',
+ ],
+ ];
+ RenderElement::processGroup($form['paragraph_categories'][$element_key], $form_state, $complete_form);
+ }
+ $this->processVerticalTabs($form, $form_state);
+
+ $variables['menu'] = $form;
+ $variables['#attached']['library'][] = 'ncms_paragraphs/component_menu';
+ }
+
+}
diff --git a/html/modules/custom/ncms_paragraphs/src/Entity/NcmsParagraphBase.php b/html/modules/custom/ncms_paragraphs/src/Entity/NcmsParagraphBase.php
new file mode 100644
index 00000000..475f38e4
--- /dev/null
+++ b/html/modules/custom/ncms_paragraphs/src/Entity/NcmsParagraphBase.php
@@ -0,0 +1,23 @@
+get('field_emphasize_first_row')->value) {
+ $variables['attributes']['class'][] = 'gho-article-card-list--show-2col';
+ }
+ }
+
+}
diff --git a/html/modules/custom/ncms_paragraphs/src/Entity/Paragraph/InteractiveContent.php b/html/modules/custom/ncms_paragraphs/src/Entity/Paragraph/InteractiveContent.php
new file mode 100644
index 00000000..0d0d6507
--- /dev/null
+++ b/html/modules/custom/ncms_paragraphs/src/Entity/Paragraph/InteractiveContent.php
@@ -0,0 +1,36 @@
+getValue($element_parents);
+ if (strpos($embed_code, '