Block, schema and rendering hooks

These hooks sit on the output side: the four blocks that run an occurrence query, the venue map, the weather panel, the term color swatches, and the Event JSON-LD printed in wp_head.

Most of them run on every front-end render, so keep callbacks cheap and avoid extra queries inside them. Where HappenBoard Pro already listens, the note on the hook says so and gives its priority.

happenboard_favorite_frontend_global_printed

Action Free Since 1.0 src/Block/FavoriteToggle/Block.php

Fires in wp_footer right after the Favorite Toggle block prints the inline script that defines window.happenBoardFrontend.isLoggedIn. Hook it to print further inline JavaScript that depends on that global, exactly once per page.

Signature

do_action( 'happenboard_favorite_frontend_global_printed' )

Example

add_action(
    'happenboard_favorite_frontend_global_printed',
    function () {
        wp_print_inline_script_tag(
            'window.happenBoardFrontend.myLocale = '
            . wp_json_encode( get_locale() ) . ';'
        );
    }
);

Notes

  • No arguments. A plain add_action( 'happenboard_favorite_frontend_global_printed', $cb ) is enough.
  • It fires on wp_footer at priority 5, and only once per request: the printing callback is guarded by did_action(), so additional Favorite Toggle blocks on the same page print nothing and fire nothing.
  • It only fires when at least one Favorite Toggle block actually renders, and the block itself bails unless the resolved post is an hboard_event. Pages without the block never reach it.
  • By the time it fires, wp_enqueue_script() is only useful for footer scripts. Print inline JavaScript with wp_print_inline_script_tag() instead.
  • Firing the action yourself earlier in the request suppresses the built-in output: the guard is ! did_action( 'happenboard_favorite_frontend_global_printed' ), so window.happenBoardFrontend.isLoggedIn is never defined and the toggle’s view script loses its auth state.

happenboard_query_attributes

Filter Free Since 1.0 src/Block/EventQueryLoop/Block.php

Rewrites the attributes of the four blocks that run an occurrence query, at the very top of their render callback and before any value is read. $context tells you which block is asking, so a single callback can serve all four.

Signature

apply_filters( 'happenboard_query_attributes', array<string,mixed> $attributes, string $context )

Parameters

$attributes array<string,mixed>
Block attributes as saved in the editor, plus whatever earlier callbacks changed. Keys left out fall back to per-block defaults after the filter.
$context string
One of event-query-loop, events-grid, events-list, upcoming-events.

Return value

array<string,mixed> The attribute array the block will read. Cast with (array): a non-array return becomes an empty array and every attribute falls back to its per-block default.

Example

add_filter(
    'happenboard_query_attributes',
    function ( $attributes, $context ) {
        if ( 'event-query-loop' !== $context ) {
            return $attributes;
        }

        // Members-only events (term 12) stay hidden from logged-out visitors.
        if ( ! is_user_logged_in() ) {
            $attributes['categoryIds'] = array( 12 );
        }

        return $attributes;
    },
    10,
    2
);

Notes

  • Two arguments. Without add_filter( ..., 10, 2 ) you cannot tell the four blocks apart.
  • The four call sites are Block\EventQueryLoop, Block\EventsGrid, Block\EventsList and Block\UpcomingEvents. No other block or admin screen uses this seam.
  • The blocks do not share a vocabulary. dateRange, organizerId, layout and order are read only by event-query-loop; events-list and events-grid read categoryIds, tagIds, venueId and count but ignore organizerId; upcoming-events reads only count. A key the block does not read is silently ignored.
  • Values are clamped after the filter, so you cannot raise a limit here: count is capped at 50 for event-query-loop, events-list and upcoming-events, and at 24 for events-grid.
  • Pro registers Service\Filtering\QueryArgsResolver::filter_attributes at priority 10 with 2 args to project the FilterBar’s URL parameters. Its class docblock lists ?hboard_venue= and ?hboard_organizer=, but the code reads happenboard_venue and happenboard_organizer because every parameter goes through the happenboard_ prefix constant. It also parses happenboard_from, happenboard_to and happenboard_q and then never projects them onto any attribute, and it early-returns for upcoming-events.

happenboard_schema_disable

Filter Free Since 1.0 src/Service/Schema/EventSchemaPrinter.php

Suppresses HappenBoard’s Event JSON-LD for a given post. It is the first thing EventSchemaPrinter::maybe_print() evaluates, before any part of the schema is built, so returning true costs nothing.

Signature

apply_filters( 'happenboard_schema_disable', bool $disabled, int $post_id )

Parameters

$disabled bool
Default false.
$post_id int
The queried single event, from get_queried_object_id().

Return value

bool Truthy to skip the output entirely. The value is cast with (bool).

Example

add_filter(
    'happenboard_schema_disable',
    function ( $disabled, $post_id ) {
        return $disabled || (bool) get_post_meta( $post_id, '_my_no_event_schema', true );
    },
    10,
    2
);

Notes

  • Two arguments. Register with add_filter( ..., 10, 2 ) to receive $post_id, otherwise you can only switch the whole site off.
  • It runs from a wp_head callback at priority 30 and only on is_singular( 'hboard_event' ) views. It has no effect on schema emitted anywhere else, including Pro’s SEOPress schema-editor integration in wp-admin, which calls the builder directly.
  • Returning true short-circuits the rest of the chain: happenboard_schema_event_type, happenboard_schema_offers, happenboard_schema_fallback_currency and happenboard_schema_event never fire.
  • You do not need it for SEOPress. When the post carries a manually configured Event schema in the _seopress_pro_schemas meta and SEOPress is active, the printer already steps aside on its own.
  • The value is cast with (bool). Returning the string 'false' disables the schema, because a non-empty string is truthy.

happenboard_schema_fallback_currency

Filter Free Since 1.0 src/Service/Schema/EventSchemaBuilder.php

Sets the currency of the placeholder free Offer that EventSchemaBuilder emits when an event ends up with no offers at all. It has no effect on real offers.

Signature

apply_filters( 'happenboard_schema_fallback_currency', string $currency )

Parameters

$currency string
Default 'USD'.

Return value

string A three-letter ISO 4217 code. The builder applies strtoupper() to it, so lowercase input is fine.

Example

add_filter(
    'happenboard_schema_fallback_currency',
    function ( $currency ) {
        return 'EUR';
    }
);

Notes

  • One argument.
  • It fires only when happenboard_schema_offers returned an empty array. As soon as one offer exists, the placeholder is never built and this filter never runs.
  • The placeholder is a single Offer with price '0' and availability InStock, pointing at the event permalink, alongside isAccessibleForFree set to true. Changing the currency does not make the event look paid.
  • The value is cast to string and uppercased but never validated. An invalid code goes straight into the JSON-LD and Google flags the offer.
  • It runs inside EventSchemaBuilder, which Pro’s SEOPress integration also calls in wp-admin to pre-fill the schema editor. Keep the callback free of front-end-only conditions such as is_singular().

happenboard_schema_offers

Filter Free Since 1.0 src/Service/Schema/EventSchemaBuilder.php

Supplies the offers array of the Event JSON-LD. Free passes an empty array, so this is the seam a ticketing integration uses to emit one schema.org Offer per ticket type.

Signature

apply_filters( 'happenboard_schema_offers', array<int,array<string,mixed>> $offers, int $event_id, string $url )

Parameters

$offers array<int,array<string,mixed>>
Always array() when the filter first runs in Free.
$event_id int
The event post ID.
$url string
Canonical event permalink, ready to use as each Offer’s url.

Return value

array<int,array<string,mixed>> A list of Offer arrays. The result goes through is_array(), so a non-array return is replaced by an empty array without any warning.

Example

add_filter(
    'happenboard_schema_offers',
    function ( $offers, $event_id, $url ) {
        $price = (string) get_post_meta( $event_id, '_my_ticket_price', true );
        if ( '' === $price ) {
            return $offers;
        }

        $offers[] = array(
            '@type'         => 'Offer',
            'url'           => $url,
            'price'         => $price,
            'priceCurrency' => 'EUR',
            'availability'  => 'https://schema.org/InStock',
        );

        return $offers;
    },
    10,
    3
);

Notes

  • Three arguments. Use add_filter( ..., 10, 3 ) to receive $url.
  • Return an empty array and the builder substitutes a free placeholder Offer priced at '0', in the currency from happenboard_schema_fallback_currency. There is no way to emit an Event with no offers key.
  • isAccessibleForFree is computed from what you return, not from the placeholder: it stays true when every entry has a price of 0 or less, and when you return nothing. Set a numeric price on each offer or the event is advertised as free.
  • Entry shape is not validated. A missing @type, priceCurrency or availability is emitted as is and Google rejects the offer.
  • The docblock on EventSchemaBuilder::offers() states that Pro hooks this filter to inject one Offer per ticket type. No callback in the current Pro tree does, so the hook is unclaimed and priority 10 is free.

happenboard_term_color_presets

Filter Free Since 1.0 src/Taxonomy/ColorPresets.php

Changes the swatch palette offered when picking a color for a HappenBoard category, tag or calendar term. The result is localised as happenBoardVars.colorPresets for the admin SPA and the block editor sidebar.

Signature

apply_filters( 'happenboard_term_color_presets', array<int,array{name:string,color:string}> $presets )

Parameters

$presets array<int,array{name:string,color:string}>
Ten default swatches, each an array with name (translated label) and color (lowercase #RRGGBB).

Return value

array<int,array{name:string,color:string}> The palette. Every entry is re-validated afterwards, so malformed swatches disappear rather than breaking the picker.

Example

add_filter(
    'happenboard_term_color_presets',
    function ( $presets ) {
        $presets[] = array(
            'name'  => 'Brand',
            'color' => '#1a7f5a',
        );

        return $presets;
    }
);

Notes

  • One argument.
  • Only #RRGGBB survives. After the filter, each entry is dropped unless strtolower( $preset['color'] ) matches /^#[0-9a-f]{6}$/, so three-digit shorthand, rgb(), hsl(), named colors and 8-digit hex with alpha all vanish silently.
  • name is optional and falls back to the hex string. An entry with no color key, or one that is not an array, is dropped.
  • It is consumed on two admin surfaces only: the HappenBoard admin SPA bootstrap (Admin\ScreenContext) and the block editor sidebar bundle (Admin\PostEditor). It changes the picker, not the colors already saved on terms, and has no effect on front-end rendering.
  • A second, client-side filter runs after it: the JS layer applies happenboard.term.colorPresets through @wordpress/hooks in assets/js/shared/colorPresets.js. A palette you set in PHP can still be changed there.

happenboard_venue_map_html

Filter Free Since 1.0 src/Block/EventVenue/Block.php

Replaces the entire map markup rendered by the Event Venue block. The returned string is inserted into the block output verbatim, with no escaping applied afterwards, so you own the escaping.

Signature

apply_filters( 'happenboard_venue_map_html', string $html, string $provider, array $context )

Parameters

$html string
Default markup: a <dd> wrapper containing a lazy-loaded <iframe>, already escaped.
$provider string
Resolved provider: osm, google or custom.
$context array
lat (float), lng (float), zoom (int), height (int), name (string) and settings (the full General settings array).

Return value

string The markup to output. Cast with (string).

Example

add_filter(
    'happenboard_venue_map_html',
    function ( $html, $provider, $context ) {
        return sprintf(
            '<dd class="my-map" data-lat="%s" data-lng="%s" data-zoom="%d" style="height:%dpx"></dd>',
            esc_attr( (string) $context['lat'] ),
            esc_attr( (string) $context['lng'] ),
            (int) $context['zoom'],
            (int) $context['height']
        );
    },
    20,
    3
);

Notes

  • Three arguments. Use add_filter( ..., 10, 3 ).
  • $context['settings'] is GeneralRepository::all(), which includes the raw server-only secrets: google_maps_api_key, weather_api_key, turnstile_secret_key and akismet_api_key. Never echo that array into markup or into a JavaScript payload.
  • It only runs when the block has showMap enabled, the event format is not virtual, and the resolved venue has both a latitude and a longitude. No coordinates means no map and no filter.
  • Pro’s Service\Maps\Renderer::filter_map_html is already registered at priority 10 with 3 args. When the Map Design Studio is active, a callback at a higher priority receives Pro’s MapLibre <div> rather than the iframe, while $provider still names the Free provider.
  • custom reaches $provider only when a Venue block explicitly sets its mapProvider attribute to custom and custom_iframe_url is configured. It is unreachable through the site default: the settings sanitizer coerces maps_provider to osm for anything other than osm or google.

happenboard_venue_map_iframe_src

Filter Free Since 1.0 src/Block/EventVenue/Block.php

Swaps the URL loaded in the Event Venue block’s map iframe without touching the wrapper markup. It runs just before the iframe is assembled, so the height, lazy loading, title and referrer policy of the default output are kept.

Signature

apply_filters( 'happenboard_venue_map_iframe_src', string $src, string $provider, array $context )

Parameters

$src string
Provider-built URL: the OpenStreetMap embed with a computed bounding box, the Google Maps Embed API URL, or the custom_iframe_url template with {lat}, {lng} and {zoom} already expanded.
$provider string
Resolved provider: osm, google or custom.
$context array
lat (float), lng (float), zoom (int) and name (string). Nothing else.

Return value

string The iframe URL. It is passed through esc_url() before output.

Example

add_filter(
    'happenboard_venue_map_iframe_src',
    function ( $src, $provider, $context ) {
        return add_query_arg(
            array(
                'lat'  => $context['lat'],
                'lng'  => $context['lng'],
                'zoom' => $context['zoom'],
            ),
            'https://maps.example.com/embed'
        );
    },
    10,
    3
);

Notes

  • Three arguments. Use add_filter( ..., 10, 3 ). The method docblock above the call announces a filter named happenboard/venue/map/iframe_src, with slashes: that name does not exist, the hook is happenboard_venue_map_iframe_src.
  • It runs before happenboard_venue_map_html. A callback that replaces the whole markup on that later filter makes whatever you return here irrelevant, which is exactly what Pro’s Map Design Studio does.
  • esc_url() is applied after the filter. data: and javascript: URLs are stripped and the iframe ends up with an empty src.
  • $context here is the short form. There is no height and no settings key: those exist only on happenboard_venue_map_html.
  • A filter keyed on 'custom' === $provider may never fire, because the block falls back to osm when custom_iframe_url is empty, and also when google is requested without google_maps_api_key. Handle osm too, or key on the coordinates instead.

happenboard_weather_data

Filter Free Since 1.0 src/Service/Weather/WeatherRepository.php

Adjusts the normalised weather payload WeatherRepository::get() hands back to the Event Weather block, after the cache lookup and any Open-Meteo call. Add fields or correct values without triggering another fetch.

Signature

apply_filters( 'happenboard_weather_data', array<string,mixed> $result, array{lat:float,lng:float,date:string,regime:string} $ctx )

Parameters

$result array<string,mixed>
Normalised day data merged with regime (historical, forecast or climatology), date and unit_labels. The block reads weather_code, is_day, temp, temp_max and unit_labels.
$ctx array{lat:float,lng:float,date:string,regime:string}
Request context: lat, lng, date (Y-m-d in the site timezone) and regime.

Return value

array<string,mixed> The payload the renderer consumes. Cast with (array): a scalar return becomes a single-element array and the block then renders with regime and every other key missing.

Example

add_filter(
    'happenboard_weather_data',
    function ( $result, $ctx ) {
        // Long-range climatology has no meaningful daily high.
        if ( 'climatology' === $ctx['regime'] ) {
            $result['temp_max'] = null;
        }

        return $result;
    },
    10,
    2
);

Notes

  • Two arguments. Use add_filter( ..., 10, 2 ).
  • Weather is off by default. Nothing fires until weather_enabled is switched on in the General settings, and get() also returns before the filter on out-of-range coordinates or a date that is not Y-m-d.
  • It runs after the cache, not before it. The upstream response is cached under a key built from regime, coordinates rounded to two decimals, date and units, so your changes are re-applied on every request and never stored.
  • regime drives both the block’s CSS class (is-regime-*) and the label prefix. Removing or renaming it breaks the rendered output.
  • The source docblock says Pro adds fields here, such as alerts and an hourly strip. No callback in the current Pro tree listens on this filter, so priority 10 is free.