Events, recurrence and series hooks

HappenBoard stores one post per event and expands recurrence into occurrences at query time. The hooks on this page sit on that pipeline: what an event description may render, what happens when a visitor submits an event, what is pushed to Google Calendar and Microsoft Graph, and what the Event JSON-LD contains.

The last three filters control the series URL, the secondary /events/{slug}/all-dates/ permalink that lists every date of a recurring event. Two of them run inside an init callback at priority 25, so register them early and flush the rewrite rules after any change.

happenboard_event_description_allowed_html

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

Changes the KSES allow-list applied to the Event Description block. The block runs the_content first, then passes the result through wp_kses() with this list, so any tag or attribute missing from it is stripped before output.

Signature

apply_filters( 'happenboard_event_description_allowed_html', array<string,array<string,bool>> $allowed )

Parameters

$allowed array<string,array<string,bool>>
KSES definition, tag name to allowed attributes. Built from wp_kses_allowed_html( 'post' ) plus the iframe, video, audio, source and track entries the block adds back.

Return value

array<string,array<string,bool>> The allow-list handed to wp_kses(). The return value is cast with (array).

Example

add_filter(
    'happenboard_event_description_allowed_html',
    function ( $allowed ) {
        $allowed['details'] = array(
            'open'  => true,
            'class' => true,
        );
        $allowed['summary'] = array( 'class' => true );

        return $allowed;
    }
);

Notes

  • One argument only. A plain add_filter() with the default accepted-args count is enough.
  • The block re-adds iframe, video, audio, source and track on top of wp_kses_allowed_html( 'post' ) so oEmbed iframes and self-hosted media survive. Unsetting those keys breaks embeds inside event descriptions.
  • The filter runs after the_content, so it cannot bring back markup another plugin already removed. It can only remove more.
  • The return value is cast with (array). Returning null or false produces an empty allow-list: wp_kses() then strips every tag and only the plain text is rendered.
  • Scope is the Event Description block alone. Content rendered by core blocks or by the theme’s own the_content call is untouched.

happenboard_event_submitted

Action Pro Since 1.0 src/REST/EventSubmissions.php

Fires at the end of POST /happenboard/v1/pro/events/submit, once the visitor’s event has been inserted as a pending hboard_event and every meta field written. Use it to notify a channel, tag the submission, or push it to an external moderation queue.

Signature

do_action( 'happenboard_event_submitted', int $post_id, array<string,string> $data )

Parameters

$post_id int
ID of the newly created event. Its post_status is always pending.
$data array<string,string>
Exactly three keys: submitter_name, submitter_email, title.

Example

add_action(
    'happenboard_event_submitted',
    function ( $post_id, $data ) {
        update_post_meta(
            $post_id,
            '_my_submitter_ref',
            sanitize_email( $data['submitter_email'] )
        );
        wp_set_object_terms( $post_id, 'needs-review', 'happenboard_tag', true );
    },
    10,
    2
);

Notes

  • Two arguments. Register with add_action( 'happenboard_event_submitted', $cb, 10, 2 ) or $data never reaches your callback.
  • Pro already listens: Service\Submissions\SubmissionNotifier::on_submitted is hooked at priority 10 and emails the moderator. Your callback runs alongside it, not instead of it.
  • $data carries only the submitter name, the submitter e-mail and the title. Dates, venue, format and category are already saved as _happenboard_* post meta when the action fires, so read them from the post.
  • The endpoint self-gates on the submissions_enabled setting and on the full anti-abuse stack (REST nonce, per-IP and per-e-mail throttle, honeypot, signed form timestamp, optional Turnstile and Akismet). The action never fires for a rejected submission.
  • This is the only call site. Events created in wp-admin, imported through a migration adapter, or inserted via the REST CRUD routes do not trigger it.

happenboard_pro_gcal_event_payload

Filter Pro Since 1.0 src/Service/Calendar/Push/EventMapper.php

Last chance to change the Google Calendar Event resource before GooglePusher sends it to events.insert or events.patch. Add attendees, reminder overrides or extra fields, or rewrite anything HappenBoard mapped.

Signature

apply_filters( 'happenboard_pro_gcal_event_payload', array<string,mixed> $payload, WP_Post $event )

Parameters

$payload array<string,mixed>
Google Event resource. Always contains summary, description, location, status, extendedProperties, start and end. recurrence appears only when the event has an RRULE.
$event WP_Post
The source hboard_event post. Meta is not attached to the object, read it with get_post_meta().

Return value

array<string,mixed> The payload sent to Google. Cast with (array), so a non-array return becomes an empty payload and the API call fails.

Example

add_filter(
    'happenboard_pro_gcal_event_payload',
    function ( $payload, $event ) {
        $payload['reminders'] = array(
            'useDefault' => false,
            'overrides'  => array(
                array(
                    'method'  => 'popup',
                    'minutes' => 60,
                ),
            ),
        );

        $organizer_email = (string) get_post_meta( $event->ID, '_happenboard_organizer_email', true );
        if ( is_email( $organizer_email ) ) {
            $payload['attendees'] = array(
                array(
                    'email'          => $organizer_email,
                    'responseStatus' => 'accepted',
                ),
            );
        }

        return $payload;
    },
    10,
    2
);

Notes

  • Two arguments. Use add_filter( ..., 10, 2 ) to receive $event.
  • Do not drop extendedProperties.private.happenboard_origin. GooglePuller uses that marker to recognise events HappenBoard itself pushed; without it the next delta sync treats the event as foreign and can re-import it as a duplicate.
  • The same payload feeds both the insert and the patch path, and the patch path re-sends the whole resource. A key you remove is removed upstream too.
  • Everything you add is sent verbatim. An invalid field or date block comes back as an API error, which the pusher stores in last_error and the event stays unsynced.
  • The source docblock suggests conferenceData for Meet auto-creation, but GoogleClient::insert_event() and patch_event() send no conferenceDataVersion query parameter, which Google requires before it acts on a conference create request. Adding that block here has no effect today.

happenboard_pro_outlook_event_payload

Filter Pro Since 1.0 src/Service/Calendar/Push/OutlookEventMapper.php

Filters the Microsoft Graph Event resource before OutlookPusher sends it. Same role as happenboard_pro_gcal_event_payload on the Google side: inject attendees, wrap the body HTML, add an online meeting block.

Signature

apply_filters( 'happenboard_pro_outlook_event_payload', array<string,mixed> $payload, WP_Post $event )

Parameters

$payload array<string,mixed>
Graph Event resource. Always contains subject, body, location, isAllDay, isCancelled, showAs, singleValueExtendedProperties, start and end. recurrence only when an RRULE maps cleanly.
$event WP_Post
The source hboard_event post.

Return value

array<string,mixed> The payload sent to Graph. Cast with (array).

Example

add_filter(
    'happenboard_pro_outlook_event_payload',
    function ( $payload, $event ) {
        $payload['categories']  = array( 'HappenBoard' );
        $payload['sensitivity'] = 'normal';

        $payload['body']['content'] = '<p>' . esc_html( get_the_title( $event ) ) . '</p>'
            . $payload['body']['content'];

        return $payload;
    },
    10,
    2
);

Notes

  • Two arguments. Use add_filter( ..., 10, 2 ).
  • Keep the singleValueExtendedProperties entry whose id ends in happenboard_origin. OutlookPuller scans that list for it to recognise its own pushes; drop it and the event can be re-imported on the next sync.
  • All-day events follow strict Graph rules the mapper already satisfies: start.dateTime at local midnight, end.dateTime at local midnight of the following day, identical timeZone on both. Rewriting start or end by hand usually breaks that contract.
  • body.content is HTML and already ends with a “Synced from HappenBoard” footer plus the permalink, truncated to 16384 characters. Append after the footer or rebuild the string.
  • isCancelled is mapped from the _happenboard_event_status meta and performs a soft archive. Hard deletions go through OutlookClient::delete_event() and never reach this filter.

happenboard_schema_event

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

Last filter in the Event JSON-LD pipeline. It receives the fully built payload just before EventSchemaPrinter encodes it and prints the <script type="application/ld+json"> tag. Use it to add properties HappenBoard does not emit, or to overwrite what the builder produced.

Signature

apply_filters( 'happenboard_schema_event', array<string,mixed> $schema, int $event_id, \WP_Post $post )

Parameters

$schema array<string,mixed>
Complete payload. Always has @context, @type, name, description, startDate, endDate, eventStatus, inLanguage, url, eventAttendanceMode, offers and isAccessibleForFree. image, location, organizer, performer and maximumAttendeeCapacity appear only when the data exists.
$event_id int
The event post ID.
$post \WP_Post
The same post, already fetched by the builder.

Return value

array<string,mixed> The array that gets JSON-encoded. Returning null suppresses the whole JSON-LD block.

Example

add_filter(
    'happenboard_schema_event',
    function ( $schema, $event_id, $post ) {
        $schema['typicalAgeRange'] = (string) get_post_meta( $event_id, '_my_age_range', true );
        $schema['keywords']        = wp_get_post_terms(
            $post->ID,
            'happenboard_tag',
            array( 'fields' => 'names' )
        );

        return $schema;
    },
    10,
    3
);

Notes

  • Three arguments. Use add_filter( ..., 10, 3 ) to receive $post.
  • It fires last. happenboard_schema_event_type, happenboard_schema_offers and, when there are no offers, happenboard_schema_fallback_currency have already run, so $schema reflects their results.
  • EventSchemaBuilder::build() is typed : ?array. Returning a string, an object or a number raises a TypeError and fatals the request.
  • It does not fire at all when the output is skipped upstream: happenboard_schema_disable returning true, or SEOPress already holding a manually configured Event schema for the post in the _seopress_pro_schemas meta. In both cases the printer returns before the builder ever runs.
  • The builder also runs in wp-admin. Pro’s SEOPress integration calls it from seopress_schemas_default_blocks_event to pre-fill SEOPress’s schema editor, so your filter shapes what an editor sees there too. Note that this is a direct builder call, not a listener on this filter: nothing shipped hooks it, priority 10 is free.

happenboard_schema_event_type

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

Sets the @type of the Event JSON-LD. Return a schema.org Event subtype such as MusicEvent, BusinessEvent, Festival or SportsEvent so search engines classify the event more precisely than the generic Event.

Signature

apply_filters( 'happenboard_schema_event_type', string $type, int $event_id, \WP_Post $post )

Parameters

$type string
Default 'Event'.
$event_id int
The event post ID.
$post \WP_Post
The event post object.

Return value

string The value written straight into $schema['@type']. No cast and no validation are applied.

Example

add_filter(
    'happenboard_schema_event_type',
    function ( $type, $event_id, $post ) {
        if ( has_term( 'concerts', 'happenboard_category', $post ) ) {
            return 'MusicEvent';
        }

        return $type;
    },
    10,
    3
);

Notes

  • Three arguments, none of them documented. The call passes 'Event', $event_id and $post but carries no docblock, so tooling that reads docblocks reports zero parameters. Use add_filter( ..., 10, 3 ).
  • First hook in the schema chain. It runs at the top of EventSchemaBuilder::build(), before offers, and happenboard_schema_event can still overwrite @type afterwards.
  • The return value is not cast. Returning an array is emitted as a JSON array, which schema.org accepts for multi-typing, but an object or a non-serialisable value corrupts the JSON-LD.
  • Only Event subtypes make sense here. Returning an unrelated type keeps the Event properties (startDate, offers, eventAttendanceMode) in the payload and Google reports invalid structured data.
  • Like every builder filter, it is skipped when happenboard_schema_disable returns true or when SEOPress owns the Event schema for the post.

happenboard_series_rewrite_pattern

Filter Free Since 1.0 src/FSE/SeriesRewrite.php

Replaces the regex behind the series rewrite rule, the /events/{slug}/all-dates/ URL that renders every date of a recurring event. Use it when the event permastruct is deeper than a single slug segment.

Signature

apply_filters( 'happenboard_series_rewrite_pattern', string $pattern )

Parameters

$pattern string
Default '^' . preg_quote( $prefix, '/' ) . '/([^/]+)/all-dates/?$', where $prefix comes from happenboard_series_rewrite_prefix.

Return value

string The regex passed as the first argument of add_rewrite_rule(). It must capture the event slug in group 1, because the rewrite target is index.php?post_type=hboard_event&name=$matches[1]&happenboard_view=series.

Example

add_filter(
    'happenboard_series_rewrite_pattern',
    function ( $pattern ) {
        // Event permalinks live under /agenda/{category}/{slug}/.
        return '^agenda/[^/]+/([^/]+)/all-dates/?$';
    }
);

Notes

  • One argument, and the docblock contradicts the code: it describes the pattern as anchor-free, while the default it hands you starts with ^. Keep the anchor unless you know exactly what you are replacing it with.
  • Group 1 must be the event slug. The rewrite target hard-codes name=$matches[1], so an extra capture group placed before it shifts the slug and the rule resolves to a 404.
  • The filter runs inside an init callback at priority 25. Register it earlier: from a plugin main file, or from an init hook at a priority below 25.
  • Rewrite rules are cached in the rewrite_rules option. A new pattern has no effect until the rules are flushed, for example by re-saving Settings, Permalinks.
  • Changing the pattern does not change the URL happenboard_series_url hands out. Adjust both, or the advertised link stops resolving.

happenboard_series_rewrite_prefix

Filter Free Since 1.0 src/FSE/SeriesRewrite.php

Sets the first path segment of the series rewrite rule. The default 'events' matches the stock hboard_event permastruct; change it when the post type rewrite slug has been customised.

Signature

apply_filters( 'happenboard_series_rewrite_prefix', string $prefix )

Parameters

$prefix string
Default 'events'. It goes through preg_quote(), so it is treated as a literal path segment.

Return value

string The literal slug prefix. It is only used to build the default pattern.

Example

add_filter(
    'happenboard_series_rewrite_prefix',
    function ( $prefix ) {
        return 'agenda';
    }
);

Notes

  • One argument, and no docblock in the source, so the extracted signature listing zero parameters is wrong.
  • It runs immediately before happenboard_series_rewrite_pattern and feeds it. A pattern callback that ignores the value it is given makes this filter irrelevant.
  • Same constraints as the pattern filter: register it before init priority 25, and flush the rewrite rules afterwards or nothing changes.
  • It moves the rewrite rule only. It does not change the post type’s own permalink structure, so set it to the hboard_event rewrite slug that is already in use, not to a new value.

happenboard_series_url

Filter Free Since 1.0 src/FSE/SeriesRewrite.php

Resolves the series URL of an event, the /all-dates/ page listing every date of a recurring event. HappenBoard never applies this filter itself: it registers a callback on it and expects themes and plugins to call apply_filters( 'happenboard_series_url', '', $post ) when they need the link. Hook it to override what that built-in callback returns.

Signature

apply_filters( 'happenboard_series_url', string $url, WP_Post|int $post )

Parameters

$url string
Incoming value, returned unchanged on a mismatch. Callers normally pass an empty string.
$post WP_Post|int
The event to resolve. A post ID is accepted: the built-in callback runs get_post() on it.

Return value

string The series URL, built as trailingslashit( $permalink ) . 'all-dates/'. The built-in callback returns $url untouched when the post it was registered for is not the one being asked about.

Example

add_filter(
    'happenboard_series_url',
    function ( $url, $post ) {
        $post = $post instanceof WP_Post ? $post : get_post( (int) $post );
        if ( null === $post || 'hboard_event' !== $post->post_type ) {
            return $url;
        }

        return trailingslashit( get_permalink( $post ) ) . 'toutes-les-dates/';
    },
    20,
    2
);

Notes

  • Two arguments. Register with add_filter( ..., 10, 2 ) at minimum, and use a priority above 10 to run after HappenBoard’s own callback.
  • The docblock in SeriesRewrite::expose_series_link() shows apply_filters( 'happenboard_series_url', $post ), which contradicts the code: the registered callback takes ( $url, $for_post ) and is registered with 2 accepted args. Passing the post as the filtered value simply gives it back to you unchanged.
  • The built-in callback exists only as a side effect of post_type_link. It is registered while WordPress builds a permalink, so get_permalink( $event ), or anything that calls it such as a rendered event list, must have run for that post earlier in the request. Otherwise the filter has no callback at all and returns whatever you passed in.
  • expose_series_link() does not check the post type, so a closure is registered for every permalink WordPress builds, of any post type. Asking for the series URL of a page returns that page’s URL with all-dates/ appended, even though no rewrite rule matches it. Check get_post_type() yourself before trusting the result.
  • The URL it returns assumes the default rewrite pattern. If you changed happenboard_series_rewrite_prefix or happenboard_series_rewrite_pattern, this filter is where you correct the advertised link.