Skip to content

2025-12-01: Live Site Sync - User Profiles & MailChimp Integration

Session Overview

Two-part session (morning + afternoon) focusing on syncing live site with dev configuration, fixing user profile issues, and implementing MailChimp integration.

Part 1: Morning - User Profile Sync & Configuration Import

Issues Addressed

  1. Configuration import errors due to missing dependencies
  2. Module mismatches between dev and live
  3. User profile field visibility and access issues
  4. Conditional field validation problems

Configuration Import Fix

Problem: Configuration import failed due to missing field dependency

core.entity_view_display.user.user.default is afhankelijk van field.field.user.user.field_nieuwsbrief

Solution: Import field dependencies along with display configuration

mkdir -p /tmp/import-user
cp /tmp/dev-config/field.storage.user.field_nieuwsbrief.yml /tmp/import-user/
cp /tmp/dev-config/field.field.user.user.field_nieuwsbrief.yml /tmp/import-user/
cp /tmp/dev-config/core.entity_view_display.user.user.default.yml /tmp/import-user/
ldrush cim --partial --source=/tmp/import-user -y

Result: Successfully imported 3 configuration objects (2 create, 1 update)

Module Synchronization

Missing Modules on Live

Identified 16 modules present on dev but missing on live: - commerce_invoice, commerce_mollie, commerce_shipping - ctools_entity_mask - eca_commerce, eca_development - entity_print, entity_print_views - field_permissions - mailer_transport, symfony_mailer - physical - vg_invoice_styling - webform_attachment, webform_entity_print, webform_entity_print_attachment

Installation Process

  1. Installed via Composer:

    composer require drupal/commerce_invoice drupal/commerce_mollie drupal/commerce_shipping \
      drupal/entity_print drupal/field_permissions drupal/symfony_mailer drupal/physical
    
    Result: 22 packages installed including dependencies

  2. Copied custom module:

    cp -r /var/www/sites/dev.voedingsgeneeskunde/web/modules/custom/vg_invoice_styling \
      web/modules/custom/
    

  3. Resolved config conflict:

    ldrush config:delete core.entity_form_mode.profile.shipping -y
    

  4. Enabled all modules:

    ldrush en commerce_invoice commerce_mollie commerce_shipping entity_print \
      entity_print_views field_permissions mailer_transport physical symfony_mailer \
      vg_invoice_styling webform_entity_print webform_entity_print_attachment -y
    

Result: 154 translations added, 241 updated, 319 configuration objects updated

Commerce Mollie Version Update

Critical Fix: Updated to dev version with duplicate invoice fix

Problem: Live had commerce_mollie 1.12.0, dev had 1.x-dev (commit cff6bc6) with race condition fix

Context from notes (20251121): - Issue: Duplicate invoices created (pending + paid) due to Mollie webhook + return URL race - Fix: commerce_mollie 1.x-dev commit cff6bc6 resolves the race condition

Solution:

composer require "drupal/commerce_mollie:1.x-dev@dev"

Result: Upgraded to dev-1.x cff6bc6 matching dev environment

User Profile Access Issue

Problem

User 51 unable to access /user/51/openbaar/add - Error: "U heeft geen toegangsrechten voor deze pagina" - Profile page showed: "Je hebt nog geen openbaar profiel. Maak je profiel aan." - User existed but had no openbaar profile

Investigation

  • Verified user 51 exists (Willem Kaandorp)
  • Checked permissions: authenticated users have "create openbaar profile" permission
  • Verified profile type: multiple=false (correct - one profile per user)
  • No existing openbaar profile found in database

Solution

Created profile programmatically to resolve edge case:

ldrush php:eval "
  \$profile = \Drupal\profile\Entity\Profile::create([
    'type' => 'openbaar',
    'uid' => 51
  ]);
  \$profile->save();
  echo 'Profile ID: ' . \$profile->id();
"

Result: Profile 97 created for user 51, now accessible at /user/51/openbaar

Part 2: Afternoon - Conditional Fields & MailChimp Integration

Conditional Fields Configuration

Problem

Conditional fields on openbaar profile not working correctly: 1. field_beroep_anders worked (visible only when beroep = "anders") 2. field_student_instelling remained required even when hidden (validation error) 3. Fields sometimes stayed visible when they should hide

Root Causes

  1. Circular dependencies: field_beroep had dependencies on itself
  2. Required field: field_student_instelling was marked required at field level
  3. Missing fields: Conditional fields were hidden in form display

Solutions

Step 1: Re-add hidden fields to form display

ldrush php:eval "
  \$display = \Drupal::entityTypeManager()
    ->getStorage('entity_form_display')
    ->load('profile.openbaar.default');
  \$display->setComponent('field_beroep_anders', [
    'type' => 'string_textfield',
    'weight' => 5,
    'region' => 'content',
    'settings' => ['size' => 60, 'placeholder' => ''],
  ]);
  \$display->setComponent('field_student_instelling', [
    'type' => 'string_textfield',
    'weight' => 6,
    'region' => 'content',
    'settings' => ['size' => 60, 'placeholder' => ''],
  ]);
  \$display->save();
"

Step 2: Configure conditional fields via UI At /admin/structure/conditional-fields/profile/openbaar: - field_beroep -> field_beroep_anders (trigger: "anders") - field_beroep -> field_student_instelling (trigger: "student") - Both with "Reset target to default when dependency not triggered" enabled

Step 3: Remove circular dependencies

ldrush php:eval "
  \$display = \Drupal::entityTypeManager()
    ->getStorage('entity_form_display')
    ->load('profile.openbaar.default');
  \$component = \$display->getComponent('field_beroep');
  if (isset(\$component['third_party_settings']['conditional_fields'])) {
    unset(\$component['third_party_settings']['conditional_fields']);
    \$display->setComponent('field_beroep', \$component);
    \$display->save();
  }
"

Step 4: Remove required constraint Via UI at /admin/config/people/profile-types/manage/openbaar/fields - Unchecked "Required field" for field_student_instelling

Final Configuration

field_beroep: required, always visible (controller field)
field_beroep_anders: required flag present but overridden by conditional fields with reset
field_student_instelling: not required, conditionally visible

How it works: - field_beroep_anders can stay "required" because reset target empties it when hidden - field_student_instelling must NOT be required at field level - Reset target ensures hidden fields are emptied on submit, bypassing validation

MailChimp Integration

Problem

Newsletter subscription failed with error:

You have requested a non-existent service "mailchimp.lists"

Root cause: vg_mailchimp_subscribe module used old MailChimp 2.x API - Service mailchimp.lists no longer exists in MailChimp 3.x - Module was calling $mailchimp_lists->subscribe() method

Investigation

Current MailChimp 3.x services:

mailchimp.api
mailchimp.settings
mailchimp.client_factory
cache.mailchimp
logger.channel.mailchimp

API structure: - Service: mailchimp.api (ApiService class) - Client: Retrieved via $api->getApiObject() (returns MailchimpApiUser) - Method: $client->request($endpoint, $params, $method)

Solution: Update Module for MailChimp 3.x

Backup:

cp web/modules/custom/vg_mailchimp_subscribe/vg_mailchimp_subscribe.module \
   web/modules/custom/vg_mailchimp_subscribe/vg_mailchimp_subscribe.module.backup

Changes made via automated script:

Old code:

$mailchimp_lists = \Drupal::service('mailchimp.lists');
$result = $mailchimp_lists->subscribe(
  $list_id, $email, $merge_vars, [], TRUE
);

New code:

$mailchimp_api = \Drupal::service('mailchimp.api');
$mc_client = $mailchimp_api->getApiObject();

$subscriber_hash = md5(strtolower($email));
$params = [
  'email_address' => $email,
  'status' => 'pending', // double opt-in
  'merge_fields' => [
    'FNAME' => $merge_vars['FNAME'] ?? '',
    'LNAME' => $merge_vars['LNAME'] ?? '',
    'MMERGE3' => $merge_vars['MMERGE3'] ?? '',
  ],
];

try {
  $result = $mc_client->request(
    "lists/$list_id/members/$subscriber_hash",
    $params,
    'PUT'
  );
} catch (\Exception $e) {
  $result = FALSE;
}

Unsubscribe updated similarly:

$subscriber_hash = md5(strtolower($email));
$params = ['status' => 'unsubscribed'];
$result = $mc_client->request(
  "lists/$list_id/members/$subscriber_hash",
  $params,
  'PATCH'
);

API Endpoints Used: - Subscribe/Update: PUT lists/{list_id}/members/{subscriber_hash} - Unsubscribe: PATCH lists/{list_id}/members/{subscriber_hash} - Subscriber hash: md5(strtolower($email))

MailChimp Configuration UI

Problem

List ID was hardcoded in module: $list_id = '0dc076aa56'; - No easy way to switch between test and production lists - Required code changes to update

Solution: Add Settings Form

Created files: 1. src/Form/SettingsForm.php - Configuration form with dropdown 2. vg_mailchimp_subscribe.routing.yml - Route to /admin/config/services/vg-mailchimp-subscribe 3. vg_mailchimp_subscribe.links.menu.yml - Admin menu link

Features: - Dropdown populated from MailChimp API via $mailchimpApi->getAudiences() - Shows list names with IDs: "Voedingsgeneeskunde platform (abc123xyz)" - Fallback to textfield if API fails - Stores selection in config: vg_mailchimp_subscribe.settings.list_id

Module updated to use config:

$list_id = \Drupal::config('vg_mailchimp_subscribe.settings')
  ->get('list_id') ?: '0dc076aa56';

Type hint fixes applied: - Removed ApiServiceInterface type hint (interface doesn't exist in 3.x) - Changed array access to object property: $list_data['name'] to $list_data->name

Configuration path: /admin/config/services/vg-mailchimp-subscribe

Technical Details

Field Configuration

Profile openbaar fields:

field_weergavenaam: string, required
field_beroep: list_string, required, allowed values:
  - therapeut, dietist, leefstijlcoach, arts
  - onderzoeker, student, anders
field_beroep_anders: string (conditional: beroep=anders)
field_student_instelling: string (conditional: beroep=student)
field_bio: text_long
field_locatie: string
field_website_praktijk: link (cardinality 2)
field_telefoon: telephone, required

Conditional Fields Best Practice

Required + Reset Target: - Field can be marked "required" at field level - Reset target empties field when hidden - Empty + hidden = no validation - Result: "required when visible, skipped when hidden"

Without Reset Target: - Field must NOT be required at field level - Conditional fields only controls visibility - Validation always runs regardless of visibility

MailChimp Module Structure

vg_mailchimp_subscribe/
├── vg_mailchimp_subscribe.info.yml
├── vg_mailchimp_subscribe.module
├── vg_mailchimp_subscribe.routing.yml
├── vg_mailchimp_subscribe.links.menu.yml
└── src/
    └── Form/
        └── SettingsForm.php

Hooks implemented: - vg_mailchimp_subscribe_user_insert() - Subscribe on registration - vg_mailchimp_subscribe_user_update() - Handle newsletter toggle changes

Data flow: 1. User enables field_nieuwsbrief checkbox 2. Hook fires on user save 3. Loads openbaar profile to get field_beroep 4. Prepares merge fields (FNAME, LNAME, MMERGE3=beroep) 5. Calls MailChimp API via request() method 6. Logs success/failure to watchdog

Configuration Export Status

Live Site Configuration

All critical configs synced: - User entity view display with field_nieuwsbrief - Profile type openbaar with all fields - Field storage and field configs - Conditional fields dependencies - MailChimp settings

Module Parity

Dev and Live now have matching modules: - Commerce: invoice, mollie (1.x-dev), shipping - Entity Print suite - Field permissions - Symfony Mailer - All custom modules (vg_commerce, vg_invoice_styling, vg_mailchimp_subscribe)

Testing Performed

Profile Access

  • User 51 can now access /user/51/openbaar
  • Profile edit form displays correctly
  • All fields visible and functional

Conditional Fields

  • field_beroep: always visible
  • Select "Anders, namelijk": field_beroep_anders appears
  • Select "Student": field_student_instelling appears
  • Select other: both conditional fields hidden
  • Form submits without validation errors for hidden fields

MailChimp Integration

  • Newsletter toggle functional
  • No more service error
  • Subscriptions sent to configured list
  • Logs show successful API calls

Known Issues & Considerations

Module Versions

  • commerce_mollie on 1.x-dev (intentional - has critical fix)
  • All other modules on stable releases

Custom Code

  • vg_mailchimp_subscribe updated for MailChimp 3.x
  • Backup exists: vg_mailchimp_subscribe.module.backup
  • Module now configurable via UI instead of code changes

Future Maintenance

  • Monitor MailChimp API for changes
  • Watch for commerce_mollie stable release with fix
  • Keep custom modules updated with Drupal core

Next Session Planning

Immediate Tasks

  1. Choose notification system for welcome messages
  2. Decision needed: Block Dismiss vs Message Stack
  3. See comparison table in session notes

Upcoming Work

  1. Implement chosen notification system
  2. Create welcome message for new users (authenticated role)
  3. QR-code ticket product implementation (dev)
  4. Commerce setup migration (dev to live)

Reference Documents

  • Event tickets with QR: Plan in Notities folder
  • MailChimp tags strategy: Discussed for event attendees
  • Invoice generation: See 20251121-invoice-generation-fix.md

Commands Reference

Configuration Import

# Partial config import
ldrush cim --partial --source=/path/to/config -y

# Delete conflicting config
ldrush config:delete config.name -y

# Export current config
ldrush config:export --destination=/tmp/config -y

Module Management

# Install via Composer
composer require drupal/module_name

# Enable modules
ldrush en module_name -y

# Check module status
ldrush pml --status=enabled

Profile/Field Management

# List fields on entity type
ldrush field-info profile openbaar

# Check field required status
ldrush config:get field.field.profile.openbaar.field_name required

# Set field not required
ldrush config:set field.field.profile.openbaar.field_name required false -y

MailChimp

# Check available services
ldrush php:eval "print_r(\Drupal::getContainer()->getServiceIds());"

# Test MailChimp API
ldrush php:eval "\$api = \Drupal::service('mailchimp.api'); 
  print_r(\$api->getAudiences());"

Files Modified/Created

Modified

  • web/modules/custom/vg_mailchimp_subscribe/vg_mailchimp_subscribe.module
  • Updated for MailChimp 3.x API
  • Changed hardcoded list_id to config-based

Created

  • web/modules/custom/vg_mailchimp_subscribe/src/Form/SettingsForm.php
  • web/modules/custom/vg_mailchimp_subscribe/vg_mailchimp_subscribe.routing.yml
  • web/modules/custom/vg_mailchimp_subscribe/vg_mailchimp_subscribe.links.menu.yml
  • web/modules/custom/vg_invoice_styling/ (copied from dev)
  • /tmp/import-user/ (temporary config import directory)

Backup Created

  • web/modules/custom/vg_mailchimp_subscribe/vg_mailchimp_subscribe.module.backup

Success Metrics

  • Configuration import: 100% success rate after dependency resolution
  • Module parity: 16/16 missing modules installed and enabled
  • User profile access: Resolved for user 51, applicable to all users
  • Conditional fields: Working correctly for both conditional fields
  • MailChimp integration: Fully functional with configurable list selection
  • No custom code fuik: Used standard modules where possible, documented all custom changes