Skip to main content
Home
Schatzchaeschtli 2026

Main navigation

  • Home
User account menu
  • Log in

Breadcrumb

  1. Home

Protokoll Update Drupal 8 > 10 (hbmhealthcare)

By PierreAdmin , 15 October 2026

Detailliertes Protokolle der Schritte zum Update von hbmhealthcare Drupal 8 auf Drupal 10, vom ersten Schritt bis zum Golive bei mva.

Intro

  • Ziel: hbmhealthcare.com (hci) von Drupal 8.9.8 auf D10 bringen (Zwischenhalt). D11 kommt später als sauberer Neuaufbau.
  • Arbeitskopie: Portiert wird auf einer Kopie hci-update mit eigener DB. Das Original bildet den Livestand ab und bleibt unberührt.
  • Diese Etappe: Grossreinigung vor dem D9-Sprung. Composer 2, alle Module unter Composer, ungenutzte Module entfernt, eigene Sachen D9-tauglich, Core auf 8.9.20.

1 -- Arbeitskopie anlegen

1.1 -- Projektordner duplizieren

  • Im Finder ~/01_webroot/10-drupal-dev/drupal-8/hbmhealthcare.com kopieren, daneben einfügen und in hci-update umbenennen.
  • Versteckte Dateien (.git, .htaccess) kopiert der Finder automatisch mit.

1.2 -- Datenbank duplizieren

  • Neue DB und Benutzer anlegen (hci_update_drupal), Dump des Originals importieren.
  • Danach einen eigenen Dump der Kopie machen (Sequel Ace > Export).

1.3 -- Settings anpassen

  • Multisite-Ordner web/sites/dev-hbmhealthcare.selfip.net in web/sites/local.hci-update.test umbenennen.
  • DB-Zugang auf die neue DB umstellen und trusted_host_patterns auf den neuen Hostnamen anpassen.
  • MAMP: neuer Host local.hci-update.test, Site folder hci-update/web, PHP 7.4.

1.4 -- Drush-Alias für die Kopie

  • ### WICHTIG: Der Alias drushhbm zeigt mit absolutem Pfad aufs Original. Für die Kopie braucht es einen eigenen, sonst arbeitet man aus Versehen am Original.
  • In ~/.zshrc ergänzen:
  • alias drushci='/Users/pierre/01_webroot/10-drupal-dev/drupal-8/hci-update/vendor/drush/drush/drush -l local.hci-update.test'
  • Pfad endet auf /drush/drush/drush (die eigentliche Datei, nicht der Ordner).
  • Aktivieren und prüfen:
  • source ~/.zshrc
    usephp74
    drushci status
  • Kontrolle: Site URI local.hci-update.test, DB name der Kopie, Drupal root in hci-update.

2 -- Repository von Bitbucket auf GitLab

Das alte Repo lag auf Bitbucket (Atlassian), der Rückfallpunkt war nur lokal. Neu liegt alles auf GitLab, damit ein externes Backup besteht.

2.1 -- GitLab-Projekt anlegen

  • Gruppe: per-net > web
  • Projektname: hci-update
  • Sichtbarkeit: privat
  • Keine Readme-Datei erstellen (Projekt muss leer sein, sonst Konflikt beim Push)

2.2 -- Remote umstellen

  • git remote set-url origin git@gitlab.com:per-net/web/hci-update.git
    git remote -v
  • ### WICHTIG: set-url, nicht add. origin existiert schon (zeigte auf Bitbucket), git remote add origin bricht mit «remote origin already exists» ab.

2.3 -- Kontrolle: keine DB-Dumps im Git

  • git ls-files | grep -iE "\.sql|\.gz|\.zip"
  • Keine Ausgabe = sauber.

2.4 -- Push

  • git push -u origin master
  • Altes Projekt: Branch heisst master, nicht main. Danach reicht git push.
  • ### TIPP: «Your branch is ahead of 'origin/master' by 1 commit» heisst nur: lokaler Commit, noch nicht gepusht. Mit «working tree clean» muss nichts committet werden.
  • ### TIPP: Warnung «too many unreachable loose objects» beim Push: git prune ausführen, danach im Finder .git/gc.log löschen (Cmd + Shift + . für versteckte Dateien). gc.log ist nur das Protokoll und bleibt stehen, bis man es löscht.

3 -- Composer 2 Umstellung

3.1 -- Das Problem

  • composer require bricht ab mit «requires composer-plugin-api ^1.0». Vier alte Plugins verstehen Composer 2 nicht:
    • composer/installers und cweagans/composer-patches: alte Versionen
    • drupal-composer/drupal-scaffold: veraltet, ersetzt durch drupal/core-composer-scaffold
    • drupal/console: tot, gibt es für D10 nicht
  • ### WICHTIG: Nicht auf Composer 1 zurückwechseln. Packagist hat die Daten für Composer 1 eingefroren, und D10 verlangt Composer 2.
  • Schritte 3.2 bis 3.5 ändern nur die composer.json (--no-update), erst 3.7 installiert. Die Plugin-Warnungen bis dahin ignorieren.

3.2 -- Drupal Console entfernen

  • composer remove drupal/console --no-update

3.3 -- Scaffold-Plugin austauschen

  • composer remove drupal-composer/drupal-scaffold --no-update
    composer require drupal/core-composer-scaffold:^8.9 --no-update

3.4 -- composer.json von Hand anpassen

  • Im Block "scripts" die Zeile des alten Plugins löschen:
  • "drupal-scaffold": "DrupalComposer\\DrupalScaffold\\Plugin::scaffold",
  • Im Block "extra" zuoberst den Webroot angeben und die automatischen .gitignore-Dateien abschalten (wie im Bauplan, siehe Starter 1.7):
  • "drupal-scaffold": {
        "gitignore": false,
        "locations": {
            "web-root": "web/"
        }
    },
  • Im Block "config" die Plugins freigeben (Composer 2 verlangt das ausdrücklich):
  • "config": {
        "sort-packages": true,
        "allow-plugins": {
            "composer/installers": true,
            "cweagans/composer-patches": true,
            "drupal/core-composer-scaffold": true,
            "dealerdirect/phpcodesniffer-composer-installer": true
        }
    },
  • Das vierte Plugin kommt über drupal/core-dev. Ohne Eintrag fragt Composer mittendrin nach.

3.5 -- installers und patches anheben

  • composer require composer/installers:^1.9 cweagans/composer-patches:^1.7 --no-update
  • Ab diesen Versionen Composer-2-tauglich. Bewusst in der 1er-Reihe bleiben.

3.6 -- composer.json prüfen

  • composer validate --no-check-publish
  • «is valid, but with a few warnings» ist ok. Die «Lock file errors» behebt 3.7.

3.7 -- Gezieltes Update

  • composer update composer/installers cweagans/composer-patches drupal/core-composer-scaffold drupal-composer/drupal-scaffold "drupal/console*" --with-dependencies
  • Fasst nur diese Pakete an. Core und Module werden neu ausgepackt, bleiben aber in denselben Versionen (Wechsel Composer 1 > 2).
  • Anführungszeichen um "drupal/console*" nötig, sonst versteht das Terminal das Sternchen falsch.
  • ### WICHTIG: Dabei wurde enshrined/svg-sanitize entfernt, weil das Modul SVG image von Hand reinkopiert war und Composer nichts davon wusste. Behoben in 4.2.

3.8 -- Test, .gitignore-Reste, Commit

  • drushci cr
  • Site und Backend durchklicken.
  • Falls Composer vor dem Eintrag "gitignore": false schon gelaufen ist: Zusatzzeilen in der Haupt-.gitignore mit git restore .gitignore zurücknehmen und die neuen .gitignore-Dateien unter web/ im Finder löschen.
  • Die neuen Scaffold-Dateien (.editorconfig, .gitattributes, README-Dateien) werden versioniert.
  • git add -A
    git commit -m "Composer 2: Console entfernt, core-composer-scaffold, Plugins angehoben, Scaffold-Dateien versioniert"
    git push

4 -- Handkopierte Module in Composer übernehmen

Etliche aktive Module lagen in web/modules/contrib, standen aber nicht in der composer.json. Sie wurden damals von Hand reinkopiert. Vor dem D9-Sprung muss Composer alle Module kennen.

4.1 -- Abgleich und Sicherheitskopie

  • Ordnerliste mit der composer.json vergleichen:
  • ls web/modules/contrib
  • Vorher web/modules/contrib im Finder ausserhalb des Projekts sichern (hier: 00--backups--hci-update--8-10/modules/contrib). Composer überschreibt die Handkopien, und contrib ist nicht im Git.

4.2 -- SVG image

  • composer require drupal/svg_image:^1.16
  • Bringt enshrined/svg-sanitize selbst wieder mit. Test: Seite mit SVG-Logos (Portfolio).

4.3 -- Die übrigen Module in exakt der installierten Version

  • composer require drupal/backup_migrate:5.0.1 drupal/better_exposed_filters:5.2.0 drupal/entity_reference_unpublished:1.1.0 drupal/feeds:3.0.0-beta2 drupal/feeds_tamper:2.0.0-beta3 drupal/flood_control:2.3.0 drupal/jquery_ui_datepicker:1.4.0 drupal/jquery_ui_slider:1.1.0 drupal/jquery_ui_touch_punch:1.1.0 drupal/tamper:1.0.0-alpha4 drupal/twig_tweak:2.10.0
  • Exakte Versionen, damit sich am Code nichts ändert. Aus 8.x-5.2 wird für Composer 5.2.0.

4.4 -- Kontrolle auf Handänderungen

  • diff -rq /Users/pierre/01_webroot/10-drupal-dev/drupal-8/00--backups--hci-update--8-10/modules/contrib web/modules/contrib
    echo $?
  • Keine Ausgabe und 0 = identisch, es gab keine Handänderungen. 1 = Unterschiede, 2 = Fehler (z. B. falscher Pfad).
  • Danach Test (drushci cr, Structure > Feed types) und Commit.

5 -- Upgrade Status

5.1 -- Installieren und aktivieren

  • Upgrade Status braucht eine neuere webflo/drupal-finder, darum zuerst diese gezielt anheben:
  • composer update webflo/drupal-finder
    composer require drupal/upgrade_status:^3.0 --dev
    drushci pm:enable upgrade_status
  • ### WICHTIG: In Drush 9.2.1 wurde die Kurzform drushci en mit entity:updates verwechselt. Darum ausgeschrieben pm:enable verwenden.
  • Upgrade Status ist ein Prüfwerkzeug (--dev). Vor dem nächsten cex deaktivieren, damit es nicht in die Config gelangt.

5.2 -- Scannen

  • Reports > Upgrade status
  • Nur die Gruppe SCAN (eigene Module und Theme) anwählen und Scan selected. Die übrigen Gruppen beurteilt Upgrade Status über die Daten von drupal.org.
  • Die rote Drush-Warnung «DRUSH_BASE_PATH» oben ist harmlos, sie kommt vom alten Drush 9.

5.3 -- Eigene Sachen D9-tauglich machen

  • web/themes/custom/hbm/hbm.info.yml und web/modules/custom/ckeditor_div/ckeditor_div.info.yml: unter core: 8.x ergänzen:
  • core_version_requirement: ^8 || ^9
  • web/modules/custom/hbmcustom/src/Plugin/Block/hbmcustomSharePriceBlock.php, Zeile 61: path.alias_manager ersetzen durch path_alias.manager.
  • Test: Aktienkurs im Hero der Startseite (Block-Referenz auf hbmsharepriceblock). Die Platzierung im Blocklayout (hbmsharepriceblock_2, nur /node/7) ist ein Überbleibsel, node/7 gibt es nicht mehr.
  • Die drei Twig-Warnungen zu pernet/node bleiben vorerst: Die Library aus dem alten, nicht aktiven pernet-Theme wird schon heute nicht geladen.

6 -- Aufräumen und Module anheben

6.1 -- Ungenutzte Module entfernen

  • Nicht aktiv, per Composer entfernt:
  • composer remove drupal/draggableviews drupal/config_installer
  • devel_php: nie in Composer, Ordner im Finder gelöscht.
  • Aktiv, aber nicht mehr gebraucht: zuerst deinstallieren, dann aus Composer entfernen:
  • drushci pm:uninstall search_kint
    composer remove drupal/search_kint
    drushci pm:uninstall contentimport
    composer remove drupal/contentimport
  • Content Import war nur ein Formular für manuelle CSV-Importe. Team und Portfolio kommen über Feeds, die News über den Aggregator.
  • ### WICHTIG: Vor dem ersten Deinstallieren DB-Dump machen, ab hier wird die Datenbank verändert.

6.2 -- Module auf D9-taugliche Versionen anheben

  • Spielraum «alte oder neue Hauptversion» vorgeben, Composer wählt die höchste Version, die noch mit D8.9 läuft:
  • composer require "drupal/crop:^1.5 || ^2.0" "drupal/csv_serialization:^1.4 || ^2.0 || ^3.0" "drupal/entity_reference_unpublished:^1.1 || ^2.0" "drupal/libraries:^3.0@alpha || ^4.0" "drupal/login_emailusername:^1.1 || ^2.0 || ^3.0" --no-update
    composer update drupal/crop drupal/csv_serialization drupal/views_data_export drupal/entity_reference_unpublished drupal/libraries drupal/login_emailusername -w
    drushci updb
  • Anführungszeichen wegen || nötig.
  • Test: Bilder (Startseite, Team, Portfolio), Login mit E-Mail-Adresse.

6.3 -- Core auf 8.9.20

  • Letzte D8-Version vor dem Sprung auf D9 (empfohlen von Upgrade Status und drupal.org):
  • composer update drupal/core drupal/core-dev --with-all-dependencies
    drushci updb
    drushci cr
  • --with-all-dependencies (kurz -W) erlaubt auch Abhängigkeiten, die selbst in der composer.json stehen, mitzuziehen. Grenze bleibt ^8.8, ein Sprung auf D9 ist so nicht möglich.
  • --with-dependencies (kurz -w) zieht nur Abhängigkeiten mit, die nicht selbst in der composer.json stehen.

✎ Claude – offen:

  • Nächste Etappe: D8.9.20 > D9.5 (Core, Drush 10/11, exakte Versionen aus 4.3 lockern).
  • Vor cex: Upgrade Status deaktivieren.
  • Aufräumen (Fahrplan 5): pernet-Theme (war Startvorlage, kopiert, nicht abgeleitet), Twig-Verweise pernet/node, __pernet_help im Theme, Focal Point (evtl. ungenutzt), Blockplatzierung hbmsharepriceblock_2.
  • D10-Merkpunkte: CKEditor 4 > 5 (ckeditor_div fällt weg), Aggregator aus dem Core (News!), Theme-Basis stable, jQuery-UI-Module, PHP 8.3 für Martins Feed-Skript.

7 -- D8.9.20 > D9.5 (PHP 7.4)

Core, Drush und Module auf D9 heben, danach Datenbank nachziehen. Erledigt am 15.10.2026, Ergebnis: Core 9.5.11, Drush 11.6.0.

### WICHTIG: Rückfallpunkt vorher: Dump --d8-9--8-9-20 + Commit.

7.1 -- PHP 7.4 prüfen

  • Terminal und MAMP müssen beide auf PHP 7.4 stehen:
  • usephp74
    php -v
    composer --version

7.2 -- composer.json anpassen

  • drupal/core, drupal/core-composer-scaffold, drupal/core-dev auf ^9.5
  • drush/drush auf ^11.0, webflo/drupal-finder auf ^1.2
  • drupal/upgrade_status auf ^4.0, drupal/devel auf ^4.1
  • symfony/debug aus require-dev entfernt
  • Exakte Versionen aus 4.3 mit ^ gelockert, Hauptversion beibehalten
  • In config > allow-plugins ergänzt (braucht core-dev 9.5):
  • "phpstan/extension-installer": true

7.3 -- Toolbar-Falle

  • Erstes Update brach ab mit «drupal/toolbar requires drupal/core ^11.5». Toolbar gibt es seit 2026 auch als Contrib-Projekt (für D11.5+/D12), Composer griff fälschlich dazu statt zum Toolbar im Core.
  • Lösung in der composer.json, nach dem conflict-Block:
  • "replace": {
        "drupal/toolbar": "*"
    },

### WICHTIG: Gilt für D9 und D10. Beim D11-Neuaufbau wieder entfernen.

7.4 -- Composer-Update

  • composer update --with-all-dependencies
  • Fehleranalyse, was eine Version blockiert:
  • composer why-not drupal/core 9.5.11
  • Symfony-, Twig- usw. Zeilen sind dort Lärm, die zieht das volle Update ohnehin mit.

### WICHTIG: Nicht composer require "drupal/core:^9.5" --dry-run verwenden. Das ist ein Teil-Update, hält alle anderen Pakete fest und liefert eine lange, irreführende Fehlerliste.

7.5 -- Scaffolding nachholen

  • Update brach am Schluss ab: «Could not delete .../web/sites/default/default.services.yml». Ordner ist schreibgeschützt (macht Drupal so).
  • chmod u+w web/sites/default
    composer drupal:scaffold

7.6 -- PHP-7.4-Fallen

  • Neuere Modul-Versionen geben D9 an, brauchen aber PHP 8. Composer merkt das nicht. Symptom bei drushci status: «ParseError: syntax error, unexpected ')'».
  • Alle Contrib-Module auf einmal prüfen (liest nur):
  • find web/modules/contrib -type f \( -name "*.php" -o -name "*.module" -o -name "*.inc" -o -name "*.install" \) -not -path "*/tests/*" -exec php -l {} \; | grep -v "No syntax errors"
  • Funktionen, die es erst ab D10.1 gibt (fiel nur im Status Report auf):
  • grep -rl "DeprecationHelper" web/modules/contrib --include="*.php" --include="*.module" --include="*.inc" --exclude-dir=tests
  • Betroffene Module auf die vorherige, laufende Version zurückgesetzt:
  • composer require "drupal/diff:1.0.0"
    composer require "drupal/backup_migrate:5.0.1" "drupal/field_group:3.1.0" "drupal/auto_entitylabel:3.0.0-beta3" "drupal/crop:2.2.0" "drupal/redirect:1.6.0"
    composer require "drupal/superfish:1.4.0" --update-with-all-dependencies
    composer require --dev "drupal/upgrade_status:3.19.0" --update-with-all-dependencies
  • Superfish 1.4.0 holt die alte Bibliothek mehrpadin/superfish zurück.
  • tamper: src/Attribute/Tamper.php meldet Fehler, wird von D9 aber nicht geladen. So gelassen.

### TIPP: Die alte Version steht im Composer-Log bei «Upgrading x (alt => neu)».

7.7 -- Datenbank nachziehen

  • drushci status
    drushci updatedb
    drushci cache:rebuild
  • Warnung «Your PHP installation is too old» ist bei 7.4 normal.
  • «Uninstall Stable if it is no longer needed»: blieb installiert, weil Theme hbm darauf aufbaut.
  • Dump danach: --d8-9--9-5-updb

7.8 -- Status Report

  • Erwartete Errors: Core security coverage / update status (D9 ohne Support), Module update status (Sicherheitsupdates), PHP 7.4, rebuild_access.
  • «Mismatched entity and/or field definitions» für user.field_pending_expire_sent (Password Policy) behoben mit:
  • drushci php:eval '$d = \Drupal::service("entity_field.manager")->getFieldStorageDefinitions("user")["field_pending_expire_sent"]; \Drupal::entityDefinitionUpdateManager()->updateFieldStorageDefinition($d);'
    drushci cache:rebuild
  • Kein Feedback = gut.

7.9 -- Sichern

  • Dump: --d8-9--9-5-ok
  • git add --all
    git commit --message "D8.9.20 > D9.5.11: Core, Drush 11, Module D9, updatedb"
    git push

✎ Claude – offen:

  • Nächste Etappe: D9.5 > D10. Vorschlag: zuerst auf D9.5 PHP 8.1 einstellen, fixierte Module aus 7.6 wieder lockern und anheben, Upgrade Status 4, dann Core-Sprung mit PHP 8.3.
  • Vor cex: Upgrade Status deaktivieren.
  • Aufräumen (Fahrplan 5): pernet-Theme (war Startvorlage, kopiert, nicht abgeleitet), Twig-Verweise pernet/node, __pernet_help im Theme, Focal Point (evtl. ungenutzt), Blockplatzierung hbmsharepriceblock_2, rebuild_access in den lokalen Settings ausschalten, Google Analytics prüfen (nicht konfiguriert), Sicherheitskopie 00--backups--hci-update--8-10 löschen.
  • D10-Merkpunkte: CKEditor 4 > 5 (ckeditor_div fällt weg), Aggregator aus dem Core (News!), Color, Quick Edit, RDF aus dem Core, Themes Bartik, Seven (> Claro) und Stable aus dem Core, Theme-Basis stable, jQuery-UI-Module, PHP 8.3 für Martins Feed-Skript und hbmcustom, replace für Toolbar bleibt.

8 -- D9.5 > D10, Teil 1: PHP 8.1 und Module heben

8.1 -- PHP 8.1 einrichten

  • MAMP PRO: Settings > Languages > PHP, bei PHP 8.1 die neueste Version (8.1.34) installieren.
  • Alias in ~/.zshrc ergänzt (Muster wie usephp74):
  • usephp81() { export PATH="/Applications/MAMP/bin/php/php8.1.34/bin:$PATH"; php -v; }
  • MAMP PRO: Site local.hci-update.test > PHP version 8.1.34, dann Save (oben steht sonst «Unsaved»).
  • Terminal umschalten und prüfen:
  • source ~/.zshrc
    usephp81
    composer --version
  • composer --version zeigt am Schluss die PHP-Version, mit der Composer läuft. Muss 8.1.34 sein.
  • Prüfen, ob composer.json eine PHP-Version vorgibt (keine Ausgabe = gut):
  • grep -n -A 3 platform composer.json

8.2 -- Erster Test auf PHP 8.1

  • Site lief sofort (Status Report: Drupal 9.5.11, PHP 8.1.34).
  • Auf allen Seiten «Deprecated function: parse_str()» aus dem alten Redirect 1.6.0. Harmlos, verschwindet mit dem Modul-Update.

8.3 -- Fixierte Module heben

  • Ablauf pro Modul: composer require mit «^»-Version (Composer nimmt die neueste passende), dann updatedb, Cache, kurz testen, Commit.
  • Versions-Angabe immer in Anführungszeichen, sonst stolpert zsh über das ^.
  • composer require "drupal/redirect:^1.9" --update-with-all-dependencies
    drushci updatedb
    drushci cache:rebuild
    git add --all
    git commit --message "PHP 8.1, redirect 1.6.0 > 1.11.0"
    git push
  • Weitere Module nach gleichem Muster:
  • composer require "drupal/field_group:^3.1" --update-with-all-dependencies
    composer require "drupal/diff:^1.0" --update-with-all-dependencies
    composer require "drupal/backup_migrate:^5.0" --update-with-all-dependencies
    composer require "drupal/auto_entitylabel:^3.0" "drupal/crop:^2.2" --update-with-all-dependencies
  • Ergebnis: redirect 1.11.0, field_group 3.6.0, diff 1.6.0, backup_migrate 5.1.4, auto_entitylabel 3.1.0, crop 2.6.0.
  • Tests: Field Group = Manage form display (Tabs da?), Diff = Inhalt > Revisions > zwei Versionen > Compare selected revisions (durchgestrichen = entfernt, unterstrichen = neu), Backup and Migrate = Configuration > Development > Backup and Migrate, Auto Entity Label = Structure > Paragraphs types > Par textblock > Automatic label.
  • Superfish bleibt auf 1.4.0 (neuere brauchen D10.1). Läuft auf PHP 8.1 ohne Meldung. Heben nach dem Core-Wechsel.

8.4 -- Conditional Fields (war nicht fixiert, aber zu alt)

  • Auf Manage form display: «Warning: Trying to access array offset on value of type null» in conditional_fields/src/DependencyHelper.php. Unter PHP 7.4 nur ein stiller Hinweis.
  • Version 1.0.0-alpha10 war uralt, Nachfolger 4.x läuft mit D9, D10 und D11. Ohne stabile Version braucht es @alpha:
  • composer require "drupal/conditional_fields:^4.0@alpha" --update-with-all-dependencies
  • 1.0.0-alpha10 > 4.0.0-alpha6, keine DB-Updates nötig. Neuer Reiter «Manage Dependencies» bei den Typen ist normal.
  • Structure > Conditional fields listet nur die Inhaltsarten, nicht die Bedingungen.

8.5 -- Upgrade Status 4

  • Zuerst prüfen, wie fixiert wurde:
  • grep -n -E "upgrade_status|php-parser" composer.json
  • Nur upgrade_status war fixiert, php-parser nicht. Also:
  • composer require --dev "drupal/upgrade_status:^4.0" --update-with-all-dependencies
  • 3.19.0 > 4.3.10, nikic/php-parser 4 > 5. Laminas-servicemanager und laminas-text wurden entfernt (brauchte nur das alte Upgrade Status). News und Aggregator laufen weiter.

8.6 -- Cron und Team-Import

  • drushci cron lief lange und brachte 25 Warnungen: «Non-translatable fields can only be changed when updating the original language», HBM Team Import DE-EN.
  • Ursache: Nach dem PHP-Wechsel hielt Feeds einmalig alle Einträge für geändert (Portfolio: 101 neu gespeichert, harmlos). Live meldete gleichzeitig «keine neuen Einträge», die Quelle war also unverändert.
  • Dabei kam ein alter, versteckter Fehler zum Vorschein: Beim Team ist «Hide non translatable fields on translation forms» aktiv. Dann darf die englische Übersetzung keine nicht übersetzbaren Felder ändern (Area, Email, First name, Last name, Order of List, HBM Global Node ID, Hide CV page), der Import tut das aber.
  • Prüfweg: Reports > Recent log messages, Filter Type feeds, lokal und auf Live vergleichen.
  • Lösung: Configuration > Regional and language > Content language and translation > Content > Team: Häkchen «Hide non translatable fields on translation forms» entfernen, Save configuration.
  • Danach drushci cron: «Aktualisiert 25 Einträge vom Typ Team».
  • Einstellung steckt in der DB, nicht im Git.

8.7 -- Sichern

  • Dump: --d9-10--9-5-php81
  • git add --all
    git commit --message "upgrade_status 3.19.0 > 4.3.10, nikic/php-parser 5"
    git push

✎ Claude – offen:

  • Nächster Schritt: Reports > Upgrade status durchlaufen lassen und alles D10-tauglich machen. Dann Dump + Commit, Core auf ^10, Drush ^12 oder ^13, PHP 8.3, updatedb, testen. Danach Superfish heben.
  • D10-Go-live: Auf Live wird Feeds nach dem PHP-Wechsel ebenfalls alles neu einlesen. Team-Einstellung «Hide non translatable fields on translation forms» muss dort aus sein.
  • Test nach D10: in hbmglobal bei einem Team-Mitglied etwas ändern, prüfen ob es in hci ankommt (DE und EN). Portfolio und News analog.
  • Vor cex: Upgrade Status deaktivieren.
  • Aufräumen (Fahrplan 5): pernet-Theme (war Startvorlage, kopiert, nicht abgeleitet), Twig-Verweise pernet/node, __pernet_help im Theme, Focal Point (evtl. ungenutzt), Conditional Fields und Auto Entity Label (prüfen, ob genutzt), Blockplatzierung hbmsharepriceblock_2, rebuild_access in den lokalen Settings ausschalten, Google Analytics prüfen (nicht konfiguriert), Sicherheitskopie 00--backups--hci-update--8-10 löschen.
  • D10-Merkpunkte: CKEditor 4 > 5 (ckeditor_div fällt weg), Aggregator aus dem Core (News!), Color, Quick Edit, RDF aus dem Core, Themes Bartik, Seven (> Claro) und Stable aus dem Core, Theme-Basis stable, jQuery-UI-Module, PHP 8.3 für Martins Feed-Skript und hbmcustom, replace für Toolbar bleibt, Superfish heben.

9.1 -- Upgrade Status: Übersicht

  • Reports > Upgrade status: Core 9.5.11, PHP 8.1.34, Datenbank und Drush grün.
  • Offen: veraltete Core-Erweiterungen (Aggregator, Color, Quick Edit, RDF, Bartik, Seven, Stable), Remove: 5, Update: 16, Fix manually: 1 (Theme hbm, 3 Probleme).
  • Status Report bestätigt: aktiv ist «CKEditor 4 (contrib)» 1.0.2. CKEditor 5 ist in Core 9.5 schon drin, Umstellung daher noch auf D9.5.
  • Rote Sicherheitsmeldung und «81 security vulnerability advisories» vorerst ignoriert: lokale Kopie, betrifft meist Core 9.5 und alte Module.

9.2 -- Remove: ungenutzte Module entfernt

  • composer remove drupal/devel drupal/jquery_ui_accordion drupal/magnific_popup drupal/views_slideshow
  • devel stand in require-dev, Composer fragt nach, mit Enter bestätigen.
  • Dabei 6 Doctrine-Pakete mit entfernt (nur von devel gebraucht).
  • pernet-Theme bewusst noch nicht entfernt: 3 Twig-Templates im Theme hbm verweisen noch darauf.
  • Kein Dump nötig, Module waren deinstalliert.

9.3 -- Methode für Modul-Updates

  • Die neuesten Versionen auf drupal.org brauchen oft schon D10.1+ oder D11. Gesucht ist jeweils eine Brückenversion für D9.5 und D10.
  • Composer nur die Hauptversionen vorgeben, er wählt selbst die höchste Version, die mit Core 9.5.11 läuft:

    composer require "drupal/MODUL:^ALT || ^NEU" --update-with-all-dependencies
    drushci updatedb
    drushci cache:rebuild
  • Danach testen und committen. Der breite Constraint bleibt in composer.json und erlaubt beim Core-Wechsel den Sprung auf die D10-Version.

9.4 -- Modul-Updates

  • admin_toolbar 2.5.0 > 3.6.3 (8 DB-Updates, doctrine/lexer 1 > 2). Test: Toolbar und Dropdowns.
  • allowed_formats 1.5.0 > 2.0.0 (Brückenversion). Test: Textformat-Auswahl im Editor.
  • better_exposed_filters 5.2.0 > 6.0.6 (Brückenversion). Test: Filter-Links auf Team-Seite (bef-links, Ajax) und Media Release.
  • csv_serialization: bleibt 3.0.1, nur league/csv 9.8.0 > 9.28.0. Keine Brückenversion (3.x nur D9, 4.x nur D10), muss mit dem Core-Wechsel mit.
  • editor_advanced_link 1.9.0 > 2.2.4 (Brückenversion). Test: Link-Dialog im Editor.
  • flexslider 2.0.0 > 3.0.0. Test: Slider Startseite (automatisch und auf Klick).

9.5 -- Prüfen, ob ein Modul benutzt wird

  • drushci sql:query "SELECT name FROM config WHERE data LIKE '%MODULNAME%'"
  • Erwartet sind nur core.extension und die eigenen Einstellungen des Moduls. Taucht views.view.… oder core.entity_view_display.… auf, wird es benutzt.
  • Details:

    drushci config:get CONFIGNAME | grep -i -B 3 MODULNAME
  • FlexSlider: benutzt in views.view.view_hbm_news (Startseite). views.view.view_team_paragraph_slider ist vermutlich ein Überbleibsel (Team-Seite ist heute ein Raster).
  • Achtung: Ein Modul kann auch versteckt benutzt werden, zum Beispiel als Effekt in einem Bildstil (Focal Point). Dann taucht image.style.… auf. Ob der Bildstil selbst benutzt wird, zeigt diese Abfrage (s:4 = Länge des Namens «team»):

    drushci sql:query "SELECT name FROM config WHERE data LIKE '%\"image_style\";s:4:\"team\"%'"
  • Die Kette dabei: Feld > Ausgabe «Bild» > Bildstil > Effekt im Bildstil. Im Formular und bei «Manage display» sieht man den Effekt nicht.
  • Wirksame Einstellungen inklusive Überschreibungen aus settings.php:

    drushci config:get CONFIGNAME --include-overridden

9.6 -- Sichern

  • Nach jedem Modul:

    git add composer.json composer.lock
    git commit --message "MODUL alt > neu"
    git push
  • Dumps: --d9-10--9-5-module-teil1, --d9-10--9-5-module-teil2, --d9-10--9-5-module-teil3, --d9-10--9-5-theme-hbm, --d9-10--9-5-core-ext (aktueller Rückfallpunkt)
  • Nach reinen Deinstallationen (pm:uninstall, theme:uninstall) gibt es nichts zu committen, weil sich nur die Datenbank ändert. Dann nur Dump.

9.7 -- Mehrere Module in einem Durchgang

  • Harmlose Module lassen sich bündeln. Der Rückweg ist einfach: Dump zurückspielen und composer.json / composer.lock aus Git holen.

    composer require "drupal/MODUL1:^1 || ^2" "drupal/MODUL2:^1 || ^2" --update-with-all-dependencies
  • In der Ausgabe auf «Upgrading» achten. Findet Composer keine passende Version, bleibt das Modul einfach auf der alten Version stehen.
  • Einzeln machen, was Daten oder viel Config umbaut (simple_sitemap, viewsreference) oder im Theme verwendet wird (twig_tweak).
  • Nützt eine Änderung an composer.json nichts (Composer hat nichts geändert), zurücknehmen:

    git checkout composer.json composer.lock
    git status
  • Gibt es von einem Modul nur Beta-Versionen, nimmt Composer sie nicht. Dann muss @beta in die Versionsangabe, z.B. "drupal/viewsreference:^1 || ^2.0@beta".

9.8 -- Upgrade Status per Drush

  • Der Scan im Browser bricht bei grossen Themes/Modulen mit Fehler 500 ab (zu lange, zu viel Speicher). Per Drush gibt es diese Grenzen nicht:

    drushci upgrade_status:analyze MODULNAME
  • Bei grep mit --include das Muster in Anführungszeichen setzen, sonst meldet zsh «no matches found»: --include="*.twig"

9.9 -- Theme für D10 anpassen

  • info.yml: core_version_requirement: ^9 || ^10, und die Zeile core: 8.x muss dann zwingend weg, sonst läuft die Site nicht mehr.
  • jQuery-once fällt weg. In libraries.yml core/jquery.once durch core/once ersetzen, im JavaScript nach diesem Muster:

    vorher:  $(context).find('.selektor').once('name').each(function() {
    nachher: $(once('name', '.selektor', context)).each(function() {
  • In VS Code mit Suchen/Ersetzen, Regex eingeschaltet (Symbol .*):

    Suchen:   \$\(context\)\.find\(('[^']+')\)\.once\(('[^']+')\)
    Ersetzen: $$(once($2, $1, context))

    Danach mit grep -n "\.once(" DATEI.js nach Resten suchen und diese von Hand anpassen.

  • Testen mit der Chrome-Konsole: Cmd+Option+J, Seite neu laden. Rote Meldungen = JavaScript-Fehler.
  • Bilder nach einem Modul-Update neu rechnen lassen, damit der Test etwas aussagt:

    drushci image:flush BILDSTIL

9.10 -- Besonderheiten Etappe 06

  • Simple Sitemap im Backend erzeugen, nicht per Drush (Drush erzeugt http://-Adressen, und die Tabellen-Darstellung fehlt):
    Configuration > Search and metadata > Simple XML Sitemap > Sitemaps > Status > «Rebuild queue & generate»
  • Google Analytics war mit einer GA3-ID (UA-…) konfiguriert, die seit Juli 2023 nichts mehr sammelt. Entfernt.
  • Claro als Admin-Theme: Vertikale Reiter mit verschachtelten Paragraphs werden abgeschnitten. Deshalb bleibt für D10 Seven (Contrib-Version beim Core-Wechsel).

✎ Claude – offen:

  • Nächster Schritt: CKEditor 4 > 5, noch auf D9.5.
  • Danach: Upgrade Status nochmals komplett per Drush scannen, Dump + Commit, dann Core-Wechsel. Mit dabei: csv_serialization 4.x, viewsreference 2.x Beta (Dump vorher, Daten werden umgewandelt), Contrib-Versionen von Aggregator, Stable und Seven.
  • Nach dem Core-Wechsel heben: superfish, allowed_formats, better_exposed_filters, editor_advanced_link, twig_tweak.

9.11 -- CKEditor 4 > 5 (noch auf D9.5)

  • D9.5 ist dafür gebaut: CKEditor 4 und 5 können gleichzeitig aktiv sein, jedes Textformat einzeln umstellen und testen.
  • Vorgehen: drushci en ckeditor5, dann pro Format unter Configuration > Content authoring > Text formats and editors den Editor auf «CKEditor 5» umstellen. Die grüne Meldung zeigt, was übernommen wurde. Zusätzliche Buttons (z.B. Kursiv, Code, Quelltext) nicht entfernen: Sie halten erlaubte Tags am Leben.
  • Formate ohne Filter «Limit allowed HTML tags» (meist Full HTML) lassen in CKEditor 5 beliebiges HTML durch. Divs, Klassen, Inline-Styles bleiben beim Speichern erhalten.
  • Test komplexer Seiten: Quelltext vor dem Öffnen und nach dem Speichern kopieren und in VS Code diffen.
  • Zusatzmodule ohne CKEditor-5-Version (z.B. ckeditor_div) deinstallieren. Neue Divs gehen über den Button «Quelltext».
  • editor_file (PDF-Upload) kann CKEditor 5 erst ab 2.x, und 2.x läuft nur auf D10. Deshalb vorübergehend ohne Upload-Button, Version 2.x beim Core-Wechsel. Bestehende PDF-Links bleiben, die Dateinutzung zählt der Core.
  • Danach CKEditor 4 deinstallieren: drushci pmu ckeditor (nimmt editor_file 1.x mit).
  • Eigenheit CKEditor-5-Einstellungen: Plugin-Felder melden teilweise «field is required», obwohl ausgefüllt. Trotzdem speichern, es wird korrekt übernommen.

9.12 -- Core-Wechsel D9.5 > D10

  • Vorher: Upgrade Status per Drush komplett scannen. Entscheidend sind nur Module, deren core_version_requirement D10 nicht zulässt, der Rest ist meist Rauschen (Tests, nicht installierte Untermodule).
  • composer.json von Hand anpassen (core, core-composer-scaffold, core-dev auf ^10, Drush ^13, Module ohne Brückenversion auf die D10-Version), dann zuerst Probelauf:

    composer update --with-all-dependencies --dry-run
  • Drush 13 braucht PHP 8.3. Composer im Terminal mit 8.3 laufen lassen, MAMP erst nach dem Composer-Durchgang auf 8.3 stellen, dann updb.
  • Composer-Ausgaben nie in eine Datei umleiten: Fragt Composer etwas (z.B. allow-plugins), sieht man es nicht und es hängt. Für D10 in allow-plugins: "php-http/discovery": true, "tbachert/spi": false.
  • D10 verlangt core_version_requirement in jedem info.yml, auch bei deinstallierten Themes und Modulen. Alte Ordner vorher löschen.
  • Twig 3.30 ist mit Drupal (Stand Okt. 2026) nicht kompatibel: Fehler EscaperRuntime::escape(): Argument #4 ($autoescape) must be of type bool, null given. Lösung:

    composer require "twig/twig:<3.30" --update-with-all-dependencies

    Sperre wieder entfernen, sobald Drupal den Fix ausgeliefert hat.

  • Twig-Fehler nach Updates: Ordner web/sites/default/files/php löschen, dann drushci cr.
  • Scaffold überschreibt .htaccess und robots.txt. Mit git diff web/.htaccess prüfen, ob eigene Regeln verloren gingen.
  • Alte composer.json-Vorlagen haben oft "replace": {"drupal/toolbar": "*"}. Ab Core 10.6.15 blockiert das Core-Updates (beide ersetzen dasselbe Paket). Block löschen.
  • Wenn Composer eine Version nicht nimmt, zeigt das den Grund (why-not reicht nicht immer):

    composer require "paket:version" --update-with-all-dependencies --dry-run

9.13 -- Nach dem Core-Wechsel

  • Statusbericht (Reports > Status report) und verfügbare Updates (Reports > Available updates) durchgehen. Hauptversionssprünge (z.B. Metatag 1 > 2, ctools 3 > 4) hebt Composer nicht von selbst, die müssen per composer require "drupal/modul:^2" geholt werden.
  • Veraltete Module deinstallieren: Tour, Admin Toolbar Links Access Filter (im Core seit 10.3), Transliterate Filenames (im Core seit 10.2).
  • Transliteration im Core einschalten, sonst gehen Umlaute und Leerzeichen in Dateinamen wieder durch:
    Configuration > Media > File system > Sanitize filenames, alle 5 Häkchen, Replacement character «Dash».
  • Wichtig: D10 gibt Datumsfelder als <time datetime="...">…</time> aus. Wo eine View ein Datum per Custom text, Gruppierung oder JavaScript weiterverarbeitet, beim Feld unter Rewrite results «Strip HTML tags» und «Remove whitespace» setzen. Symptome: Code-Reste im Text, Gruppierung pro Eintrag statt pro Jahr, JS-Fehler mit <time datetime=.
  • Fehlersuche: Chrome-Konsole zuerst. Die Meldung zeigt oft direkt, welcher Wert falsch ankommt.
  • Drush 13: Kurzformen funktionieren wieder (cr, updb, en, pmu, st).
  • D10 hat Sicherheits-Support bis 9. Dezember 2026.

9.14 -- Besonderheiten Etappe 07

  • Superfish 1.4 > 1.17 ging problemlos (Theme nutzt nur sf-menu, sf-depth, sf-accordion). Neue Bibliothek unter libraries/drupal-superfish.
  • CanvasJS 3.17.7: Donut-Kreise waren unterschiedlich gross (Grösse hing von den Beschriftungen ab). Lösung in main.js:

    radius: chart_wrapper.width() < 450 ? "60%" : "100%",
    indexLabelFontSize: chart_wrapper.width() < 450 ? 12 : 14,

✎ Claude – offen:

  • Offene Tests: Medienmitteilung Backup erfassen, Sitemap im Backend (230 URLs, https://), News per Cron, Team- und Portfolio-Import, Feed-Skript im Webroot und hbmcustom unter PHP 8.3.
  • Twig-Sperre prüfen und entfernen, sobald Drupal den Fix hat.
  • Danach Aufräumen (Liste im Übergabeprotokoll 08).

9.15 -- Tests nach D10

  • Feeds-Import testen: In der Quelle (hbmglobal) eine unauffällige Änderung machen (zweiter Punkt, Doppelpunkt hinter Überschrift), lokal Configuration > System > Cron > Run cron. Erwartet: «Updated 1 Team». Danach in der Quelle über Revisions zurücksetzen und nochmals Cron.
  • Log nach Problemen filtern: Reports > Recent log messages, Severity «Error» bzw. «Warning» wählen und Filter. Der Button «Reset» zeigt, dass ein Filter aktiv ist.
  • Martins Feed-Skript (web/hbmcustomFeed.php) läuft unter PHP 8.3. Lokal meldet es «SSL certificate verify failed», weil es sich selbst über https aufruft und MAMP ein selbst ausgestelltes Zertifikat hat. Auf Servern mit echtem Zertifikat tritt das nicht auf.
  • Backup-Medienmitteilungen prüfen ohne Feed: /json-medienmitteilung-backup/de bzw. /en aufrufen. [] = keine veröffentlichten Einträge. Datum muss als reines ISO-Datum kommen (z.B. 2023-01-17T00:00:00Z), ohne <time>.
  • Twig-Sperre entfernen, sobald der Core den Fix hat (hier ab 10.6.18):

    composer remove twig/twig

    «Removal failed, twig/twig is still present» ist normal: Die Sperrzeile ist weg, Twig selbst bleibt, weil der Core es braucht.

  • VS-Code-Diff: links alt, rechts neu. Ohne Farben lesbar: «–» und Schraffur rechts = gelöscht, «+» und Schraffur links = neu.

9.16 -- Aufräumen

  • Vor dem Entfernen eines deinstallierten Moduls prüfen, ob noch Verweise bestehen (keine Ausgabe = sauber):

    drushci sql:query "SELECT name FROM config WHERE data LIKE '%block_class%'"
  • Alles unter web/ ist öffentlich abrufbar, auch .md- und .html-Notizen im Theme. Interne Notizen gehören nach --pernet-docs im Projektroot (in .gitignore: /--pernet-docs/).
  • Variablen aus hbm.theme werden nur in Twig-Templates verwendet. Prüfen in Theme und Custom-Modulen:

    grep -rn "NAME" web/themes/custom/hbm --include="*.twig"
    grep -rn "NAME" web/modules/custom --include="*.twig"
  • node_modules/ ohne führenden Schrägstrich in .gitignore gilt in jeder Tiefe. Gulp-Ordner selbst bleibt im Git (Build-Einstellungen), nur die Pakete sind ausgeschlossen.

9.17 -- Datenbank-Voraussetzungen D10 / D11

  • DB-Version des Servers: Reports > Status report (Zeile Datenbank). «5.5.5-10.3.39-MariaDB» heisst MariaDB 10.3.39.
  • D10 läuft mit MariaDB 10.3. D11 verlangt mindestens MariaDB 10.6 (harte Grenze, Installation und Update brechen sonst ab). Beim Hoster eine Langzeit-Version verlangen (10.11 oder 11.4).
  • D8 auf MariaDB 10.6: Risiken sind das reservierte Wort OFFSET (Spaltennamen) und COMPRESSED-Tabellen (ab 10.6.6 behoben). Prüfen mit Gegenprobe:

    SELECT DATABASE(), TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND COLUMN_NAME = 'nid';
    SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND COLUMN_NAME = 'offset';

    Erste Abfrage muss Zeilen liefern (richtige DB), zweite leer bleiben.

10 -- Settings nach per.net-Konzept (Multisite entfernt)

  • Vorlage: Starter. Für hci nur local, stage, live (kein dev). Pfade mit dirname($app_root), weil Composer-Root = Projektordner (im Starter zwei Ebenen, hier eine).
  • Alte Werte vorher sichern: Kopie der alten sites/default/settings.php ausserhalb von web/ ablegen (enthält DB-Passwort).
  • In web/sites alles löschen, was nicht Original ist: Multisite-Ordner (local.hci-update.test, dev-…, test.hbmhealthcare.com), simpletest, sites.php. Die Originale legt Composer selbst ab (composer drupal:scaffold), default/files bleibt.
  • In web/sites/default: neue settings.php, settings.local.php (Hash, DB-Zugang aus alter Datei), settings.development.php. In web/sites: development.pernet.services.yml. Ordner private/tmp anlegen.
  • settings.stage.php und settings.live.php nie in web/sites/default lokal ablegen (Umgebungsweiche bricht ab). Sie liegen in private/deployment-settings.
  • Test: drushci status > Site path sites/default, Files Private/Temp in private/. Seitenname im Browser-Tab zeigt «(Local)» (kommt aus settings.local.php, wird nicht exportiert).
  • .gitignore:

    # Ignore sensitive information
    /web/sites/*/settings.local.php
    /web/sites/*/settings.stage.php
    /web/sites/*/settings.live.php
    
    # Interne per.net-Dateien (nur lokal, nicht versioniert)
    /--pernet-docs/
  • Alter DB-Treiber Drupal\Core\Database\Driver\mysql läuft unter D11 nicht mehr. Neu: Drupal\mysql\Driver\Database\mysql plus autoload-Zeile.
  • Vor dem ersten Deployment: drushci cex, nur config/sync darf sich ändern (Kontrolle: git status --short | grep -v "config/sync" ohne Ausgabe). Dev-Module (Upgrade Status) vorher deinstallieren.

11 -- Stage einrichten (Plesk-Server hbm01)

  • Plesk: Subdomain stage, Dokumentenstamm stage.hbmhealthcare.com/web. PHP 8.3, upload/post 64M, max_execution_time 120. Let's Encrypt. Apache & nginx gleich wie live (Proxymodus an).
  • Terminal-PHP ist ein eigener Wert (hier 7.4). In ~/.bash_profile (wird beim Login geladen, .bashrc gibt es hier nicht):

    usephp83() { export PATH="/opt/plesk/php/8.3/bin:$PATH"; php -v; }
    PS1='\n\[\e[31m\][MVA] \u@\h \w\[\e[0m\]\n\$ '
    alias dr='./vendor/bin/drush'

    Gilt pro Terminal-Fenster. Ohne 8.3 bricht composer install ab, Drush 13 läuft nicht.

  • Deploy Key (Server liest bei GitLab, nur dieses Projekt, nur lesen), in ~/.ssh:

    ssh-keygen -t ed25519 -f id_ed25519_gitlab_hbm -C "HBM Healthcare - GitLab Deploy"

    Passphrase leer. ~/.ssh/config:

    Host gitlab.com
      HostName gitlab.com
      User git
      IdentityFile ~/.ssh/id_ed25519_gitlab_hbm
      IdentitiesOnly yes

    chmod 600 config. Öffentlichen Schlüssel in GitLab: Projekt > Settings > Repository > Deploy keys (ohne write). Test: ssh -T git@gitlab.com, Frage mit yes ausgeschrieben beantworten (Enter = nein, «Host key verification failed»).

  • Clone braucht leeren Ordner. Plesk-Platzhalter index.html löschen, web/ (mit .php-ini, .php-version) zur Seite stellen, clonen, die beiden Dateien zurück nach web/:

    cd ~
    mv stage.hbmhealthcare.com/web stage-web-tmp
    cd stage.hbmhealthcare.com
    git clone git@gitlab.com:per-net/web/hci-update.git .
    mv ../stage-web-tmp/.php-ini ../stage-web-tmp/.php-version web/
    rmdir ../stage-web-tmp
  • usephp83, dann composer install --no-dev. Meldung «path-util is abandoned» harmlos. «Create a sites/default/files directory with chmod 0777» legt nur den leeren Ordner an (Rechte später korrigieren).
  • settings.stage.php nach web/sites/default hochladen (DB-Host 127.0.0.1, Plesk zeigt localhost:3306, beides geht). hash_salt wie live.
  • Dateien direkt auf dem Server kopieren (Transmit lädt auch bei Kopien auf demselben Server über den Mac, 3 GB dauert ewig und blieb hängen). cp verändert die Quelle nie:

    cp -a ../httpdocs/private .
    mkdir private/tmp
    cp -a ../httpdocs/web/sites/default/files/. web/sites/default/files/

    Mit Fortschrittsanzeige: rsync -a --info=progress2 QUELLE/ ZIEL/ (zweiter Lauf kopiert nur Änderungen).
    Danach in web/sites/default/files (pwd prüfen!) Caches löschen: rm -rf php css js tmp.

  • private/tmp nicht vergessen (private/ ist nicht im Git, .gitkeep hilft nicht). Fehlt er: «Temporary file could not be created», z.B. beim Laden von Übersetzungen.
  • Collation: Lokal MySQL 8 legte bei den Updates neue Tabellen mit utf8mb4_0900_ai_ci an, MariaDB kennt das nicht (ERROR 1273). Die Collation der neuen DB hilft nicht, der Dump bringt sie pro Tabelle mit. Lösung an der Quelle, lokal pro betroffene Tabelle:

    ALTER TABLE tabellenname CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

    Betroffene Tabellen finden: in Sequel Ace / phpMyAdmin nach Kollation sortieren.

  • DB-Import in Plesk: Datenbanken > DB > Dump importieren, Häkchen «Datenbank neu erstellen» (leert vorher, Benutzer bleibt).
  • Prüfen: dr status (Database: Connected, sonst Zugangsdaten in settings.stage.php), dann dr updb, dr cr.
  • Bibliotheken: web/libraries ist nicht im Git. Was Composer nicht holt (hier flexslider), fehlt auf dem Server. Symptom: «flexslider is not a function» in der Konsole. Vergleich ls web/libraries lokal und Server, fehlendes kopieren.
  • Shield nachrüsten: lokal composer require drupal/shield, drushci en shield, drushci cex, Commit/Push. Stage: git pull, composer install --no-dev, dr cim (nur core.extension und shield.settings bestätigen), dr cr.

12 -- Inhalte von live nachtragen

  • Live-DB (D8) lässt sich nicht auf D10-Code hochziehen. Darum: Änderungen seit der Kopie von Hand übertragen. News, Team, Portfolio kommen per Import automatisch.
  • Finden: auf live Content, Filter pro Inhaltstyp (ohne Team/Portfolio), nach «Updated» sortieren. Was geändert wurde: Revisions > zwei Versionen > Compare selected revisions (Diff).
  • Als Kunden-Benutzer erfassen: Masquerade (Block im Footer des Frontend-Themes, im Backend nicht sichtbar) oder beim Speichern «Authored by» setzen.
  • Nach D10-Go-live gilt: Inhalte von live nach unten holen, Code und Konfiguration über Git und cim nach oben.

✎ Claude – offen:

  • Stage: Rechte setzen, Status report, Cron einrichten, Backup-Medienmitteilung bis in die News-Liste testen, Seiten durchklicken.
  • Flexslider-Bibliothek dauerhaft lösen (Composer oder Git).
  • Go-live-Ablauf festlegen (Checkliste im Übergabeprotokoll 09), Kunde informieren (Shield-Zugang, Doppel-Erfassung).
  • Danach D10 > D11 (Hoster: MariaDB ≥ 10.6), Altlasten auf dem Server entsorgen.

13 -- Stage testen: open_basedir und private Pfade

  • Symptom: Cron meldet «Cron-Lauf erfolgreich», im Protokoll aber RuntimeException: Feeds directory "private://feeds/in_progress/1" either cannot be created or is not writable. Kein Zeit- oder Speicherproblem, die Meldung sagt genau, was fehlt.
  • Ursache: Bei absolutem privatem Pfad (/var/www/…) prüft Drupal beim Anlegen mehrerer Ordner jede Stufe ab «/». open_basedir (Plesk-Standard {WEBSPACEROOT}{/}{:}{TMP}{/}) erlaubt nur den Webspace, darum scheitert schon /var. Der öffentliche Pfad ist relativ (sites/default/files) und daher nicht betroffen. Lokal (MAMP) gibt es kein open_basedir.
  • Ordner von Hand anlegen hilft nicht: Feeds legt in_progress/<Feed-ID> bei jedem Import neu an und löscht ihn danach.
  • Test: open_basedir auf none > Import läuft. Damit ist die Ursache bewiesen, aber der Schutz ist weg.
  • Lösung mit Schutz: Pfade in settings.php relativ zu web/ angeben, open_basedir bleibt auf Standard:

    // Relative to web/ so that open_basedir (Plesk) does not block directory creation.
    $settings['file_private_path'] = '../private/files';
    $settings['file_temp_path'] = '../private/tmp';

    Kontrolle: dr status zeigt «Files, Private: ../private/files» und «Files, Temp: ../private/tmp».

  • Testen ohne neue Daten: Inhalt > Feeds > Feed > «Importieren» (nicht «Import in background»). Die Zwischendatei wird bei jedem Import angelegt, auch wenn danach «keine neuen Einträge» kommt. Temp und private zusammen testet ein Backup and Migrate-Backup ins «Private Files Directory».
  • Gesperrter Feed: keine Statusspalte. Erkennbar nur am Eintrag «Entsperren» im Aufklappmenü neben «Bearbeiten». Nach einem abgebrochenen Import entsperren.

14 -- Git pull mit Schreibschutz, Shell-Prompt

  • Drupal schützt sites/default (555) und settings.php (444). Ändert ein Pull dort etwas, kommt error: unable to unlink old 'web/sites/default/settings.php': Permission denied. Ablauf:

    chmod u+w web/sites/default web/sites/default/settings.php
    git pull
    chmod 555 web/sites/default
    chmod 444 web/sites/default/settings.php
    git status
  • Drush färbt seine Meldungen selbst ein («success» grün), Git nicht. Fehlschläge im Prompt sichtbar machen (~/.bash_profile, ersetzt die PS1-Zeilen, schwarz auf gelb statt rot/grün):

    __set_prompt() {
      local rc=$?
      local mark=''
      if [ $rc -ne 0 ]; then
        mark="\[\e[1;30;43m\] FEHLER $rc \[\e[0m\] "
      fi
      PS1="\n${mark}"'\[\e[31m\][MVA] \u@\h \w\[\e[0m\]\n\$ '
    }
    PROMPT_COMMAND=__set_prompt

    Danach source ~/.bash_profile, Test mit ls gibtsnicht.

  • web/.php-ini und web/.php-version schreibt Plesk pro Domain. Sie sind im Git und erscheinen auf dem Server als «modified». Harmlos, aber noch aus dem Git nehmen (siehe offen).

15 -- Feeds und Übersetzung (Team)

  • Der Import kommt pro Sprache als eigener Eintrag: erste Sprache «Erstellt», zweite «Aktualisiert» (Übersetzung derselben Person).
  • Fehler «Non-translatable fields can only be changed when updating the original language»: Einstellung «Hide non translatable fields on translation forms» beim Inhaltstyp Team ausschalten (Konfiguration > Regionale Einstellungen und Sprache > Inhaltssprache und Übersetzung > Team). Sonst kommt nur die erste Sprache an.

16 -- Go-live D10 (19.10.2026)

  • Ablauf: D8-Cron in Plesk deaktivieren (Aufgabe anklicken > Haken «Aktiv» weg), Backup-Ordner anlegen, Dumps sichern, Stage-Ordner kopieren (cp -a stage.hbmhealthcare.com httpdocs-neu), settings.stage.php löschen, settings.live.php hochladen, Rechte schliessen, git status, Stage-Dump in neue Live-DB, umschalten, PHP 8.3 in Plesk, dr status, dr updb, dr cr.
  • Backup-Schema: _backups_website/drupal/2026/, rückwärts nummeriert (95---19.10.2026--…), darin DB/ und die Dateikopie. Alter Live-Ordner nicht löschen, sondern ins Backup schieben: das ist der Rückweg.
  • Stolperstein 403 Forbidden: httpdocs braucht Gruppe psaserv und 750 (drwxr-x---). Eine Kopie bekommt psacln, und chgrp psaserv ist als Benutzer nicht erlaubt. Notbehelf chmod 755 httpdocs.
  • Saubere Lösung: Plesk-Ordner nie ersetzen, nur Inhalte tauschen. Alten Ordner leeren, leer zurückholen, neuen Inhalt hineinschieben:

    mv -- * .[!.]* ../ziel/

    .[!.]* nimmt die versteckten Dateien mit. -- ist nötig, weil Namen wie --gulp-2.1-susy2 sonst als Option gelesen werden. Kontrolle: ls -ld httpdocs.

  • Vor jedem mv ins Backup prüfen, ob der Zielordner genau so heisst und noch keinen gleichnamigen Unterordner hat (sonst landet alles eine Ebene tiefer).
  • Cron neu: Plesk > Geplante Aufgaben > Aufgabe hinzufügen > «URL abrufen», URL aus Konfiguration > System > Cron, */2 * * * *. Der Cron-Schlüssel kam mit der DB und ist derselbe wie unter D8. Der alte find-Job für feeds_http_fetcher ist unter D10 überflüssig.
  • Sitemap: «Rebuild queue» stellt nur in die Warteschlange, abgearbeitet per Cron oder «Resume generation». Danach sitemap.xml prüfen: Links mit https://www, keine Stage-Adressen.
  • Änderung erst nach Shift-Reload sichtbar: Browser-Cache, kein Drupal-Problem (Konfiguration > Entwicklung > Leistung > Browser- und Proxy-Cache maximales Alter). Windows: Ctrl+F5 oder Ctrl+Shift+R, F5 allein genügt nicht.
  • Ergebnis: Statusbericht live mit 1 Warnung (D10-Support bis 9.12.2026), sonst grün. Seit einer Woche ohne Probleme. Hoster hat MariaDB auf 11.8 aktualisiert.

✎ Claude – offen:

  • 95er-Backup zippen, alte D8-Cronjobs löschen, Altlasten entsorgen (test., dev., privat-hbm).
  • web/.php-ini und web/.php-version aus dem Git nehmen (git rm --cached, .gitignore). Vorsicht: Der nächste Pull löscht sie auf dem Server.
  • Flexslider-Bibliothek dauerhaft lösen, Terminal-Standard auf PHP 8.3.
  • D10 > D11 als eigener Eintrag (Übergabeprotokoll 10). Erster Schritt: frischer Live-Dump nach lokal und stage.
  • drupal
  • drupal 10
  • upgrade
  • update
  • dateirechte
  • kollation
  • git
  • hci
  • plesk
Drupal

Tag Suche

Categories (Taxonomie-Menu)

  • Drupal
    • Module
    • Theming
    • Views
  • Webentwicklung
    • Composer
    • CSS/SCSS
    • Deployment
    • Drush
    • Git
    • HTML
    • JavaScript
    • PHP
    • SSH
    • SVG
    • VS Code
  • Datenbank
    • mySQL
  • Server
    • Apache
    • DNS
    • Domains
    • Plesk
    • SSL/TLS
  • System
    • Mac
    • Unix
    • Windows