{
    "generator": "WPpiloot 1.1.0",
    "date_created": "2026-11-10 00:13",
    "snippets": [
        {
            "name": "WPpiloot Connector (ACF-velden voor WPpiloot)",
            "desc": "<p>Laat <a href=\"https://wppiloot.nl\">WPpiloot</a> ACF-velden lezen en aanpassen (berichten, pagina's, eigen berichttypes en optiepagina's), ook als \"Show in REST API\" uit staat. WordPress-rechten blijven gelden. Versie 1.1.0.</p>",
            "tags": [
                "wppiloot",
                "acf"
            ],
            "scope": "global",
            "priority": 10,
            "code": "/**\n * WPpiloot Connector 1.1.0 (snippet-versie) - https://wppiloot.nl\n * Laat WPpiloot ACF-velden lezen en aanpassen, ook als \"Show in REST API\" uit staat.\n * WordPress-rechten blijven gelden. Staat de plugin ook aan, dan laadt maar één van beide.\n */\nif (! defined('WPPILOOT_CONNECTOR_VERSION')) {\n    define('WPPILOOT_CONNECTOR_SOURCE', 'snippet');\n\n    define('WPPILOOT_CONNECTOR_VERSION', '1.1.0');\n\n    /**\n     * Eindpunten (namespace wppiloot/v1), alleen voor ingelogde gebruikers\n     * (applicatiewachtwoord):\n     *   GET  /status                 versie, ACF aanwezig?\n     *   GET  /acf/overview           optiepagina's en berichttypes met ACF-velden\n     *   GET  /acf/fields?target=...  velddefinities + huidige waarden\n     *   POST /acf/fields             {target, values: {veldnaam: waarde}}\n     * target = \"post:123\" (elk berichttype) of \"options:<menu_slug>\".\n     */\n    add_action('rest_api_init', function () {\n        $ns = 'wppiloot/v1';\n\n        register_rest_route($ns, '/status', [\n            'methods' => 'GET',\n            'permission_callback' => 'is_user_logged_in',\n            'callback' => function () {\n                return [\n                    'version' => WPPILOOT_CONNECTOR_VERSION,\n                    'acf' => function_exists('acf_get_field_groups'),\n                    'acf_version' => defined('ACF_VERSION') ? ACF_VERSION : null,\n                    'acf_pro' => defined('ACF_PRO') && ACF_PRO,\n                    'source' => defined('WPPILOOT_CONNECTOR_SOURCE') ? WPPILOOT_CONNECTOR_SOURCE : 'plugin',\n                ];\n            },\n        ]);\n\n        register_rest_route($ns, '/acf/overview', [\n            'methods' => 'GET',\n            'permission_callback' => 'is_user_logged_in',\n            'callback' => 'wppiloot_acf_overview',\n        ]);\n\n        register_rest_route($ns, '/acf/fields', [\n            [\n                'methods' => 'GET',\n                'permission_callback' => 'wppiloot_acf_can_edit',\n                'callback' => 'wppiloot_acf_get',\n                'args' => ['target' => ['required' => true, 'type' => 'string']],\n            ],\n            [\n                'methods' => 'POST',\n                'permission_callback' => 'wppiloot_acf_can_edit',\n                'callback' => 'wppiloot_acf_update',\n                'args' => [\n                    'target' => ['required' => true, 'type' => 'string'],\n                    'values' => ['required' => true, 'type' => 'object'],\n                ],\n            ],\n        ]);\n    });\n\n    /**\n     * Zet \"post:123\" of \"options:slug\" om naar wat ACF verwacht.\n     *\n     * @return array{kind: string, acf_id: int|string, post?: WP_Post, page?: array}|WP_Error\n     */\n    function wppiloot_acf_target($target)\n    {\n        if (! function_exists('acf_get_field_groups')) {\n            return new WP_Error('wppiloot_no_acf', 'Advanced Custom Fields is niet actief op deze site.', ['status' => 400]);\n        }\n\n        if (preg_match('/^post:(\\d+)$/', (string) $target, $m)) {\n            $post = get_post((int) $m[1]);\n            if (! $post || $post->post_type === 'revision') {\n                return new WP_Error('wppiloot_not_found', 'Dit bericht bestaat niet.', ['status' => 404]);\n            }\n\n            return ['kind' => 'post', 'acf_id' => $post->ID, 'post' => $post];\n        }\n\n        if (preg_match('/^options:([A-Za-z0-9_-]+)$/', (string) $target, $m)) {\n            $page = function_exists('acf_get_options_page') ? acf_get_options_page($m[1]) : null;\n            if (! $page) {\n                return new WP_Error('wppiloot_not_found', 'Deze optiepagina bestaat niet.', ['status' => 404]);\n            }\n\n            return ['kind' => 'options', 'acf_id' => $page['post_id'] ?: 'options', 'page' => $page];\n        }\n\n        return new WP_Error('wppiloot_bad_target', 'target moet \"post:<id>\" of \"options:<slug>\" zijn.', ['status' => 400]);\n    }\n\n    /** Dezelfde rechten als in de WordPress-backend. */\n    function wppiloot_acf_can_edit(WP_REST_Request $request)\n    {\n        if (! is_user_logged_in()) {\n            return false;\n        }\n        $t = wppiloot_acf_target($request['target']);\n        if (is_wp_error($t)) {\n            return $t;\n        }\n\n        return $t['kind'] === 'post'\n            ? current_user_can('edit_post', $t['post']->ID)\n            : current_user_can($t['page']['capability'] ?: 'edit_posts');\n    }\n\n    /** Veldgroepen die bij dit doel horen (zoals ACF ze in de editor toont). */\n    function wppiloot_acf_groups(array $t)\n    {\n        if ($t['kind'] === 'post') {\n            $args = ['post_id' => $t['post']->ID, 'post_type' => $t['post']->post_type];\n            $template = get_page_template_slug($t['post']);\n            if ($template) {\n                $args['page_template'] = $template;\n            }\n        } else {\n            $args = ['options_page' => $t['page']['menu_slug']];\n        }\n\n        return array_values(array_filter(acf_get_field_groups($args), function ($g) {\n            return ! empty($g['active']);\n        }));\n    }\n\n    /** Beknopte definitie van een veld, voor het taalmodel. */\n    function wppiloot_acf_describe(array $field)\n    {\n        $d = [\n            'name' => $field['name'],\n            'label' => $field['label'],\n            'type' => $field['type'],\n        ];\n        if (! empty($field['instructions'])) {\n            $d['instructions'] = wp_strip_all_tags($field['instructions']);\n        }\n        if (! empty($field['required'])) {\n            $d['required'] = true;\n        }\n        if (! empty($field['choices']) && is_array($field['choices'])) {\n            $d['choices'] = $field['choices'];\n        }\n        if (! empty($field['multiple'])) {\n            $d['multiple'] = true;\n        }\n        if (! empty($field['max'])) {\n            $d['max'] = $field['max'];\n        }\n        if (in_array($field['type'], ['image', 'file', 'gallery'], true)) {\n            $d['value_format'] = 'media-id (gallery: lijst van media-id\\'s)';\n        } elseif (in_array($field['type'], ['post_object', 'relationship', 'page_link'], true)) {\n            $d['value_format'] = 'bericht-id (of lijst van id\\'s)';\n        } elseif ($field['type'] === 'taxonomy') {\n            $d['value_format'] = 'term-id (of lijst van id\\'s)';\n        } elseif ($field['type'] === 'link') {\n            $d['value_format'] = '{\"title\",\"url\",\"target\"}';\n        } elseif ($field['type'] === 'true_false') {\n            $d['value_format'] = '0 of 1';\n        }\n        if (! empty($field['sub_fields'])) {\n            $d['sub_fields'] = array_map('wppiloot_acf_describe', $field['sub_fields']);\n        }\n        if (! empty($field['layouts'])) {\n            $d['layouts'] = array_values(array_map(function ($l) {\n                return [\n                    'name' => $l['name'],\n                    'label' => $l['label'],\n                    'sub_fields' => array_map('wppiloot_acf_describe', $l['sub_fields'] ?? []),\n                ];\n            }, $field['layouts']));\n        }\n\n        return $d;\n    }\n\n    /**\n     * Ruwe waarde (zoals opgeslagen, zonder opmaak) met veldnamen in plaats van\n     * veldsleutels in repeaters, groepen en flexibele inhoud. Zo komt het weer\n     * terug in update_field().\n     */\n    function wppiloot_acf_export(array $field, $value)\n    {\n        switch ($field['type']) {\n            case 'repeater':\n                if (! is_array($value)) {\n                    return [];\n                }\n\n                return array_values(array_map(function ($row) use ($field) {\n                    return wppiloot_acf_export_row($field['sub_fields'] ?? [], (array) $row);\n                }, $value));\n\n            case 'group':\n                return wppiloot_acf_export_row($field['sub_fields'] ?? [], (array) $value);\n\n            case 'flexible_content':\n                if (! is_array($value)) {\n                    return [];\n                }\n                $layouts = [];\n                foreach ($field['layouts'] ?? [] as $l) {\n                    $layouts[$l['name']] = $l;\n                }\n\n                return array_values(array_map(function ($row) use ($layouts) {\n                    $row = (array) $row;\n                    $name = $row['acf_fc_layout'] ?? '';\n                    $out = ['acf_fc_layout' => $name];\n                    if (isset($layouts[$name])) {\n                        $out += wppiloot_acf_export_row($layouts[$name]['sub_fields'] ?? [], $row);\n                    }\n\n                    return $out;\n                }, $value));\n\n            default:\n                return $value;\n        }\n    }\n\n    function wppiloot_acf_export_row(array $sub_fields, array $row)\n    {\n        $out = [];\n        foreach ($sub_fields as $sub) {\n            $raw = $row[$sub['key']] ?? $row[$sub['name']] ?? null;\n            $out[$sub['name']] = wppiloot_acf_export($sub, $raw);\n        }\n\n        return $out;\n    }\n\n    function wppiloot_acf_get(WP_REST_Request $request)\n    {\n        $t = wppiloot_acf_target($request['target']);\n        if (is_wp_error($t)) {\n            return $t;\n        }\n\n        $groups = [];\n        foreach (wppiloot_acf_groups($t) as $group) {\n            $fields = [];\n            foreach (acf_get_fields($group) ?: [] as $field) {\n                if (in_array($field['type'], ['tab', 'message', 'accordion'], true) || $field['name'] === '') {\n                    continue;\n                }\n                $fields[] = wppiloot_acf_describe($field) + [\n                    'value' => wppiloot_acf_export($field, get_field($field['key'], $t['acf_id'], false)),\n                ];\n            }\n            $groups[] = ['title' => $group['title'], 'fields' => $fields];\n        }\n\n        return [\n            'target' => $request['target'],\n            'title' => $t['kind'] === 'post' ? get_the_title($t['post']) : $t['page']['page_title'],\n            'post_type' => $t['kind'] === 'post' ? $t['post']->post_type : null,\n            'groups' => $groups,\n        ];\n    }\n\n    /** Alle velden (op het hoogste niveau) die bij het doel horen, op naam. */\n    function wppiloot_acf_field_map(array $t)\n    {\n        $map = [];\n        foreach (wppiloot_acf_groups($t) as $group) {\n            foreach (acf_get_fields($group) ?: [] as $field) {\n                if ($field['name'] !== '' && ! in_array($field['type'], ['tab', 'message', 'accordion'], true)) {\n                    $map[$field['name']] = $field;\n                }\n            }\n        }\n\n        return $map;\n    }\n\n    /** Tekst zonder unfiltered_html-recht door wp_kses_post, net als in de editor. */\n    function wppiloot_acf_clean($value)\n    {\n        if (is_array($value)) {\n            return array_map('wppiloot_acf_clean', $value);\n        }\n        if (is_string($value) && ! current_user_can('unfiltered_html')) {\n            return wp_kses_post($value);\n        }\n\n        return $value;\n    }\n\n    function wppiloot_acf_update(WP_REST_Request $request)\n    {\n        $t = wppiloot_acf_target($request['target']);\n        if (is_wp_error($t)) {\n            return $t;\n        }\n\n        $values = (array) $request['values'];\n        if (! $values) {\n            return new WP_Error('wppiloot_empty', 'Geen waarden meegegeven.', ['status' => 400]);\n        }\n\n        $map = wppiloot_acf_field_map($t);\n        $unknown = array_diff(array_keys($values), array_keys($map));\n        if ($unknown) {\n            return new WP_Error('wppiloot_unknown_field', 'Onbekende velden voor dit doel: '.implode(', ', $unknown).'. Bestaande velden: '.implode(', ', array_keys($map)).'.', ['status' => 400]);\n        }\n\n        foreach ($values as $name => $value) {\n            $field = $map[$name];\n            if (in_array($field['type'], ['select', 'radio', 'button_group', 'checkbox'], true) && ! empty($field['choices']) && $value !== '' && $value !== null) {\n                foreach ((array) $value as $v) {\n                    if (! array_key_exists((string) $v, $field['choices'])) {\n                        return new WP_Error('wppiloot_bad_choice', \"Waarde \\\"{$v}\\\" is niet toegestaan voor {$name}. Kies uit: \".implode(', ', array_keys($field['choices'])).'.', ['status' => 400]);\n                    }\n                }\n            }\n        }\n\n        $updated = [];\n        foreach ($values as $name => $value) {\n            $field = $map[$name];\n            update_field($field['key'], wppiloot_acf_clean($value), $t['acf_id']);\n            $updated[$name] = wppiloot_acf_export($field, get_field($field['key'], $t['acf_id'], false));\n        }\n\n        if ($t['kind'] === 'post') {\n            // Wijzigingsdatum bijwerken (caches, sitemaps), zonder de inhoud aan te raken.\n            wp_update_post(['ID' => $t['post']->ID]);\n        }\n\n        return ['target' => $request['target'], 'updated' => $updated];\n    }\n\n    function wppiloot_acf_overview()\n    {\n        if (! function_exists('acf_get_field_groups')) {\n            return new WP_Error('wppiloot_no_acf', 'Advanced Custom Fields is niet actief op deze site.', ['status' => 400]);\n        }\n\n        $options = [];\n        foreach (function_exists('acf_get_options_pages') ? (acf_get_options_pages() ?: []) : [] as $page) {\n            if (current_user_can($page['capability'] ?: 'edit_posts')) {\n                $options[] = ['target' => 'options:'.$page['menu_slug'], 'title' => $page['page_title']];\n            }\n        }\n\n        $post_types = [];\n        foreach (acf_get_field_groups() as $group) {\n            if (empty($group['active'])) {\n                continue;\n            }\n            foreach ($group['location'] as $rules) {\n                foreach ($rules as $rule) {\n                    if ($rule['param'] === 'post_type' && $rule['operator'] === '==') {\n                        $post_types[$rule['value']][] = $group['title'];\n                    } elseif ($rule['param'] === 'page_template' && $rule['operator'] === '==') {\n                        $post_types['page'][] = $group['title'].' (sjabloon '.$rule['value'].')';\n                    }\n                }\n            }\n        }\n\n        $types = [];\n        foreach ($post_types as $slug => $titles) {\n            $obj = get_post_type_object($slug);\n            if ($obj && current_user_can($obj->cap->edit_posts)) {\n                $types[] = [\n                    'post_type' => $slug,\n                    'label' => $obj->labels->name,\n                    'rest_base' => $obj->show_in_rest ? ($obj->rest_base ?: $slug) : null,\n                    'field_groups' => array_values(array_unique($titles)),\n                ];\n            }\n        }\n\n        return ['options_pages' => $options, 'post_types' => $types];\n    }\n}\n"
        }
    ]
}