Admin Interface
Tools > DB Optimalisatie
Section titled “Tools > DB Optimalisatie”- URL:
/wp-admin/tools.php?page=werkbon-db-optimize - Capability:
manage_options - Bestand:
functions/db-optimize.php
MySQL indexes op wp_postmeta voor snellere contact queries. Zonder deze indexes scant MySQL alle postmeta rijen (kan 100.000+ zijn). Met de index springt MySQL direct naar de juiste rijen.
Geindexeerde meta keys
Section titled “Geindexeerde meta keys”| Index Naam | Meta Key | Gebruik |
|---|---|---|
idx_moneybird_id | moneybird_id | Moneybird integratie lookup |
idx_contact_zipcode | contact-zipcode | Adres matching op postcode |
idx_contact_streetname | contact-streetname | Adres matching op straat |
idx_contact_email | contact-email | Contact zoeken op email |
idx_contact_type | contact-type | Sorteren/filteren op type |
idx_contact_city | contact-city | Sorteren/filteren op stad |
Index type: composite (meta_key, meta_value(100)) op de wp_postmeta tabel.
SQL: Index aanmaken
Section titled “SQL: Index aanmaken”De indexnaam wordt afgeleid van de meta key. Het SQL-statement dat per key wordt uitgevoerd:
CREATE INDEX idx_moneybird_id ON wp_postmeta (meta_key, meta_value(100))CREATE INDEX idx_contact_zipcode ON wp_postmeta (meta_key, meta_value(100))CREATE INDEX idx_contact_streetname ON wp_postmeta (meta_key, meta_value(100))CREATE INDEX idx_contact_email ON wp_postmeta (meta_key, meta_value(100))CREATE INDEX idx_contact_type ON wp_postmeta (meta_key, meta_value(100))CREATE INDEX idx_contact_city ON wp_postmeta (meta_key, meta_value(100))Voordat een index wordt aangemaakt, controleert de functie via information_schema.statistics of de index al bestaat:
function werkbon_create_meta_index(string $meta_key): bool { global $wpdb;
$index_name = 'idx_' . preg_replace('/[^a-z0-9_]/', '_', strtolower($meta_key)); $table = $wpdb->postmeta;
// Check of index al bestaat $exists = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(1) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = %s AND index_name = %s", $table, $index_name ));
if ($exists) { return true; // Al aanwezig }
$sql = "CREATE INDEX {$index_name} ON {$table} (meta_key, meta_value(100))"; $result = $wpdb->query($sql);
if ($result === false) { error_log("[DB_OPTIMIZE] Failed to create index {$index_name}: " . $wpdb->last_error); return false; }
return true;}Functies
Section titled “Functies”| Functie | Doel |
|---|---|
werkbon_get_indexed_meta_keys() | Retourneert array met 6 meta keys die geindexeerd worden |
werkbon_create_meta_index($key) | Maakt 1 index aan (idempotent) |
werkbon_drop_meta_index($key) | Verwijdert 1 index |
werkbon_create_all_meta_indexes() | Maakt alle 6 indexes aan, retourneert [$key => bool] |
werkbon_check_meta_indexes() | Retourneert status per key: ['index_name' => string, 'exists' => bool] |
Admin UI
Section titled “Admin UI”De pagina toont:
- Status tabel per meta key met index naam en status (groen vinkje / oranje kruis)
- Samenvatting:
X/6 indexes actief - Actieknop: “Indexes Aanmaken” - maakt alle ontbrekende indexes aan
- Uitleg sectie: waarom indexes nodig zijn en welke queries profiteren
Bescherming: nonce via wp_nonce_field('werkbon_db_optimize') + manage_options capability check.
Automatisch bij theme activatie
Section titled “Automatisch bij theme activatie”add_action('after_switch_theme', function() { werkbon_create_all_meta_indexes();});Tools > Adressen Database
Section titled “Tools > Adressen Database”- URL:
/wp-admin/tools.php?page=werkbon-addresses - Capability:
manage_options - Bestand:
functions/db-addresses.php
Database tabel: {prefix}_werkbon_addresses
Section titled “Database tabel: {prefix}_werkbon_addresses”De volledige CREATE TABLE statement:
CREATE TABLE IF NOT EXISTS wp_werkbon_addresses ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, streetname VARCHAR(255) NOT NULL DEFAULT '', house_number VARCHAR(20) NOT NULL DEFAULT '', addition VARCHAR(20) NOT NULL DEFAULT '', zipcode VARCHAR(10) NOT NULL DEFAULT '', city VARCHAR(100) NOT NULL DEFAULT '', is_maasdelta TINYINT(1) NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY address_unique (zipcode, house_number, addition), KEY zipcode_idx (zipcode), KEY streetname_idx (streetname(50)), KEY street_city_idx (streetname(50), city(30), is_maasdelta)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;Tabel indexes
Section titled “Tabel indexes”| Index | Kolommen | Doel |
|---|---|---|
PRIMARY | id | Auto increment primary key |
address_unique | zipcode, house_number, addition | Voorkomt duplicaten |
zipcode_idx | zipcode | Snelle postcode lookups |
streetname_idx | streetname(50) | Snelle straatnaam zoekquery’s |
street_city_idx | streetname(50), city(30), is_maasdelta | Gecombineerde zoekquery’s met Maasdelta filter |
CSV import formaat
Section titled “CSV import formaat”CSV-bestanden staan in /assets/csv/addresses/. Delimiter wordt automatisch gedetecteerd (komma of puntkomma).
| Kolom | Index | Voorbeeld | Transformatie |
|---|---|---|---|
| Straatnaam | 0 | Burgemeester de Villeneuvesingel | trim() |
| Huisnummer | 1 | 42 | trim() |
| Toevoeging | 2 | A | strtoupper(trim()) |
| Postcode | 3 | 3221 AJ | strtoupper(), spaties verwijderd -> 3221AJ |
| Plaats | 4 | Hellevoetsluis | trim() |
Importregels:
maasdelta.csvwordt altijd eerst geimporteerd (prioriteit bij duplicaten)- Rijen zonder postcode, huisnummer of straatnaam worden overgeslagen
INSERT IGNOREslaat duplicaten over (op basis vanUNIQUE KEY address_unique)- Batch insert per 500 rijen voor performance
TRUNCATE TABLEvoor elke import (volledige herlaad)- Transacties:
SET autocommit = 0+COMMITaan het einde
Functies
Section titled “Functies”| Functie | Doel |
|---|---|
werkbon_addresses_table_name() | Retourneert {prefix}_werkbon_addresses |
werkbon_addresses_create_table() | Maakt tabel aan via dbDelta() |
werkbon_addresses_drop_table() | Verwijdert tabel |
werkbon_addresses_count() | Telt rijen in tabel |
werkbon_addresses_import_csv($truncate) | Importeert alle CSV’s, retourneert ['imported', 'skipped', 'errors'] |
werkbon_addresses_insert_batch($table, $batch) | Voert batch INSERT IGNORE uit |
werkbon_addresses_lookup($zip, $nr, $add) | Zoekt 1 adres op, retourneert ['streetname', 'is_maasdelta'] |
werkbon_addresses_search_streets($search, $limit) | Zoekt straten voor Select2, LIKE %term% |
werkbon_addresses_get_numbers($street, $city, $maas) | Huisnummers voor straat+stad |
werkbon_addresses_get_additions($street, $city, $nr, $maas) | Toevoegingen voor straat+stad+nummer |
werkbon_addresses_get_zipcode($street, $city, $nr, $add, $maas) | Postcode+stad ophalen, formatteerd postcode met spatie |
werkbon_addresses_resolve_street($streetname) | Exacte straatnaam resolve, retourneert matches met street_key |
werkbon_addresses_table_ready() | Check of tabel bestaat en data bevat |
Admin UI
Section titled “Admin UI”De pagina toont:
- Status: tabel actief/niet gereed + aantal adressen
- Knop: “Tabel aanmaken” (alleen als tabel niet bestaat)
- Knop: “CSV bestanden importeren” (met confirmatie dialog)
Bescherming: nonce + manage_options capability.
Automatisch bij theme activatie
Section titled “Automatisch bij theme activatie”add_action('after_switch_theme', 'werkbon_addresses_create_table');Zie ook Adressysteem voor de AJAX endpoint documentatie.
Contact Admin Kolommen
Section titled “Contact Admin Kolommen”- Bestand:
functions/contact-admin-columns.php - Post type:
contact
Kolommen
Section titled “Kolommen”| Kolom ID | Label | Meta key(s) | Sorteerbaar |
|---|---|---|---|
acs_address | Adres | contact-streetname + contact-number + contact-addition | Nee |
acs_zipcode | Postcode | contact-zipcode | Ja |
acs_city | Plaats | contact-city | Ja |
acs_moneybird | Moneybird | moneybird_id | Ja |
acs_type | Type | contact-type | Ja (met ucFirst) |
Kolom render functie
Section titled “Kolom render functie”De kolommen worden gerenderd via de manage_contact_posts_custom_column action. Elke kolom leest direct uit de meta cache:
add_action('manage_contact_posts_custom_column', function (string $column, int $post_id) {
// Meta is already cached by pre_get_posts hook, dus O(1) lookups if ($column === 'acs_address') { $street = trim((string) get_post_meta($post_id, 'contact-streetname', true)); $number = trim((string) get_post_meta($post_id, 'contact-number', true)); $add = trim((string) get_post_meta($post_id, 'contact-addition', true));
$address = trim($street . ' ' . $number . ($add !== '' ? ' ' . $add : '')); echo esc_html($address !== '' ? $address : "\u2014"); return; }
if ($column === 'acs_zipcode') { $zip = trim((string) get_post_meta($post_id, 'contact-zipcode', true)); echo esc_html($zip !== '' ? $zip : "\u2014"); return; }
if ($column === 'acs_city') { $city = trim((string) get_post_meta($post_id, 'contact-city', true)); echo esc_html($city !== '' ? $city : "\u2014"); return; }
if ($column === 'acs_moneybird') { $mb = trim((string) get_post_meta($post_id, 'moneybird_id', true)); echo esc_html($mb !== '' ? $mb : "\u2014"); return; }
if ($column === 'acs_type') { $type = trim((string) get_post_meta($post_id, 'contact-type', true)); echo ucFirst(esc_html($type !== '' ? $type : "\u2014")); return; }
}, 10, 2);Lege waarden worden weergegeven als -- (em-dash).
N+1 query optimalisatie
Section titled “N+1 query optimalisatie”Zonder optimalisatie zou WordPress per kolom per post een aparte SELECT uitvoeren op wp_postmeta. Met 20 posts en 5 kolommen = 100 queries. De oplossing is meta cache primen via pre_get_posts:
add_action('pre_get_posts', function (WP_Query $query) { if (!is_admin() || !$query->is_main_query()) return; if ($query->get('post_type') !== 'contact') return;
// Na query uitvoering: prime meta cache in 1 keer add_action('the_posts', function ($posts) use ($query) { if (empty($posts) || !$query->is_main_query()) return $posts;
// Prime meta cache voor alle posts in 1 batch query $post_ids = wp_list_pluck($posts, 'ID'); update_meta_cache('post', $post_ids);
return $posts; }, 10, 1);});Dit reduceert 100+ queries naar 1 SELECT ... WHERE post_id IN (...) query. Alle get_post_meta() calls in de kolom render functie zijn daarna O(1) lookups uit de object cache.
Sorteerlogica
Section titled “Sorteerlogica”Sorteerbare kolommen worden afgehandeld via een tweede pre_get_posts hook:
add_action('pre_get_posts', function (WP_Query $query) { if (!is_admin() || !$query->is_main_query()) return; if ($query->get('post_type') !== 'contact') return;
$orderby = $query->get('orderby');
$meta_key_map = [ 'acs_zipcode' => 'contact-zipcode', 'acs_city' => 'contact-city', 'acs_moneybird' => 'moneybird_id', 'acs_type' => 'contact-type', ];
if (isset($meta_key_map[$orderby])) { $query->set('meta_key', $meta_key_map[$orderby]); $query->set('orderby', 'meta_value'); }});ACF Options Page
Section titled “ACF Options Page”- Bestand:
functions/admin.php - Geregistreerd via
acf_add_options_page()(zonder parameters = standaard “Options” pagina) - Alleen als ACF actief is:
if( function_exists('acf_add_options_page') )
Menu registratie
Section titled “Menu registratie”Twee navigatiemenu’s geregistreerd via register_nav_menus():
add_action('init', 'register_menus');function register_menus() { register_nav_menus( array( 'menu' => __( 'Main menu' ), 'mobile' => __( 'Mobile menu' ) ) );}| Location | Gebruik |
|---|---|
menu | Desktop horizontaal menu in navigatie |
mobile | Mobiel menu in account dropdown |
Custom Login Styling
Section titled “Custom Login Styling”- Logo:
/assets/img/logo-login.png(300x100px) via CSS injectie inlogin_head - Footer: “Made by Angelo Vaudo” via
admin_footer_textfilter - Admin bar: Verborgen voor alle gebruikers:
add_filter('show_admin_bar', '__return_false') - Admin menu z-index fix:
#adminmenu { transform: translateZ(0); }
Bestandsoverzicht
Section titled “Bestandsoverzicht”| Bestand | Pad | Registratie | Functies |
|---|---|---|---|
db-optimize.php | functions/db-optimize.php | admin_menu, after_switch_theme | Meta index management, admin UI |
db-addresses.php | functions/db-addresses.php | admin_menu, after_switch_theme | Adres tabel, CSV import, lookup functies |
contact-admin-columns.php | functions/contact-admin-columns.php | manage_contact_posts_columns, manage_contact_posts_custom_column, pre_get_posts | Contact kolommen, meta cache, sorteerlogica |
admin.php | functions/admin.php | login_head, admin_footer_text, admin_head, init | Login styling, admin bar, ACF options, menu registratie |