Migration, import and export hooks

Available in:Free

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.

MethodReturnsWhat it is for
id()stringStable 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()stringTitle and one-line hint on the source row.
logo_url()stringLogo URL, or '' to show a letter instead.
detect()boolWhether the source data is on this site. An undetected source is hidden from the screen and refused by the REST route.
summary()arrayCounts shown before the import: events, venues, organizers, categories, attendees. A missing key counts as 0.
migrate( array $options )ResultImports 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:

Code
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

Filter Free src/Service/Migration/Registry.php

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

$adapters array<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 is the-events-calendar, events-manager, sugar-calendar, eventkoi or amelia and 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} (the happenboard_manage_settings capability, administrators by default). It gets no WP-CLI command: wp happenboard migrate only knows tec, em, sugar and ics.
  • 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() and migrate().
  • The Migration screen sends batches of 50 until done is 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 the events count of summary() needs (200 at least) and offers Continue. Count your events in summary() so the progress bar and that guard fit your source.

happenboard_migration_before_events

Action Free src/Service/Migration/Sources/TheEventsCalendar.php

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_id string
The adapter ID. Always the-events-calendar today, since no other source fires this action. Test it anyway.
$dry bool
True for a preview (the Preview (dry run) button, or --dry-run on 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 an ArgumentCountError and 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 include option leaves out events. Pro listens here to create custom field definitions from The Events Calendar’s additional fields.

happenboard_migration_event_imported

Action Free src/Service/Migration/Sources/TheEventsCalendar.php

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_id int
The new HappenBoard event (hboard_event post ID).
$source_id int
The source post ID, a The Events Calendar tribe_events post. Its original meta is still there.
$adapter_id string
The adapter ID, the-events-calendar.
$result Result
The HappenBoard\Service\Migration\Result of 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 with add_error() appear in the error list, and warnings added with add_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

Filter Free src/Service/IO/SettingsBackup.php

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

$payload array<string,mixed>
The document HappenBoard builds: format (always happenboard.settings), version (1), plugin, site, exportedAt and general (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 format is not happenboard.settings is 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

Filter Free src/Service/IO/SettingsBackup.php

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

$applied array<string,bool>
Section name => true or false. It starts as array( 'general' => false ), and general becomes true when the file held HappenBoard settings and they were saved.
$payload array<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 format and version are checked before it runs. Treat your section as untrusted input and sanitize it. HappenBoard merges its own general section 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 the happenboard_manage_settings capability (administrators by default).

Next steps