diff --git a/.travis.yml b/.travis.yml index fe4c0ca121..b72fdf9142 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ branches: only: - develop - master + - CI_working php: - 8.1 @@ -36,7 +37,6 @@ cache: - node_modules notifications: - # see https://docs.travis-ci.com/user/notifications/#configuring-slack-notifications slack: - if: branch = master on_pull_requests: false @@ -60,7 +60,7 @@ notifications: - ":red_circle: *WARNING:* Deploy from GitHub to the Acquia *FAILED* (in Travis)." - "This unsuccessful deploy attempt was initiated by %{author}." - "EXPLANATION (from Travis): %{message}" - - if: branch = master OR branch = develop + - if: branch = master OR branch = develop OR branch = CI_working on_pull_requests: true on_success: always on_failure: never @@ -92,6 +92,16 @@ notifications: template: - "Deployment of <%{build_url}|#%{build_number}> from GitHub to Acquia has been started." - "_Check the #drupal channel for the deploy completion notice._" + - if: branch = CI_working + on_pull_requests: false + on_success: always + on_failure: never + rooms: + # digital_builds - whenever travis runs on PR/Merge actions on CI_working branch. + - secure: "HX7tOsr8pnedT2CWJ053VWQ3gIT8E912Kh4RezdjeZM3Pk67esNQqp8pelvm3FRLGLNTmULaQ/T8RANEvm3AnlxbYClIJ/z/5TH1Mr+elZ1gFbBmpubFQSlt+qUGoILa1vHf0bmNG2L7g6dnVjNRkq+HTPnUffE/WCSpBEfATzbIMQ9xY6Wz5HP1YgQVKII01/VKXwe1/uO9cQaXCegXHaNQUj9mLAKoPqyz5oUxRQ2U2jO74TS9E+yLCout0kzvnnGlRVeEKvM4y3ZGPTgT8VvnjljS4Ftf4Xd8hV8EQAWCLUrlyjtalPiNanZkHZWAq83ATzUYteZZ4P/sjepwv0sEy8mQGMYWybeUfw+423nQnMexAgK+byA553Xn0nFvPxpawkeh8B+nkpCWjSA0wrZY6BKCukdWc9mMb3rYfWgY+yS4aOnF9esEjPMs0llxZQ8H1BYf60Soa7T5jCwSQYbUcviWdPM02OTWJrQAD8uXSXw2EoE6KOHuEJcVrw+FGPMrp3+Czx8MvzyFFddzZ7u/DWjyByvuP4LmkmVF0V2loFG9gwHAGDU+0KcKgMwrtWOzD4rYn0L+mpJCnWaokFubXa3p0v2T5ZQiP9hBnTDeZDA84nxmQATyL6ChGaQDkYTmv2qnIwNTuQJ/YacPiMiWdpCuqKunUP9v1Bm9zF0=" + template: + - "Deployment of <%{build_url}|#%{build_number}> from GitHub to Acquia has been started." + - "_Check the #drupal channel for the deploy completion notice._" before_install: # Add in some required packages (ref: /.lando.yml @@ -113,7 +123,6 @@ deploy: - provider: script skip_cleanup: true script: bash $TRAVIS_BUILD_DIR/scripts/deploy/travis_deploy.sh $TRAVIS_BRANCH - script: bash $TRAVIS_BUILD_DIR/scripts/deploy/travis_deploy.sh $TRAVIS_BRANCH on: branch: develop - provider: script @@ -121,3 +130,8 @@ deploy: script: bash $TRAVIS_BUILD_DIR/scripts/deploy/travis_deploy.sh $TRAVIS_BRANCH on: branch: master + - provider: script + skip_cleanup: true + script: bash $TRAVIS_BUILD_DIR/scripts/deploy/travis_deploy.sh $TRAVIS_BRANCH + on: + branch: CI_working diff --git a/composer.json b/composer.json index 6e52cb9461..08aa084c81 100644 --- a/composer.json +++ b/composer.json @@ -1,322 +1,296 @@ { - "name": "drupal-composer/drupal-project", - "description": "Project template for Drupal 8 projects with composer", - "type": "project", - "minimum-stability": "dev", - "prefer-stable": true, - "license": "GPL-2.0-or-later", - "authors": [ - { - "name": "Stella Ubaha", - "role": "Frontend Developer", - "email": "stella.ubaha@boston.gov" - }, - { - "name": "David Upton", - "role": "Drupal Developer.", - "email": "david.upton@boston.gov" - } - ], - "repositories": [ - { - "type": "composer", - "url": "https://packages.drupal.org/8" + "name": "drupal-composer/drupal-project", + "description": "Project template for Drupal 8 projects with composer", + "type": "project", + "minimum-stability": "dev", + "prefer-stable": true, + "license": "GPL-2.0-or-later", + "authors": [ + { + "name": "Stella Ubaha", + "role": "Frontend Developer", + "email": "stella.ubaha@boston.gov" + }, + { + "name": "David Upton", + "role": "Drupal Developer.", + "email": "david.upton@boston.gov" + } + ], + "repositories": [ + { + "type": "composer", + "url": "https://packages.drupal.org/8" + }, + { + "type": "composer", + "url": "https://asset-packagist.org" + }, + { + "type": "github", + "url": "git@github.com:CityOfBoston/contextual_range_filter.git", + "vendor-alias": "cityofboston" + }, + { + "type": "package", + "package": { + "name": "jackmoore/colorbox", + "version": "1.6.4", + "type": "drupal-library", + "dist": { + "url": "https://github.com/jackmoore/colorbox/archive/1.6.4.zip", + "type": "zip" + } + } + }, + { + "type": "package", + "package": { + "name": "furf/jquery-ui-touch-punch", + "version": "1.0.0", + "type": "drupal-library", + "source": { + "url": "https://github.com/furf/jquery-ui-touch-punch.git", + "type": "git", + "reference": "master" + } + } + } + ], + "require": { + "cityofboston/contextual_range_filter": "dev-drupal10-compat", + "composer/installers": "^1.6", + "cweagans/composer-patches": "^1.7", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "drupal/acquia_connector": "^4.0", + "drupal/acquia_purge": "^1.0", + "drupal/address": "^1.4", + "drupal/addtocal": "^3.0@beta", + "drupal/admin_toolbar": "^3.1", + "drupal/adminimal_theme": "^1.7", + "drupal/adminimal_admin_toolbar": "1.x-dev@dev", + "drupal/advanced_text_formatter": "^3.0@RC", + "drupal/ape": "^1.6", + "drupal/autologout": "^1.0", + "drupal/better_exposed_filters": "^6.0", + "drupal/bg_image_formatter": "^1.1", + "drupal/chosen": "^3.0", + "drupal/chosen_lib": "^3.0", + "drupal/coder": "^8.0", + "drupal/colorbox": "^2.0", + "drupal/config_ignore": "^2.1", + "drupal/config_split": "^2.0@RC", + "drupal/config_update": "^2.0@alpha", + "drupal/content_access": "^2.0@RC", + "drupal/content_moderation_notifications": "^3.5", + "drupal/core": "^10.1", + "drupal/core-composer-scaffold": "^9.1", + "drupal/ctools": "^3.0", + "drupal/date_popup": "^1.0", + "drupal/date_recur": "^3.0", + "drupal/date_recur_modular": "^3.0", + "drupal/diff": "^1.0", + "drupal/draggableviews": "^2.0", + "drupal/dynamic_entity_reference": "^3.0", + "drupal/editor_advanced_link": "^2.0", + "drupal/editor_file": "^1.4", + "drupal/entity": "^1.0", + "drupal/entity_browser": "^2.0", + "drupal/entity_clone": "^2.0", + "drupal/entity_embed": "^1.0-beta2", + "drupal/entity_usage": "^2.0", + "drupal/environment_indicator": "^4.0", + "drupal/externalauth": "^2.0", + "drupal/fences": "^2.0", + "drupal/field_display_label": "^1.0", + "drupal/field_group": "^3.0", + "drupal/file_entity": "^2.0", + "drupal/file_mdm": "^2.2.0", + "drupal/geolocation": "^3.0", + "drupal/google_tag": "^1.0", + "drupal/honeypot": "^2.1", + "drupal/image_url_formatter": "^1.0", + "drupal/imagemagick": "^3.2.0", + "drupal/inline_entity_form": "^1.0@beta", + "drupal/jquery_ui_accordion": "^2.0", + "drupal/jquery_ui_slider": "^2.0", + "drupal/jsonapi_extras": "^3.20", + "drupal/key": "^1.17", + "drupal/key_asymmetric": "^1.1", + "drupal/libraries": "^4.0", + "drupal/lightning_media": "^4.6", + "drupal/lightning_workflow": "^3.6", + "drupal/linkit": "^6.0", + "drupal/login_security": "^2.0", + "drupal/maillog": "^1.1", + "drupal/mailsystem": "^4.4", + "drupal/masquerade": "^2.0@RC", + "drupal/media_entity_browser": "^2.0", + "drupal/memcache": "^2.0", + "drupal/menu_block": "^1.5", + "drupal/metatag": "^1", + "drupal/mimemail": "^1.0@alpha", + "drupal/moderation_sidebar": "^1.1", + "drupal/module_filter": "^4.0", + "drupal/new_relic_rpm": "^2.1", + "drupal/node_revision_delete": "^2.0@alpha", + "drupal/office_hours": "^1.1", + "drupal/paragraphs": "^1.15", + "drupal/paragraphs_browser": "^1.1", + "drupal/paragraphs_edit": "^2.0", + "drupal/pathauto": "^1.2", + "drupal/poll": "^1.2", + "drupal/profile": "^1.0", + "drupal/publication_date": "^2.0", + "drupal/purge": "^3.0", + "drupal/queue_ui": "^3.0", + "drupal/r4032login": "^2.2", + "drupal/rabbit_hole": "^1.0-beta5", + "drupal/realname": "^2.0@beta", + "drupal/recaptcha_v3": "^1.8", + "drupal/redirect": "^1.2", + "drupal/rest_export_nested": "^1.0", + "drupal/restui": "^1.16", + "drupal/rollbar": "^2.1", + "drupal/salesforce": "^5.0", + "drupal/samlauth": "^3.0", + "drupal/schema_metatag": "^2.2", + "drupal/seckit": "^2.0", + "drupal/seven": "^1.0", + "drupal/shortcode": "^2.0", + "drupal/smart_trim": "^2.1", + "drupal/social_media": "^2.0", + "drupal/sophron": "^1.2", + "drupal/stage_file_proxy": "^2.1", + "drupal/svg_image": "^3.0", + "drupal/token": "^1.3", + "drupal/token_custom": "^1.0", + "drupal/token_filter": "^2.0", + "drupal/twig_tweak": "^3.1", + "drupal/twig_xdebug": "^1.2", + "drupal/typed_data": "^1.0@beta", + "drupal/viewfield": "^3.0", + "drupal/views_accordion": "^2.0", + "drupal/views_bulk_operations": "^4.0", + "drupal/views_infinite_scroll": "^2.0", + "drupal/votingapi": "^3.0", + "drupal/webform": "^6.2@beta", + "drupal/workbench": "^1.0", + "drupal/workflow": "^1.1_", + "drupal/xmlsitemap": "^1.0", + "drush/drush": "^11.0", + "furf/jquery-ui-touch-punch": "^1.0", + "gasparesganga/php-shapefile": "^3.4", + "jackmoore/colorbox": "^1.6", + "oomphinc/composer-installers-extender": "^2.0", + "picqer/php-barcode-generator": "2.3.3", + "sainsburys/guzzle-oauth2-plugin": "^3.0", + "setasign/fpdf": "^1.8", + "setasign/fpdi": "^2.3", + "vlucas/phpdotenv": "^2.4" }, - { - "type": "composer", - "url": "https://asset-packagist.org" + "require-dev": { + "drupal/config_devel": "^1.2", + "drupal/core-dev": "^10", + "drupal/devel": "^5.0", + "drupal/upgrade_status": "^4.0", + "phpspec/prophecy-phpunit": "^2", + "phpunit/phpunit": "*", + "php-parallel-lint/php-console-highlighter": "^0.5.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "squizlabs/php_codesniffer": "^3.5" }, - { - "type": "github", - "url": "git@github.com:CityOfBoston/shortcode.git", - "vendor-alias": "cityofboston" - }, - { - "type": "github", - "url": "git@github.com:CityOfBoston/adminimal_theme.git", - "vendor-alias": "cityofboston" - }, - { - "type": "github", - "url": "git@github.com:CityOfBoston/ape.git", - "vendor-alias": "cityofboston" - }, - { - "type": "github", - "url": "git@github.com:CityOfBoston/contextual_range_filter.git", - "vendor-alias": "cityofboston" - }, - { - "type": "github", - "url": "git@github.com:CityOfBoston/rollbar.git", - "vendor-alias": "cityofboston" - }, - { - "type": "github", - "url": "git@github.com:CityOfBoston/adminimal_admin_toolbar.git", - "vendor-alias": "cityofboston" - }, - { - "type": "package", - "package": { - "name": "jackmoore/colorbox", - "version": "1.6.4", - "type": "drupal-library", - "dist": { - "url": "https://github.com/jackmoore/colorbox/archive/1.6.4.zip", - "type": "zip" - } - } + "conflict": { + "drupal/core": "7.*" }, - { - "type": "package", - "package": { - "name": "furf/jquery-ui-touch-punch", - "version": "1.0.0", - "type": "drupal-library", - "source": { - "url": "https://github.com/furf/jquery-ui-touch-punch.git", - "type": "git", - "reference": "master" + "config": { + "sort-packages": true, + "discard-changes": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "composer/installers": true, + "cweagans/composer-patches": true, + "drupal/core-composer-scaffold": true, + "drupal/console-extend-plugin": false, + "oomphinc/composer-installers-extender": true, + "simplesamlphp/composer-module-installer": false, + "phpstan/extension-installer": true } - } - } - ], - "require": { - "cityofboston/adminimal_admin_toolbar": "dev-drupal10-compat", - "cityofboston/adminimal_theme": "dev-drupal10-compat", - "cityofboston/ape": "dev-drupal10-compat", - "cityofboston/contextual_range_filter": "dev-drupal10-compat", - "cityofboston/rollbar": "dev-drupal10-compat", - "cityofboston/shortcode": "dev-drupal10-compat", - "composer/installers": "^1.6", - "cweagans/composer-patches": "^1.7", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "drupal/acquia_connector": "^4.0", - "drupal/acquia_purge": "^1.0", - "drupal/address": "^1.4", - "drupal/addtocal": "^3.0@beta", - "drupal/admin_toolbar": "^3.1", - "drupal/advanced_text_formatter": "^3.0@RC", - "drupal/autologout": "^1.0", - "drupal/better_exposed_filters": "^6.0", - "drupal/bg_image_formatter": "^1.1", - "drupal/chosen": "^3.0", - "drupal/chosen_lib": "^3.0", - "drupal/coder": "^8.0", - "drupal/colorbox": "^2.0", - "drupal/config_ignore": "^2.1", - "drupal/config_split": "^2.0@RC", - "drupal/config_update": "^2.0@alpha", - "drupal/content_access": "^2.0@RC", - "drupal/content_moderation_notifications": "^3.5", - "drupal/core": "^10.1", - "drupal/core-composer-scaffold": "^9.1", - "drupal/ctools": "^3.0", - "drupal/date_popup": "^1.0", - "drupal/date_recur": "^3.0", - "drupal/date_recur_modular": "^3.0", - "drupal/diff": "^1.0", - "drupal/draggableviews": "^2.0", - "drupal/dynamic_entity_reference": "^3.0", - "drupal/editor_advanced_link": "^2.0", - "drupal/editor_file": "^1.4", - "drupal/entity": "^1.0", - "drupal/entity_browser": "^2.0", - "drupal/entity_clone": "^2.0", - "drupal/entity_embed": "^1.0-beta2", - "drupal/entity_usage": "^2.0", - "drupal/environment_indicator": "^4.0", - "drupal/externalauth": "^2.0", - "drupal/fences": "^2.0", - "drupal/field_display_label": "^1.0", - "drupal/field_group": "^3.0", - "drupal/file_entity": "^2.0", - "drupal/file_mdm": "^2.2.0", - "drupal/geolocation": "^3.0", - "drupal/google_tag": "^1.0", - "drupal/honeypot": "^2.1", - "drupal/image_url_formatter": "^1.0", - "drupal/imagemagick": "^3.2.0", - "drupal/inline_entity_form": "^1.0@beta", - "drupal/jquery_ui_accordion": "^2.0", - "drupal/jquery_ui_slider": "^2.0", - "drupal/jsonapi_extras": "^3.20", - "drupal/key": "^1.17", - "drupal/key_asymmetric": "^1.1", - "drupal/libraries": "^4.0", - "drupal/lightning_media": "^4.6", - "drupal/lightning_workflow": "^3.6", - "drupal/linkit": "^6.0", - "drupal/login_security": "^2.0", - "drupal/maillog": "^1.1", - "drupal/mailsystem": "^4.4", - "drupal/masquerade": "^2.0@RC", - "drupal/media_entity_browser": "^2.0", - "drupal/memcache": "^2.0", - "drupal/menu_block": "^1.5", - "drupal/metatag": "^1", - "drupal/mimemail": "^1.0@alpha", - "drupal/moderation_sidebar": "^1.1", - "drupal/module_filter": "^4.0", - "drupal/new_relic_rpm": "^2.1", - "drupal/node_revision_delete": "^2.0@alpha", - "drupal/office_hours": "^1.1", - "drupal/paragraphs": "^1.15", - "drupal/paragraphs_browser": "^1.1", - "drupal/paragraphs_edit": "^2.0", - "drupal/pathauto": "^1.2", - "drupal/poll": "^1.2", - "drupal/profile": "^1.0", - "drupal/publication_date": "^2.0", - "drupal/purge": "^3.0", - "drupal/queue_ui": "^3.0", - "drupal/r4032login": "^2.2", - "drupal/rabbit_hole": "^1.0-beta5", - "drupal/realname": "^2.0@beta", - "drupal/recaptcha_v3": "^1.8", - "drupal/redirect": "^1.2", - "drupal/rest_export_nested": "^1.0", - "drupal/restui": "^1.16", - "drupal/salesforce": "^5.0", - "drupal/samlauth": "^3.0", - "drupal/schema_metatag": "^2.2", - "drupal/seckit": "^2.0", - "drupal/seven": "^1.0", - "drupal/smart_trim": "^2.1", - "drupal/social_media": "^2.0", - "drupal/sophron": "^1.2", - "drupal/stage_file_proxy": "^2.1", - "drupal/svg_image": "^3.0", - "drupal/token": "^1.3", - "drupal/token_custom": "^1.0", - "drupal/token_filter": "^2.0", - "drupal/twig_tweak": "^3.1", - "drupal/twig_xdebug": "^1.2", - "drupal/typed_data": "^1.0@beta", - "drupal/viewfield": "^3.0", - "drupal/views_accordion": "^2.0", - "drupal/views_bulk_operations": "^4.0", - "drupal/views_infinite_scroll": "^2.0", - "drupal/votingapi": "^3.0", - "drupal/webform": "^6.2@beta", - "drupal/workbench": "^1.0", - "drupal/workflow": "^1.1_", - "drupal/xmlsitemap": "^1.0", - "drush/drush": "^11.0", - "furf/jquery-ui-touch-punch": "^1.0", - "gasparesganga/php-shapefile": "^3.4", - "jackmoore/colorbox": "^1.6", - "oomphinc/composer-installers-extender": "^2.0", - "picqer/php-barcode-generator": "^2.2", - "sainsburys/guzzle-oauth2-plugin": "^3.0", - "setasign/fpdf": "^1.8", - "setasign/fpdi": "^2.3", - "vlucas/phpdotenv": "^2.4" - }, - - "require-dev": { - "drupal/config_devel": "^1.2", - "drupal/core-dev": "^10", - "drupal/devel": "^5.0", - "drupal/upgrade_status": "^4.0", - "phpspec/prophecy-phpunit": "^2", - "phpunit/phpunit": "*", - "php-parallel-lint/php-console-highlighter": "^0.5.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "squizlabs/php_codesniffer": "^3.5" - }, - "conflict": { - "drupal/core": "7.*" - }, - "config": { - "sort-packages": true, - "discard-changes": true, - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true, - "composer/installers": true, - "cweagans/composer-patches": true, - "drupal/core-composer-scaffold": true, - "drupal/console-extend-plugin": false, - "oomphinc/composer-installers-extender": true, - "simplesamlphp/composer-module-installer": false, - "phpstan/extension-installer": true - } - }, - "autoload": { - "classmap": [ - "scripts/composer/ScriptHandler.php" - ] - }, - "extra": { - "installer-paths": { - "docroot/core": ["type:drupal-core"], - "docroot/libraries/{$name}": ["type:drupal-library"], - "docroot/libraries/chosen": ["harvesthq/chosen"], - "docroot/modules/contrib/{$name}": ["type:drupal-module"], - "docroot/profiles/contrib/{$name}": ["type:drupal-profile"], - "docroot/themes/contrib/{$name}": ["type:drupal-theme"], - "drush/Commands/{$name}": ["type:drupal-drush"] }, - "drupal-scaffold": { - "allowed-packages": [ - "drupal/core" - ], - "locations": { - "web-root": "./docroot" - }, - "file-mapping": { - "[web-root]/.htaccess": false - } + "autoload": { + "classmap": [ + "scripts/composer/ScriptHandler.php" + ] }, - "patches": { - "drupal/core": { - "CoB patch - Fix original node is null on migration.": "patches/cob_migration_missing_original_001.patch", - "Optional end-date in date_range field type": "https://www.drupal.org/files/issues/2020-08-09/2794481-109.patch", - "Title is a render array - Fixes media library ajax popup.": "https://www.drupal.org/files/issues/2019-10-21/2663316-76.drupal.Broken-title-in-modal-dialog-when-title-is-a-render-array.patch", - "CoB patch - Simple decimals fail to pass validation (https://www.drupal.org/project/drupal/issues/2230909) - DU 03-11-2020": "https://www.drupal.org/files/issues/2021-10-07/drupal-simple-decimal-validation-ver_9.2.7-2230909-234.patch", - "Multi-language paragraphs du - 05/2022": "https://www.drupal.org/files/issues/2022-05-10/3025039-68.patch" - }, - "gasparesganga/php-shapefile": { - "Patch for PHP8 compatibility for Drupal 10": "patches/php-shapefile-php8-compat.patch" + "extra": { + "installer-paths": { + "docroot/core": ["type:drupal-core"], + "docroot/libraries/{$name}": ["type:drupal-library"], + "docroot/libraries/chosen": ["harvesthq/chosen"], + "docroot/modules/contrib/{$name}": ["type:drupal-module"], + "docroot/profiles/contrib/{$name}": ["type:drupal-profile"], + "docroot/themes/contrib/{$name}": ["type:drupal-theme"], + "drush/Commands/{$name}": ["type:drupal-drush"] }, - "drupal/password_policy": { - "Patch for install of Password Policy module.": "https://www.drupal.org/files/issues/2019-05-20/password_policy-config_import_field_error-2771129-81.patch", - "CoB patch - Reverts extension change for drupal < 8.7": "patches/fixes_unsupported_ConfigurableInterface_extension_001.patch" - }, - "drupal/viewfield":{ - "https://www.drupal.org/project/viewfield/issues/2860523": "https://www.drupal.org/files/issues/2018-06-08/d7_upgrade_path-2860523-6.patch" - }, - "drupal/entity_clone":{ - "resolves memory issue when cloning - du July/2023": "https://www.drupal.org/files/issues/2023-06-23/entity_clone-avoid_unnecessary_recursion_3010626-16.patch" - }, - "drupal/media_entity_browser": { - "CoB patch - Skip confirmation step": "./patches/media_entity_browser__skip_confirmation.patch" - }, - "drupal/config_ignore": { - "CoB patch - Improve config_filter pattern recognition du 03/2022": "./patches/config_filter_match_node_implement_or_logic.patch" + "drupal-scaffold": { + "allowed-packages": [ + "drupal/core" + ], + "locations": { + "web-root": "./docroot" + }, + "file-mapping": { + "[web-root]/.htaccess": false + } }, - "drupal/date_recur": { - "CoB patch - Add helper for exdate retreival": "patches/exdate_helper.patch", - "CoB patch - Make filter work without exposed input": "patches/makeFilterWorkWithoutExposedInput.patch", - "Multi-language patch -du 05/2022": "https://www.drupal.org/files/issues/2020-05-28/3088544-11.patch" - }, - "drupal/social_media": { - "CoB patch - Adds TikTok to social_media component": "patches/social_media_tiktok.patch" - }, - "drupal/imagemagick": { - "CoB patch - implements move/copy logic": "patches/ImagemagickToolkit_identify_migrate_patch_01.patch", - "CoB patch - reduce errors from svg processing while Acquia does not have svg libraries.": "patches/imageMagick_svg_parse.patch" - }, - "drupal/geolocation_address": { - "CoB patch - Adds dependency which breaks config import": "patches/geolocation_address_fix_dependency.patch" - }, - "drupal/environment_indicator": { - "CoB patch - Alters the label for environment indicator switcher. DU 02/2022": "patches/environment_indicator.patch" - }, - "drupal/redirect": { - "Adds wilcard redirect to URL redirect": "https://www.drupal.org/files/issues/2021-10-18/support-for-wildcard-2831605-104.patch" - } + "patches": { + "drupal/core": { + "CoB patch - Fix original node is null on migration.": "patches/cob_migration_missing_original_001.patch", + "Optional end-date in date_range field type": "https://www.drupal.org/files/issues/2020-08-09/2794481-109.patch", + "Title is a render array - Fixes media library ajax popup.": "https://www.drupal.org/files/issues/2019-10-21/2663316-76.drupal.Broken-title-in-modal-dialog-when-title-is-a-render-array.patch", + "CoB patch - Simple decimals fail to pass validation (https://www.drupal.org/project/drupal/issues/2230909) - DU 03-11-2020": "https://www.drupal.org/files/issues/2021-10-07/drupal-simple-decimal-validation-ver_9.2.7-2230909-234.patch", + "Multi-language paragraphs du - 05/2022": "https://www.drupal.org/files/issues/2022-05-10/3025039-68.patch" + }, + "gasparesganga/php-shapefile": { + "Patch for PHP8 compatibility for Drupal 10": "patches/php-shapefile-php8-compat.patch" + }, + "drupal/password_policy": { + "Patch for install of Password Policy module.": "https://www.drupal.org/files/issues/2019-05-20/password_policy-config_import_field_error-2771129-81.patch", + "CoB patch - Reverts extension change for drupal < 8.7": "patches/fixes_unsupported_ConfigurableInterface_extension_001.patch" + }, + "drupal/viewfield":{ + "https://www.drupal.org/project/viewfield/issues/2860523": "https://www.drupal.org/files/issues/2018-06-08/d7_upgrade_path-2860523-6.patch" + }, + "drupal/entity_clone":{ + "resolves memory issue when cloning - du July/2023": "https://www.drupal.org/files/issues/2023-06-23/entity_clone-avoid_unnecessary_recursion_3010626-16.patch" + }, + "drupal/media_entity_browser": { + "CoB patch - Skip confirmation step": "./patches/media_entity_browser__skip_confirmation.patch" + }, + "drupal/config_ignore": { + "CoB patch - Improve config_filter pattern recognition du 03/2022": "./patches/config_filter_match_node_implement_or_logic.patch" + }, + "drupal/date_recur": { + "CoB patch - Add helper for exdate retreival": "patches/exdate_helper.patch", + "CoB patch - Make filter work without exposed input": "patches/makeFilterWorkWithoutExposedInput.patch", + "Multi-language patch -du 05/2022": "https://www.drupal.org/files/issues/2020-05-28/3088544-11.patch" + }, + "drupal/social_media": { + "CoB patch - Adds TikTok to social_media component": "patches/social_media_tiktok.patch" + }, + "drupal/imagemagick": { + "CoB patch - implements move/copy logic": "patches/ImagemagickToolkit_identify_migrate_patch_01.patch", + "CoB patch - reduce errors from svg processing while Acquia does not have svg libraries.": "patches/imageMagick_svg_parse.patch" + }, + "drupal/geolocation_address": { + "CoB patch - Adds dependency which breaks config import": "patches/geolocation_address_fix_dependency.patch" + }, + "drupal/environment_indicator": { + "CoB patch - Alters the label for environment indicator switcher. DU 02/2022": "patches/environment_indicator.patch" + }, + "drupal/redirect": { + "Adds wilcard redirect to URL redirect": "https://www.drupal.org/files/issues/2021-10-18/support-for-wildcard-2831605-104.patch" + } + } } - } } diff --git a/composer.lock b/composer.lock index df30606fb7..31b2289031 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": "5511021c24f6314d5a7f21af60d26929", + "content-hash": "bd1dc2c0835670595f862dcc66cd2896", "packages": [ { "name": "ajgl/breakpoint-twig-extension", @@ -249,93 +249,6 @@ }, "time": "2022-11-11T15:34:04+00:00" }, - { - "name": "cityofboston/adminimal_admin_toolbar", - "version": "dev-drupal10-compat", - "source": { - "type": "git", - "url": "https://github.com/CityOfBoston/adminimal_admin_toolbar.git", - "reference": "cdc4b0a9a33782bd653a50ef27ff945530762964" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/CityOfBoston/adminimal_admin_toolbar/zipball/cdc4b0a9a33782bd653a50ef27ff945530762964", - "reference": "cdc4b0a9a33782bd653a50ef27ff945530762964", - "shasum": "" - }, - "default-branch": true, - "type": "drupal-module", - "license": [ - "GPL-2.0-or-later" - ], - "authors": [ - { - "name": "Ted Slesinski", - "homepage": "https://www.drupal.org/u/energee" - } - ], - "description": "Adminimal styling brought to admin toolbar.", - "homepage": "https://www.drupal.org/project/adminimal_admin_toolbar", - "support": { - "issues": "https://www.drupal.org/project/issues/adminimal_admin_toolbar", - "source": "http://cgit.drupalcode.org/adminimal_admin_toolbar" - }, - "time": "2023-07-11T19:48:24+00:00" - }, - { - "name": "cityofboston/adminimal_theme", - "version": "dev-drupal10-compat", - "source": { - "type": "git", - "url": "https://github.com/CityOfBoston/adminimal_theme.git", - "reference": "e90910fca614c301c0e1e29ac3696c1057e9fb97" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/CityOfBoston/adminimal_theme/zipball/e90910fca614c301c0e1e29ac3696c1057e9fb97", - "reference": "e90910fca614c301c0e1e29ac3696c1057e9fb97", - "shasum": "" - }, - "default-branch": true, - "type": "drupal-theme", - "license": [ - "GPL-2.0+" - ], - "description": "D10 port for: Drupal administration theme with modern minimalist design.", - "homepage": "https://www.drupal.org/project/adminimal_theme", - "support": { - "issues": "https://www.drupal.org/project/issues/adminimal_theme", - "source": "https://github.com/CityOfBoston/adminimal_theme/tree/drupal10-compat" - }, - "time": "2023-07-11T16:12:12+00:00" - }, - { - "name": "cityofboston/ape", - "version": "dev-drupal10-compat", - "source": { - "type": "git", - "url": "https://github.com/CityOfBoston/ape.git", - "reference": "f8428fd10e7746ae090ebf6895a745da2d0c561c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/CityOfBoston/ape/zipball/f8428fd10e7746ae090ebf6895a745da2d0c561c", - "reference": "f8428fd10e7746ae090ebf6895a745da2d0c561c", - "shasum": "" - }, - "default-branch": true, - "type": "drupal-module", - "license": [ - "GPL-2.0+" - ], - "description": "Drupal 10 port for shortcode: Provides ShortCodes filter framework and API (like WP ShortCodes)", - "homepage": "https://drupal.org/project/shortcode", - "support": { - "issues": "https://www.drupal.org/project/issues/shortcode", - "source": "https://github.com/CityOfBoston/drupal10-shortcode" - }, - "time": "2023-07-13T15:33:05+00:00" - }, { "name": "cityofboston/contextual_range_filter", "version": "dev-drupal10-compat", @@ -363,63 +276,6 @@ }, "time": "2023-07-11T17:45:53+00:00" }, - { - "name": "cityofboston/rollbar", - "version": "dev-drupal10-compat", - "source": { - "type": "git", - "url": "https://github.com/CityOfBoston/rollbar.git", - "reference": "c5d737f8d3624063a023057947f9ac1eb944d277" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/CityOfBoston/rollbar/zipball/c5d737f8d3624063a023057947f9ac1eb944d277", - "reference": "c5d737f8d3624063a023057947f9ac1eb944d277", - "shasum": "" - }, - "require": { - "drupal/core": "^8.8.0 || ^9.0 || ^10.0", - "rollbar/rollbar": "^2.1 || ^3.0 || ^4.0" - }, - "default-branch": true, - "type": "drupal-module", - "license": [ - "GPL 2.0+" - ], - "description": "Drupal10 compatible: Rollbar integration for Drupal.", - "support": { - "source": "https://github.com/CityOfBoston/rollbar/tree/drupal10-compat", - "issues": "https://github.com/CityOfBoston/rollbar/issues" - }, - "time": "2023-07-13T14:06:03+00:00" - }, - { - "name": "cityofboston/shortcode", - "version": "dev-drupal10-compat", - "source": { - "type": "git", - "url": "https://github.com/CityOfBoston/shortcode.git", - "reference": "efc21fc1faedcba3220817994f36dc766aed5436" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/CityOfBoston/shortcode/zipball/efc21fc1faedcba3220817994f36dc766aed5436", - "reference": "efc21fc1faedcba3220817994f36dc766aed5436", - "shasum": "" - }, - "default-branch": true, - "type": "drupal-module", - "license": [ - "GPL-2.0+" - ], - "description": "Drupal 10 port for shortcode: Provides ShortCodes filter framework and API (like WP ShortCodes)", - "homepage": "https://drupal.org/project/shortcode", - "support": { - "issues": "https://www.drupal.org/project/issues/shortcode", - "source": "https://github.com/CityOfBoston/drupal10-shortcode" - }, - "time": "2023-07-11T15:58:37+00:00" - }, { "name": "commerceguys/addressing", "version": "v1.4.2", @@ -2138,6 +1994,107 @@ "issues": "https://www.drupal.org/project/issues/admin_toolbar" } }, + { + "name": "drupal/adminimal_admin_toolbar", + "version": "dev-1.x", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/adminimal_admin_toolbar.git", + "reference": "70607c618a605916161235b89e6141e25e383872" + }, + "require": { + "drupal/admin_toolbar": "*", + "drupal/core": "^8 || ^9 || ^10" + }, + "type": "drupal-module", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + }, + "drupal": { + "version": "8.x-1.11+4-dev", + "datestamp": "1684167895", + "security-coverage": { + "status": "not-covered", + "message": "Dev releases are not covered by Drupal security advisories." + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Ted Slesinski", + "homepage": "https://www.drupal.org/u/energee" + } + ], + "description": "Adminimal styling brought to admin toolbar.", + "homepage": "https://www.drupal.org/project/adminimal_admin_toolbar", + "support": { + "source": "http://cgit.drupalcode.org/adminimal_admin_toolbar", + "issues": "https://www.drupal.org/project/issues/adminimal_admin_toolbar" + } + }, + { + "name": "drupal/adminimal_theme", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/adminimal_theme.git", + "reference": "8.x-1.7" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/adminimal_theme-8.x-1.7.zip", + "reference": "8.x-1.7", + "shasum": "0fe020fecab6a1f6d877f2e622fb9f916ada52bb" + }, + "require": { + "drupal/core": "^9.3 || ^10", + "drupal/seven": "~1.0" + }, + "type": "drupal-theme", + "extra": { + "drupal": { + "version": "8.x-1.7", + "datestamp": "1691504486", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0+" + ], + "authors": [ + { + "name": "ANDiTKO", + "homepage": "https://www.drupal.org/user/1428124" + }, + { + "name": "andrey.troeglazov", + "homepage": "https://www.drupal.org/user/3145389" + }, + { + "name": "realityloop", + "homepage": "https://www.drupal.org/user/139189" + }, + { + "name": "rjjakes", + "homepage": "https://www.drupal.org/user/3457245" + } + ], + "description": "Drupal administration theme with modern minimalist design.", + "homepage": "https://www.drupal.org/project/adminimal_theme", + "support": { + "source": "https://git.drupalcode.org/project/adminimal_theme", + "issues": "https://www.drupal.org/project/issues/adminimal_theme" + } + }, { "name": "drupal/advanced_text_formatter", "version": "3.0.0-rc1", @@ -2186,6 +2143,58 @@ "source": "https://git.drupalcode.org/project/advanced_text_formatter" } }, + { + "name": "drupal/ape", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/ape.git", + "reference": "8.x-1.6" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/ape-8.x-1.6.zip", + "reference": "8.x-1.6", + "shasum": "649e5f5f1d0faa9d69f2f33b5757015442bd019e" + }, + "require": { + "drupal/core": ">=9.2" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "8.x-1.6", + "datestamp": "1695135848", + "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": "steel-track", + "homepage": "https://www.drupal.org/user/847488" + }, + { + "name": "codebymikey", + "homepage": "https://www.drupal.org/user/3573206" + }, + { + "name": "navneet0693", + "homepage": "https://www.drupal.org/user/3200545" + } + ], + "description": "Advanced control of your cache-control header", + "homepage": "https://www.drupal.org/project/ape", + "support": { + "source": "https://git.drupalcode.org/project/ape" + } + }, { "name": "drupal/autologout", "version": "1.4.0", @@ -2453,17 +2462,17 @@ }, { "name": "drupal/blazy", - "version": "2.16.0", + "version": "2.17.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/blazy.git", - "reference": "8.x-2.16" + "reference": "8.x-2.17" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/blazy-8.x-2.16.zip", - "reference": "8.x-2.16", - "shasum": "ae84ac6083f734968eb9c7cc008b32aaa04c5b9c" + "url": "https://ftp.drupal.org/files/projects/blazy-8.x-2.17.zip", + "reference": "8.x-2.17", + "shasum": "84ab9c7b7dc516890300f252dd64c8404e333d4b" }, "require": { "drupal/core": "^8.8 || ^9 || ^10" @@ -2474,8 +2483,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-2.16", - "datestamp": "1685788557", + "version": "8.x-2.17", + "datestamp": "1695019671", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -3460,16 +3469,16 @@ }, { "name": "drupal/core", - "version": "10.1.2", + "version": "10.1.4", "source": { "type": "git", "url": "https://github.com/drupal/core.git", - "reference": "949779f2244b955bdd6946c8af5dee8054501fe6" + "reference": "443b8e72b4dff11cd0d6b40de5cd887c7a9bd94d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drupal/core/zipball/949779f2244b955bdd6946c8af5dee8054501fe6", - "reference": "949779f2244b955bdd6946c8af5dee8054501fe6", + "url": "https://api.github.com/repos/drupal/core/zipball/443b8e72b4dff11cd0d6b40de5cd887c7a9bd94d", + "reference": "443b8e72b4dff11cd0d6b40de5cd887c7a9bd94d", "shasum": "" }, "require": { @@ -3614,13 +3623,13 @@ ], "description": "Drupal is an open source content management platform powering millions of websites and applications.", "support": { - "source": "https://github.com/drupal/core/tree/10.1.2" + "source": "https://github.com/drupal/core/tree/10.1.4" }, - "time": "2023-08-02T10:10:11+00:00" + "time": "2023-09-19T17:57:50+00:00" }, { "name": "drupal/core-composer-scaffold", - "version": "9.5.10", + "version": "9.5.11", "source": { "type": "git", "url": "https://github.com/drupal/core-composer-scaffold.git", @@ -3664,7 +3673,7 @@ "drupal" ], "support": { - "source": "https://github.com/drupal/core-composer-scaffold/tree/9.5.10" + "source": "https://github.com/drupal/core-composer-scaffold/tree/9.5.11" }, "time": "2023-04-30T16:17:33+00:00" }, @@ -6487,30 +6496,27 @@ }, { "name": "drupal/jsonapi_extras", - "version": "3.23.0", + "version": "3.24.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/jsonapi_extras.git", - "reference": "8.x-3.23" + "reference": "8.x-3.24" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/jsonapi_extras-8.x-3.23.zip", - "reference": "8.x-3.23", - "shasum": "2f3518bc1372f01a1ccebcffaa4e7e08478103e0" + "url": "https://ftp.drupal.org/files/projects/jsonapi_extras-8.x-3.24.zip", + "reference": "8.x-3.24", + "shasum": "5031650d17b62f5da5586d3a2c551ac071dbd294" }, "require": { "drupal/core": "^9.2 || ^10", "e0ipso/shaper": "^1" }, - "require-dev": { - "drupal/jsonapi": "*" - }, "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-3.23", - "datestamp": "1669374477", + "version": "8.x-3.24", + "datestamp": "1694442796", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -8130,7 +8136,7 @@ "datestamp": "1671210021", "security-coverage": { "status": "not-covered", - "message": "Project has not opted into security advisory coverage!" + "message": "RC releases are not covered by Drupal security advisories." } }, "branch-alias": { @@ -8151,10 +8157,18 @@ "name": "EclipseGc", "homepage": "https://www.drupal.org/user/61203" }, + { + "name": "ivnish", + "homepage": "https://www.drupal.org/user/3547706" + }, { "name": "japerry", "homepage": "https://www.drupal.org/user/45640" }, + { + "name": "joelpittet", + "homepage": "https://www.drupal.org/user/160302" + }, { "name": "manuel.adan", "homepage": "https://www.drupal.org/user/516420" @@ -9364,6 +9378,71 @@ "source": "https://git.drupalcode.org/project/restui" } }, + { + "name": "drupal/rollbar", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/rollbar.git", + "reference": "2.1.0" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/rollbar-2.1.0.zip", + "reference": "2.1.0", + "shasum": "cad53eb2b0373643800e6f3527113e699b2e523b" + }, + "require": { + "drupal/core": "^10", + "rollbar/rollbar": "^4.0" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "2.1.0", + "datestamp": "1691656051", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL 2.0+" + ], + "authors": [ + { + "name": "eddie_c", + "homepage": "https://www.drupal.org/user/681326" + }, + { + "name": "iainp999", + "homepage": "https://www.drupal.org/user/642098" + }, + { + "name": "intrafusion", + "homepage": "https://www.drupal.org/user/424800" + }, + { + "name": "thepearson", + "homepage": "https://www.drupal.org/user/1957110" + }, + { + "name": "vijaycs85", + "homepage": "https://www.drupal.org/user/93488" + }, + { + "name": "wiifm", + "homepage": "https://www.drupal.org/user/358731" + } + ], + "description": "Rollbar integration for Drupal.", + "homepage": "https://www.drupal.org/project/rollbar", + "support": { + "source": "https://git.drupalcode.org/project/rollbar" + } + }, { "name": "drupal/salesforce", "version": "5.0.3", @@ -9777,29 +9856,81 @@ "source": "https://git.drupalcode.org/project/seven" } }, + { + "name": "drupal/shortcode", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/shortcode.git", + "reference": "2.0.2" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/shortcode-2.0.2.zip", + "reference": "2.0.2", + "shasum": "3e2351c8b50ba06b8b1b364c27826bbec71c1982" + }, + "require": { + "drupal/core": "^9.3 || ^10" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "2.0.2", + "datestamp": "1690014464", + "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": "Denes.Szabo", + "homepage": "https://www.drupal.org/user/582668" + }, + { + "name": "squall3d", + "homepage": "https://www.drupal.org/user/1256364" + }, + { + "name": "sthomps5", + "homepage": "https://www.drupal.org/user/3534579" + } + ], + "description": "Provides ShortCodes filter framework and API (like WP ShortCodes)", + "homepage": "https://www.drupal.org/project/shortcode", + "support": { + "source": "https://git.drupalcode.org/project/shortcode" + } + }, { "name": "drupal/slick", - "version": "2.9.0", + "version": "2.10.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/slick.git", - "reference": "8.x-2.9" + "reference": "8.x-2.10" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/slick-8.x-2.9.zip", - "reference": "8.x-2.9", - "shasum": "1c2cec19148a096f875390e2fd6a9e56caf5bacd" + "url": "https://ftp.drupal.org/files/projects/slick-8.x-2.10.zip", + "reference": "8.x-2.10", + "shasum": "818d4aab661d94c06e2047eaa14bd45df589e14b" }, "require": { - "drupal/blazy": ">=2.14", + "drupal/blazy": "^2.17", "drupal/core": "^8.8 || ^9 || ^10" }, "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-2.9", - "datestamp": "1685789618", + "version": "8.x-2.10", + "datestamp": "1695019811", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -13938,16 +14069,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.21", + "version": "3.0.23", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "4580645d3fc05c189024eb3b834c6c1e4f0f30a1" + "reference": "866cc78fbd82462ffd880e3f65692afe928bed50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/4580645d3fc05c189024eb3b834c6c1e4f0f30a1", - "reference": "4580645d3fc05c189024eb3b834c6c1e4f0f30a1", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/866cc78fbd82462ffd880e3f65692afe928bed50", + "reference": "866cc78fbd82462ffd880e3f65692afe928bed50", "shasum": "" }, "require": { @@ -14028,7 +14159,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.21" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.23" }, "funding": [ { @@ -14044,20 +14175,20 @@ "type": "tidelift" } ], - "time": "2023-07-09T15:24:48+00:00" + "time": "2023-09-18T17:22:01+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.23.1", + "version": "1.24.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26" + "reference": "9f854d275c2dbf84915a5c0ec9a2d17d2cd86b01" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/846ae76eef31c6d7790fac9bc399ecee45160b26", - "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9f854d275c2dbf84915a5c0ec9a2d17d2cd86b01", + "reference": "9f854d275c2dbf84915a5c0ec9a2d17d2cd86b01", "shasum": "" }, "require": { @@ -14089,9 +14220,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.23.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.1" }, - "time": "2023-08-03T16:32:59+00:00" + "time": "2023-09-18T12:18:02+00:00" }, { "name": "picqer/php-barcode-generator", @@ -14581,16 +14712,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.20", + "version": "v0.11.21", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "0fa27040553d1d280a67a4393194df5228afea5b" + "reference": "bcb22101107f3bf770523b65630c9d547f60c540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/0fa27040553d1d280a67a4393194df5228afea5b", - "reference": "0fa27040553d1d280a67a4393194df5228afea5b", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/bcb22101107f3bf770523b65630c9d547f60c540", + "reference": "bcb22101107f3bf770523b65630c9d547f60c540", "shasum": "" }, "require": { @@ -14620,6 +14751,10 @@ "extra": { "branch-alias": { "dev-main": "0.11.x-dev" + }, + "bamarni-bin": { + "bin-links": false, + "forward-command": false } }, "autoload": { @@ -14651,9 +14786,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.20" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.21" }, - "time": "2023-07-31T14:32:22+00:00" + "time": "2023-09-17T21:15:54+00:00" }, { "name": "ralouphie/getallheaders", @@ -18615,16 +18750,16 @@ }, { "name": "composer/composer", - "version": "2.6.2", + "version": "2.6.3", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "623e5e1de055e65bc6c3c61b8348dc4662d75e2b" + "reference": "ff477832e6d838a736556d4a39a3b80f4412abfd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/623e5e1de055e65bc6c3c61b8348dc4662d75e2b", - "reference": "623e5e1de055e65bc6c3c61b8348dc4662d75e2b", + "url": "https://api.github.com/repos/composer/composer/zipball/ff477832e6d838a736556d4a39a3b80f4412abfd", + "reference": "ff477832e6d838a736556d4a39a3b80f4412abfd", "shasum": "" }, "require": { @@ -18709,7 +18844,7 @@ "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", "security": "https://github.com/composer/composer/security/policy", - "source": "https://github.com/composer/composer/tree/2.6.2" + "source": "https://github.com/composer/composer/tree/2.6.3" }, "funding": [ { @@ -18725,7 +18860,7 @@ "type": "tidelift" } ], - "time": "2023-09-03T12:09:15+00:00" + "time": "2023-09-15T07:38:22+00:00" }, { "name": "composer/metadata-minifier", @@ -19429,7 +19564,7 @@ }, { "name": "drupal/core-dev", - "version": "10.1.2", + "version": "10.1.4", "source": { "type": "git", "url": "https://github.com/drupal/core-dev.git", @@ -19477,7 +19612,7 @@ ], "description": "require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.", "support": { - "source": "https://github.com/drupal/core-dev/tree/10.1.2" + "source": "https://github.com/drupal/core-dev/tree/10.1.4" }, "time": "2023-05-25T11:39:24+00:00" }, @@ -19778,30 +19913,30 @@ }, { "name": "laminas/laminas-stdlib", - "version": "3.17.0", + "version": "3.18.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-stdlib.git", - "reference": "dd35c868075bad80b6718959740913e178eb4274" + "reference": "e85b29076c6216e7fc98e72b42dbe1bbc3b95ecf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/dd35c868075bad80b6718959740913e178eb4274", - "reference": "dd35c868075bad80b6718959740913e178eb4274", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/e85b29076c6216e7fc98e72b42dbe1bbc3b95ecf", + "reference": "e85b29076c6216e7fc98e72b42dbe1bbc3b95ecf", "shasum": "" }, "require": { - "php": "~8.1.0 || ~8.2.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0" }, "conflict": { "zendframework/zend-stdlib": "*" }, "require-dev": { "laminas/laminas-coding-standard": "^2.5", - "phpbench/phpbench": "^1.2.9", - "phpunit/phpunit": "^10.0.16", + "phpbench/phpbench": "^1.2.14", + "phpunit/phpunit": "^10.3.3", "psalm/plugin-phpunit": "^0.18.4", - "vimeo/psalm": "^5.8" + "vimeo/psalm": "^5.15.0" }, "type": "library", "autoload": { @@ -19833,7 +19968,7 @@ "type": "community_bridge" } ], - "time": "2023-03-20T13:51:37+00:00" + "time": "2023-09-19T10:15:21+00:00" }, { "name": "laminas/laminas-text", @@ -20594,16 +20729,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.33", + "version": "1.10.35", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "03b1cf9f814ba0863c4e9affea49a4d1ed9a2ed1" + "reference": "e730e5facb75ffe09dfb229795e8c01a459f26c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/03b1cf9f814ba0863c4e9affea49a4d1ed9a2ed1", - "reference": "03b1cf9f814ba0863c4e9affea49a4d1ed9a2ed1", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e730e5facb75ffe09dfb229795e8c01a459f26c3", + "reference": "e730e5facb75ffe09dfb229795e8c01a459f26c3", "shasum": "" }, "require": { @@ -20652,7 +20787,7 @@ "type": "tidelift" } ], - "time": "2023-09-04T12:20:53+00:00" + "time": "2023-09-19T15:27:56+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -20756,16 +20891,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.27", + "version": "9.2.29", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1" + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1", - "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", "shasum": "" }, "require": { @@ -20822,7 +20957,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" }, "funding": [ { @@ -20830,7 +20965,7 @@ "type": "github" } ], - "time": "2023-07-26T13:44:30+00:00" + "time": "2023-09-19T04:57:46+00:00" }, { "name": "phpunit/php-file-iterator", @@ -21075,16 +21210,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.11", + "version": "9.6.13", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "810500e92855eba8a7a5319ae913be2da6f957b0" + "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/810500e92855eba8a7a5319ae913be2da6f957b0", - "reference": "810500e92855eba8a7a5319ae913be2da6f957b0", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be", + "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be", "shasum": "" }, "require": { @@ -21099,7 +21234,7 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-code-coverage": "^9.2.28", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -21158,7 +21293,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.11" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13" }, "funding": [ { @@ -21174,7 +21309,7 @@ "type": "tidelift" } ], - "time": "2023-08-19T07:10:56+00:00" + "time": "2023-09-19T05:39:22+00:00" }, { "name": "react/promise", @@ -22813,13 +22948,9 @@ "aliases": [], "minimum-stability": "dev", "stability-flags": { - "cityofboston/adminimal_admin_toolbar": 20, - "cityofboston/adminimal_theme": 20, - "cityofboston/ape": 20, "cityofboston/contextual_range_filter": 20, - "cityofboston/rollbar": 20, - "cityofboston/shortcode": 20, "drupal/addtocal": 10, + "drupal/adminimal_admin_toolbar": 20, "drupal/advanced_text_formatter": 5, "drupal/config_split": 5, "drupal/config_update": 15, @@ -22836,5 +22967,5 @@ "prefer-lowest": false, "platform": [], "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/config/default/node_buildinghousing.settings.yml b/config/default/node_buildinghousing.settings.yml index 12aded2f12..a7de479df2 100644 --- a/config/default/node_buildinghousing.settings.yml +++ b/config/default/node_buildinghousing.settings.yml @@ -1,4 +1,4 @@ delete_parcel: 0 -pause_auto: 1 +pause_auto: 0 log_actions: 1 maxfilesize: '101' diff --git a/config/default/salesforce_mapping.salesforce_mapping.listing_record.yml b/config/default/salesforce_mapping.salesforce_mapping.listing_record.yml deleted file mode 100644 index 328fb32e3b..0000000000 --- a/config/default/salesforce_mapping.salesforce_mapping.listing_record.yml +++ /dev/null @@ -1,64 +0,0 @@ -langcode: en -id: listing_record -label: 'Listing Record' -weight: -2 -type: salesforce_mapping -key: '' -always_upsert: false -async: false -push_standalone: true -pull_standalone: false -pull_trigger_date: LastModifiedDate -pull_frequency: 0 -push_frequency: 0 -push_limit: 0 -push_retries: 3 -pull_where_clause: '' -sync_triggers: - push_create: true - push_update: true - push_delete: true - pull_create: false - pull_update: false - pull_delete: false -salesforce_object_type: Case -drupal_entity_type: webform_submission -drupal_bundle: metrolist_listing -field_mappings: - - - drupal_field_type: WebformElements - drupal_field_value: request_type - direction: drupal_sf - salesforce_field: Type - id: 0 - - - drupal_field_type: Constant - drupal_field_value: 0121A000000i6oZ - direction: drupal_sf - salesforce_field: RecordTypeId - id: 1 - - - drupal_field_type: WebformElements - drupal_field_value: contact_company - direction: drupal_sf - salesforce_field: Contact_Company__c - id: 2 - - - drupal_field_type: Constant - drupal_field_value: 0013F00000WotLdQAJ - direction: drupal_sf - salesforce_field: AccountId - id: 3 - - - drupal_field_type: Constant - drupal_field_value: '' - direction: drupal_sf - salesforce_field: '' - id: 4 -status: true -dependencies: - config: - - webform.webform.metrolist_listing - module: - - salesforce_push -uuid: 6ab9ca74-bf24-4d91-85cb-dc0ef2e811e5 diff --git a/config/default/webform.webform.metrolist_listing.yml b/config/default/webform.webform.metrolist_listing.yml index 900ea12b09..98e7229e06 100644 --- a/config/default/webform.webform.metrolist_listing.yml +++ b/config/default/webform.webform.metrolist_listing.yml @@ -22,8 +22,6 @@ elements: |- '#type': processed_text '#text': |

Thanks for using Metrolist to market your affordable housing opportunities! By filling out this form with correct contact, building, and unit information, you can expect your listing to be live on our site within 1 business day.

- -

If you have trouble using this form, please watch this video guide for help.

'#format': filtered_html select_contact: '#type': select @@ -142,6 +140,9 @@ elements: |- - ':input[name="select_development"]': empty: true '#default_value': true + '#wrapper_attributes': + class: + - m-t300 '#attributes': class: - cb-f @@ -155,6 +156,9 @@ elements: |- - xor - ':input[name="select_development"]': empty: true + '#wrapper_attributes': + class: + - m-t300 '#attributes': class: - cb-f @@ -168,6 +172,9 @@ elements: |- - xor - ':input[name="select_development"]': empty: true + '#wrapper_attributes': + class: + - m-t300 '#attributes': class: - cb-f @@ -176,14 +183,14 @@ elements: |- '#title': 'Update public listing information' '#states': visible: - - ':input[name="select_development"]': - '!value': new - - xor - - ':input[name="select_development"]': - empty: true + ':input[name="select_development"]': + '!value': new + '#states_clear': false '#default_value': true '#wrapper_attributes': - style: 'display:none;' + class: + - m-t300 + style: 'display:none' '#attributes': class: - cb-f @@ -418,25 +425,6 @@ elements: |- '#more': '
' horizontal_rule_04: '#type': webform_horizontal_rule - flexbox_22: - '#type': webform_flexbox - type_of_listing: - '#type': select - '#title': 'Rental or Ownership?' - '#options': - rental: Rental - ownership: Ownership - '#empty_option': 'Type of Listing' - '#wrapper_attributes': - class: - - sel - '#attributes': - class: - - sel-f - '#label_attributes': - class: - - sel-l - '#required': true current_units_fieldset: '#type': fieldset '#title': 'Current Units' @@ -455,7 +443,10 @@ elements: |- '#description_display': before '#required': true '#states_clear': false + '#multiple__header': false '#multiple__item_label': unit + '#multiple__min_items': 1 + '#multiple__empty_items': 0 '#multiple__sorting': false '#multiple__operations': false '#multiple__add_more': false @@ -467,16 +458,39 @@ elements: |- '#element': relist_unit: '#type': checkbox - '#title': 'Re-list Unit' + '#title': 'Active Listing' '#help': 'This Unit will be re-listed as a "First Come, First Served"' + '#wrapper_attributes': + class: + - g--2 + style: + - 'float:left;' + - 'margin-top: 1.8rem;' + - 'clear:both;' + '#label_attributes': + style: + - 'float:right;' + - 'flex-direction: column-reverse' '#attributes': + style: + - 'margin: 0 auto;' class: - cb-f + - cb-f-wf ami: '#type': textfield '#title': 'AMI %' '#readonly': true + '#access': false + '#wrapper_attributes': + class: + - g--2 + style: + - 'float:left;' + - 'display:none;' '#attributes': + class: + - sel-f style: - 'width: 7rem;' - 'border: none;' @@ -486,63 +500,202 @@ elements: |- '#type': textfield '#title': Bedrooms '#readonly': true + '#access': false + '#wrapper_attributes': + class: + - g--2 + style: + - 'float:left;' + - 'display:none;' '#attributes': + class: + - sel-f style: - 'width: 4rem;' - 'border: none;' - 'text-align: center;' - 'background: none;' + ada: + '#type': textarea + '#title': ADA + '#help': 'This Unit has been built to be ADA compliant.' + '#readonly': true + '#wrapper_attributes': + class: + - g--2 + style: + - 'float:left;' + '#label_attributes': + class: + - m-t300 + - m-b200 + style: + - 'margin-top:1.5em;' + '#attributes': + style: + - 'border: none;' + - 'resize: none;' + - 'line-height: 1.2rem;' + - 'background: none;' + '#cols': 15 + '#rows': 3 + summary: + '#type': textarea + '#title': Summary + '#readonly': true + '#wrapper_attributes': + class: + - g--2 + style: + - 'float:left;' + '#attributes': + style: + - 'border: none;' + - 'resize: none;' + - 'line-height: 1.2rem;' + - 'background: none;' + '#cols': 15 + '#rows': 4 + rental_type: + '#type': checkbox + '#title': 'Income Based' + '#wrapper_attributes': + class: + - g--2 + style: + - 'float:left;' + - 'margin-top: 1.6rem;' + '#label_attributes': + style: + - 'float:right;' + - 'flex-direction: column-reverse' + '#attributes': + style: + - 'margin: 0 auto;' + class: + - cb-f + - cb-f-wf price: '#type': textfield - '#required': true '#title': Price '#help': 'Please enter Sale price or Rental price per month.' + '#wrapper_attributes': + class: + - g--2 + style: + - 'float:left;' + '#label_attributes': + class: + - m-t300 + - m-b200 '#attributes': style: - 'width: 12rem;' - '#input_mask': '''alias'': ''currency''' + '#input_mask': "'alias': 'currency','digits':'0'" minimum_income_threshold: '#type': textfield '#title': 'Minimum Income' '#help': 'Please enter the minimum annual salary you require for applicants. (This field can be left empty or 0)' + '#wrapper_attributes': + class: + - g--2 + - m-b300 + style: + - 'float:left;' + '#label_attributes': + class: + - m-t300 + - m-b200 '#attributes': style: - 'width: 12rem;' - '#input_mask': '''alias'': ''currency''' - ada: + '#input_mask': "'alias': 'currency','digits':'0'" + status: + '#type': textfield + '#title': ' ' + '#placeholder': Status + '#readonly': true + '#access': false + '#attributes': + style: + - 'width: 7rem;' + - 'border: none;' + - 'display:none;' + pending: '#type': textarea - '#title': ADA - '#help': 'This Unit has been built to be ADA compliant.' + '#title': pending '#readonly': true '#wrapper_attributes': + class: + - g--2 + style: + - 'float:left;' + - 'clear:both;' + '#label_attributes': style: - - 'margin: 2rem 0 0 0;' + - 'display:none;' '#attributes': style: - 'border: none;' - 'resize: none;' - 'line-height: 1.2rem;' - 'background: none;' - '#cols': 10 - '#rows': 3 - status: - '#type': textfield - '#title': ' ' - '#placeholder': Status + - 'text-align: center;' + '#rows': 1 + '#cols': 90 + note: + '#type': textarea + '#title': notes '#readonly': true - '#access': false + '#wrapper_attributes': + class: + - g--8 + style: + - 'float:left;' + '#label_attributes': + style: + - 'display:none;' '#attributes': style: - - 'width: 7rem;' - 'border: none;' - - 'display:none;' + - 'resize: none;' + - 'line-height: 1.2rem;' + - 'background: none;' + '#rows': 1 + '#cols': 90 sfid: '#type': textfield '#title': ' ' '#readonly': true - '#access': false + '#access': true '#attributes': style: 'display:none;' + flexbox_22: + '#type': webform_flexbox + '#states': + visible: + - ':input[name="select_development"]': + value: new + - xor + - ':input[name="add_additional_units"]': + checked: true + type_of_listing: + '#type': select + '#title': 'Rental or Ownership?' + '#options': + rental: Rental + ownership: Ownership + '#empty_option': 'Type of Listing' + '#wrapper_attributes': + class: + - sel + '#attributes': + class: + - sel-f + '#label_attributes': + class: + - sel-l + '#required': true units_fieldset: '#type': fieldset '#title': 'Additional Units' @@ -555,6 +708,9 @@ elements: |- - xor - ':input[name="add_additional_units"]': checked: true + '#attributes': + class: + - m-t300 units: '#type': webform_custom_composite '#title': Units @@ -571,18 +727,24 @@ elements: |- unit_count: '#type': number '#required': true - '#title': 'Number of Units' + '#title': Number '#help': 'How many units with this AMI, Bedroom count at this price point do you have available?' '#wrapper_attributes': class: - - g--3 + - g--2 style: - 'float:left;' - 'margin-top: 0.25rem;' '#label_attributes': + style: + - 'min-height: 18px;' class: - m-t300 - m-b200 + '#attributes': + min: 0 + style: + - 'min-height: 68px;' ami: '#type': select '#options': @@ -619,6 +781,11 @@ elements: |- '#attributes': class: - sel-f + '#label_attributes': + class: + - m-t300 + - m-b200 + - l-wf bedrooms: '#type': select '#options': @@ -641,6 +808,11 @@ elements: |- '#attributes': class: - sel-f + '#label_attributes': + class: + - m-t300 + - m-b200 + - l-wf bathrooms: '#type': select '#options': @@ -664,75 +836,179 @@ elements: |- '#attributes': class: - sel-f - price: - '#type': textfield - '#required': true - '#title': Price - '#help': 'Please enter Sale price or Rental price per month.' - '#wrapper_attributes': - class: - - g--3 - style: - - 'float:right;' - - 'margin-right: 0;' '#label_attributes': class: - m-t300 - m-b200 - '#input_mask': '''alias'': ''currency''' + - l-wf ada_m: '#type': checkbox - '#title': 'ADA - Mobility' + '#title': ADA
Mobility '#wrapper_attributes': - class: - - g--3 style: - 'float:left;' - - 'margin-top: 1.5rem;' - - 'clear:both;' + - 'margin-top: 1.6rem;' + '#label_attributes': + class: + - cb-dh + style: + - 'float:right;' + - 'flex-direction: column-reverse;' + - 'line-height: 1rem;' '#attributes': + style: + - 'margin: 0 auto;' class: - cb-f + - cb-f-wf ada_v: '#type': checkbox - '#title': 'ADA - Visual' + '#title': ADA
Vision '#wrapper_attributes': - class: - - g--3 style: - 'float:left;' - - 'margin-top: 1.5rem;' + - 'margin-top: 1.6rem;' + '#label_attributes': + class: + - cb-dh + style: + - 'float:right;' + - 'flex-direction: column-reverse;' + - 'line-height: 1rem;' '#attributes': + style: + - 'margin: 0 auto;' class: - cb-f + - cb-f-wf ada_h: '#type': checkbox - '#title': 'ADA - Hearing' + '#title': ADA
Hearing '#wrapper_attributes': + style: + - 'float:left;' + - 'margin-top: 1.6rem;' + '#label_attributes': class: - - g--3 + - cb-dh + style: + - 'float:right;' + - 'flex-direction: column-reverse;' + - 'line-height: 1rem;' + '#attributes': + style: + - 'margin: 0 auto;' + - 'min-width: 42px;' + class: + - cb-f + - cb-f-wf + rental_type: + '#type': checkbox + '#title': 'Income Based' + '#wrapper_attributes': + class: + - g--2 style: - 'float:left;' - - 'margin-top: 1.5rem;' + - 'clear:both;' + - 'margin-top: 1.6rem;' + '#label_attributes': + style: + - 'float:right;' + - 'flex-direction: column-reverse;' '#attributes': + style: + - 'margin: 0 auto;' + - 'min-width: 42px;' class: - cb-f + - cb-f-wf + price: + '#type': textfield + '#title': Price + '#help': 'Please enter Sale price or Rental price per month.' + '#wrapper_attributes': + class: + - g--4 + style: + - 'float:left;' + '#label_attributes': + class: + - m-t300 + - m-b200 + '#input_mask': "'alias': 'currency', 'digits':'0'" minimum_income_threshold: '#type': textfield '#title': 'Minimum Income Threshold' '#help': 'Please enter the minimum annual salary you require for applicants. (This field can be left empty or 0)' '#wrapper_attributes': class: - - g--3 + - g--4 - m-b300 style: - - 'float:right;' - - 'margin-top: 1.5rem;' + - 'float:left;' + - 'margin-left: 1.5rem;' '#label_attributes': class: - - m-t100 + - m-t300 - m-b200 - '#input_mask': '''alias'': ''currency''' + '#input_mask': "'alias': 'currency', 'digits':'0'" + scriptblock: + '#type': processed_text + '#text': |- + + '#format': json_friendly public_listing_information: '#type': webform_wizard_page '#title': 'Availability Information' @@ -912,11 +1188,10 @@ elements: |- class: - txt-l '#format_items': comma - '#date_date_element': datepicker '#date_year_range': '0:+3' - '#date_time_element': timepicker - '#date_time_format': 'g:i A' '#date_time_step': '900' + '#datepicker': true + '#step': '' remove_posting_date: '#type': date '#title': 'Remove Posting Date' @@ -1091,7 +1366,7 @@ elements: |- class: - m-v600 style: 'float:right;margin-bottom:1rem;' -css: "#seal {\r\n display:none;\r\n}\r\n\r\n.webform-submission-form .sel-c {\r\n display: block; \r\n}\r\n.webform-submission-form .webform-multiple-table td {\r\n border-bottom: solid 3px;\r\n padding-left: 1.5rem;\r\n}\r\n\r\n.webform-submission-form .form-checkboxes .js-form-type-checkbox {\r\n -webkit-box-align: center;\r\n -ms-flex-align: center;\r\n align-items: center;\r\n display: -webkit-box;\r\n display: -ms-flexbox;\r\n -js-display: flex;\r\n display: flex;\r\n margin: 0;\r\n position: relative;\r\n}\r\n\r\n.webform-submission-form .form-checkboxes .js-form-type-checkbox:not(:last-child) {\r\n margin: 0 0 8px;\r\n margin: 0 0 .5rem;\r\n}\r\n\r\n.webform-submission-form .form-checkboxes .js-form-type-checkbox label {\r\n color: #091f2f;\r\n font-family: Lora,Georgia,serif;\r\n font-size: calc(16px + 4 * ((100vw - 480px) / 960));\r\n line-height: 1.2;\r\n margin: 0 7px;\r\n width: calc(100% - 42px);\r\n text-transform: none;\r\n}\r\n\r\n.webform-submission-form .ml-contact-info .js-form-type-email label,\r\n.webform-submission-form .ml-contact-info .js-form-type-textfield label,\r\n.webform-submission-form .js-form-type-checkboxes label.cb-l, \r\n.webform-submission-form .fieldset-legend {\r\n color: #091f2f;\r\n font-family: Montserrat,Arial,sans-serif;\r\n font-size: calc(14px + 2 * ((100vw - 480px) / 960));\r\n font-weight: 700;\r\n line-height: 1.4;\r\n letter-spacing: 1px;\r\n text-transform: uppercase;\r\n display: block; \r\n}\r\n.webform-submission-form ul li {\r\n background: none;\r\n}\r\n.webform-submission-form .js-webform-multiple-add {\r\n margin: 1.5rem;\r\n}\r\n\r\n.webform-submission-form .js-webform-multiple-add .form-number {\r\n padding-top: 0; \r\n}\r\n\r\n.webform-submission-form .webform-options-display-three-columns {\r\n height: 11rem; \r\n}\r\n\r\n.webform-submission-form select.error {\r\n border-color: #d22d23; \r\n}\r\n\r\n.webform-submission-data--view-mode-preview #metrolist_listing--your_contact_information summary,\r\n.webform-submission-data--view-mode-preview #metrolist_listing--select_building summary {\r\n display:none;\r\n}\r\n\r\n.hide-till-confirm {\r\n display: none;\r\n}\r\n\r\n[data-webform-wizard-current-page=\"webform_preview\"] .hide-till-confirm {\r\n display: block;\r\n}\r\n\r\n\r\n@media screen and (min-width: 768px){\r\n .webform-submission-form .form-checkboxes .js-form-type-checkbox label {\r\n margin: 0 1rem;\r\n }\r\n}\r\n\r\n@media screen and (min-width: 1440px){\r\n .webform-submission-form .form-checkboxes .js-form-type-checkbox label {\r\n font-size: 20px;\r\n }\r\n\r\n .webform-submission-form .ml-contact-info .js-form-type-email label,\r\n .webform-submission-form .ml-contact-info .js-form-type-textfield label,\r\n .webform-submission-form .js-form-type-checkboxes label.cb-l, \r\n .webform-submission-form .fieldset-legend { \r\n font-size: 16px;\r\n }\r\n}\r\n\r\n@media screen {\r\n .webform-submission-metrolist-listing-form .form-actions {\r\n margin-bottom: 1rem;\r\n margin-top: 1rem;\r\n }\r\n details.webform-submission-information {\r\n border: 1px solid darkgray;\r\n padding: 0 8px;\r\n }\r\n .webform-submission-information summary {\r\n color: #1871bd;\r\n }\r\n input.webform-button--submit[value=Save] {\r\n display: none;\r\n }\r\n form:not(#webform-submission-metrolist-listing-edit-form) .webform-submission-information summary::before {\r\n content: \"This form has been submitted.\";\r\n color: #d22d23;\r\n font-family: Lora,serif;\r\n display: block;\r\n } \r\n form:not(#webform-submission-metrolist-listing-edit-form) .webform-submission-information summary::after {\r\n content: \"Please request a new form to edit this property or add a new property.\";\r\n color: #58585b;\r\n font-family: Lora,serif;\r\n display: block;\r\n } \r\n form:not(#webform-submission-metrolist-listing-edit-form) .webform-submission-information span.summary::after {\r\n content: \" (click for details)\";\r\n font-size: 75%;\r\n font-family: Lora,serif;\r\n color: initial;\r\n font-style: italic;\r\n } \r\n}\r\ndiv[id^=\"metrolist_listing--i_agree2\"],\r\ninput[id^=\"edit-select-building\"],\r\ninput[id^=\"edit-your-contact-information\"],\r\ndiv[id^=\"metrolist_listing--i_agree\"] label {\r\n display:none;\r\n}\r\n\r\nlabel[for=\"edit-i-agree-agree\"] {\r\n margin: 20px 0;\r\n}\r\n\r\n#edit-i-agree--description {\r\n line-height: 35px;\r\n}\r\n\r\n#edit-actions-01 {\r\n margin-top: -3px;\r\n}\r\n\r\n.form-item-update-public-listing-information {\r\n display: none !important;\r\n}\r\n\r\n#edit-actions-01-submit {\r\n position: relative;\r\n float: right;\r\n}\r\n\r\n.webform-submission-metrolist-listing-form {\r\n display: block !important\"\r\n}" +css: "#seal {\r\n display:none;\r\n}\r\n\r\n.webform-submission-form .sel-c {\r\n display: block; \r\n}\r\n.webform-submission-form .webform-multiple-table td {\r\n border-bottom: solid 3px;\r\n padding-left: 1.5rem;\r\n}\r\n\r\n.webform-submission-form .form-checkboxes .js-form-type-checkbox {\r\n -webkit-box-align: center;\r\n -ms-flex-align: center;\r\n align-items: center;\r\n display: -webkit-box;\r\n display: -ms-flexbox;\r\n -js-display: flex;\r\n display: flex;\r\n margin: 0;\r\n position: relative;\r\n}\r\n\r\n.webform-submission-form .form-checkboxes .js-form-type-checkbox:not(:last-child) {\r\n margin: 0 0 8px;\r\n margin: 0 0 .5rem;\r\n}\r\n\r\n.webform-submission-form .form-checkboxes .js-form-type-checkbox label {\r\n color: #091f2f;\r\n font-family: Lora,Georgia,serif;\r\n font-size: calc(16px + 4 * ((100vw - 480px) / 960));\r\n line-height: 1.2;\r\n margin: 0 7px;\r\n width: calc(100% - 42px);\r\n text-transform: none;\r\n}\r\n\r\n.webform-submission-form .ml-contact-info .js-form-type-email label,\r\n.webform-submission-form .ml-contact-info .js-form-type-textfield label,\r\n.webform-submission-form .js-form-type-checkboxes label.cb-l, \r\n.webform-submission-form .fieldset-legend {\r\n color: #091f2f;\r\n font-family: Montserrat,Arial,sans-serif;\r\n font-size: calc(14px + 2 * ((100vw - 480px) / 960));\r\n font-weight: 700;\r\n line-height: 1.4;\r\n letter-spacing: 1px;\r\n text-transform: uppercase;\r\n display: block; \r\n}\r\n.webform-submission-form ul li {\r\n background: none;\r\n}\r\n.webform-submission-form .js-webform-multiple-add {\r\n margin: 1.5rem;\r\n}\r\n\r\n.webform-submission-form .js-webform-multiple-add .form-number {\r\n padding-top: 0; \r\n}\r\n\r\n.webform-submission-form .webform-options-display-three-columns {\r\n height: 11rem; \r\n}\r\n\r\n.webform-submission-form select.error {\r\n border-color: #d22d23; \r\n}\r\n\r\n.webform-submission-data--view-mode-preview #metrolist_listing--your_contact_information summary,\r\n.webform-submission-data--view-mode-preview #metrolist_listing--select_building summary {\r\n display:none;\r\n}\r\n\r\n.hide-till-confirm {\r\n display: none;\r\n}\r\n\r\n[data-webform-wizard-current-page=\"webform_preview\"] .hide-till-confirm {\r\n display: block;\r\n}\r\n\r\n\r\n@media screen and (min-width: 768px){\r\n .webform-submission-form .form-checkboxes .js-form-type-checkbox label {\r\n margin: 0 1rem;\r\n }\r\n}\r\n\r\n@media screen and (min-width: 1440px){\r\n .webform-submission-form .form-checkboxes .js-form-type-checkbox label {\r\n font-size: 20px;\r\n }\r\n\r\n .webform-submission-form .ml-contact-info .js-form-type-email label,\r\n .webform-submission-form .ml-contact-info .js-form-type-textfield label,\r\n .webform-submission-form .js-form-type-checkboxes label.cb-l, \r\n .webform-submission-form .fieldset-legend { \r\n font-size: 16px;\r\n }\r\n}\r\n\r\n@media screen {\r\n .webform-submission-metrolist-listing-form .form-actions {\r\n margin-bottom: 1rem;\r\n margin-top: 1rem;\r\n }\r\n details.webform-submission-information {\r\n border: 1px solid darkgray;\r\n padding: 0 8px;\r\n }\r\n .webform-submission-information summary {\r\n color: #1871bd;\r\n }\r\n input.webform-button--submit[value=Save] {\r\n display: none;\r\n }\r\n form:not(#webform-submission-metrolist-listing-edit-form) .webform-submission-information summary::before {\r\n content: \"This form has been submitted.\";\r\n color: #d22d23;\r\n font-family: Lora,serif;\r\n display: block;\r\n } \r\n form:not(#webform-submission-metrolist-listing-edit-form) .webform-submission-information summary::after {\r\n content: \"Please request a new form to edit this property or add a new property.\";\r\n color: #58585b;\r\n font-family: Lora,serif;\r\n display: block;\r\n } \r\n form:not(#webform-submission-metrolist-listing-edit-form) .webform-submission-information span.summary::after {\r\n content: \" (click for details)\";\r\n font-size: 75%;\r\n font-family: Lora,serif;\r\n color: initial;\r\n font-style: italic;\r\n } \r\n}\r\ndiv[id^=\"metrolist_listing--i_agree2\"],\r\ninput[id^=\"edit-select-building\"],\r\ninput[id^=\"edit-your-contact-information\"],\r\ndiv[id^=\"metrolist_listing--i_agree\"] label {\r\n display:none;\r\n}\r\n\r\nlabel[for=\"edit-i-agree-agree\"] {\r\n margin: 20px 0;\r\n}\r\n\r\n#edit-i-agree--description {\r\n line-height: 35px;\r\n}\r\n\r\n#edit-actions-01 {\r\n margin-top: -3px;\r\n}\r\n\r\n.form-item-update-public-listing-information {\r\n display: none !important;\r\n}\r\n\r\n#edit-actions-01-submit {\r\n position: relative;\r\n float: right;\r\n}\r\n\r\n.webform-submission-metrolist-listing-form {\r\n display: block !important\r\n}\r\n.cb-f-wf {\r\n margin-top: 1.5rem !important;\r\n \r\n}\r\n.cb-f-wf::before {\r\n position:relative !important;\r\n top: -3px !important;\r\n left: -3px !important;\r\n}\r\n.l-wf {\r\n min-height: 18px;\r\n padding-top: 10px;\r\n}\r\n.cb-l-ws {\r\n margin: 0 10px;\r\n height: 0px;\r\n text-align: center;\r\n}\r\n.cb-dh .cb-l-ws {\r\n height: 14px;\r\n}" javascript: '' settings: ajax: true @@ -1159,13 +1434,7 @@ settings: previous_submissions_message: '' autofill: true autofill_message: '' - autofill_excluded_elements: - neighborhood: neighborhood - property_name: property_name - street_address: street_address - zip_code: zip_code - amenities_features: amenities_features - type_of_listing: type_of_listing + autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false @@ -1184,6 +1453,8 @@ settings: wizard_toggle: true wizard_toggle_show_label: '' wizard_toggle_hide_label: '' + wizard_page_type: container + wizard_page_title_tag: h2 preview: 2 preview_label: 'Submission Review' preview_title: 'Submission Review' @@ -1218,7 +1489,7 @@ settings: draft_pending_single_message: '' draft_pending_multiple_message: '' confirmation_type: url_message - confirmation_url: departments/neighborhood-development/metrolist/metrolist-listing-form + confirmation_url: metrolist/listing-request confirmation_title: 'Thank you for your submission.' confirmation_message: "Thank you for listing your property with Metrolist!
\r\nIf there are any issues posting your listing, our Program Manager will be in touch soon. \r\n

If you have questions, contact us at metrolist@boston.gov

\r\n\r\n

- City of Boston Mayor's Office of Housing

" confirmation_attributes: { } @@ -1307,7 +1578,7 @@ handlers: disabled: ':input[name="formerrors"]': value: '0' - weight: 3 + weight: -47 settings: states: - completed @@ -1352,15 +1623,13 @@ handlers: theme_name: '' parameters: { } debug: false - create_a_metrolist_listing: - id: 'Create a MetroList Listing' - handler_id: create_a_metrolist_listing - label: 'Create a MetroList Listing' - notes: 'Creates or updates a development in salesforce.' + manage_and_post_a_submission: + id: post_metrolist_listing_submission + handler_id: manage_and_post_a_submission + label: 'Manage and post a Submission' + notes: '' status: true - conditions: { } - weight: -50 - settings: { } + conditions: { } confirmation_to_submitter: id: remote_post handler_id: confirmation_to_submitter @@ -1371,7 +1640,7 @@ handlers: disabled: ':input[name="formerrors"]': '!value': '0' - weight: 2 + weight: -49 settings: method: POST type: x-www-form-urlencoded @@ -1460,7 +1729,7 @@ handlers: disabled: ':input[name="formerrors"]': '!value': '0' - weight: 2 + weight: -48 settings: method: POST type: x-www-form-urlencoded diff --git a/config/default/webform.webform.metrolist_listing_entry_form.yml b/config/default/webform.webform.metrolist_listing_entry_form.yml index 403b0398d4..0f12c7cc1e 100644 --- a/config/default/webform.webform.metrolist_listing_entry_form.yml +++ b/config/default/webform.webform.metrolist_listing_entry_form.yml @@ -1,7 +1,8 @@ uuid: 8fa47d8c-1c9b-427b-9d30-c7246d8763b8 langcode: en status: open -dependencies: { } +dependencies: + - bos_metrolist weight: 0 open: null close: null @@ -142,6 +143,8 @@ settings: wizard_toggle: false wizard_toggle_show_label: '' wizard_toggle_hide_label: '' + wizard_page_type: container + wizard_page_title_tag: h2 preview: 0 preview_label: '' preview_title: '' @@ -183,7 +186,7 @@ settings: purge: none purge_days: null results_disabled: true - results_disabled_ignore: false + results_disabled_ignore: true results_customize: false token_view: false token_update: false @@ -285,7 +288,18 @@ handlers: draft_updated_custom_data: '' converted_url: '' converted_custom_data: '' - message: '[webform:handler:remote_post:message]' + message: '' messages: { } error_url: '' + + create_a_metrolist_listing_submission: + id: make_metrolist_listing_submission + handler_id: create_a_metrolist_listing_submission + label: 'New Metrolist-Listing submission for email address in entry form submission.' + notes: '' + status: true + conditions: { } + weight: -50 + settings: { } + variants: { } diff --git a/docroot/modules/custom/bos_components/modules/bos_email/src/Controller/PostmarkAPI.php b/docroot/modules/custom/bos_components/modules/bos_email/src/Controller/PostmarkAPI.php index 3626fb16e6..3ed7f0b963 100644 --- a/docroot/modules/custom/bos_components/modules/bos_email/src/Controller/PostmarkAPI.php +++ b/docroot/modules/custom/bos_components/modules/bos_email/src/Controller/PostmarkAPI.php @@ -178,8 +178,17 @@ private function formatEmail(array &$emailFields) { } if ($this->debug) { + try { + $json = json_encode(@$emailFields["postmark_data"]->data()); + } + catch(\Exception $e) { + $json = "Error encountered {$e->getMessage} "; + if ($emailFields["postmark_data"]->hasValidationErrors()) { + $json .= implode(", ", $emailFields["postmark_data"]->getValidationErrors()); + } + } \Drupal::logger("bos_email:PostmarkAPI") - ->info("Email prepped {$this->server}:
" . json_encode($emailFields["postmark_data"]->data())); + ->info("Email prepped {$this->server}:
" . $json); } // Validate the email data @@ -312,8 +321,22 @@ public function begin(string $service = 'contactform') { if ($this->request->getCurrentRequest()->getMethod() == "POST") { // Get the request payload. - $payload = $this->request->getCurrentRequest()->get('email'); - + if ($this->request->getCurrentRequest()->getContentTypeFormat() == "form") { + $payload = $this->request->getCurrentRequest()->get('email'); + } + elseif ($this->request->getCurrentRequest()->getContentTypeFormat() == "json") { + if ($_payload = $this->request->getCurrentRequest()->getContent()) { + $_payload = json_decode($_payload); + foreach ($_payload as $key => $value) { + if (str_contains($key, "email")) { + $payload[preg_replace('~email\[(.*)\]~', '$1', $key)] = $value; + } + else { + $payload[$key] = $value; + } + } + } + } // Check the honeypot if there is one. if (!empty($this->honeypot) && !empty($payload[$this->honeypot])) { PostmarkOps::alertHandler($payload, [], "", [], "honeypot"); @@ -683,7 +706,7 @@ public function callback(string $service, string $stream) { if ($this->debug) { \Drupal::logger("bos_email:PostmarkAPI") - ->info("Finished {$service}: " . json_encode($response_array)); + ->info("Finished Callback {$service}: " . json_encode($response_array)); } } diff --git a/docroot/modules/custom/bos_components/modules/bos_email/src/Templates/MetrolistInitiationForm.php b/docroot/modules/custom/bos_components/modules/bos_email/src/Templates/MetrolistInitiationForm.php index d06e890198..6776259094 100644 --- a/docroot/modules/custom/bos_components/modules/bos_email/src/Templates/MetrolistInitiationForm.php +++ b/docroot/modules/custom/bos_components/modules/bos_email/src/Templates/MetrolistInitiationForm.php @@ -104,7 +104,7 @@ public static function formatOutboundEmail(array &$emailFields): void { $cobdata = &$emailFields["postmark_data"]; $cobdata->setField("Tag", "metrolist form initiation"); - $cobdata->setField("endpoint", $emailFields["endpoint"] ?: PostmarkAPI::POSTMARK_DEFAULT_ENDPOINT); + $cobdata->setField("endpoint", $emailFields["endpoint"] ?? PostmarkAPI::POSTMARK_DEFAULT_ENDPOINT); self::templatePlainText($emailFields); if (!empty($emailFields["useHtml"])) { diff --git a/docroot/modules/custom/bos_components/modules/bos_iframe/bos_iframe.module b/docroot/modules/custom/bos_components/modules/bos_iframe/bos_iframe.module index f0117cdf85..2e2fc07fc1 100644 --- a/docroot/modules/custom/bos_components/modules/bos_iframe/bos_iframe.module +++ b/docroot/modules/custom/bos_components/modules/bos_iframe/bos_iframe.module @@ -22,6 +22,7 @@ function bos_iframe_theme() { * Implements hook_preprocess_paragraph(). */ function bos_iframe_preprocess_paragraph(&$vars) { + if (!empty($vars['paragraph'])) { $paragraph = $vars['paragraph']; switch ($paragraph->bundle()) { @@ -30,6 +31,7 @@ function bos_iframe_preprocess_paragraph(&$vars) { $url = $paragraph->get('field_source_url')->getValue(); $vars['source_url'] = $url['0']['value']; $vars['iframe_title'] = $vars['content']['field_short_title'][0]['#context']['value']; + $vars['attributes']['class'][] = "b--fw"; } $vars['id'] = Html::getUniqueId('iframe'); if (!$paragraph->get('field_iframe_size')->isEmpty()) { diff --git a/docroot/modules/custom/bos_components/modules/bos_iframe/templates/paragraph--iframe.html.twig b/docroot/modules/custom/bos_components/modules/bos_iframe/templates/paragraph--iframe.html.twig index 135a09e2db..08c28e6d09 100644 --- a/docroot/modules/custom/bos_components/modules/bos_iframe/templates/paragraph--iframe.html.twig +++ b/docroot/modules/custom/bos_components/modules/bos_iframe/templates/paragraph--iframe.html.twig @@ -27,7 +27,7 @@
{{ content.contextual_links }}
-
+
{{ content.field_component_title }} {{ content.field_contact }} diff --git a/docroot/modules/custom/bos_components/modules/bos_link_collections/bos_link_collections.module b/docroot/modules/custom/bos_components/modules/bos_link_collections/bos_link_collections.module index 78690093d3..5cb9def838 100644 --- a/docroot/modules/custom/bos_components/modules/bos_link_collections/bos_link_collections.module +++ b/docroot/modules/custom/bos_components/modules/bos_link_collections/bos_link_collections.module @@ -230,6 +230,7 @@ function bos_link_collections_preprocess_paragraph(&$vars) { case 'group_of_links_mini_grid': //@TODO: Move to a new class and method // set this block to be centered if it gets wrapped in a full width block for the theme change + $vars['attributes']['class'][] = "b--fw"; if ($paragraph->field_component_theme->value) { $vars['attributes']['class'][] = "b--fw"; } diff --git a/docroot/modules/custom/bos_components/modules/bos_text/templates/paragraph--text.html.twig b/docroot/modules/custom/bos_components/modules/bos_text/templates/paragraph--text.html.twig index 8158b1f04b..99983276fd 100644 --- a/docroot/modules/custom/bos_components/modules/bos_text/templates/paragraph--text.html.twig +++ b/docroot/modules/custom/bos_components/modules/bos_text/templates/paragraph--text.html.twig @@ -26,7 +26,7 @@ */ #} - +
{% if content.field_component_title and not hideTitle %}
diff --git a/docroot/modules/custom/bos_content/modules/bos_metrolist/bos_metrolist.module b/docroot/modules/custom/bos_content/modules/bos_metrolist/bos_metrolist.module index 8a19004cfb..2c548a98c8 100644 --- a/docroot/modules/custom/bos_content/modules/bos_metrolist/bos_metrolist.module +++ b/docroot/modules/custom/bos_content/modules/bos_metrolist/bos_metrolist.module @@ -12,11 +12,11 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\views\ViewExecutable; - -use Drupal\webform\Entity\Webform; use Drupal\webform\Entity\WebformSubmission; - use Drupal\bos_metrolist\MetroListSalesForceConnection; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; + +require_once "includes/metrolist_listing_webform.extensions.php"; /** * Implements hook_help(). @@ -30,8 +30,8 @@ function bos_metrolist_help($route_name, RouteMatchInterface $route_match) { $output .= '

' . t('MetroList features for the City of Boston') . '

'; return $output; - default: } + return ""; } /** @@ -149,7 +149,7 @@ function bos_metrolist_preprocess_page(array &$variables) { } } \Drupal::messenger()->deleteByType(\Drupal::messenger()::TYPE_STATUS); - foreach ($keep as $newmsg) { + foreach ($keep ?? [] as $newmsg) { \Drupal::messenger()->addStatus($newmsg); } } @@ -669,40 +669,6 @@ function bos_metrolist_set_views_row_group(&$rowGroups = [], $bounds = [], $row } -/** - * Create a new Metrolist Listing form after requested via the email form. - */ -function bos_metrolist_create_listing_submission($email = 'John.Smith@example.com') { - - $webform_id = 'metrolist_listing'; - $webform = Webform::load($webform_id); - // Create webform submission. - $values = [ - 'webform_id' => $webform->id(), - // @TODO: not sure we need this... need to test - 'in_draft' => TRUE, - 'data' => [ - 'contact_email' => $email, - ], - ]; - - /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ - $webform_submission = WebformSubmission::create($values); - $webform_submission->save(); - $webform_submission->getTokenUrl('update'); - return $webform_submission; -} - -/** - * Implements hook_webform_handler_invoke_alter(). - */ -function bos_metrolist_webform_handler_invoke_alter(WebformHandlerInterface $handler, $method_name, array &$args) { - if ($method_name == 'pre_save') { - $test = 2; - // $args[1]->data["hidden_subject"] = $args[1]->data["hidden_subject"] . "-ALTERD"; - } -} - /** * Implements hook_form_alter(). */ @@ -710,112 +676,19 @@ function bos_metrolist_form_alter(&$form, FormStateInterface $form_state, $form_ $test = $form_state->getValues(); } -/** - * Used to inject to Listing form token into the email from the email entry form. - */ -function bos_metrolist_webform_submission_presave(WebformSubmission $webform_submission) { - if ($webform_submission->getWebform()->id() == 'metrolist_listing_entry_form') { - $email = $webform_submission->getElementData('email'); - $token = bos_metrolist_create_listing_submission($email)->getToken(); - $message = str_replace('#metrolist:new-listing:token#', $token, $webform_submission->getElementData('hidden_message')); - $webform_submission->setElementData('hidden_message', $message); - } -} - -/** - * Implements hook_preprocess_HOOK(). - */ -function bos_metrolist_preprocess_form_element(&$variables) { - if (isset($variables["element"]["#webform_id"]) && $variables["element"]["#webform_id"] == "metrolist_listing--select_contact" && $variables["element"]["#type"] == "select") { - if ($variables['attributes'] instanceof Attribute) { - $variables["attributes"]->addClass("m-b300"); - } - else { - $variables["attributes"]["class"][] = "m-b300"; - } - } -} - /** * Implements hook_webform_element_alter(). - * - * @param array $element - * Webform element / field. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * Active Webform form_state. - * @param array $context - * Context data about the webform. */ function bos_metrolist_webform_element_alter(array &$element, FormStateInterface $form_state, array $context) { - if (isset($element['#webform_id']) && $element['#webform_id'] == 'metrolist_listing--select_contact') { - $salesForce = new MetroListSalesForceConnection(); - $webformSID = $element['#webform_submission'] ?? NULL; - - if (substr($form_state->getFormObject()->getFormId(), -9) == "edit_form") { - $salesForce->loadWebformSubmission($webformSID); - } - $contactOptions = $salesForce->getContactOptionsByEmail($salesForce->getContactEmail()); - - if ($contactOptions) { - $element['#options'] = array_merge($element['#options'], $contactOptions); - } - - } - - if (isset($element['#webform_id']) && $element['#webform_id'] == 'metrolist_listing--select_development') { - $salesForce = new MetroListSalesForceConnection(); - $webformSID = $element['#webform_submission'] ?? NULL; - // Another anonymous user may have a webform in the anon session. - if ((empty($salesForce->webformSubmission()) || !empty($_SESSION['webform_submissions'])) - && substr($form_state->getFormObject()->getFormId(), -9) == "edit_form") { - $salesForce->loadWebformSubmission($webformSID); - } - $contactSID = $salesForce->webformSubmission() ? $salesForce->webformSubmission()->getElementData('select_contact') : NULL; - - $developmentOptions = $salesForce->getDevelopmentOptionsByContactSid($contactSID); - - if ($developmentOptions) { - $element['#options'] = array_merge($element['#options'], $developmentOptions); - } - - } - - if (isset($element['#webform_id']) && $element['#webform_id'] == 'metrolist_listing--units') { - $salesForce = new MetroListSalesForceConnection(); - $excludeOptions = ['Homeless Set Aside']; - $amiOptions = $salesForce->getPickListValues('Development_unit__C', 'Income_Eligibility_AMI_Threshold__c', $excludeOptions) ?? NULL; - - if ($amiOptions) { - $element['#element']['ami']['#options'] = $amiOptions; - } - } - - if (isset($element['#webform_id']) && $element['#webform_id'] == 'metrolist_listing--region') { - $salesForce = new MetroListSalesForceConnection(); - $regionOptions = $salesForce->getPickListValues('Development__C', 'Region__c') ?? NULL; - - if ($regionOptions) { - $element['#options'] = $regionOptions; - } + if (!isset($element['#webform_id'])) { + return; } - - if (isset($element['#webform_id']) && $element['#webform_id'] == 'metrolist_listing--city') { - $salesForce = new MetroListSalesForceConnection(); - $cityOptions = $salesForce->getPickListValues('Development__C', 'City__c') ?? NULL; - - if ($cityOptions) { - $element['#options'] = $cityOptions; - } + if ($context['form']["#webform_id"] != 'metrolist_listing' || $element["#webform"] != 'metrolist_listing' ) { + return; } - if (isset($element['#webform_id']) && $element['#webform_id'] == 'metrolist_listing--neighborhood') { - $salesForce = new MetroListSalesForceConnection(); - $neighborhoodOptions = $salesForce->getPickListValues('Development__C', 'Neighborhood__c') ?? NULL; - - if ($neighborhoodOptions) { - $element['#options'] = $neighborhoodOptions; - } - } + // Function contained in includes. + metrolist_listing_webform_element_alter($element, $form_state, $context); } diff --git a/docroot/modules/custom/bos_content/modules/bos_metrolist/includes/metrolist_listing_webform.extensions.php b/docroot/modules/custom/bos_content/modules/bos_metrolist/includes/metrolist_listing_webform.extensions.php new file mode 100644 index 0000000000..caf4c91cab --- /dev/null +++ b/docroot/modules/custom/bos_content/modules/bos_metrolist/includes/metrolist_listing_webform.extensions.php @@ -0,0 +1,174 @@ +getRequestUri(), "/test"); + $isNewContact = TRUE; + $isNewBuilding = TRUE; + + // For the dropdown boxes we need to get the webform submission - so we can + // work out which page we are on, and the query needs to be run. + if (in_array($element['#webform_id'], [ + 'metrolist_listing--select_contact', + 'metrolist_listing--select_development', + 'metrolist_listing--units', + 'metrolist_listing--region', + 'metrolist_listing--city', + 'metrolist_listing--neighborhood', + ])) { + + $salesForce = new MetroListSalesForceConnection(); + $webformSID = $element['#webform_submission'] ?? NULL; + $webform_submission = $salesForce->webformSubmission() ?? NULL; + + // if (!$webformSID && isset($form_state->getBuildInfo()["callback_object"])) { + // $salesForce->setWebformSubmission($form_state->getBuildInfo()["callback_object"]->getEntity()); + // } + + // Load the webform submission from the submissionID supplied. + if ($webformSID && !isset($webform_submission)) { + $salesForce->loadWebformSubmission($webformSID); + $webform_submission = $salesForce->webformSubmission() ?? NULL; + } + + if (!$webform_submission) { + // Last ditch attempt to get the submission from the token. + if (str_contains(\Drupal::request()->getQueryString() ?? "", "token")) { + $token = explode("=", \Drupal::request()->getQueryString() ?? "=")[1]; + $webform_submission_storage = \Drupal::entityTypeManager()->getStorage('webform_submission'); + if ($entities = $webform_submission_storage->loadByProperties(['token' => $token])) { + $webformSID = array_key_first($entities); + $webform_submission = reset($entities); + } + } + } + + if ($webform_submission) { + $currentPage = $webform_submission->getCurrentPage() ?? "your_contact_information"; + $isNewContact = ($webform_submission->getElementData("select_contact") == "new"); + $isNewBuilding = ($webform_submission->getElementData("select_development") == "new"); + } + else { + // This error is typically caused because the metrolist_listing submission + // and token was generated by the metrolist_listing_entry_form + // in bos_metrolist_webform_submission_presave(). The confirmation email + // was sent but the new metrolist_listing submission did not save. + // This may be because the database is locked during the pre_save of the + // entry_form submission and therefore if the entry_form submission does + // not save, then neither does the new metrolist_listing submission. + if (!$isTest) { + \Drupal::logger("bos_metrolist")->error("This token does not exist.",); + throw new BadRequestException("This token does not exist", 404); + } + } + + } + + switch ($element['#webform_id']) { + case 'metrolist_listing--select_contact': + if ($currentPage == "your_contact_information") { + $contactOptions = $salesForce->getContactOptionsByEmail($salesForce->getContactEmail()); + if ($contactOptions) { + $element['#options'] = array_merge($element['#options'], $contactOptions); + } + } + break; + + case 'metrolist_listing--select_development': + if (!empty($currentPage) && $currentPage == "select_building") { + $contactSID = $webform_submission->getElementData('select_contact') ?? NULL; + $developmentOptions = $salesForce->getDevelopmentOptionsByContactSid($contactSID); + if ($developmentOptions) { + $element['#options'] = array_merge($element['#options'], $developmentOptions); + } + } + break; + + case 'metrolist_listing--units': + if (!empty($currentPage) && $currentPage == "unit_information") { + $excludeOptions = ['Homeless Set Aside']; + $amiOptions = $salesForce->getPickListValues('Development_unit__C', 'Income_Eligibility_AMI_Threshold__c', $excludeOptions) ?? NULL; + if ($amiOptions) { + $element['#element']['ami']['#options'] = $amiOptions; + } + } + break; + + case 'metrolist_listing--region': + if ($isNewBuilding && !empty($currentPage) && $currentPage == "property_information") { + $regionOptions = $salesForce->getPickListValues('Development__C', 'Region__c') ?? NULL; + if ($regionOptions) { + $element['#options'] = $regionOptions; + } + } + break; + + case 'metrolist_listing--city': + if ($isNewBuilding && !empty($currentPage) && $currentPage == "property_information") { + $cityOptions = $salesForce->getPickListValues('Development__C', 'City__c') ?? NULL; + if ($cityOptions) { + $element['#options'] = $cityOptions; + } + } + break; + + case 'metrolist_listing--neighborhood': + if ($isNewBuilding && !empty($currentPage) && $currentPage == "property_information") { + $neighborhoodOptions = $salesForce->getPickListValues('Development__C', 'Neighborhood__c') ?? NULL; + if ($neighborhoodOptions) { + $element['#options'] = $neighborhoodOptions; + } + } + break; + } + +} + +/** + * Implements hook_preprocess_form_alter() + * + * @param $variables + * + * @return void + */ +function bos_metrolist_preprocess_form_element(&$variables) { + if (isset($variables["element"]["#webform_id"]) && $variables["element"]["#webform_id"] == "metrolist_listing--select_contact" && $variables["element"]["#type"] == "select") { + if ($variables['attributes'] instanceof Attribute) { + $variables["attributes"]->addClass("m-b300"); + } + else { + $variables["attributes"]["class"][] = "m-b300"; + } + } +} diff --git a/docroot/modules/custom/bos_content/modules/bos_metrolist/src/MetroListSalesForceConnection.php b/docroot/modules/custom/bos_content/modules/bos_metrolist/src/MetroListSalesForceConnection.php index a451628383..73d4944235 100644 --- a/docroot/modules/custom/bos_content/modules/bos_metrolist/src/MetroListSalesForceConnection.php +++ b/docroot/modules/custom/bos_content/modules/bos_metrolist/src/MetroListSalesForceConnection.php @@ -29,7 +29,8 @@ class MetroListSalesForceConnection { * {@inheritdoc} */ public function client() { - return \Drupal::service('salesforce.client'); + $client = \Drupal::service('salesforce.client'); + return $client; } /** @@ -52,7 +53,7 @@ public function getSalesforceUrl() { * @param string $sid * Submission ID. * - * @return \Drupal\Core\Entity\EntityInterface|null + * @return \Drupal\Core\Entity\EntityInterface|MetroListSalesForceConnection|null * Webform Submission. */ public function loadWebformSubmission(string $sid = NULL) { @@ -83,6 +84,11 @@ public function webformSubmission() { return $this->webformSubmission; } + public function setWebformSubmission($webformSubmission) { + $this->webformSubmission = $webformSubmission; + $this->getContactEmail(); + } + /** * Get Contact email from webform submission. * @@ -107,7 +113,18 @@ public function getContactEmail() { */ public function getContactByEmail($email = '') { - $contactSFObject = $this->client()->objectReadbyExternalId('Contact', 'Email', $email); + try{ + $contactSFObject = $this->client()->objectReadbyExternalId('Contact', 'Email', $email); + } + catch (\Exception $e) { + $contacts = $this->getContactsByEmail($email); + if (!empty($contacts)) { + $contactSFObject = (string) $contacts[array_key_first($contacts)]; + } + else { + return NULL; + } + } return $contactSFObject; } @@ -118,7 +135,7 @@ public function getContactByEmail($email = '') { * @param string $email * Contact Email. * - * @return bool|null + * @return array|bool|null * Contacts. */ public function getContactsByEmail(string $email) { @@ -233,11 +250,18 @@ public function getUnitsByDevelopmentSid(string $developmentSFID) { 'Availability_Status__c', 'Income_Eligibility_AMI_Threshold__c', 'Number_of_Bedrooms__c', + 'Number_of_Bathrooms__c', 'Rent_or_Sale_Price__c', 'Minimum_Income_Threshold__c', + 'Occupancy_Type__c', + 'Availability_Type__c', + 'Lottery_Application_Deadline__c', + 'Rent_Type__c', 'ADA_H__c', 'ADA_V__c', 'ADA_M__c', + 'Published_Date__c', + 'Suggested_Removal_Date__c', ]; return $this->client()->query($developmentQuery)->records() ?? NULL; diff --git a/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/SalesforceMappingField/RelatedTermStrings.php b/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/SalesforceMappingField/RelatedTermStrings.php index 3f5d23cd32..f699fba53d 100644 --- a/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/SalesforceMappingField/RelatedTermStrings.php +++ b/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/SalesforceMappingField/RelatedTermStrings.php @@ -2,6 +2,7 @@ namespace Drupal\bos_metrolist\Plugin\SalesforceMappingField; +use Drupal\Component\Annotation\Plugin; use Drupal\salesforce_mapping\Plugin\SalesforceMappingField\RelatedTermString; use Drupal\Core\Entity\EntityInterface; diff --git a/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/WebformHandler/CreateMetroListingWebformHandler.php b/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/WebformHandler/CreateMetroListingWebformHandler.php deleted file mode 100644 index d15609df7a..0000000000 --- a/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/WebformHandler/CreateMetroListingWebformHandler.php +++ /dev/null @@ -1,693 +0,0 @@ -authMan()->getProvider()->getInstanceUrl() . '/' . $this->salesforce_id->value; - } - - /** - * Lookup a SF Contact by Email. - * - * @param string $email - * Value of Contact email address. - * - * @return \Drupal\salesforce\SObject - * Object of the requested Salesforce object, Contact that matches the email. - */ - public function getContactByEmail($email = '') { - - $contactSFObject = $this->client()->objectReadbyExternalId('Contact', 'Email', $email); - - return $contactSFObject; - } - - /** - * Add or update a SF Contact. - * - * @param array $developmentData - * Submission Data. - * - * @return mixed - * Return SFID of Contact or false - */ - public function addContact(array $developmentData) { - - $contactSFID = $developmentData['contactsfid'] ?? NULL; - - $contactEmail = $developmentData['contact_email'] ?? NULL; - $contactName = $developmentData['contact_name'] ?? NULL; - $contactPhone = $developmentData['contact_phone'] ?? NULL; - $contactAddress = $developmentData['contact_address'] ?? []; - - $fieldData = [ - // @TODO: Make Config?, Hardcoded the SFID to `DND Contacts` - 'AccountId' => $this->addAccount($developmentData), - 'Email' => $contactEmail, - ]; - - if (empty($fieldData['AccountId'])) { - // Effectively bubble up the addAccount error. - return FALSE; - } - - if ($contactPhone) { - $fieldData['Phone'] = $contactPhone; - } - - $contactName = explode(' ', $contactName); - if (isset($contactName[1])) { - $fieldData['FirstName'] = $contactName[0]; - $fieldData['LastName'] = $contactName[1]; - } - else { - $fieldData['FirstName'] = ''; - $fieldData['LastName'] = $contactName[0]; - } - - if (!empty($contactAddress)) { - $fieldData['MailingStreet'] = $contactAddress['address']; - $fieldData['MailingCity'] = $contactAddress['city']; - $fieldData['MailingState'] = $contactAddress['state_province']; - $fieldData['MailingPostalCode'] = $contactAddress['postal_code']; - } - - return $this->updateSalesforce('Contact', $fieldData, "Id", $contactSFID ?? NULL); - - } - - /** - * Add or update a SF Account. - * - * @param array $developmentData - * Submission Data. - * - * @return mixed - * Return SFID or false - */ - public function addAccount(array $developmentData) { - - try { - $accountQuery = new SelectQuery('Account'); - $company = $developmentData['contact_company']; - // DU ticket #2494 DIG-79 - // Escape apostrophes b/c of the way the string is built on the next line. - $company = str_replace("'", "\'", $company); - $accountQuery->addCondition('Name', "'$company'"); - $accountQuery->addCondition('Type', "'Property Manager'"); - $accountQuery->addCondition('Division__c', "'DND'"); - // @TODO: hardcoded to SFID for Account Record Type: "Vendor" - $accountQuery->addCondition('RecordTypeId', "'012C0000000I0hCIAS'"); - $accountQuery->fields = ['Id', 'Name', 'Type']; - - $existingAccount = $this->client()->query($accountQuery)->records() ?? NULL; - $existingAccount = reset($existingAccount) ?? NULL; - - } - catch (Exception $exception) { - \Drupal::logger('bos_metrolist')->error($exception->getMessage()); - return FALSE; - } - - if ($existingAccount) { - // Just return the SFID of the account, no need to update anything.... - return (string) $existingAccount->id(); - } - else { - // Create a new Account in SF. - $fieldData = [ - 'Name' => $developmentData['contact_company'], - 'Business_Legal_Name__c' => $developmentData['contact_company'], - 'Type' => 'Property Manager', - 'Division__c' => 'DND', - // @TODO: hardcoded to SFID for Account Record Type: "Vendor" - 'RecordTypeId' => '012C0000000I0hCIAS', - ]; - return $this->updateSalesforce('Account', $fieldData); - } - - } - - /** - * Add or update a SF Development. - * - * @param string $developmentName - * Development Name. - * @param array $developmentData - * Submission Data. - * @param string $contactId - * Contact ID. - * - * @return mixed - * Return SFID or false - */ - public function addDevelopment(string $developmentName, array $developmentData, string $contactId) { - $developmentSFID = $developmentData['developmentsfid'] ?? NULL; - - $fieldData = [ - 'Name' => $developmentName, - 'Region__c' => !empty($developmentData['region']) ? $developmentData['region'] : 'Boston', - 'Street_Address__c' => $developmentData['street_address'] ?? '', - 'City__c' => !empty($developmentData['city']) ? $developmentData['city'] : 'Boston', - 'ZIP_Code__c' => $developmentData['zip_code'] ?? '', - 'Wheelchair_Access__c' => empty($developmentData['wheelchair_accessible']) ? FALSE : TRUE, - 'Listing_Contact_Company__c' => $developmentData['contact_company'] ?? NULL, - ]; - - if (isset($contactId)) { - $fieldData['Listing_Contact__c'] = $contactId; - - // @TODO: change to Man_Comp_Contact__c and set the Account on the Contact and not the Development. - $fieldData['Management_Company_Contact__c'] = $contactId; - } - - if (isset($developmentData['neighborhood'])) { - $fieldData['Neighborhood__c'] = !empty($developmentData['neighborhood']) ? $developmentData['neighborhood'] : NULL; - } - - if (isset($developmentData['utilities_included'])) { - $fieldData['Utilities_included__c'] = !empty($developmentData['utilities_included']) ? implode(';', $developmentData['utilities_included']) : NULL; - } - - if (isset($developmentData['upfront_fees'])) { - $fieldData['Due_at_signing__c'] = !empty($developmentData['upfront_fees']) ? implode(';', $developmentData['upfront_fees']) : NULL; - } - - if (isset($developmentData['utilities_included'])) { - $fieldData['Features__c'] = !empty($developmentData['amenities_features']) ? implode(';', $developmentData['amenities_features']) : NULL; - } - - if (empty($developmentData['same_as_above_contact_info'])) { - if (!empty($developmentData['public_contact_address'])) { - $addr = $developmentData['public_contact_address']; - $fieldData['Public_Contact_Address__c'] = $addr['address'] . "\r\n" . $addr['city'] . ", " . $addr['state_province'] . " " . $addr['postal_code']; - } - $fieldData['Public_Contact_Email__c'] = $developmentData['public_contact_email']; - $fieldData['Public_Contact_Name__c'] = $developmentData['public_contact_name']; - $fieldData['Public_Contact_Phone__c'] = $developmentData['public_contact_phone']; - } - else { - if (!empty($developmentData['contact_address'])) { - $addr = $developmentData['contact_address']; - $fieldData['Public_Contact_Address__c'] = $addr['address'] . "\r\n" . $addr['city'] . ", " . $addr['state_province'] . " " . $addr['postal_code']; - } - $fieldData['Public_Contact_Email__c'] = $developmentData['contact_email']; - $fieldData['Public_Contact_Name__c'] = $developmentData['contact_name']; - $fieldData['Public_Contact_Phone__c'] = $developmentData['contact_phone']; - } - - return $this->updateSalesforce('Development__c', $fieldData, 'Id', $developmentSFID ?? NULL); - - } - - /** - * Add or update a SF Units. - * - * @param array $developmentData - * Submission Data. - * @param string $developmentId - * Development ID. - * - * @return mixed - * Return SFID or false - */ - public function addUnits(array $developmentData, string $developmentId) { - - $units = $developmentData['units']; - - if (!empty($units)) { - foreach ($units as $unitGroup) { - if (empty($unitGroup['price'])) { - continue; - } - for ($unitNumber = 1; $unitNumber <= $unitGroup['unit_count']; $unitNumber++) { - - $unitName = $developmentData['street_address'] . ' Unit #' . $unitNumber; - - // @TODO: Change out the values for some of these by updating the options values in the webform configs to match SF. - $fieldData = [ - 'Name' => $unitName, - 'Development_new__c' => $developmentId, - 'Availability_Status__c' => 'Pending', - 'Suggested_Removal_Date__c' => $developmentData['remove_posting_date'] ?? NULL, - 'Income_Restricted_new__c' => $developmentData['units_income_restricted'] ?? 'Yes', - 'Availability_Type__c' => $developmentData['available_how'] == 'first_come_first_serve' ? 'First come, first served' : 'Lottery', - 'User_Guide_Type__c' => $developmentData['available_how'] == 'first_come_first_serve' ? 'First come, first served' : 'Lottery', - 'Occupancy_Type__c' => $developmentData['type_of_listing'] == 'rental' ? 'Rent' : 'Own', - // @TODO: Need to add this to the Listing Form somehow for "% of Income" - 'Rent_Type__c' => 'Fixed $', - 'Income_Eligibility_AMI_Threshold__c' => isset($unitGroup['ami']) ? $unitGroup['ami'] : 'N/A', - 'Number_of_Bedrooms__c' => isset($unitGroup['bedrooms']) ? (double) $unitGroup['bedrooms'] : 0.0, - 'Rent_or_Sale_Price__c' => isset($unitGroup['price']) ? (double) filter_var($unitGroup['price'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) : 0.0, - 'ADA_V__c' => empty($unitGroup['ada_v']) ? FALSE : TRUE, - 'ADA_H__c' => empty($unitGroup['ada_h']) ? FALSE : TRUE, - 'ADA_M__c' => empty($unitGroup['ada_m']) ? FALSE : TRUE, - 'Waitlist_Open__c' => $developmentData['waitlist_open'] == 'No' || empty($developmentData['waitlist_open']) ? FALSE : TRUE, - ]; - - if (isset($unitGroup['bathrooms'])) { - $fieldData['Number_of_Bathrooms__c'] = isset($unitGroup['bathrooms']) ? (double) $unitGroup['bathrooms'] : 0.0; - } - - if (isset($unitGroup['minimum_income_threshold'])) { - $fieldData['Minimum_Income_Threshold__c'] = !empty($unitGroup['minimum_income_threshold']) ? (double) filter_var($unitGroup['minimum_income_threshold'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) : 0.0; - } - - if (isset($developmentData['posted_to_metrolist_date'])) { - $fieldData['Requested_Publish_Date__c'] = $developmentData['posted_to_metrolist_date']; - } - - if (isset($developmentData['application_deadline_datetime'])) { - $fieldData['Lottery_Application_Deadline__c'] = $developmentData['application_deadline_datetime']; - } - - if (isset($developmentData['website_link'])) { - $fieldData['Lottery_Application_Website__c'] = $developmentData['website_link'] ?? NULL; - } - - if (isset($developmentData['pdf_upload']) && $developmentData['direct_visitors'] == 'pdf') { - $fieldData['Lottery_Application_Website__c'] = \Drupal::service('file_url_generator')->generateAbsoluteString(File::load($developmentData['pdf_upload'])->getFileUri()) ?? NULL; - } - - if ($this->updateSalesforce('Development_Unit__c', $fieldData) === FALSE) { - return FALSE; - } - - } - - } - } - return TRUE; - } - - /** - * Update a SF Units. - * - * @param array $developmentData - * Submission Data. - * @param string $developmentId - * Development ID. - * - * @return mixed - * Return SFID or false - */ - public function updateUnits(array $developmentData, string $developmentId) { - - $currentUnits = $developmentData['current_units']; - - if (!empty($currentUnits)) { - foreach ($currentUnits as $unit) { - - if (empty($unit['relist_unit'])) { - continue; - } - - // @TODO: Change out the values for some of these by updating the options values in the webform configs to match SF. - $fieldData = [ - 'Availability_Status__c' => 'Pending', - 'Availability_Type__c' => 'First come, first served', - 'Suggested_Removal_Date__c' => $developmentData['remove_posting_date'] ?? NULL, - 'User_Guide_Type__c' => 'First come, first served', - 'Rent_or_Sale_Price__c' => isset($unit['price']) ? (double) filter_var($unit['price'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) : 0.0, - 'Waitlist_Open__c' => $developmentData['waitlist_open'] == 'No' || empty($developmentData['waitlist_open']) ? FALSE : TRUE, - ]; - - if (!empty($unit['minimum_income_threshold'])) { - $fieldData['Minimum_Income_Threshold__c'] = !empty($unit['minimum_income_threshold']) ? (double) filter_var($unit['minimum_income_threshold'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) : 0.0; - } - - if (!empty($developmentData['posted_to_metrolist_date'])) { - $fieldData['Requested_Publish_Date__c'] = $developmentData['posted_to_metrolist_date']; - } - - if (!empty($developmentData['application_deadline_datetime'])) { - $fieldData['Lottery_Application_Deadline__c'] = $developmentData['application_deadline_datetime']; - } - - if (!empty($developmentData['website_link'])) { - $fieldData['Lottery_Application_Website__c'] = $developmentData['website_link'] ?? NULL; - } - - if ($this->updateSalesforce('Development_Unit__c', $fieldData, NULL, $unit['sfid']) === FALSE) { - return FALSE; - } - - } - } - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) { - - // TODO: Change the autogenerated stub. - parent::postSave($webform_submission, $update); - } - - /** - * {@inheritdoc} - */ - public function preSave(WebformSubmissionInterface $webform_submission) { - - /* - * Populate the form with the contact info from SF. - * If an existing contact is selected in the contact dropdown, then the - * contactsfid field is set with the SF ID for that contact. For new - * contacts the field is blank. - */ - if ($contactSFID = $webform_submission->getElementData('select_contact')) { - // If the contact selected is not new and: either the contactsfid field is - // empty or the contactsfid does not equal the contact ID already selected, - // then fetch the contact from SF and populate the information into the - // form. - if ($contactSFID != 'new' - && (empty($webform_submission->getElementData('contactsfid')) - || $contactSFID != $webform_submission->getElementData('contactsfid') - || empty($webform_submission->getElementData('contact_name', '')))) { - - $contactData = $this->client()->objectRead('Contact', $contactSFID); - $accountData = $contactData->hasField('AccountId') ? $this->client() - ->objectRead('Account', $contactData->field('AccountId')) : NULL; - - $fields = [ - 'contact_name' => 'Name', - 'contact_address' => 'MailingAddress', - 'contact_phone' => 'Phone', - 'contact_company' => 'Account' - ]; - - foreach ($fields as $webform_field => $sf_field) { - if ($webform_field == 'contact_address') { - $sf_field = [ - 'address' => $contactData->field('MailingStreet'), - 'city' => $contactData->field('MailingCity'), - 'state_province' => $contactData->field('MailingState'), - 'postal_code' => $contactData->field('MailingPostalCode'), - 'address_2' => "", - 'country' => "", - ]; - - $webform_submission->setElementData($webform_field, $sf_field); - - } - elseif ($webform_field == 'contact_company' && $accountData) { - $sf_field = $accountData->field('Name'); - $webform_submission->setElementData($webform_field, $sf_field); - } - else { - $webform_submission->setElementData($webform_field, $contactData->field($sf_field)); - } - } - - if (empty($webform_submission->getElementData("contact_email"))) { - $webform_submission->setElementData("contact_email", $contactData->field("Email")); - } - - $webform_submission->setElementData('contactsfid', $contactSFID); - } - } - - /* - * Populate the form with the developments info from SF. - * If an existing development (Building) is selected in the Building - * dropdown, then the developmentsfid field is set with the SF ID for that - * development. For new buildings (developments) the field is blank. - */ - if ($developmentSFID = $webform_submission->getElementData('select_development')) { - // If the development selected is not new and: either the developmentsfid - // field is empty or the developmentsfid does not equal the development ID - // already selected, then fetch the development from SF and populate the - // information into the form. - if ($developmentSFID != 'new' - && (empty($webform_submission->getElementData('developmentsfid')) - || $developmentSFID != $webform_submission->getElementData('developmentsfid') - || empty($webform_submission->getElementData('property_name', '')))) { - - $developmentData = $this->client() - ->objectRead('Development__c', $developmentSFID); - - $fields = [ - 'property_name' => 'Name', - 'city' => 'City__c', - 'region' => 'Region__c', - 'neighborhood' => 'Neighborhood__c', - 'street_address' => 'Street_Address__c', - 'zip_code' => 'ZIP_Code__c', - 'upfront_fees' => 'Due_at_signing__c', - 'amenities_features' => 'Features__c', - 'utilities_included' => 'Utilities_included__c', - 'public_contact_email' => 'Public_Contact_Email__c', - 'public_contact_name' => 'Public_Contact_Name__c', - 'property_management_company' => 'Listing_Contact_Company__c', - 'public_contact_phone' => 'Public_Contact_Phone__c', - 'public_contact_address' => 'Public_Contact_Address__c', - 'wheelchair_accessible' => 'Wheelchair_Access__c' - ]; - - foreach ($fields as $webform_field => $sf_field) { - // If (empty($webform_submission->getElementData($webform_field))) {. - - if ($webform_field == 'upfront_fees' || $webform_field == 'amenities_features' || $webform_field == 'utilities_included') { - $sf_field = explode(';', $developmentData->field($sf_field)); - $webform_submission->setElementData($webform_field, $sf_field); - - } - elseif ($webform_field == 'public_contact_address') { - - // @TODO: Re-factor this!!! - $sf_public_address = explode("\r\n", $developmentData->field('Public_Contact_Address__c')); - $sf_public_street = $sf_public_address[0]; - $sf_public_address = explode(', ', $sf_public_address[1]); - $sf_public_city = $sf_public_address[0]; - $sf_public_address = explode(' ', $sf_public_address[1]); - $sf_public_state = $sf_public_address[0]; - $sf_public_zipcode = $sf_public_address[1]; - - $sf_field = [ - 'address' => $sf_public_street, - 'city' => $sf_public_city, - 'state_province' => $sf_public_state, - 'postal_code' => $sf_public_zipcode, - 'address_2' => "", - 'country' => "", - ]; - $webform_submission->setElementData($webform_field, $sf_field); - - } - else { - - $webform_submission->setElementData($webform_field, $developmentData->field($sf_field)); - } - // } - } - - $this->updatedDevelopmentData = TRUE; - $webform_submission->setElementData('developmentsfid', $developmentSFID); - } - } - // Then also load the units for the development found into the form. - if (isset($developmentSFID) - && $developmentSFID == $webform_submission->getElementData('developmentsfid') - && $webform_submission->getElementData('update_unit_information')) { - - $salesForce = new MetroListSalesForceConnection(); - - $currentUnits = $salesForce->getUnitsByDevelopmentSid($developmentSFID); - - if ($currentUnits - && empty($webform_submission->getElementData('current_units')[0]['sfid']) - || ($currentUnits && $this->updatedDevelopmentData)) { - $this->updatedDevelopmentData = FALSE; - $webformCurrentUnits = []; - foreach ($currentUnits as $unitSFID => $currentUnit) { - // $adaUnit = ($currentUnit->field('ADA_H__c') || $currentUnit->field('ADA_V__c') || $currentUnit->field('ADA_M__c')) ? "👁" : '-'; - - $adaUnit = []; - - if ($currentUnit->field('ADA_H__c')) { - $adaUnit[] = '✓ Hearing'; - } - if ($currentUnit->field('ADA_V__c')) { - $adaUnit[] = '✓ Visual'; - } - if ($currentUnit->field('ADA_M__c')) { - $adaUnit[] = '✓ Mobility'; - } - - $adaUnit = !empty($adaUnit) ? implode("\r", $adaUnit) : ""; - - $webformCurrentUnits[] = [ - 'relist_unit' => 0, - 'ada' => $adaUnit, - 'ami' => $currentUnit->field('Income_Eligibility_AMI_Threshold__c'), - 'bedrooms' => $currentUnit->field('Number_of_Bedrooms__c'), - 'price' => $currentUnit->field('Rent_or_Sale_Price__c'), - 'minimum_income_threshold' => $currentUnit->field('Minimum_Income_Threshold__c'), - 'status' => $currentUnit->field('Availability_Status__c'), - 'sfid' => $currentUnit->field('Id'), - ]; - - } - $webform_submission->setElementData('current_units', $webformCurrentUnits); - // $webform_submission->save(); - } - - } - - if ($webform_submission->isCompleted()) { - // If the form is complete, then update salesforce and save the sfid's - // if we don't already have them. - // DU Note, this block was previously in postSave() but that did not give - // the opportunity to save sfids for new developments and contacts. - // DU Note, I am re-purposing the sfid fields, hopefully there is not any - // code which uses them to determine upsert/insert activities. - $fieldData = $webform_submission->getData(); - $contactId = $this->addContact($fieldData) ?? NULL; - // Reset this b/c it gets saved with the form. - $webform_submission->setElementData('formerrors', 0); - - if (empty($fieldData["contact_email"])) { - // This should not happen. - if (!empty($contactId) && empty($contactData)) { - $contactData = $this->client()->objectRead('Contact', $contactId); - } - if (!empty($contactData)) { - $webform_submission->setElementData("contact_email", $contactData->field("Email")); - $fieldData['contact_email'] = $contactData->field("Email"); - } - else { - // This is only an issue at this point b/c the confirmation email - // will not be sent to the contact. Also sf may be updated with - // the empty email address. - } - } - - if ($contactId === FALSE) { - // An error occurred. - // Set this flag so that confirmation emails are not sent out. - $webform_submission->setElementData('formerrors', '1'); - } - else { - if (empty($fieldData['contactsfid'])) { - $webform_submission->setElementData('contactsfid', $contactId); - } - $developmentId = $this->addDevelopment($fieldData['property_name'], $fieldData, $contactId) ?? NULL; - - if ($developmentId === FALSE) { - // An error occurred. - // Set this flag so that confirmation emails are not sent out. - $webform_submission->setElementData('formerrors', '1'); - } - else { - if (empty($fieldData['developmentsfid'])) { - $webform_submission->setElementData("developmentsfid", $developmentId); - } - if ($fieldData['update_unit_information']) { - if ($this->updateUnits($fieldData, $developmentId) === FALSE) { - // An error occurred. - // Set this flag so that confirmation emails are not sent out. - $webform_submission->setElementData('formerrors', '1'); - } - } - if ($this->addUnits($fieldData, $developmentId) === FALSE) { - // An error occurred. - // Set this flag so that confirmation emails are not sent out. - $webform_submission->setElementData('formerrors', '1'); - } - } - } - } - - parent::preSave($webform_submission); - } - - /** - * {@inheritdoc} - */ - public function getSummary() { - $a = parent::getSummary(); - unset($a["#theme"]); - $a["#markup"] = "Custom WebformHandler defined in module bos_metrolist"; - return $a; - } - - /** - * Helper function to interract with salesforce client. - * - * @param string $name The sf object name to use - * @param array $params The data to insert/update into the sf object - * @param string $key The unique key for the sf object for upsert (if missing then create will be used) - * @param string $value The unique key value for upsert - * - * @return false|string The response from Salesforce. - */ - private function updateSalesforce(string $name, array $params, $key = NULL, $value = NULL) { - - try { - if (!empty($key) && !empty($value)) { - $result = (string) $this->client()->objectUpsert($name, $key, $value, $params); - } - elseif (empty($key) && !empty($value)) { - $result = (string) $this->client()->objectUpdate($name, $value, $params); - } - else { - $result = (string) $this->client()->objectCreate($name, $params); - } - return $result; - } - catch (RestException | Exception $exception) { - \Drupal::logger('bos_metrolist')->error($exception->getMessage()); - $this->messenger()->addError("Error saving submission"); - return FALSE; - } - - } -} diff --git a/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/WebformHandler/MakeMetroListingWebformHandler.php b/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/WebformHandler/MakeMetroListingWebformHandler.php new file mode 100644 index 0000000000..333ef34d55 --- /dev/null +++ b/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/WebformHandler/MakeMetroListingWebformHandler.php @@ -0,0 +1,124 @@ +Creates a new, blank Metrolist-Listing submission using the provided email address."; + return $a; + } + + /** + * On submission of metrolist_listing_entry_form. + * + * Note: FYI, the way we have configured the listing_entry_form, it does not + * actually save the submission, but this event still fires. + * + * Create a new metrolist_listing submission and inject the token into the + * body of email sent back to the submitter. + * + * @inheritdoc + */ + public function submitForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) { + + parent::submitForm($form, $form_state, $webform_submission); + + $email = $webform_submission->getElementData('email') ?? ($webform_submission->getElementData('contact_email') ?? $_POST["email"]); + $token = $this->_createMetrolistListingSubmission($email); + + $message = $webform_submission->getElementData('hidden_message') ?? $_POST["hidden_message"]; + $message = str_replace('#metrolist:new-listing:token#', $token, $message); + $webform_submission->setElementData('hidden_message', $message); + + } + + /** + * @inheritdoc + */ + public function confirmForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) { + parent::confirmForm($form, $form_state, $webform_submission); // TODO: Change the autogenerated stub + } + + /** + * @inheritdoc + */ + public function validateForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) { + parent::validateForm($form, $form_state, $webform_submission); // TODO: Change the autogenerated stub + } + + /** + * Creates a new metrolist_listing submission, popultaes with the email address + * of the submitter and then saves the submission. + * Returns a token for the submission or FALSE. + * + * @param $contact_email + * + * @return array|bool + * @throws \Drupal\Core\Entity\EntityStorageException + */ + private function _createMetrolistListingSubmission($contact_email) { + + // Find the contact ID for this contact if possible. + $salesForce = new MetroListSalesForceConnection(); + $contacts = $salesForce->getContactsByEmail($contact_email); + $contactId = 'new'; + if (!empty($contacts)) { + $contactId = (string) $contacts[array_key_first($contacts)]->id(); + } + + // Create new metrolist_listing webform submission. + $webform = Webform::load('metrolist_listing'); + $values = [ + 'webform_id' => $webform->id(), + 'in_draft' => TRUE, + 'data' => [ + 'contact_email' => $contact_email, + 'select_contact' => $contactId, + ], + ]; + + $new_submission = WebformSubmission::create($values); + + // Save the new submission. + $saved = $new_submission->save(); + if ($saved != SAVED_NEW) { + \Drupal::logger('bos_metrolist')->error("Error creating a new metrolist listing submission"); + } + + // Get a token for the new submission and add to the email response body. + $token = $new_submission->getToken(); + + + return $token ?? FALSE; + + } + +} diff --git a/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/WebformHandler/PostMetroListingWebformHandler.php b/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/WebformHandler/PostMetroListingWebformHandler.php new file mode 100644 index 0000000000..064659b9f9 --- /dev/null +++ b/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/WebformHandler/PostMetroListingWebformHandler.php @@ -0,0 +1,996 @@ +authMan()->getProvider()->getInstanceUrl() . '/' . $this->salesforce_id->value; + } + + /** + * Lookup a SF Contact by Email. + * + * @param string $email + * Value of Contact email address. + * + * @return \Drupal\salesforce\SObject + * Object of the requested Salesforce object, Contact that matches the email. + */ + public function getContactByEmail($email = '') { + + $contactSFObject = $this->client()->objectReadbyExternalId('Contact', 'Email', $email); + + return $contactSFObject; + } + + /** + * Add or update a SF Contact. + * + * @param array $developmentData + * Submission Data. + * + * @return mixed + * Return SFID of Contact or false + */ + public function upsertContact(array $developmentData) { + + $contactSFID = $developmentData['contactsfid'] ?? NULL; + + $contactEmail = $developmentData['contact_email'] ?? NULL; + $contactName = $developmentData['contact_name'] ?? NULL; + $contactPhone = $developmentData['contact_phone'] ?? NULL; + $contactAddress = $developmentData['contact_address'] ?? []; + + $fieldData = [ + 'AccountId' => $developmentData['accountsfid'], + 'Email' => $contactEmail, + ]; + + if (empty($fieldData['AccountId'])) { + // Effectively bubble up the addAccount error. + \Drupal::logger('bos_metrolist')->error("Error encountered saving Contact. SF rejected addition of {$contactName}"); + return FALSE; + } + + if ($contactPhone) { + $fieldData['Phone'] = $contactPhone; + } + + $contactName = explode(' ', $contactName); + if (isset($contactName[1])) { + $fieldData['FirstName'] = $contactName[0]; + $fieldData['LastName'] = $contactName[1]; + } + else { + $fieldData['FirstName'] = ''; + $fieldData['LastName'] = $contactName[0]; + } + + if (!empty($contactAddress)) { + $fieldData['MailingStreet'] = $contactAddress['address']; + $fieldData['MailingCity'] = $contactAddress['city']; + $fieldData['MailingState'] = $contactAddress['state_province']; + $fieldData['MailingPostalCode'] = $contactAddress['postal_code']; + } + + $result = $this->updateSalesforce('Contact', $fieldData, "Id", $contactSFID ?? NULL); + if (!$result) { + $contactName = implode(" ", $contactName); + \Drupal::logger('bos_metrolist')->error("Error encountered updating Contact. SF rejected update of {$contactName}"); + } + return $result; + + } + + /** + * Add or update a SF Account. + * + * @param array $developmentData + * Submission Data. + * + * @return mixed + * Return SFID or false + */ + public function upsertAccount(array $developmentData) { + + try { + $accountQuery = new SelectQuery('Account'); + $company = $developmentData['contact_company']; + // DU ticket #2494 DIG-79 + // Escape apostrophes b/c of the way the string is built on the next line. + $company = str_replace("'", "\'", $company); + $accountQuery->addCondition('Name', "'$company'"); + $accountQuery->addCondition('Type', "'Property Manager'"); + // @TODO: Make Config?, Hardcoded the SFID to `DND Contacts` + $accountQuery->addCondition('Division__c', "'DND'"); + // @TODO: hardcoded to SFID for Account Record Type: "Vendor" + $accountQuery->addCondition('RecordTypeId', "'012C0000000I0hCIAS'"); + $accountQuery->fields = ['Id', 'Name', 'Type']; + + $existingAccount = $this->client()->query($accountQuery)->records() ?? NULL; + $existingAccount = reset($existingAccount) ?? NULL; + + } + catch (Exception $exception) { + \Drupal::logger('bos_metrolist')->error($exception->getMessage()); + return FALSE; + } + + if ($existingAccount) { + // Just return the SFID of the account, no need to update anything.... + return (string) $existingAccount->id(); + } + else { + // Create a new Account in SF. + $fieldData = [ + 'Name' => $developmentData['contact_company'], + 'Business_Legal_Name__c' => $developmentData['contact_company'], + 'Type' => 'Property Manager', + 'Division__c' => 'DND', + // @TODO: hardcoded to SFID for Account Record Type: "Vendor" + 'RecordTypeId' => '012C0000000I0hCIAS', + ]; + return $this->updateSalesforce('Account', $fieldData); + } + + } + + /** + * Add or update a SF Development. + * + * @param string $developmentName + * Development Name. + * @param array $developmentData + * Submission Data. + * @param string $contactId + * Contact ID. + * + * @return mixed + * Return SFID or false + */ + public function upsertDevelopment(string $developmentName, array $developmentData, string $contactId) { + $developmentSFID = $developmentData['developmentsfid'] ?? NULL; + + $fieldData = [ + 'Name' => $developmentName, + 'Region__c' => !empty($developmentData['region']) ? $developmentData['region'] : 'Boston', + 'Street_Address__c' => $developmentData['street_address'] ?? '', + 'City__c' => !empty($developmentData['city']) ? $developmentData['city'] : 'Boston', + 'ZIP_Code__c' => $developmentData['zip_code'] ?? '', + 'Wheelchair_Access__c' => empty($developmentData['wheelchair_accessible']) ? FALSE : TRUE, + 'Listing_Contact_Company__c' => $developmentData['contact_company'] ?? NULL, + ]; + + if (isset($contactId)) { + $fieldData['Listing_Contact__c'] = $contactId; + + // @TODO: change to Man_Comp_Contact__c and set the Account on the Contact and not the Development. + $fieldData['Management_Company_Contact__c'] = $contactId; + } + + if (isset($developmentData['neighborhood'])) { + $fieldData['Neighborhood__c'] = !empty($developmentData['neighborhood']) ? $developmentData['neighborhood'] : NULL; + } + + if (isset($developmentData['utilities_included'])) { + $fieldData['Utilities_included__c'] = !empty($developmentData['utilities_included']) ? implode(';', $developmentData['utilities_included']) : NULL; + } + + if (isset($developmentData['upfront_fees'])) { + $fieldData['Due_at_signing__c'] = !empty($developmentData['upfront_fees']) ? implode(';', $developmentData['upfront_fees']) : NULL; + } + + if (isset($developmentData['amenities_features'])) { + $fieldData['Features__c'] = !empty($developmentData['amenities_features']) ? implode(';', $developmentData['amenities_features']) : NULL; + } + + if (empty($developmentData['same_as_above_contact_info'])) { + if (!empty($developmentData['public_contact_address'])) { + $addr = $developmentData['public_contact_address']; + $fieldData['Public_Contact_Address__c'] = $addr['address'] . "\r\n" . $addr['city'] . ", " . $addr['state_province'] . " " . $addr['postal_code']; + } + $fieldData['Public_Contact_Email__c'] = $developmentData['public_contact_email']; + $fieldData['Public_Contact_Name__c'] = $developmentData['public_contact_name']; + $fieldData['Public_Contact_Phone__c'] = $developmentData['public_contact_phone']; + } + else { + if (!empty($developmentData['contact_address'])) { + $addr = $developmentData['contact_address']; + $fieldData['Public_Contact_Address__c'] = $addr['address'] . "\r\n" . $addr['city'] . ", " . $addr['state_province'] . " " . $addr['postal_code']; + } + $fieldData['Public_Contact_Email__c'] = $developmentData['contact_email']; + $fieldData['Public_Contact_Name__c'] = $developmentData['contact_name']; + $fieldData['Public_Contact_Phone__c'] = $developmentData['contact_phone']; + } + + $result = $this->updateSalesforce('Development__c', $fieldData, 'Id', $developmentSFID ?? NULL); + if (!$result) { + \Drupal::logger('bos_metrolist')->error("Error encountered saving the Development. Addition/update of {$developmentName} was rejected by SF."); + } + return $result; + + + } + + /** + * Add or update a SF Units. + * + * @param array $developmentData + * Submission Data. + * @param string $developmentId + * Development ID. + * + * @return mixed + * Return SFID or false + */ + public function addUnits(array $developmentData, string $developmentId) { + + $units = $developmentData['units']; + + if (!empty($units)) { + foreach ($units as $unitGroup) { + if (empty($unitGroup['unit_count'])) { + continue; + } + + $waitlist_open = $developmentData['waitlist_open'] == 'Yes' || (empty($developmentData['waitlist_open']) ? FALSE : TRUE); + $available_how = $developmentData['available_how'] == "first_come_first_serve" ? "First come, first served" : "Lottery"; + $unitGroup["price"] = !empty($unitGroup['price']) ? (double) filter_var($unitGroup['price'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) : 0.0; + $unitGroup["minimum_income_threshold"] = !empty($unitGroup['minimum_income_threshold']) ? (double) filter_var($unitGroup['minimum_income_threshold'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) : 0.0; + $unitGroup['bedrooms'] = !empty($unitGroup['bedrooms']) ? (double) $unitGroup['bedrooms'] : 0.0; + $unitGroup['bathrooms'] = !empty($unitGroup['bathrooms']) ? (double) $unitGroup['bathrooms'] : 0.0;; + + for ($unitNumber = 1; $unitNumber <= $unitGroup['unit_count']; $unitNumber++) { + + $unitName = $developmentData['street_address'] . ' Unit #' . $unitNumber; + + // @TODO: Change out the values for some of these by updating the options values in the webform configs to match SF. + $fieldData = [ + 'Name' => $unitName, + 'Development_new__c' => $developmentId, + 'Availability_Status__c' => 'Pending', + 'Income_Restricted_new__c' => $developmentData['units_income_restricted'] ?? 'Yes', + 'Availability_Type__c' => $available_how, +// 'User_Guide_Type__c' => $waitlist_open ? 'Waitlist' : $available_how, + 'Occupancy_Type__c' => $developmentData['type_of_listing'] == 'rental' ? 'Rent' : 'Own', // "Rent" or "Own" + 'Rent_Type__c' => $unitGroup['rental_type'] == 0 ? 'Fixed $' : 'Variable %', + 'Income_Eligibility_AMI_Threshold__c' => isset($unitGroup['ami']) ? $unitGroup['ami'] : 'N/A', + 'Number_of_Bedrooms__c' => $unitGroup['bedrooms'], + 'Rent_or_Sale_Price__c' => $unitGroup['price'], + 'ADA_V__c' => empty($unitGroup['ada_v']) ? FALSE : TRUE, + 'ADA_H__c' => empty($unitGroup['ada_h']) ? FALSE : TRUE, + 'ADA_M__c' => empty($unitGroup['ada_m']) ? FALSE : TRUE, + 'Waitlist_Open__c' => $waitlist_open, + ]; + + if(isset($developmentData['remove_posting_date'])) { + $fieldData['Suggested_Removal_Date__c'] = $developmentData['remove_posting_date']; + } + + if (isset($unitGroup['bathrooms'])) { + $fieldData['Number_of_Bathrooms__c'] = $unitGroup['bathrooms']; + } + + if (isset($unitGroup['minimum_income_threshold'])) { + $fieldData['Minimum_Income_Threshold__c'] = $unitGroup['minimum_income_threshold']; + } + + if (isset($developmentData['posted_to_metrolist_date'])) { + $fieldData['Requested_Publish_Date__c'] = $developmentData['posted_to_metrolist_date']; + } + + if (isset($developmentData['application_deadline_datetime'])) { + $fieldData['Lottery_Application_Deadline__c'] = $developmentData['application_deadline_datetime']; + } + + if (isset($developmentData['website_link'])) { + $fieldData['Lottery_Application_Website__c'] = $developmentData['website_link'] ?? NULL; + } + + if (isset($developmentData['pdf_upload']) && $developmentData['direct_visitors'] == 'pdf') { + $fieldData['Lottery_Application_Website__c'] = \Drupal::service('file_url_generator')->generateAbsoluteString(File::load($developmentData['pdf_upload'])->getFileUri()) ?? NULL; + } + + if ($result = $this->updateSalesforce('Development_Unit__c', $fieldData) === FALSE) { + \Drupal::logger('bos_metrolist')->error("Error encountered adding Development Units. SF rejected the new record for unit {$unitNumber} in {$unitName}."); + return FALSE; + } + + } + + } + } + return TRUE; + } + + /** + * Update a SF Units. + * + * @param array $developmentData + * Submission Data. + * @param string $developmentId + * Development ID. + * + * @return mixed + * Return SFID or false + */ + public function updateUnits(array $developmentData, string $developmentId) { + + $currentUnits = $developmentData['current_units']; + + if (!empty($currentUnits)) { + + // Fetch the existing development unit information from SF. + $salesForce = new MetroListSalesForceConnection(); + $sf_currentUnits = $salesForce->getUnitsByDevelopmentSid($developmentId); + + foreach ($currentUnits as $unit) { + + // Find the SF current values for this unit. + $_sf_unit = NULL; + $fieldData = []; + if (empty($unit["sfid"])) { + // Consider this a non-fatal issue, a change won't be made for this unit. + \Drupal::logger('bos_metrolist')->warning("Error encountered updating Development Units. Existing unit has no SFID on form." . print_r($unit, TRUE)); + return FALSE; + } + foreach($sf_currentUnits as $_sf_unit) { + if ($_sf_unit->id() == $unit["sfid"]) { + $sf_unit = $_sf_unit->fields(); break; + } + } + if (empty($sf_unit)) { + // Consider this a non-fatal issue, a change won't be made for this unit. + \Drupal::logger('bos_metrolist')->warning("Error encountered updating Development Units. The unit {$unit['sfid']} was not found in SalesForce." . print_r($unit, TRUE)); + continue; + } + + $unitChanged = ( + intval(str_replace(["$",","], "", $unit["minimum_income_threshold"])) != intval($sf_unit["Minimum_Income_Threshold__c"] ?? 0) + || intval(str_replace(["$",","], "", $unit["price"])) != intval($sf_unit["Rent_or_Sale_Price__c"] ?? 0) + ); + + if (empty($unit['relist_unit'])) { + // The active listing checkbox is unselected. + + if (in_array($sf_unit["Availability_Status__c"] ?? "", ["Closed"])) { + // The Availability status in SF is already Closed. + // Don't change anything at all. + continue; + } + else { + // Mark this unit as closed. No need to change anything else on + // this unit. + $fieldData = ['Availability_Status__c' => 'Closed']; + } + } + + else { + // The active listing checkbox is selected. + + if (in_array($sf_unit["Availability_Status__c"] ?? "", ["Available", "Pending", "Reviewed"]) + && !$unitChanged) { + // The availabily status in SF is already Available + // If the information on the units form is unchanged, do nothing. + continue; + } + + $unitRelist = in_array($sf_unit["Availability_Status__c"] ?? "", ["Closed"]); + + // The SF status is currently closed, or the submitted form is + // changing the status of the unit. + $waitlist_open = $developmentData['waitlist_open'] == 'Yes' || (empty($developmentData['waitlist_open']) ? FALSE : TRUE); + $available_how = $developmentData['available_how'] == 'first_come_first_serve' ? 'First come, first served' : 'Lottery'; + $fieldData = [ + 'Availability_Status__c' => 'Pending', + 'Rent_Type__c' => $unit['rental_type'] == 0 ? 'Fixed $' : 'Variable %', + 'Rent_or_Sale_Price__c' => isset($unit['price']) ? (double) filter_var($unit['price'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) : 0.0, + ]; + if (!empty($unit['minimum_income_threshold'])) { + $fieldData['Minimum_Income_Threshold__c'] = !empty($unit['minimum_income_threshold']) ? (double) filter_var($unit['minimum_income_threshold'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) : 0.0; + } + if ($unitRelist) { + $fieldData = array_merge($fieldData, [ + 'Availability_Type__c' => $available_how, +// 'User_Guide_Type__c' => $waitlist_open ? 'Waitlist' : $available_how, + 'Waitlist_Open__c' => $developmentData['waitlist_open'] == 'Yes' || empty($developmentData['waitlist_open']) ? FALSE : TRUE, + 'Income_Restricted_new__c' => $developmentData['units_income_restricted'] ?? 'Yes' + ]); + + if (!empty($developmentData['posted_to_metrolist_date'])) { + $fieldData['Requested_Publish_Date__c'] = $developmentData['posted_to_metrolist_date']; + } + + if (!empty($developmentData['application_deadline_datetime'])) { + $fieldData['Lottery_Application_Deadline__c'] = $developmentData['application_deadline_datetime']; + } + if (!empty($developmentData['remove_posting_date'])) { + $fieldData['Suggested_Removal_Date__c'] = $developmentData['remove_posting_date']; + } + + if (!empty($developmentData['website_link'])) { + $fieldData['Lottery_Application_Website__c'] = $developmentData['website_link']; + } + } + } + + // Only make the update if we found some fields to update ... + if (!empty($fieldData)) { + if ($this->updateSalesforce('Development_Unit__c', $fieldData, NULL, $unit['sfid']) === FALSE) { + \Drupal::logger('bos_metrolist')->error("Error encountered updating Development Units. SF rejected the update for {$unit['sfid']}."); + return FALSE; + } + } + + } + } + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) { + parent::postSave($webform_submission, $update); + if ($webform_submission->getElementData('formerrors') != 0) { + \Drupal::logger('bos_metrolist')->error("Data saving issue. Confirmation emails will not be sent (to MOH or Submitter)."); + } + } + + /** + * {@inheritdoc} + */ + public function postLoad(WebformSubmissionInterface $webform_submission) { + return; + } + + /** + * {@inheritdoc} + */ + public function postCreate(WebformSubmissionInterface $webform_submission) { + return; + } + + /** + * {@inheritdoc} + */ + public function preSave(WebformSubmissionInterface $webform_submission) { + + if ($webform_submission->getWebform()->id() == "metrolist_listing") { + + switch ($webform_submission->getCurrentPage()) { + + case "your_contact_information": + // Do nothing + break; + + case "update_contact_information": + /* + * Populate the form with the Contact/Account info from SF. + * If an existing contact is selected in the contact dropdown, then the + * contactsfid field is set with the SF ID for that contact. For new + * contacts the field is blank. + */ + if ($contactSFID = $webform_submission->getElementData('select_contact')) { + if ($contactSFID != 'new') { + /* If the contact selected is not new and: either the contactsfid + field is empty or the contactsfid does not equal the contact ID + already selected, then fetch the contact from SF and populate the + information into the form. */ + if (empty($webform_submission->getElementData('contactsfid')) + || $contactSFID != $webform_submission->getElementData('contactsfid')) { + + // MAPPING ARRAY for Drupal field => SF field. Used for data taken + // from the SF Development object (Contact). + $fields = [ + 'contact_name' => 'Name', + 'contact_address' => 'MailingAddress', + 'contact_phone' => 'Phone', + 'contact_company' => 'Account', + ]; + + $contactData = $this->client() + ->objectRead('Contact', $contactSFID); + $accountData = $contactData->hasField('AccountId') ? $this->client() + ->objectRead('Account', $contactData->field('AccountId')) : NULL; + + foreach ($fields as $webform_field => $sf_field) { + if ($webform_field == 'contact_address') { + $sf_field = [ + 'address' => $contactData->field('MailingStreet'), + 'city' => $contactData->field('MailingCity'), + 'state_province' => $contactData->field('MailingState'), + 'postal_code' => $contactData->field('MailingPostalCode'), + 'address_2' => "", + 'country' => "", + ]; + + $webform_submission->setElementData($webform_field, $sf_field); + } + elseif ($webform_field == 'contact_company' && $accountData) { + $sf_field = $accountData->field('Name'); + $webform_submission->setElementData($webform_field, $sf_field); + } + else { + $webform_submission->setElementData($webform_field, $contactData->field($sf_field)); + } + } + + if (empty($webform_submission->getElementData("contact_email"))) { + $webform_submission->setElementData("contact_email", $contactData->field("Email")); + } + + $webform_submission->setElementData('contactsfid', $contactSFID); + } + + } + } + break; + + case "property_information": + case "unit_information": + case "public_listing_information": + /* + * Populate the form fields with the developments data from SF. + */ + if ($developmentSFID = $webform_submission->getElementData('select_development')) { + + // MAPPING ARRAY for Drupal field => SF field. Used for data taken + // from the SF Development object (Development__c). + $fields = [ + 'property_name' => 'Name', + 'city' => 'City__c', + 'region' => 'Region__c', + 'neighborhood' => 'Neighborhood__c', + 'street_address' => 'Street_Address__c', + 'zip_code' => 'ZIP_Code__c', + 'upfront_fees' => 'Due_at_signing__c', + 'amenities_features' => 'Features__c', + 'utilities_included' => 'Utilities_included__c', + 'public_contact_email' => 'Public_Contact_Email__c', + 'public_contact_name' => 'Public_Contact_Name__c', + 'property_management_company' => 'Listing_Contact_Company__c', + 'public_contact_phone' => 'Public_Contact_Phone__c', + 'public_contact_address' => 'Public_Contact_Address__c', + 'wheelchair_accessible' => 'Wheelchair_Access__c', + ]; + + /* If the development selected is not new and the developmentsfid + field value does not equal the developmentID (including if it is + empty) selected, then fetch the development from SF and populate + the data into the form fields. */ + if ($developmentSFID != 'new') { + /* This is an existing development, so grab the field data from + SF.*/ + if ($developmentSFID != $webform_submission->getElementData('developmentsfid')) { + /* The developmentsfid has not yet been set, or else the + selected item in the development dropdown has been changed. */ + + $developmentData = $this->client() + ->objectRead('Development__c', $developmentSFID); + + foreach ($fields as $webform_field => $sf_field) { + if (NULL != $developmentData->field($sf_field)) { + if ($webform_field == 'upfront_fees' || $webform_field == 'amenities_features' || $webform_field == 'utilities_included') { + $sf_field = explode(';', $developmentData->field($sf_field)); + $webform_submission->setElementData($webform_field, $sf_field); + } + elseif ($webform_field == 'public_contact_address') { + // @TODO: Re-factor this!!! + $sf_public_address = explode("\r\n", $developmentData->field('Public_Contact_Address__c')); + $sf_public_street = $sf_public_address[0]; + $sf_public_address = explode(', ', $sf_public_address[1]); + $sf_public_city = $sf_public_address[0]; + $sf_public_address = explode(' ', $sf_public_address[1]); + $sf_public_state = $sf_public_address[0]; + $sf_public_zipcode = $sf_public_address[1]; + + $sf_field = [ + 'address' => $sf_public_street, + 'city' => $sf_public_city, + 'state_province' => $sf_public_state, + 'postal_code' => $sf_public_zipcode, + 'address_2' => "", + 'country' => "", + ]; + $webform_submission->setElementData($webform_field, $sf_field); + } + else { + $webform_submission->setElementData($webform_field, $developmentData->field($sf_field)); + } + } + } + + $salesForce = new MetroListSalesForceConnection(); + $currentUnits = $salesForce->getUnitsByDevelopmentSid($developmentSFID); + + $webform_submission->setElementData('current_units', NULL); + $webformCurrentUnits = []; + + foreach ($currentUnits as $unitSFID => $currentUnit) { + if (!empty($currentUnit->field('Id')) + && !empty($currentUnit->field('Occupancy_Type__c'))) { + $adaUnit = []; + + if ($currentUnit->field('ADA_H__c')) { + $adaUnit[] = '✓ Hearing'; + } + if ($currentUnit->field('ADA_V__c')) { + $adaUnit[] = '✓ Visual'; + } + if ($currentUnit->field('ADA_M__c')) { + $adaUnit[] = '✓ Mobility'; + } + + $adaUnit = !empty($adaUnit) ? implode("\r", $adaUnit) : "✕ None"; + + $summary = [($currentUnit->field('Occupancy_Type__c') == "Rent" ? "RENTAL" : "OWNERSHIP")]; + + if ($currentUnit->field('Number_of_Bedrooms__c')) { + $summary[] = "{$currentUnit->field('Number_of_Bedrooms__c')} Bed"; + } + if ($currentUnit->field('Number_of_Bathrooms__c')) { + $summary[] = "{$currentUnit->field('Number_of_Bathrooms__c')} Bath"; + } + if ($currentUnit->field('Income_Eligibility_AMI_Threshold__c') && $currentUnit->field('Income_Eligibility_AMI_Threshold__c') != "N/A") { + $summary[] = $currentUnit->field('Income_Eligibility_AMI_Threshold__c'); + } + $summary = !empty($summary) ? implode("\r", $summary) : ""; + + $pending = ""; + if ($currentUnit->field('Availability_Status__c') == "Pending" + || $currentUnit->field('Availability_Status__c') == "Reviewed") { + // Closed, Available + $pending = "PENDING REVIEW"; + } + $notes = []; + if ($currentUnit->field('Suggested_Removal_Date__c')) { + $rmdate = "Remove " . date("m/d/Y", strtotime($currentUnit->field('Suggested_Removal_Date__c'))); + } + if ($currentUnit->field('Availability_Type__c')) { + $a = $currentUnit->field('Availability_Type__c'); + if ($currentUnit->field('Availability_Type__c') == "Lottery") { + $d = date("m/d/Y", strtotime($currentUnit->field('Lottery_Application_Deadline__c'))); + $a .= " (end {$d})"; + } + $notes[] = $a; + } + if ($rmdate + && in_array($currentUnit->field('Availability_Status__c'), [ + "Available", + "Pending", + ])) { + $notes[] = $rmdate; + } + $notes = !empty($notes) ? "Notes: " . implode(": ", $notes) : ""; + + $webformCurrentUnits[] = [ + 'relist_unit' => $currentUnit->field('Availability_Status__c') != "Closed", + 'ada' => $adaUnit, + 'summary' => $summary, + 'note' => $notes, + 'pending' => $pending, + 'ami' => $currentUnit->field('Income_Eligibility_AMI_Threshold__c'), + 'bedrooms' => $currentUnit->field('Number_of_Bedrooms__c'), + 'price' => $currentUnit->field('Rent_or_Sale_Price__c'), + 'minimum_income_threshold' => $currentUnit->field('Minimum_Income_Threshold__c'), + 'rental_type' => ($currentUnit->field('Rent_Type__c') == "Fixed $" ? 0 : 1), + 'status' => $currentUnit->field('Availability_Status__c'), + 'sfid' => $currentUnit->field('Id'), + ]; + } + } + $webform_submission->setElementData('current_units', $webformCurrentUnits); + } + } + else { + // This is new development. + if ($developmentSFID != $webform_submission->getElementData('developmentsfid')) { + // The development has been changed from an existing to "new". + // The form probably was previously filled out, so clear it. + $fields[] = "developmentsfid"; + foreach ($fields as $webform_field => $sf_field) { + $newval = NULL; + if ($webform_field == "wheelchair_accessible") { + $newval = 0; + } + $webform_submission->setElementData($webform_field, $newval); + } + $webform_submission->setElementData('current_units', []); + $webform_submission->setElementData('additional_units', []); + } + } + + /* Set the developmentsfid field on the form so that we don't + grab this data again (unless the development is changed in + the dropdown). + -> Reloading the data will overwrite any data changes made by + the submitter. */ + $webform_submission->setElementData('developmentsfid', $developmentSFID); + + } + break; + + } + + parent::preSave($webform_submission); + } + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + $a = parent::getSummary(); + unset($a["#theme"]); + $a["#markup"] = "Custom WebformHandler defined in module bos_metrolist"; + return $a; + } + + /** + * Helper function to interract with salesforce client. + * + * @param string $name The sf object name to use + * @param array $params The data to insert/update into the sf object + * @param string $key The unique key for the sf object for upsert (if missing then create will be used) + * @param string $value The unique key value for upsert + * + * @return false|string The response from Salesforce. + */ + private function updateSalesforce(string $name, array $params, $key = NULL, $value = NULL) { + + try { + if ($value == "new") { + // forces an upsert for new items. + $value = NULL; + } + + if (!empty($key) && !empty($value)) { + $result = (string) $this->client()->objectUpsert($name, $key, $value, $params); + } + elseif (empty($key) && !empty($value)) { + $this->client()->objectUpdate($name, $value, $params); + $result = TRUE; + } + else { + $result = (string) $this->client()->objectCreate($name, $params); + } + return $result; + } + catch (RestException | Exception $exception) { + \Drupal::logger('bos_metrolist')->error($exception->getMessage()); + $this->messenger()->addError("Error saving submission"); + return FALSE; + } + + } + + /** + * @inheritdoc + */ + public function validateForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) { + + // Check Dev Unit business logic. + if ($form["#form_id"] == "webform_submission_metrolist_listing_edit_form") { + switch ($webform_submission->getCurrentPage()) { + case "unit_information": + + $isNewBuilding = $form_state->getValue("select_development") == 'new'; + $addUnits = ($isNewBuilding || $form_state->getValue("add_additional_units") == 1); + $updateUnits = (!$isNewBuilding && $form_state->getValue("update_unit_information") == 1); + + if ($updateUnits) { + foreach ($form_state->getValue("current_units") as $key => $current_unit) { + + $isIncomeBased = !empty($current_unit["rental_type"]); + $minIncomeValue = self::getIntVal($current_unit["minimum_income_threshold"]); + $priceValue = self::getIntVal($current_unit["price"]); + + if ($current_unit["relist_unit"]) { + if (!$isIncomeBased && !isset($priceValue)) { + $name = "current_units][items][{$key}][_item_][price"; + $form_state->setErrorByName($name, "If eligibility is not income-based, the Price field is required"); + } + elseif ($isIncomeBased && !isset($minIncomeValue)) { + $name = "current_units][items][{$key}][_item_][minimum_income_threshold"; + $form_state->setErrorByName($name, "If eligibility is income-based, the Minimim Income field is required"); + } + } + } + } + + if ($addUnits) { + foreach ($form_state->getValue("units") as $key => $unit) { + + $isIncomeBased = !empty($unit["rental_type"]); + $minIncomeValue = self::getIntVal($unit["minimum_income_threshold"]); + $priceValue = self::getIntVal($unit["price"]); + + if (!empty($unit["unit_count"])) { + if (!$isIncomeBased && !isset($priceValue)) { + $name = "units][items][{$key}][_item_][price"; + $form_state->setErrorByName($name, "If eligibility is not income-based, the Price field is required"); + } + elseif ($isIncomeBased && !isset($minIncomeValue)) { + $name = "units][items][{$key}][_item_][minimum_income_threshold"; + $form_state->setErrorByName($name, "If eligibility is income-based, the Minimim Income field is required"); + } + } + } + } + break; + + } + } + + // Complete other validation to see that all conditions and required fields + // which are defined in the webform are compliant. + parent::validateForm($form, $form_state, $webform_submission); + + // Update Salesforce. + // Do this in this hook (rather than onSubmit / pre or post-save because we + // can stop the form from submitting by adding a validation error. + if (!$form_state->getErrors() && $form_state->getTriggeringElement()["#value"] == "Submit for Review") { + /* If the form is being submitted and has no errors, then update + salesforce and save the sfid's if we don't already have them. */ + + $fieldData = $webform_submission->getData(); + + // Reset this b/c it gets saved with the form. + $webform_submission->setElementData('formerrors', 0); + + $header = "There was an error submitting your application and we could not process it. + Please go back and check the form, and try to submit again. + If you continue to get this error after you have thoroughly checked the form, please contact us (metrolist@boston.gov) with these details: + ID: {$webform_submission->get("serial")->first()->getValue()["value"]} & "; + + /* Add or update the Account */ + if ($accountId = $this->upsertAccount($fieldData) ?? FALSE) { + if (empty($fieldData['accountsfid'])) { + $fieldData['accountsfid'] = $accountId; + $webform_submission->setElementData('accountsfid', $accountId); + } + } + else { + // An error occurred. + // Set this flag so that confirmation emails are not sent out. + $webform_submission + ->setElementData('formerrors', 'Could not save Account') + ->save(); + $form_state->setErrorByName("submit", "{$header}Code: ML-101"); + } + + /* Add or update the Contact */ + if ($contactId = $this->upsertContact($fieldData) ?? FALSE) { + if (empty($fieldData['contactsfid'])) { + $webform_submission->setElementData('contactsfid', $contactId); + } + } + else { + // An error occurred. + // Set this flag so that confirmation emails are not sent out. + $webform_submission + ->setElementData('formerrors', 'Could not save Contact') + ->save(); + $form_state->setErrorByName("submit", "{$header}Code: ML-102"); + } + + /* Add or update the Development and Units */ + if ($developmentId = $this->upsertDevelopment($fieldData['property_name'], $fieldData, $contactId) ?? FALSE) { + + if (empty($fieldData['developmentsfid'])) { + $webform_submission->setElementData("developmentsfid", $developmentId); + } + + /* Add or update the Units */ + if ($fieldData['update_unit_information']) { + if ($this->updateUnits($fieldData, $developmentId) === FALSE) { + // An error occurred. + // Set this flag so that confirmation emails are not sent out. + $webform_submission + ->setElementData('formerrors', 'Could not update Units') + ->save(); + $form_state->setErrorByName("submit", "{$header}Code: ML-103"); + } + } + if ($this->addUnits($fieldData, $developmentId) === FALSE) { + // An error occurred. + // Set this flag so that confirmation emails are not sent out. + $webform_submission + ->setElementData('formerrors', 'Could not add Units') + ->save(); + $form_state->setErrorByName("submit", "{$header}Code: ML-104"); + } + } + else { + // An error occurred. + // Set this flag so that confirmation emails are not sent out. + $webform_submission + ->setElementData('formerrors', 'Could not save Development') + ->save(); + $form_state->setErrorByName("submit", "{$header}Code: ML-105"); + } + + } + + } + + /** + * Tries to extract a numeric from a string. + * + * @param string $value + * @param bool $intOnly If TRUE an integer is returned, if not a float is. + * + * @return float|int|string + */ + private static function getIntVal(string $value, bool $intOnly = TRUE) { + + // Uses round to coerse a string into an int/float. + + try { + if (is_numeric($value)) { + return $intOnly ? round($value, 0) : round($value, 2); + } + if (NULL == $value || empty($value)) { + return 0; + } + + $value = preg_replace("/[\$,\s]/", "", $value); + if (is_numeric($value)) { + $value = $intOnly ? round($value, 0) : round($value, 2); + } + } + catch (\Error $e) { + // do nothing, the string could not be converted to a number ... + } + return $value; + + } +} diff --git a/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/views/style/MetroListSerializer.php b/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/views/style/MetroListSerializer.php index fdd90fd7b5..7113f17a67 100644 --- a/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/views/style/MetroListSerializer.php +++ b/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/views/style/MetroListSerializer.php @@ -3,6 +3,7 @@ namespace Drupal\bos_metrolist\Plugin\views\style; use Drupal\rest\Plugin\views\style\Serializer; +use Drupal\views\Annotation\ViewsStyle; /** * The style plugin for serialized output formats. diff --git a/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/views/style/MetroListZillowXMLSerializer.php b/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/views/style/MetroListZillowXMLSerializer.php index caff8cb45d..3633a74b30 100644 --- a/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/views/style/MetroListZillowXMLSerializer.php +++ b/docroot/modules/custom/bos_content/modules/bos_metrolist/src/Plugin/views/style/MetroListZillowXMLSerializer.php @@ -3,6 +3,7 @@ namespace Drupal\bos_metrolist\Plugin\views\style; use Drupal\rest\Plugin\views\style\Serializer; +use Drupal\views\Annotation\ViewsStyle; /** * The style plugin for serialized output formats. diff --git a/docroot/modules/custom/bos_content/modules/bos_metrolist/templates/form-element--webform-checkbox.html.twig b/docroot/modules/custom/bos_content/modules/bos_metrolist/templates/form-element--webform-checkbox.html.twig index 69f209c81c..a307122a4f 100644 --- a/docroot/modules/custom/bos_content/modules/bos_metrolist/templates/form-element--webform-checkbox.html.twig +++ b/docroot/modules/custom/bos_content/modules/bos_metrolist/templates/form-element--webform-checkbox.html.twig @@ -62,9 +62,9 @@ description_display == 'invisible' ? 'visually-hidden', ] %} - +{% set label_attributes = create_attribute(label["#attributes"]) %} -{# {{ devel_breakpoint() }}#} + {# {% if label_display in ['before', 'invisible'] %}#} {# {{ label }}#} @@ -78,9 +78,9 @@
{% endif %} -
")]; - } if ($pull_info = \Drupal::state()->get('salesforce.mapping_pull_info')) { @@ -1338,6 +1343,7 @@ private function processSfQueue(string $type = "ALL", bool $log = FALSE) { $queue->releaseItem($item); continue; } + catch (\Exception $e) { // Some other sort of error - delay this item for 15mins $queue->delayItem($item, 900); diff --git a/docroot/modules/custom/bos_content/modules/node_buildinghousing/src/Plugin/Field/FieldFormatter/EntityReferenceTaxonomyTermBSPublicStageFormatter.php b/docroot/modules/custom/bos_content/modules/node_buildinghousing/src/Plugin/Field/FieldFormatter/EntityReferenceTaxonomyTermBSPublicStageFormatter.php index 13b53f16e9..a9e4e39ae0 100644 --- a/docroot/modules/custom/bos_content/modules/node_buildinghousing/src/Plugin/Field/FieldFormatter/EntityReferenceTaxonomyTermBSPublicStageFormatter.php +++ b/docroot/modules/custom/bos_content/modules/node_buildinghousing/src/Plugin/Field/FieldFormatter/EntityReferenceTaxonomyTermBSPublicStageFormatter.php @@ -320,6 +320,7 @@ public function getTexts(EntityInterface $project) { if ($textUpdatesData) { foreach ($textUpdatesData as $sfid => $textUpdate) { + if (!empty($textUpdate->text)) { if (is_string($textUpdate->date)) { $textUpdate->date = '@' . strtotime($textUpdate->date); @@ -347,6 +348,7 @@ public function getTexts(EntityInterface $project) { ->render("bh_project_timeline_text", $data) ]; } + } } diff --git a/docroot/modules/custom/bos_content/modules/node_buildinghousing/src/Plugin/WebformHandler/AddBuildingHousingFollowerWebformHandler.php b/docroot/modules/custom/bos_content/modules/node_buildinghousing/src/Plugin/WebformHandler/AddBuildingHousingFollowerWebformHandler.php index 15b79a0412..851945426d 100644 --- a/docroot/modules/custom/bos_content/modules/node_buildinghousing/src/Plugin/WebformHandler/AddBuildingHousingFollowerWebformHandler.php +++ b/docroot/modules/custom/bos_content/modules/node_buildinghousing/src/Plugin/WebformHandler/AddBuildingHousingFollowerWebformHandler.php @@ -4,6 +4,7 @@ use Drupal\salesforce\Exception; use Drupal\salesforce\SelectQuery; +use Drupal\webform\Annotation\WebformHandler; use Drupal\webform\Plugin\WebformHandlerBase; use Drupal\webform\WebformSubmissionInterface; diff --git a/docroot/modules/custom/bos_content/modules/node_how_to/css/node_how_to.css b/docroot/modules/custom/bos_content/modules/node_how_to/css/node_how_to.css index 999b25f992..141ad14c64 100644 --- a/docroot/modules/custom/bos_content/modules/node_how_to/css/node_how_to.css +++ b/docroot/modules/custom/bos_content/modules/node_how_to/css/node_how_to.css @@ -107,9 +107,6 @@ } @media screen and (min-width: 980px) { - .node-type-how-to .b--fw .how-to-components .b--fw { - left: 76% - } .node-type-how-to .b--fw .how-to-components .b--fw .b--fw { left: 50% } diff --git a/docroot/modules/custom/bos_content/modules/node_how_to/templates/node--how-to.html.twig b/docroot/modules/custom/bos_content/modules/node_how_to/templates/node--how-to.html.twig index 84961aefbc..566aad54c7 100644 --- a/docroot/modules/custom/bos_content/modules/node_how_to/templates/node--how-to.html.twig +++ b/docroot/modules/custom/bos_content/modules/node_how_to/templates/node--how-to.html.twig @@ -62,7 +62,7 @@ {% if not node.field_components.isempty %}
-
+
diff --git a/docroot/modules/custom/bos_core/bos_core.links.menu.yml b/docroot/modules/custom/bos_core/bos_core.links.menu.yml index 3de5f81ac8..2214ca99b5 100644 --- a/docroot/modules/custom/bos_core/bos_core.links.menu.yml +++ b/docroot/modules/custom/bos_core/bos_core.links.menu.yml @@ -4,6 +4,12 @@ bos_core.admin: parent: system.admin_config_system route_name: bos_core.admin +bos_core.admin.query: + title: 'Query Site Content' + description: 'Run a search for content across all components' + parent: bos_core.admin + route_name: bos_core.admin.query + bos_core.content_moderation.list: title: 'Needs Review' route_name: bos_core.content_moderation.list diff --git a/docroot/modules/custom/bos_core/bos_core.routing.yml b/docroot/modules/custom/bos_core/bos_core.routing.yml index 3460f827f0..138bb17d47 100644 --- a/docroot/modules/custom/bos_core/bos_core.routing.yml +++ b/docroot/modules/custom/bos_core/bos_core.routing.yml @@ -11,7 +11,15 @@ bos_core.admin: _title: 'Boston Core Settings' _form: '\Drupal\bos_core\Form\BosCoreSettingsForm' requirements: - _permission: 'access content' + _role: 'site_administrator+administrator+developer' + +bos_core.admin.query: + path: '/admin/config/system/boston/query' + defaults: + _title: 'Query Content' + _form: '\Drupal\bos_core\Form\QueryForm' + requirements: + _role: 'site_administrator+administrator+developer' bos_core.content_moderation.list: path: '/admin/content/needs_review' diff --git a/docroot/modules/custom/bos_core/src/Form/QueryForm.php b/docroot/modules/custom/bos_core/src/Form/QueryForm.php new file mode 100644 index 0000000000..b346b95e87 --- /dev/null +++ b/docroot/modules/custom/bos_core/src/Form/QueryForm.php @@ -0,0 +1,233 @@ +config('bos_core.settings'); + + $form = [ + '#tree' => TRUE, + 'bos_core' => [ + '#type' => 'fieldset', + '#title' => 'Content Search', + '#description' => 'Run a deep search across site content components.', + '#collapsible' => FALSE, + + "query" => [ + + "search_text" => [ + '#type' => 'textfield', + '#required' => TRUE, + ], + "search_button" => [ + "#type" => "button", + '#value' => "Search", + '#ajax' => [ + 'callback' => '::makeSearch', + 'event' => 'click', + 'disable-refocus' => FALSE, + 'wrapper' => "edit-bos-core-query-search-button-results", + 'progress' => [ + 'type' => 'throbber', + ] + ], + "results" => [ + "#type" => "fieldset" + ] + ], + ] + ], + ]; + $form = parent::buildForm($form, $form_state); + $form['actions']['submit']['#attributes']=['style'=>['display:none']]; + return $form; + } + + public function makeSearch(array &$form, FormStateInterface $form_state) { + $search = $form_state->getValue("bos_core")["query"]["search_text"]; + $search = str_replace("%", "", $search); + $results = $this->makeQuery($search, []); + $formatted_results = $this->formatSearchResults($search, $results); + $form["bos_core"]["query"]["results"] = [ + "#type" => "fieldset", + "#attributes" => ["id" => "edit-bos-core-query-search-button-results"], + "markup" => [ + "#markup" => Markup::create($formatted_results) + ] + ]; + return $form["bos_core"]["query"]["results"]; + } + + private function makeQuery(string $search, array $exclude) { + + $union = []; + $query = "CREATE TEMPORARY TABLE COBQUERY \n"; + + // Always search the node title + $union[] = "SELECT nid component_id, type content_type, 'node.title' component, title result, nid nid + FROM node_field_data + WHERE title LIKE @search"; + + // Always search the node body + $union[] = "SELECT entity_id component_id, bundle content_type, 'node.body' component, body_value result, entity_id nid + FROM node__body + WHERE body_value like @search"; + + // Search the node fields + $node_tables = [ + ["table_name" => "node__field_description", "search_field"=>"field_description_value"], + ["table_name" => "node__field_intro_text", "search_field"=>"field_intro_text_value"], + ["table_name" => "node__field_details_link", "search_field"=>"field_details_link_uri"], + ["table_name" => "node__field_related_links", "search_field"=>"field_related_links_uri"], + ]; + foreach($node_tables as $table) { + $type = str_replace("node__field_","", $table["table_name"]); + $union[] = " + SELECT entity_id component_id, bundle content_type, 'node.{$type}' component, {$table["search_field"]} result, entity_id nid + FROM {$table["table_name"]} + WHERE {$table["search_field"]} LIKE @search"; + } + + $paragraph_tables = [ + ["table_name" => "paragraph__field_intro_text", "search_field"=>"field_intro_text_value"], + ["table_name" => "paragraph__field_extra_info", "search_field"=>"field_extra_info_value"], + ["table_name" => "paragraph__field_left_column", "search_field"=>"field_left_column_value"], + ["table_name" => "paragraph__field_middle_column", "search_field"=>"field_middle_column_value"], + ["table_name" => "paragraph__field_right_column", "search_field"=>"field_right_column_value"], + ["table_name" => "paragraph__field_short_description", "search_field"=>"field_short_description_value"], + ["table_name" => "paragraph__field_source_url", "search_field"=>"field_source_url_value"], + ["table_name" => "paragraph__field_external_link", "search_field"=>"field_external_link_uri"], + ["table_name" => "paragraph__field_internal_link", "search_field"=>"field_internal_link_uri"], + ["table_name" => "paragraph__field_source_url", "search_field"=>"field_source_url_value"], + ]; + foreach($paragraph_tables as $para) { + $find_parent = "IF(para.parent_type = 'node', para.parent_id, + IF(para2.parent_type = 'node', para2.parent_id, + IF(para3.parent_type = 'node', para3.parent_id, + IF(para4.parent_type = 'node', para4.parent_id, + para5.parent_id))))"; + $union[] = " + SELECT DISTINCT + entity_id component_id + , (SELECT type FROM node WHERE nid = $find_parent) content_type + , txt.bundle component, txt.{$para["search_field"]} result + , {$find_parent} node_id + FROM {$para["table_name"]} txt + INNER JOIN paragraphs_item_field_data para ON para.id = txt.entity_id + LEFT OUTER JOIN paragraphs_item_field_data para2 ON para2.id = para.parent_id + LEFT OUTER JOIN paragraphs_item_field_data para3 ON para3.id = para2.parent_id + LEFT OUTER JOIN paragraphs_item_field_data para4 ON para4.id = para3.parent_id + LEFT OUTER JOIN paragraphs_item_field_data para5 ON para5.id = para4.parent_id + WHERE txt.{$para["search_field"]} LIKE @search + HAVING node_id IS NOT NULL"; + } + + $query .= implode("\nUNION \n", $union); + + try { + $conn = \Drupal::database(); + $conn->query("DROP TABLE IF EXISTS COBQUERY")->execute(); + $conn->query("SET @search = '%{$search}%'")->execute(); + + // Do the search + $qry = $conn->query($query); + } + catch (\Exception $e) { + return FALSE; + } + + // Set up exclusions + $exclude_condition = ""; + if (!empty($exclude)) { + $exclude_condition = implode("','", $exclude); + $exclude_condition = "AND content_type NOT IN ('{$exclude_condition}') "; + } + + // Reformat and filter the search results + $query = "SELECT DISTINCT + content_type + , (SELECT CONCAT('https://boston.gov/node', alias) FROM path_alias WHERE path = CONCAT('/node/', nid) AND status = 1) alias + , (SELECT moderation_state FROM content_moderation_state_field_data WHERE content_entity_id = nid) mod_state + , component + , REPLACE(REPLACE(result, '\"', ''''), '\r\n', '') result + , nid + FROM COBQUERY outs + WHERE content_type IS NOT NULL {$exclude_condition} + ORDER BY content_type, component"; + if ($qry = $conn->query($query)) { + $result = $qry->fetchAll(); + return $result; + } + return FALSE; + + } + + private function formatSearchResults($search, $results) { + if ($results) { + $base = \Drupal::request()->getHttpHost(); + if ($base == "boston.gov" || $base = "www.boston.gov") { + $base = "content.boston.gov"; + } + $count = count($results); + $output = "

There were {$count} string matches for '{$search}' on pages in the site

The following list of pages has the search string in one or more components:

"; + $output .= ""; + $dedup = []; + foreach ($results as $result) { + if (!in_array($result->nid, $dedup)) { + $page = ($result->alias != "" ? "{$result->alias} ({$result->nid})" : "NODE {$result->nid}"); + $alias = ($result->mod_state == "published" ? $result->alias : "https://{$base}/node/{$result->nid}"); + $output .= ""; + $output .= " + + + "; + $output .= ""; + $dedup[] = $result->nid; + } + } + $output .= "
PageContent TypeStateActions
{$page}{$result->content_type} ({$result->component}){$result->mod_state} + View + Edit +
"; + } + else { + $output = "Sorry the string '{$search}' was not found.
Please alter the search and try again."; + } + return $output; + } + +} diff --git a/docroot/themes/custom/bos_theme/css/bos_theme_overrides.css b/docroot/themes/custom/bos_theme/css/bos_theme_overrides.css index f0e6d404c4..cd4d859bda 100644 --- a/docroot/themes/custom/bos_theme/css/bos_theme_overrides.css +++ b/docroot/themes/custom/bos_theme/css/bos_theme_overrides.css @@ -1713,3 +1713,18 @@ body.logged-in.toolbar-fixed .site-banner.dr { border-bottom: 5px solid #333; } } + +/* Stella Fix issue with components within another components +9/18/2023 + */ +.component-within-component .b-c{ + width: auto; + max-width: 100%; + margin-left: auto; + margin-right: auto; + padding: 2.1875rem 0; + position: relative; +} +.component-within-component .b--fw { + width: auto; +} diff --git a/scripts/.config.yml b/scripts/.config.yml index 6b881daed9..7834447409 100644 --- a/scripts/.config.yml +++ b/scripts/.config.yml @@ -97,6 +97,21 @@ build: # Note: if the commit is a hotfix, then configs wont be imported for expediency. sync: "false" + CI_working: + # With Travis, the type will control what type of build is deployed to Acquia (dev/prod). + # none = don't alter modules enabled from configuration + type: dev + suppress_output: 0 + database: + # Set source to 'initialize' to start a fresh install. + # Otherwise set it to 'sync' to sync from the drush-alias environment. + source: initialize + drush_alias: "@bostond8.dev" + config: + # Set to 'true' or 'false'. False means no configs will be imported during build/deploy + # Note: if the commit is a hotfix, then configs wont be imported for expediency. + sync: "true" + deploy: # Each element of this deploy node is a branch in the main CoB repo. # WARNING, scripts cannot track branches with spaces or dashes ("-") in their name. AVOID and use _ instead. @@ -140,6 +155,26 @@ deploy: drush_db_source: "@bostond8.prod" # user:host for (Acquia) git remote to be used for deployment. acquia_repo: bostond8@svn-29892.prod.hosting.acquia.com:bostond8.git + CI_working: + # Folder in Travis container where deploy files will be "built" + dir: ${REPO_ROOT}/deploy + # Name of the target branch in the Acquia repo. Should be quoted if it contains - or _ or spaces. + deploy_branch: "CI_working-deploy" + # Path to the drush command in the Travis container. + travis_drush_path: '${REPO_ROOT}/vendor/bin/drush' + # Alias for deploy target Aquia server. + drush_alias: "@bostond8.dev" + + # Definition of files that will and wont be copied from build to deploy. + from_file: ${REPO_ROOT}/scripts/deploy/deploy-from.txt + excludes_file: ${REPO_ROOT}/scripts/deploy/deploy-excludes.txt + # Dry-run (for testing). + dry_run: false + # Whether (and where) to sync the database on the deploy target. NB: copy-db=false means db left intact. + copy_db: false + drush_db_source: "@bostond8.dev" + # user:host for (Acquia) git remote to be used for deployment. + acquia_repo: bostond8@svn-29892.prod.hosting.acquia.com:bostond8.git # Configuration settings for new git repository. git: diff --git a/scripts/deploy/cob_utilities.sh b/scripts/deploy/cob_utilities.sh index 288ee7c744..5c32c1c8f4 100755 --- a/scripts/deploy/cob_utilities.sh +++ b/scripts/deploy/cob_utilities.sh @@ -292,7 +292,7 @@ function importConfigs() { # Also ensures some settings which are lingering in the copied D9 are removed as cim does not seem to do this. drupalVer=$(${drush_cmd} status | grep "Drupal version" | tr -dc "0-9." | head -c 2 | tr -dc "0-9") - if [[ -n $(drush pml --filter=rdf --status=enabled --format=list) ]]; then + if [[ -n $(${drush_cmd} pml --filter=rdf --status=enabled --format=list) ]]; then echo "=== DRUPAL 10 New install detected. ====" ${drush_cmd} config:delete core.extension module.color &>> ${TEMPFILE} diff --git a/scripts/deploy/travis_deploy.sh b/scripts/deploy/travis_deploy.sh index 990f5a16c4..9c676b1808 100755 --- a/scripts/deploy/travis_deploy.sh +++ b/scripts/deploy/travis_deploy.sh @@ -160,7 +160,8 @@ cd ${deploy_dir} && printf "${Bold}working tree status:${NC}\n" && git add --all && - git status --short && + git status --short > status.txt && + head -300 status.txt && res=$(git commit -m "${deploy_commitMsg}" --quiet | grep nothing) if [[ "${res}" == "nothing to commit, working tree clean" ]]; then