Skip to content

Admin Interface

  • 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.

Index NaamMeta KeyGebruik
idx_moneybird_idmoneybird_idMoneybird integratie lookup
idx_contact_zipcodecontact-zipcodeAdres matching op postcode
idx_contact_streetnamecontact-streetnameAdres matching op straat
idx_contact_emailcontact-emailContact zoeken op email
idx_contact_typecontact-typeSorteren/filteren op type
idx_contact_citycontact-citySorteren/filteren op stad

Index type: composite (meta_key, meta_value(100)) op de wp_postmeta tabel.

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;
}
FunctieDoel
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]

De pagina toont:

  1. Status tabel per meta key met index naam en status (groen vinkje / oranje kruis)
  2. Samenvatting: X/6 indexes actief
  3. Actieknop: “Indexes Aanmaken” - maakt alle ontbrekende indexes aan
  4. Uitleg sectie: waarom indexes nodig zijn en welke queries profiteren

Bescherming: nonce via wp_nonce_field('werkbon_db_optimize') + manage_options capability check.

add_action('after_switch_theme', function() {
werkbon_create_all_meta_indexes();
});
  • 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;
IndexKolommenDoel
PRIMARYidAuto increment primary key
address_uniquezipcode, house_number, additionVoorkomt duplicaten
zipcode_idxzipcodeSnelle postcode lookups
streetname_idxstreetname(50)Snelle straatnaam zoekquery’s
street_city_idxstreetname(50), city(30), is_maasdeltaGecombineerde zoekquery’s met Maasdelta filter

CSV-bestanden staan in /assets/csv/addresses/. Delimiter wordt automatisch gedetecteerd (komma of puntkomma).

KolomIndexVoorbeeldTransformatie
Straatnaam0Burgemeester de Villeneuvesingeltrim()
Huisnummer142trim()
Toevoeging2Astrtoupper(trim())
Postcode33221 AJstrtoupper(), spaties verwijderd -> 3221AJ
Plaats4Hellevoetsluistrim()

Importregels:

  • maasdelta.csv wordt altijd eerst geimporteerd (prioriteit bij duplicaten)
  • Rijen zonder postcode, huisnummer of straatnaam worden overgeslagen
  • INSERT IGNORE slaat duplicaten over (op basis van UNIQUE KEY address_unique)
  • Batch insert per 500 rijen voor performance
  • TRUNCATE TABLE voor elke import (volledige herlaad)
  • Transacties: SET autocommit = 0 + COMMIT aan het einde
FunctieDoel
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

De pagina toont:

  1. Status: tabel actief/niet gereed + aantal adressen
  2. Knop: “Tabel aanmaken” (alleen als tabel niet bestaat)
  3. Knop: “CSV bestanden importeren” (met confirmatie dialog)

Bescherming: nonce + manage_options capability.

add_action('after_switch_theme', 'werkbon_addresses_create_table');

Zie ook Adressysteem voor de AJAX endpoint documentatie.

  • Bestand: functions/contact-admin-columns.php
  • Post type: contact
Kolom IDLabelMeta key(s)Sorteerbaar
acs_addressAdrescontact-streetname + contact-number + contact-additionNee
acs_zipcodePostcodecontact-zipcodeJa
acs_cityPlaatscontact-cityJa
acs_moneybirdMoneybirdmoneybird_idJa
acs_typeTypecontact-typeJa (met ucFirst)

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).

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.

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');
}
});
  • 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') )

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' )
)
);
}
LocationGebruik
menuDesktop horizontaal menu in navigatie
mobileMobiel menu in account dropdown
  • Logo: /assets/img/logo-login.png (300x100px) via CSS injectie in login_head
  • Footer: “Made by Angelo Vaudo” via admin_footer_text filter
  • Admin bar: Verborgen voor alle gebruikers: add_filter('show_admin_bar', '__return_false')
  • Admin menu z-index fix: #adminmenu { transform: translateZ(0); }
BestandPadRegistratieFuncties
db-optimize.phpfunctions/db-optimize.phpadmin_menu, after_switch_themeMeta index management, admin UI
db-addresses.phpfunctions/db-addresses.phpadmin_menu, after_switch_themeAdres tabel, CSV import, lookup functies
contact-admin-columns.phpfunctions/contact-admin-columns.phpmanage_contact_posts_columns, manage_contact_posts_custom_column, pre_get_postsContact kolommen, meta cache, sorteerlogica
admin.phpfunctions/admin.phplogin_head, admin_footer_text, admin_head, initLogin styling, admin bar, ACF options, menu registratie