From 33a36c87e462748d3186f0d1cebecf27f1c701ea Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Tue, 17 Mar 2026 13:28:47 +1100 Subject: [PATCH 1/2] Added 'do_generated_content' module with provision script for non-production content generation. --- composer.json | 1 + composer.lock | 58 ++++- .../custom/provision-20-generated-content.sh | 44 ++++ .../do_generated_content.info.yml | 7 + .../src/Plugin/GeneratedContent/FileFile.php | 51 +++++ .../GeneratedContent/MediaCivicthemeImage.php | 61 +++++ .../GeneratedContent/NodeCivicthemePage.php | 211 ++++++++++++++++++ .../TaxonomyTermCivicthemeTopics.php | 57 +++++ 8 files changed, 489 insertions(+), 1 deletion(-) create mode 100755 scripts/custom/provision-20-generated-content.sh create mode 100644 web/modules/custom/do_generated_content/do_generated_content.info.yml create mode 100644 web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/FileFile.php create mode 100644 web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/MediaCivicthemeImage.php create mode 100644 web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/NodeCivicthemePage.php create mode 100644 web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/TaxonomyTermCivicthemeTopics.php diff --git a/composer.json b/composer.json index 0e015219..17a1ffc0 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "drupal/entity_usage": "^2.0@beta", "drupal/environment_indicator": "^4.0.25", "drupal/field_group": "^4", + "drupal/generated_content": "^2", "drupal/gin": "^4.0.6", "drupal/gin_toolbar": "^2", "drupal/google_tag": "^2.0.9", diff --git a/composer.lock b/composer.lock index 41e967ec..dc25d039 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": "fe9d7f46c02a0e5fef6257c72e52d129", + "content-hash": "fe01453e11dd34ccd16f0f704f06740e", "packages": [ { "name": "asm89/stack-cors", @@ -3527,6 +3527,62 @@ "irc": "irc://irc.freenode.org/drupal-contribute" } }, + { + "name": "drupal/generated_content", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/generated_content.git", + "reference": "2.0.0" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/generated_content-2.0.0.zip", + "reference": "2.0.0", + "shasum": "a7eb32b13c57380e39ad04c0fd44d26b1f534618" + }, + "require": { + "drupal/core": "^9 || ^10 || ^11", + "php": ">=8.2" + }, + "suggest": { + "drupal/config_filter": "Provides API to modify the configuration when it is synchronized between the database and the exported yaml files." + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "2.0.0", + "datestamp": "1773710061", + "security-coverage": { + "status": "not-covered", + "message": "Project has not opted into security advisory coverage!" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alex Skrypnyk", + "homepage": "https://www.drupal.org/u/alexskrypnyk", + "email": "alex@drevops.com", + "role": "Maintainer" + } + ], + "description": "Drupal module to programmatically generate content.", + "homepage": "https://drupal.org/project/generated_content", + "keywords": [ + "drupal", + "generate", + "random" + ], + "support": { + "source": "https://git.drupalcode.org/project/generated_content", + "issues": "https://drupal.org/project/issues/generated_content" + } + }, { "name": "drupal/gin", "version": "4.0.6", diff --git a/scripts/custom/provision-20-generated-content.sh b/scripts/custom/provision-20-generated-content.sh new file mode 100755 index 00000000..e0f90213 --- /dev/null +++ b/scripts/custom/provision-20-generated-content.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +## +# Generate content for non-production environments. +# +# shellcheck disable=SC2086 + +set -eu +[ "${VORTEX_DEBUG-}" = "1" ] && set -x + +# ------------------------------------------------------------------------------ + +info() { printf " ==> %s\n" "${1}"; } +task() { printf " > %s\n" "${1}"; } +note() { printf " %s\n" "${1}"; } + +drush() { php -d memory_limit=2G vendor/bin/drush.php -y "$@"; } + +GENERATED_CONTENT_SKIP="${GENERATED_CONTENT_SKIP:-0}" + +info "Started generated content operations." + +environment="$(drush php:eval "print \Drupal\core\Site\Settings::get('environment');")" +note "Environment: ${environment}" + +# Perform operations based on the current environment. +if echo "${environment}" | grep -q -e dev -e ci -e local; then + if [ "${VORTEX_PROVISION_OVERRIDE_DB:-0}" = "1" ]; then + + if [ "${GENERATED_CONTENT_SKIP}" = "1" ]; then + note "Skipping generation of content." + else + task "Enabling generated content module." + export GENERATED_CONTENT_CREATE=1 + drush pm:enable -y do_generated_content + note "Generated content module enabled." + fi + else + note "Using existing database with existing content." + fi +else + note "Skipping generated content operations in production environment." +fi + +info "Finished generated content operations." diff --git a/web/modules/custom/do_generated_content/do_generated_content.info.yml b/web/modules/custom/do_generated_content/do_generated_content.info.yml new file mode 100644 index 00000000..112ffcbf --- /dev/null +++ b/web/modules/custom/do_generated_content/do_generated_content.info.yml @@ -0,0 +1,7 @@ +name: DrevOps Website Generated Content +type: module +description: 'Provides generated content for non-production environments.' +package: DrevOps +core_version_requirement: ^10.3 || ^11 +dependencies: + - generated_content:generated_content diff --git a/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/FileFile.php b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/FileFile.php new file mode 100644 index 00000000..10236c7f --- /dev/null +++ b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/FileFile.php @@ -0,0 +1,51 @@ +helper::randomArrayItem($types); + $width = $this->helper::randomBool() ? 1200 : 800; + $height = $this->helper::randomBool() ? 600 : 400; + + $file = $this->helper::createFile($type, [ + 'width' => $width, + 'height' => $height, + ]); + + $entities[] = $file; + $this->helper::log('Created file: %s', $file->getFilename()); + } + + return $entities; + } + +} diff --git a/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/MediaCivicthemeImage.php b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/MediaCivicthemeImage.php new file mode 100644 index 00000000..92d1fe57 --- /dev/null +++ b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/MediaCivicthemeImage.php @@ -0,0 +1,61 @@ +helper::randomFile('jpg'); + + if (!$file instanceof FileInterface) { + $file = $this->helper::randomFile('png'); + } + + if (!$file instanceof FileInterface) { + continue; + } + + $name = sprintf('Generated image %s', $i + 1); + + $media = Media::create([ + 'bundle' => 'civictheme_image', + 'name' => $name, + 'field_c_m_image' => [ + 'target_id' => $file->id(), + 'alt' => $this->helper::staticSentence(3), + ], + ]); + + $media->save(); + $entities[] = $media; + + $this->helper::log('Created media: %s', $name); + } + + return $entities; + } + +} diff --git a/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/NodeCivicthemePage.php b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/NodeCivicthemePage.php new file mode 100644 index 00000000..bdb2b007 --- /dev/null +++ b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/NodeCivicthemePage.php @@ -0,0 +1,211 @@ +helper::staticSentence(5)); + + $components = $this->createComponents($i); + + $topic = $this->helper::randomTerm('civictheme_topics'); + $thumbnail = $this->helper::randomMediaItem('civictheme_image'); + + $values = [ + 'type' => 'civictheme_page', + 'title' => $title, + 'status' => 1, + 'field_c_n_summary' => $this->helper::staticPlainParagraph(), + 'field_c_n_vertical_spacing' => 'both', + 'field_c_n_banner_type' => 'default', + 'field_c_n_banner_theme' => 'light', + 'field_c_n_banner_blend_mode' => 'normal', + 'field_c_n_show_toc' => $this->helper::randomBool(30), + 'field_c_n_show_last_updated' => $this->helper::randomBool(50), + 'field_c_n_components' => $components, + ]; + + if ($topic instanceof TermInterface) { + $values['field_c_n_topics'] = ['target_id' => $topic->id()]; + } + + if ($thumbnail instanceof MediaInterface) { + $values['field_c_n_thumbnail'] = ['target_id' => $thumbnail->id()]; + } + + if ($this->helper::randomBool(30)) { + $banner_image = $this->helper::randomMediaItem('civictheme_image'); + if ($banner_image instanceof MediaInterface) { + $values['field_c_n_banner_background'] = ['target_id' => $banner_image->id()]; + $values['field_c_n_banner_type'] = 'large'; + $values['field_c_n_banner_theme'] = 'dark'; + } + } + + $node = Node::create($values); + $node->save(); + $entities[] = $node; + + $this->helper::log('Created page: %s', $title); + } + + return $entities; + } + + /** + * Create paragraph components for a page. + * + * @return \Drupal\paragraphs\Entity\Paragraph[] + * Array of saved paragraph entities. + */ + protected function createComponents(int $index): array { + $components = []; + + // Every page gets a content paragraph. + $components[] = $this->createContentParagraph(); + + // Alternate pages get additional components. + if ($index % 2 === 0) { + $components[] = $this->createAccordionParagraph(); + } + + if ($index % 3 === 0) { + $components[] = $this->createPromoParagraph(); + } + + if ($index % 4 === 0) { + $components[] = $this->createCalloutParagraph(); + } + + return $components; + } + + /** + * Create a content paragraph. + */ + protected function createContentParagraph(): Paragraph { + $paragraph = Paragraph::create([ + 'type' => 'civictheme_content', + 'field_c_p_theme' => $this->helper::randomBool() ? 'light' : 'dark', + 'field_c_p_vertical_spacing' => 'both', + 'field_c_p_content' => [ + 'value' => $this->helper::staticRichText(3), + 'format' => 'civictheme_rich_text', + ], + ]); + + $paragraph->save(); + + return $paragraph; + } + + /** + * Create an accordion paragraph with panels. + */ + protected function createAccordionParagraph(): Paragraph { + $panels = []; + + for ($i = 0; $i < 3; $i++) { + $panel = Paragraph::create([ + 'type' => 'civictheme_accordion_panel', + 'field_c_p_title' => sprintf('Section %s - %s', $i + 1, $this->helper::staticSentence(3)), + 'field_c_p_content' => [ + 'value' => $this->helper::staticRichText(2), + 'format' => 'civictheme_rich_text', + ], + 'field_c_p_expand' => $i === 0, + ]); + + $panel->save(); + $panels[] = $panel; + } + + $paragraph = Paragraph::create([ + 'type' => 'civictheme_accordion', + 'field_c_p_theme' => 'light', + 'field_c_p_vertical_spacing' => 'both', + 'field_c_p_panels' => $panels, + ]); + + $paragraph->save(); + + return $paragraph; + } + + /** + * Create a promo paragraph. + */ + protected function createPromoParagraph(): Paragraph { + $paragraph = Paragraph::create([ + 'type' => 'civictheme_promo', + 'field_c_p_title' => $this->helper::staticSentence(4), + 'field_c_p_theme' => 'dark', + 'field_c_p_vertical_spacing' => 'both', + 'field_c_p_content' => [ + 'value' => $this->helper::staticHtmlParagraph(), + 'format' => 'civictheme_rich_text', + ], + 'field_c_p_link' => [ + 'uri' => 'https://www.drevops.com.au', + 'title' => 'Learn more', + ], + ]); + + $paragraph->save(); + + return $paragraph; + } + + /** + * Create a callout paragraph. + */ + protected function createCalloutParagraph(): Paragraph { + $paragraph = Paragraph::create([ + 'type' => 'civictheme_callout', + 'field_c_p_title' => $this->helper::staticSentence(3), + 'field_c_p_theme' => 'light', + 'field_c_p_vertical_spacing' => 'both', + 'field_c_p_content' => [ + 'value' => $this->helper::staticHtmlParagraph(), + 'format' => 'civictheme_rich_text', + ], + 'field_c_p_links' => [ + [ + 'uri' => 'https://www.drevops.com.au', + 'title' => 'Get started', + ], + ], + ]); + + $paragraph->save(); + + return $paragraph; + } + +} diff --git a/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/TaxonomyTermCivicthemeTopics.php b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/TaxonomyTermCivicthemeTopics.php new file mode 100644 index 00000000..8bc389b5 --- /dev/null +++ b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/TaxonomyTermCivicthemeTopics.php @@ -0,0 +1,57 @@ + 'civictheme_topics', + 'name' => $topic, + ]); + + $term->save(); + $entities[] = $term; + + $this->helper::log('Created topic: %s', $topic); + } + + return $entities; + } + +} From 7ad4d05b4579e401875790b9f15fcfd4ea81241e Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Tue, 17 Mar 2026 13:43:39 +1100 Subject: [PATCH 2/2] Added code coverage exclusion for generated content plugin classes. --- .../src/Plugin/GeneratedContent/FileFile.php | 2 ++ .../src/Plugin/GeneratedContent/MediaCivicthemeImage.php | 2 ++ .../src/Plugin/GeneratedContent/NodeCivicthemePage.php | 2 ++ .../Plugin/GeneratedContent/TaxonomyTermCivicthemeTopics.php | 2 ++ 4 files changed, 8 insertions(+) diff --git a/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/FileFile.php b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/FileFile.php index 10236c7f..b8ec4c91 100644 --- a/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/FileFile.php +++ b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/FileFile.php @@ -10,6 +10,8 @@ /** * Generated files. + * + * @codeCoverageIgnore */ #[GeneratedContent( id: 'do_generated_content_file_file', diff --git a/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/MediaCivicthemeImage.php b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/MediaCivicthemeImage.php index 92d1fe57..b2e353b2 100644 --- a/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/MediaCivicthemeImage.php +++ b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/MediaCivicthemeImage.php @@ -11,6 +11,8 @@ /** * Generated image media entities. + * + * @codeCoverageIgnore */ #[GeneratedContent( id: 'do_generated_content_media_civictheme_image', diff --git a/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/NodeCivicthemePage.php b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/NodeCivicthemePage.php index bdb2b007..0b6a574f 100644 --- a/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/NodeCivicthemePage.php +++ b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/NodeCivicthemePage.php @@ -13,6 +13,8 @@ /** * Generated page nodes. + * + * @codeCoverageIgnore */ #[GeneratedContent( id: 'do_generated_content_node_civictheme_page', diff --git a/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/TaxonomyTermCivicthemeTopics.php b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/TaxonomyTermCivicthemeTopics.php index 8bc389b5..b6b5a53e 100644 --- a/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/TaxonomyTermCivicthemeTopics.php +++ b/web/modules/custom/do_generated_content/src/Plugin/GeneratedContent/TaxonomyTermCivicthemeTopics.php @@ -10,6 +10,8 @@ /** * Generated topic taxonomy terms. + * + * @codeCoverageIgnore */ #[GeneratedContent( id: 'do_generated_content_taxonomy_term_civictheme_topics',