ACF – Spezifische Speicherorte für Feldgruppen

tl;dr;
In diesem Beitrag zeige ich auf, wie die ACF-Feldgruppen JSON Dateien an spezifischen (verschiedenen!) Orten gespeichert und wieder geladen werden sollen.


Einleitung

Das Local JSON Feature von ACF bietet die Möglichkeit, dass Entwickler selber definieren können, wo die Feldgruppen-JSON Dateien gespeichert werden sollen.

Schon alleine dieses Feature ist sehr hilfreich – für unsere Bedürfnisse aber nicht gut genug. Hintergrund ist, dass wir Widgetbasiert entwickeln. Das heisst, dass wir für jedes Widget einen eigenen Ordner haben und sich darin alle Widget-spezifischen Dateien befinden. Nebst CSS und JS sind dies auch alle PHP Dateien für das Widget.

Disclaimer:
Alle folgenden Codezeilen wurden direkt aus unserem System kopiert. Es besteht kein Anspruch darauf, dass diese Codezeilen direkt kopiert und verwendet werden können. Viel mehr soll dieser Beitrag aufzeigen wie etwas gemacht werden kann. Je nach Code und Umgebung (Entwicklung / Produktion) muss natürlich sichergestellt werden, dass die nötigen Berechtigungen (z.B. Filesystem) vorhanden sind oder das gewisse Funktionen auf einem produktiven System gar nicht erst zur Verfügung stehen. Dies ist anhand des Beispielcodes nicht ersichtlich.

Erweitern der Feldgruppeneinstellungen

Damit jede Feldgruppe „weiss“, wo sie gespeichert werden soll, ergänzen wir die Feldgruppeneinstellungen um ein weiteres Feld, welches wir dann wiederum dynamisch befüllen:

public static function CustomSetting($field_group) {
    // Create our custom setting field with the specified options.
    acf_render_field_wrap([
        'label'        => 'JSON Ort',
        'type'         => 'select',
        'required'     => 1,
        'name'         => 'json_location',
        'prefix'       => 'acf_field_group',
        'value'        => $field_group['json_location'] ?? '',
        'choices'      => self::GetLocations(),
        'return_format'=> 'value',
    ]);
}
add_action('acf/render_field_group_settings', ['\\' . __NAMESPACE__ . '\\AcfJson', 'CustomSetting']);

Wie hier ersichtlich ist, werden die möglichen Werte von einer separaten Funktion „GetLocations“ zurückgegeben:

private static function GetLocations($onlyExisting = false) {
    // get possible save locations
    $saveLocations = [];

    // basic folders
    $saveLocations['acf-json' . DIRECTORY_SEPARATOR . 'theme'] = '# Theme';
    $saveLocations['acf-json' . DIRECTORY_SEPARATOR . 'core'] = '# Core';

    // module folders
    $moduleLocations = [];
    $baseDir = get_stylesheet_directory() . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR;
    $dir = new \DirectoryIterator($baseDir);
    foreach ($dir as $fileinfo) {
        if ($fileinfo->isDir() && !$fileinfo->isDot()) {
            if ($onlyExisting) {
                if (file_exists($baseDir . $fileinfo->getFilename() . DIRECTORY_SEPARATOR . 'acf-json')) {
                    $moduleLocations['modules' . DIRECTORY_SEPARATOR . $fileinfo->getFilename() . DIRECTORY_SEPARATOR . 'acf-json'] = $fileinfo->getFilename();
                }
            }
            else {
                $moduleLocations['modules' . DIRECTORY_SEPARATOR . $fileinfo->getFilename() . DIRECTORY_SEPARATOR . 'acf-json'] = $fileinfo->getFilename();
            }
        }
    }
    asort($moduleLocations);
    $saveLocations = array_merge($saveLocations, $moduleLocations);

    return $saveLocations;
}

Diese Funktion fügt fix zwei Einträge hinzu um generische Feldgruppen ablegen zu können:

  • # Theme (Order: /acf-json/theme)
  • # Core (Ordner: /acf-json/core)

Danach wird – ausgehend vom Theme-Directory – der Unterordner „/modules“ durchsucht. An dieser Stelle wird der Parameter $onlyExisting berücksichtigt:

  • $onlyExisting = true
    Es werden nur Module zurückgegeben, welche auch tatsächlich einen Unterordner „/acf-json“ enthalten.
    Dies wird für das Laden der Feldgruppen verwendet.
  • $onlyExisting = false
    Es werden alle Module zurückgegeben, egal ob diese bereits einen Unterordner „/acf-json“ enthalten oder nicht.
    Dies wird für die Auswahl innerhalb der Feldgruppeneinstellungen verwendet.

Der Rückgabewert dieser Funktion ist ein (alphabetisch nach Wert) sortiertes Array. Als Schlüssel dient uns der Speicherpfad, als Wert ein für Menschen verständlicher Name.

Speichern von Feldgruppen

Beim speichern der jeweiligen Feldgruppe wird nun der oben erwähnte „JSON Ort“ ausgelesen und die spezifische JSON-Datei dort gespeichert:

public static function SavePoint($path) {
    $location = 'acf-json';
    $key = false;
    if ($_POST && isset($_POST['acf_field_group'])) {
        // try to find location
        $location = (isset($_POST['acf_field_group']['json_location']) ? $_POST['acf_field_group']['json_location'] : 'acj-json');
        $key = (isset($_POST['acf_field_group']['key']) ? $_POST['acf_field_group']['key'] : false);
    }

    $baseDir = get_stylesheet_directory() . DIRECTORY_SEPARATOR;

    $path = \ctcore\Helper\File::GetFilePathCorrected($baseDir . $location);
    if (!file_exists($path)) {
        mkdir($path);
    }

    // cleanup
    if ($key) {
        $locations = self::GetLocations(true);
        $locations['acf-json'] = '# Root';
        foreach ($locations as $locationKey => $locationValue) {
            if (file_exists($baseDir . $locationKey . DIRECTORY_SEPARATOR . $key . '.json')) {
                if ($locationKey !== $location) {
                    unlink($baseDir . $locationKey . DIRECTORY_SEPARATOR . $key . '.json');
                }
            }
        }
    }

    return $path;
}
add_filter('acf/settings/save_json', ['\\' . __NAMESPACE__ . '\\AcfJson', 'SavePoint']);

Leider enthält der Filter für save_json nur den Pfad, wohin das JSON gespeichert werden soll.
Aus diesem Grund müssen wir ein bisschen zaubern und die $_POST Variable auslesen. Dort befindet sich nämlich die gesamte Felddefinition in der Variable „acf_field_group“. Und darin befindet sich schlussendlich auch unser gesuchter Wert (json_location).

Basierend darauf wird geprüft ob der entsprechende Ordner bereits existiert. Falls nicht, wird dieser zurst erstellt. Kurz davor wird die übermittelte $path Variable mit dem neuen Wert überschrieben.

Abschliessend braucht es noch etwas Aufräumarbeiten:

  1. Wir holen alle tatsächlich existierenden möglichen Speicherorte.
  2. Wir prüfen ob unsere Feldgruppe bereits in einem anderen Ordner existiert.
  3. Falls dies zutrifft, wird diese Feldgruppe (bzw. die zugehörige Datei!) gelöscht.

Warum machen wir das?
Gehen wir davon aus, ich erstelle eine Feldgruppe und weise sie dem Modul „A“ zu. Beim speichern wird somit die JSON-Datei im Modul „A“ gespeichert. Danach bearbeite ich die erwähnte Feldgruppe und weise sie neu Modul „B“ zu. Somit wird diese beim speichern im Modul „B“ gespeichert. Da aber die Definition vorher auf Modul „A“ lag, muss diese Datei natürlich dort noch gelöscht werden, da diese Feldgruppendatei sonst an zwei verschiedenen Orten liegt – Chaos vorprogrammiert 🙂

Laden von Feldgruppen

Das Laden der Feldgruppen ist denkbar einfach:

public static function LoadPoint($paths) {
    unset($paths[0]);

    $locations = self::GetLocations(true);
    $baseDir = get_stylesheet_directory() . DIRECTORY_SEPARATOR;
    foreach ($locations as $locationKey=>$locationValue) {
        $paths[] = $baseDir . $locationKey;
    }


    return $paths;
}
add_filter('acf/settings/load_json', ['\\' . __NAMESPACE__ . '\\AcfJson', 'LoadPoint']);

Bei diesem Filter erhalten wir ein Array von möglichen Pfaden, in welchen ACF nach Feldgruppen-JSON-Dateien sucht.
Wir löschen zuerst den Standardpfad heraus, da dieser – in unserem Fall – nicht mehr zum Einsatz kommt.

Danach lesen wir wiederum mittels GetLocations(true) alle bereits existierenden Module aus und fügen diese dem Array $paths hinzu.

Löschen von Feldgruppen

Eigentlich könnte der Beitrag hier fertig sein 🙂 Leider müssen wir noch einen kleinen Spezialfall berücksichtigen: Das löschen von Feldgruppen.

Dazu verwenden wir noch folgenden Code:

public static function DeleteGroup($group) {
    $originalKey = substr($group['key'],0, strlen($group['key']) - 9);
    $baseDir = get_stylesheet_directory() . DIRECTORY_SEPARATOR;
    if (file_exists($baseDir . $group['json_location'] . DIRECTORY_SEPARATOR . $originalKey . '.json')) {
        unlink($baseDir . $group['json_location'] . DIRECTORY_SEPARATOR . $originalKey . '.json');
    }
}
add_action('acf/delete_field_group', ['\\' . __NAMESPACE__ . '\\AcfJson', 'DeleteGroup']);

Als erstes lesen wir den Feldschlüssel der soeben gelöschten Feldgruppe aus. Da diese vorher im Papierkorb war, endet der Schlüssel mit „_trashed“ – aus diesem Grund muss via substr()-Funktion dies entfernt werden.

Danach wird geprüft ob eine solche Feld in der „json_location“ existiert – falls ja, wird es anschliessend gelöscht.

Links zum Thema

Fragen

Hast du nun Fragen zu diesem oder einem anderen Thema? Hast du eine bessere Idee das obenstehende Thema zu lösen oder hast du sogar Fehler entdeckt? Ich freue mich auf dein Feedback!

Du kannst entweder direkt unter diesem Beitrag einen Kommentar schreiben oder natürlich auch via Formular mit mir Kontakt aufnehmen.

Kommentar schreiben

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.