← Blog

Drupal

Drupal 7 naar Drupal 11: playbook voor ziekenhuisintranet

Het Drupal 7-intranet draaide al acht jaar onverstoorbaar in een Limburgs ziekenhuis. Zesduizend nodes, zeventien content types en een AD SSO uit 2018 die niemand meer aanraakte.

Jacob Molkenboer· Oprichter · A Brand New Company· 15 dec 2024· 10 min
Gesloten leren logboek met messing sleutel op crèmekaart, groen lint, rood waxfragment op ivoorpapier.

De sitemap kwam binnen als Excel-export uit een server die PHP 7.4 draaide en sinds een stroomstoring op kerstavond 2023 niet meer was herstart. Zesduizendtweehonderdvier gepubliceerde nodes, zeventien custom content types, vier rollen, en onderaan in het spreadsheet één zin: AD-login werkt nog, niemand weet meer hoe. Het intranet van het ziekenhuis draaide sinds 2018 onopvallend op Drupal 7. De EOL was gekomen en weer gegaan. Het team had Drupal 11 nodig, en het moest gebeuren zonder dat verpleegkundigen om 06.50 uur op maandag hun overdrachtsformulieren niet meer konden vinden.

Dit is de playbook die we gebruikten om die site te verhuizen. De vormen zijn toepasbaar op elke zwaar aangepaste Drupal 7-installatie die langer meeging dan haar documentatie.

Waarom de in-place upgrade geen optie is

Drupal 7 naar Drupal 11 is geen upgradepad. Het is een migratie. De Migrate API in Drupal 10 en 11 leest uit een Drupal 7 source database en schrijft naar een verse Drupal-installatie, één content type tegelijk. Dat klinkt als een karwei. Het is de meest vriendelijke tool die je krijgt.

De verleiding, vooral met zeventien custom types, is om kleine stappen te nemen: 7 naar 8, 8 naar 9, 9 naar 10, 10 naar 11. Niet doen. Drupal 8 bereikte zijn end of life in november 2021. Composer-based 8-sites zijn schaars en het geduld van de maintainers is eindig. Iedere tussenstop voegt een database state toe die niemand in je team ooit nog opstart. Het Migrate API-pad is één staging-omgeving, één runbook, één cutover.

Lees het officiële pad voordat je begint. De upgrade-documentatie van Drupal core is eerlijk over welke modules een stabiel migratiepad hebben en welke niet.

De inventarisatieweek

Voordat er één regel code werd geschreven, deden we een week pure discovery. De volgorde is belangrijk.

Eerst een audit-tabel van elk content type en alle veldinstanties, geëxporteerd met drush field-info --format=json. Zeventien rijen met honderden velden ertussen. Twee van de custom types bleken bijna-duplicaten, in 2019 aangemaakt door twee verschillende afdelingshoofden. We zetten ze op de lijst voor een merge tijdens de migratie. Drie andere hadden field_collection instanties, en op dat moment haalt elk doorgewinterd Drupal-team even adem. Field Collection heeft geen eerstelijns migratie naar Paragraphs. Er is een contributed module genaamd Field Collection Migrate, maar die heeft aannames. Test 'm op één voorbeeldtype voordat je je vastlegt.

Twee: een node-telling per type en per jaar. Het intranet had een content-vervalcurve. Ongeveer 38% van de nodes was sinds 2020 niet meer bewerkt. We gingen om tafel met de comms-lead en het hoofd verpleging en vroegen welke types nog dragend waren. Uiteindelijk archiveerden we vier content types in een read-only HTML-snapshot op een subdomein en migreerden we er nog dertien.

Drie: de SSO-archeologie. Die krijgt zijn eigen sectie.

Het goedkoopste migratiewerk gebeurt voordat er code geschreven wordt. Elk content type dat je niet migreert, is een week die je niet kwijt bent aan field mapping.

Een AD SSO uit 2018 reverse-engineeren

Het intranet van het ziekenhuis logde gebruikers in tegen de on-site Active Directory. Niemand in het huidige team had de integratie ooit aangeraakt. De contractor uit 2018 was niet meer actief. De documentatie was één Confluence-pagina getiteld SSO instellen met één screenshot van een PuTTY-venster.

We hebben het uit de database en het bestandssysteem gereconstrueerd.

De system-tabel vertelde ons welke modules actief waren. Twee waren relevant: ldap_authentication en simplesamlphp_auth. De variable-tabel bevatte de geconfigureerde endpoints. Het echte SAML-certificaat en de metadata stonden onder /var/simplesamlphp/cert/, eigendom van www-data, modus 640, laatst gewijzigd op 14 februari 2019.

Het certificaat was in zeven jaar niet geroteerd. Het was ook niet verlopen, want de AD-beheerder had ooit in een optimistische bui de levensduur op twintig jaar gezet. Dat behandelden we als een cadeau en als een bevinding die het opschrijven waard was.

Voor de nieuwe build hebben we de oude integratie niet overgenomen. SimpleSAMLphp wordt nog onderhouden, maar de Drupal-module heeft het zwaar gehad in de overgang naar D9 en D10. We hebben hem vervangen door de module SAML Authentication, een schone wrapper rond onelogin/php-saml. Het AD FS-endpoint van het ziekenhuis accepteerde de nieuwe service provider-metadata op de eerste request, en zulke kleine zegeningen neem je aan zonder te vragen.

Waarschuwing

Migreer de user-tabel niet één-op-één. De Drupal 7 user-UIDs botsen met de Drupal 11 admin-users die je tijdens de setup aanmaakt. Gebruik de migration_lookup plugin van de Migrate API om te remappen, of accepteer dat UID 1 een nieuw account wordt.

De Migrate API in de praktijk

De Migrate API heeft drie bewegende delen: een source plugin die de D7-database leest, een process pipeline die ieder veld transformeert, en een destination plugin die de D11-entity wegschrijft. Drupal core levert de source- en destination-plugins voor nodes, users, taxonomy, files en menu's. De process plugins zijn de delen waar je daadwerkelijk code voor schrijft.

Een migratiedefinitie staat in YAML, onder config/sync/ of in de migrations/-directory van een custom module. Dit is de vorm die we voor één van de custom content types van het ziekenhuis hebben gebruikt, protocol:

id: hospital_protocol
label: 'Protocol nodes from D7'
migration_group: hospital
source:
  plugin: d7_node
  node_type: protocol
process:
  type:
    plugin: default_value
    default_value: protocol
  title: title
  uid:
    plugin: migration_lookup
    migration: hospital_users
    source: node_uid
  field_afdeling:
    plugin: migration_lookup
    migration: hospital_taxonomy_afdeling
    source: field_afdeling
  'field_protocol_text/value': 'field_protocol_text/value'
  'field_protocol_text/format':
    plugin: static_map
    source: 'field_protocol_text/format'
    map:
      filtered_html: basic_html
      full_html: full_html
      plain_text: plain_text
destination:
  plugin: 'entity:node'
migration_dependencies:
  required:
    - hospital_users
    - hospital_taxonomy_afdeling
    - hospital_files

Drie dingen in dat bestand verdienen hun plek.

De migration_lookup plugin is hoe de nieuwe site de D11-user vindt die hoort bij de oude D7-user. Als de user-migratie nog niet heeft gedraaid, geeft de lookup null terug en wordt de node toegewezen aan UID 0. Je draait je migraties in dependency-volgorde, of je accepteert geanonimiseerde nodes en draait later een fix-up-pas.

De text format static_map is de kleine verrassing. Het filtered HTML-format van D7 bestaat niet op een verse D11-installatie. Als je het niet mapt, migreren je bodies wel, maar wordt elke node gerenderd als ge-escapete HTML. Dat is één keer grappig en daarna duur.

Het migration_dependencies-blok zorgt dat drush migrate:import --group=hospital de volgorde voor je oplost. Gebruik het. Het alternatief is een runbook met drieëntwintig stapnummers en stille paniek bij stap negentien.

Files en media

Het intranet had 11.400 bestanden aan nodes gekoppeld: PDF's met patiëntenfolders, een paar duizend intranetfoto's, een verontrustende hoeveelheid Word-documenten. Drupal 7 sloeg deze op als file entities. Drupal 11 wil media entities die file entities wrappen, met een media type per gebruiksdoel.

Je kunt file direct naar file migreren en media overslaan. Niet doen. De editorial workflow van het intranet gebruikt de media library, en alle nieuwe content na cutover gebruikt media entities. Als je de oude bestanden alleen als files migreert, eindig je met twee parallelle systemen en editors die nooit weten welk systeem ze moeten gebruiken.

Het pad dat werkte: migreer eerst files naar het public file system, draai dan een tweede migratie die ieder bestand in een media entity van het juiste type wikkelt, en update vervolgens de node-migraties zodat hun file-velden naar de nieuwe media entities wijzen. Drie passes, maar elke pas is op zichzelf te debuggen.

Module-triage

De D7-installatie had 86 contributed modules. Drieënveertig hadden een schone D11-opvolger, eenentwintig hadden een opvolger met een breaking config change, elf waren opgenomen in core, en de rest was óf verlaten óf vervangen door een custom module die eigenlijk een feature flag had moeten zijn. We maakten een spreadsheet, één rij per module, met kolommen voor D11-status, de editor of workflow die ervan afhing, en de kosten van het zonder doen.

Een derde van de modules sneuvelde gewoon in de was. Het ziekenhuis sleepte een Webform-installatie mee voor één PDF-download-formuliertje, dat we vervingen door een statische link, plus een workflow-module die niemand ooit had geconfigureerd. Een migratie is het enige moment waarop je gratis toestemming krijgt om dingen weg te gooien.

Staging-rehearsal en rollback

We hebben de volledige migratie drie keer geoefend voor cutover. Iedere rehearsal draaide tegen een verse kopie van productie. Iedere rehearsal leverde een delta-rapport op: hoeveel nodes gemigreerd, hoeveel met warnings, hoe lang de import duurde, hoeveel files niet kopieerden. De derde rehearsal duurde 47 minuten end-to-end op een staging-machine met vier cores. Dat getal ging in het cutover-window.

De cutover zelf was op vrijdagavond tussen 22.00 en 23.30 uur. We bevroren content editing om 21.00 uur, namen een laatste SQL-dump, draaiden de migratie op de nieuwe infrastructuur, schakelden DNS om en hielden de SSO in de gaten. De eerste verpleegkundige logde zaterdagochtend om 06.43 in. Het dashboard zag er hetzelfde uit. De URL's waren hetzelfde. Het skelet eronder niet.

Het rollback-plan was één zin: laat de oude VM read-only draaien op oud.intranet.[gemaskeerd].nl voor dertig dagen. We hebben hem nooit gebruikt. Het is nog steeds de goedkoopste verzekering die we ooit hebben opgeleverd.

Wat we anders zouden doen

Twee dingen.

Eén: we hebben onderschat hoeveel editorial content herschreven moest worden, niet gemigreerd. Sommige protocollen waren zo vaak bewerkt dat de markup een mozaïek was van WYSIWYG-keuzes uit tien jaar personeelsverloop. We migreerden ze en daarna heeft een klinisch eindredacteur er twee weken aan opgeruimd. Dat werk hadden we voor cutover moeten inplannen, niet erna.

Twee: we hadden vanaf dag één een content-audit-dashboard moeten uitrollen. Tegen de tijd dat we het in week zes bouwden, had de comms-lead de bewaar-of-archiveer-beslissing al op gevoel gemaakt. Haar gevoel klopte, maar een grafiek had het gesprek sneller gemaakt en het audit trail had de volgende stuurgroepvergadering korter.

Het kleinste wat je vandaag kunt doen

Zit je op een Drupal 7-site en maakt de EOL-datum je nerveus, begin dan niet met code. Draai drush field-info --format=json > fields.json tegen je productie-database. Open het bestand. Tel je custom content types. Verbaast het getal je, dan is dat je echte projectplan.

Toen we de migratie voor dit ziekenhuisintranet bouwden, was het deel dat ons bijna brak niet de zeventien content types, en ook niet de SSO. Het was de sprong van field collections naar paragraphs. We hebben hem opgelost door een kleine custom process plugin te schrijven die single-item collections terug platsloeg naar native velden en alleen Paragraphs gebruikte voor de multi-item varianten. Als jouw stack vergelijkbaar is: ons werk rond legacy migraties is precies om dit soort audit-week heen gebouwd.

Kern

Van Drupal 7 naar Drupal 11 is een migratie, geen upgrade. Besteed de eerste week aan inventarisatie en SSO-archeologie voordat je een Composer-bestand aanraakt.

FAQ

Kun je Drupal 7 in-place upgraden naar Drupal 11?

Nee. Van Drupal 7 naar 11 is een migratie, geen upgrade. Je zet een verse Drupal 11-installatie op en gebruikt de Migrate API uit core om content, users, taxonomy en files uit de D7-database op te halen.

Hoe lang duurt een Drupal 7-migratie van 6.000 nodes?

Plan vier tot zes weken bouwen voor een zwaar aangepaste site: één week inventarisatie, twee tot drie weken migratiecode, één week rehearsals en één cutover-window.

Wat gebeurt er met bestaande URL's na de migratie?

Als je de URL alias-tabel migreert en node-ID's stabiel houdt, overleven publieke URL's. Verifieer dat met een crawl van de oude site tegen de nieuwe voordat je DNS omzet, en zorg dat je redirects klaar hebt voor paden die toch veranderen.

Hoe ga je om met Drupal 7-modules zonder Drupal 11-versie?

Triageren in week één. De meeste hebben een D11-opvolger of een equivalent in core. De rest draagt meestal een feature mee die je kunt weggooien, kunt herbouwen als kleine custom module, of kunt vervangen door een statische link.

En Field Collection op Drupal 7?

Field Collection heeft geen eerstelijns migratie naar Paragraphs. Gebruik de contrib-module Field Collection Migrate voor multi-item collections, en sla single-item collections plat naar native velden met een custom process plugin.

drupalmigrationlegacy sitesphpmysqlsecurity

Iets bouwen?

Start een project