These hooks let you add an import from an events plugin HappenBoard does not support. They also let you copy extra data during a The Events Calendar import and add your own settings to the JSON backup file. All five come with the free plugin.
The site owner’s side is in Migrating from another plugin. The backup file is covered in CSV import/export and settings backup. If you are new to HappenBoard hooks, start with Extending HappenBoard with hooks.
Imports and exports without hooks
The .ics file import, the events CSV import and export, and Pro’s attendee CSV import and orders CSV export have no hooks of their own. Who may download the orders CSV is a permission: Pro’s happenboard_export_orders capability, given to administrators by default. You can grant it to other roles with the filters on Admin, roles and capability hooks.
Write a migration adapter
A migration source is a PHP class that implements HappenBoard\Service\Migration\MigratorAdapter. You register it with happenboard_migration_adapters. It then appears under HappenBoard → Settings → Migration whenever its detect() method finds the source data.
| Method | Returns | What it is for |
|---|---|---|
id() | string | Stable ID: lowercase letters, digits and dashes. It is part of the REST route and of each imported post’s _happenboard_migrated_from meta. |
name(), description() | string | Title and one-line hint on the source row. |
logo_url() | string | Logo URL, or '' to show a letter instead. |
detect() | bool | Whether the source data is on this site. An undetected source is hidden from the screen and refused by the REST route. |
summary() | array | Counts shown before the import: events, venues, organizers, categories, attendees. A missing key counts as 0. |
migrate( array $options ) | Result | Imports one batch. |
The screen offers Preview (dry run), then Migrate for real. Each run calls migrate() again and again with dry_run, batch_size (50), offset and include (empty means everything). Fill a HappenBoard\Service\Migration\Result with count_created(), count_skipped(), count_failed() and add_error(), then set next_offset and done. The screen stops when done is true. When next_offset stops moving without done, it shows the run as stopped, with a Continue button that carries on from the last offset.
For a record that was imported but lost something on the way, call add_warning( $resource, $source_id, $reason ): it does not count as a failure and is listed under View N warnings.
The HappenBoard\Service\Migration\MigrationHelpers trait holds the helpers the built-in adapters use. find_migrated() and mark_migrated() let a second run skip what the first one imported (an imported post in the trash counts as not imported). local_to_utc_iso() turns a local date into the UTC format HappenBoard stores. For a source that keeps its events as posts, migrate() can look like this:
public function migrate( array $options ): \HappenBoard\Service\Migration\Result {
$result = new \HappenBoard\Service\Migration\Result();
$batch = max( 1, (int) ( $options['batch_size'] ?? 50 ) );
$offset = max( 0, (int) ( $options['offset'] ?? 0 ) );
$ids = get_posts( array(
'post_type' => 'mycal_event',
'post_status' => 'any',
'posts_per_page' => $batch,
'offset' => $offset,
'orderby' => 'ID',
'order' => 'ASC',
'fields' => 'ids',
) );
foreach ( $ids as $source_id ) {
if ( $this->find_migrated( 'my-calendar', 'hboard_event', $source_id ) > 0 ) {
$result->count_skipped( 'events' ); // Imported by an earlier run.
continue;
}
if ( ! empty( $options['dry_run'] ) ) {
$result->count_created( 'events' ); // Preview: count, write nothing.
continue;
}
$new_id = wp_insert_post( array(
'post_type' => 'hboard_event',
'post_status' => get_post_status( $source_id ),
'post_title' => get_the_title( $source_id ),
), true );
if ( is_wp_error( $new_id ) ) {
$result->count_failed( 'events' );
$result->add_error( 'events', $source_id, $new_id->get_error_message() );
continue;
}
$start = $this->local_to_utc_iso( (string) get_post_meta( $source_id, 'mycal_start', true ) );
update_post_meta( $new_id, '_happenboard_start', $start );
update_post_meta( $new_id, '_happenboard_end', $start );
// The dates did not exist when wp_insert_post() ran: build them now.
( new \HappenBoard\Service\Events\OccurrenceSyncer() )->sync( (int) $new_id );
$this->mark_migrated( (int) $new_id, 'my-calendar', $source_id );
$result->count_created( 'events' );
}
$result->next_offset = $offset + count( $ids );
$result->done = count( $ids ) < $batch;
return $result;
}
happenboard_migration_adapters
Filters the list of migration sources. Add your own adapter here to offer an import from a plugin HappenBoard does not support, or replace a built-in one.
Signature
apply_filters( 'happenboard_migration_adapters', array $adapters )
Parameters
-
$adaptersarray<int,MigratorAdapter> - The built-in adapters, in this order: The Events Calendar, Events Manager, Sugar Calendar, EventKoi and Amelia. A plain list with integer keys.
Return value
array
The list of adapters. Entries that do not implement HappenBoard\Service\Migration\MigratorAdapter are dropped without a warning.
Example
add_filter(
'happenboard_migration_adapters',
function ( array $adapters ): array {
// My_Calendar_Adapter implements
// \HappenBoard\Service\Migration\MigratorAdapter.
require_once __DIR__ . '/class-my-calendar-adapter.php';
$adapters[] = new My_Calendar_Adapter();
return $adapters;
}
);
Notes
- The list is keyed by
id()and the last adapter with a given ID wins. Return an adapter whose ID isthe-events-calendar,events-manager,sugar-calendar,eventkoiorameliaand it replaces that built-in one, in the admin and in its WP-CLI command. - A new source runs from the Migration screen and from
POST /happenboard/v1/migration/{id}(thehappenboard_manage_settingscapability, administrators by default). It gets no WP-CLI command:wp happenboard migrateonly knowstec,em,sugarandics. - The filter runs each time the sources are listed or a batch runs, often several times per request. Keep the adapter constructor light and do the database work in
summary()andmigrate(). - The Migration screen sends batches of 50 until
doneis true, with no fixed limit on the source size. As a guard against an adapter that never says it is done, it pauses after twice the requests theeventscount ofsummary()needs (200 at least) and offersContinue. Count your events insummary()so the progress bar and that guard fit your source.
happenboard_migration_before_events
Fires at the start of each event batch of a The Events Calendar import, before the first event of the batch is read. Use it to prepare what the per-event step needs, such as a field map.
Signature
do_action( 'happenboard_migration_before_events', string $source_id, bool $dry )
Parameters
-
$source_idstring - The adapter ID. Always
the-events-calendartoday, since no other source fires this action. Test it anyway. -
$drybool - True for a preview (the Preview (dry run) button, or
--dry-runon WP-CLI). Write nothing to the database when it is true.
Example
add_action(
'happenboard_migration_before_events',
function ( string $source_id, bool $dry ): void {
if ( 'the-events-calendar' !== $source_id ) {
return;
}
// Read what the batch will need.
$map = my_build_field_map();
// Save it on a real run only.
if ( ! $dry ) {
update_option( 'my_field_map', $map, false );
}
},
10,
2
);
Notes
- Two arguments: register with
10, 2. A typed callback registered with the default of 1 throws anArgumentCountErrorand stops the import. - Only the The Events Calendar import fires it. Events Manager, Sugar Calendar, EventKoi, Amelia and the .ics import do not.
- It fires once per batch (50 events from the screen, 100 from WP-CLI), on previews too, and even when the batch is empty. Keep it cheap and safe to run many times.
- It does not fire when the run’s
includeoption leaves outevents. Pro listens here to create custom field definitions from The Events Calendar’s additional fields.
happenboard_migration_event_imported
Fires after the The Events Calendar import has created one event, with its dates, venue, organizer and categories, and tagged it as imported. Use it to copy extra data from the source event and report it in the import results.
Signature
do_action( 'happenboard_migration_event_imported', int $new_id, int $source_id, string $adapter_id, Result $result )
Parameters
-
$new_idint - The new HappenBoard event (
hboard_eventpost ID). -
$source_idint - The source post ID, a The Events Calendar
tribe_eventspost. Its original meta is still there. -
$adapter_idstring - The adapter ID,
the-events-calendar. -
$resultResult - The
HappenBoard\Service\Migration\Resultof the current batch. What you add to it shows in the results on the Migration screen.
Example
add_action(
'happenboard_migration_event_imported',
function ( $new_id, $source_id, $adapter_id, $result ) {
if ( 'the-events-calendar' !== $adapter_id ) {
return;
}
$cost = (string) get_post_meta( $source_id, '_EventCost', true );
if ( '' === $cost ) {
return;
}
if ( ! is_numeric( $cost ) ) {
$result->add_error( 'prices', $source_id, 'Unreadable cost.' );
return;
}
update_post_meta( $new_id, '_my_price', $cost );
$result->count_created( 'prices' ); // Adds a "prices" row to the results.
},
10,
4
);
Notes
- Four arguments: register with
10, 4. The second one is the source post ID (an integer), the third the adapter ID (a string). - It never fires on a preview. It does not fire either for events skipped because an earlier run imported them, or for the dates of an old recurring series that The Events Calendar stored as child posts. An imported event moved to the trash is imported again.
- A resource name the import does not use, such as
prices, adds its own row to the results table, labelled with that name. Errors added withadd_error()appear in the error list, and warnings added withadd_warning()in the warning list. Each list shows the first 100. - Pro listens here to copy custom field values from The Events Calendar.
happenboard_settings_backup_export
Filters the settings backup that the Export settings (JSON) button on HappenBoard → Settings → Tools downloads. Add your own section so it travels to the site that imports the file.
Signature
apply_filters( 'happenboard_settings_backup_export', array $payload )
Parameters
-
$payloadarray<string,mixed> - The document HappenBoard builds:
format(alwayshappenboard.settings),version(1),plugin,site,exportedAtandgeneral(the HappenBoard settings).
Return value
array
The full document, with your section added under your own top-level key.
Example
add_filter(
'happenboard_settings_backup_export',
function ( array $payload ): array {
$settings = (array) get_option( 'my_plugin_settings', array() );
// The file gets downloaded and passed around: keep secrets out.
unset( $settings['api_secret'] );
$payload['my_plugin'] = $settings;
return $payload;
}
);
Notes
- Use one top-level key of your own and leave the others alone. A file whose
formatis nothappenboard.settingsis refused on import. - Return an array. Anything else turns the download into an empty document, with no error.
- HappenBoard leaves its own secrets out (Turnstile secret, Akismet key, Google Maps and weather API keys), and importing the file keeps the target site’s keys. Do the same with yours.
- Pro adds nothing to this file, so Pro settings such as Stripe keys are not in the backup. Everything you add must be JSON serialisable.
happenboard_settings_backup_import
Filters the list of sections restored from an uploaded settings backup. Read your section from the file, save it, and say whether you applied it.
Signature
apply_filters( 'happenboard_settings_backup_import', array $applied, array $payload )
Parameters
-
$appliedarray<string,bool> - Section name => true or false. It starts as
array( 'general' => false ), andgeneralbecomes true when the file held HappenBoard settings and they were saved. -
$payloadarray<string,mixed> - The whole decoded file, as the other site exported it. Your section may be missing.
Return value
array
The section map with your keys added. The REST route returns it as applied; the Tools screen only shows “Settings imported.”
Example
add_filter(
'happenboard_settings_backup_import',
function ( array $applied, array $payload ): array {
// The file may come from a site without your plugin.
if ( empty( $payload['my_plugin'] ) || ! is_array( $payload['my_plugin'] ) ) {
$applied['my_plugin'] = false;
return $applied;
}
// Patch: keys missing from the file keep their value.
$current = (array) get_option( 'my_plugin_settings', array() );
$clean = map_deep( $payload['my_plugin'], 'sanitize_text_field' );
update_option( 'my_plugin_settings', array_merge( $current, $clean ) );
$applied['my_plugin'] = true;
return $applied;
},
10,
2
);
Notes
- Two arguments: register with
10, 2, or you cannot read the file. - It runs on every valid file, including files without your section. Check that your key exists and is an array before you save, or you overwrite live settings with nothing.
- Only
formatandversionare checked before it runs. Treat your section as untrusted input and sanitize it. HappenBoard merges its owngeneralsection into the current settings, so missing keys keep their value. - Return an array. Anything else is replaced with
array( 'general' => true ), which reports the general settings as restored even when they were not. Exporting and importing need thehappenboard_manage_settingscapability (administrators by default).
Next steps
- Migrating from another plugin: what the site owner sees on the Migration screen.
- Events, recurrence and series hooks: the hooks that run when imported events and their dates are saved.