Created
July 31, 2025 17:54
-
-
Save bhubbard/733482fee8fc02c9c80f2ef6b3250be1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Plugin Name: Bloomerang Data Exporter | |
| * Description: Exports data from the Bloomerang API and saves it as JSON files in the uploads directory. | |
| * Version: 1.0.0 | |
| * Author: Gemini | |
| * License: GPL-2.0-or-later | |
| * License URI: https://www.gnu.org/licenses/gpl-2.0.html | |
| * Text Domain: bloomerang-data-exporter | |
| */ | |
| // Prevent direct file access | |
| if ( ! defined( 'ABSPATH' ) ) { | |
| exit; | |
| } | |
| /** | |
| * Main class for the Bloomerang Data Exporter plugin. | |
| */ | |
| class Bloomerang_Data_Exporter { | |
| /** | |
| * The option name for storing the API key in the database. | |
| * @var string | |
| */ | |
| private $option_name = 'bde_api_key'; | |
| /** | |
| * The slug for the admin menu page. | |
| * @var string | |
| */ | |
| private $menu_slug = 'bloomerang-exporter'; | |
| /** | |
| * Bloomerang API base URL. | |
| * @var string | |
| */ | |
| private $base_url = 'https://api.bloomerang.co/v2'; | |
| /** | |
| * List of API endpoints to export data from. | |
| * @var array | |
| */ | |
| private $endpoints_to_export = [ | |
| "constituent", | |
| "transaction", | |
| "interaction", | |
| "fund", | |
| "campaign", | |
| "appeal", | |
| ]; | |
| /** | |
| * Class constructor. | |
| * Hooks into WordPress actions. | |
| */ | |
| public function __construct() { | |
| add_action( 'admin_menu', [ $this, 'add_admin_menu' ] ); | |
| add_action( 'admin_init', [ $this, 'register_settings' ] ); | |
| add_action( 'admin_post_bde_run_export', [ $this, 'handle_export_trigger' ] ); | |
| } | |
| /** | |
| * Adds the plugin page to the WordPress admin menu. | |
| */ | |
| public function add_admin_menu() { | |
| add_menu_page( | |
| __( 'Bloomerang Exporter', 'bloomerang-data-exporter' ), | |
| __( 'Bloomerang Exporter', 'bloomerang-data-exporter' ), | |
| 'manage_options', | |
| $this->menu_slug, | |
| [ $this, 'render_admin_page' ], | |
| 'dashicons-download' | |
| ); | |
| } | |
| /** | |
| * Registers the setting for the API key. | |
| */ | |
| public function register_settings() { | |
| register_setting( 'bde_settings_group', $this->option_name ); | |
| } | |
| /** | |
| * Renders the HTML for the admin page. | |
| */ | |
| public function render_admin_page() { | |
| ?> | |
| <div class="wrap"> | |
| <h1><?php echo esc_html( get_admin_page_title() ); ?></h1> | |
| <?php settings_errors(); ?> | |
| <div id="poststuff"> | |
| <div id="post-body" class="metabox-holder columns-2"> | |
| <!-- Main content --> | |
| <div id="post-body-content"> | |
| <div class="meta-box-sortables ui-sortable"> | |
| <div class="postbox"> | |
| <h2 class="hndle"><span><?php _e( 'Export Settings', 'bloomerang-data-exporter' ); ?></span></h2> | |
| <div class="inside"> | |
| <form method="post" action="options.php"> | |
| <?php | |
| settings_fields( 'bde_settings_group' ); | |
| do_settings_sections( 'bde_settings_group' ); | |
| ?> | |
| <table class="form-table"> | |
| <tr valign="top"> | |
| <th scope="row"> | |
| <label for="<?php echo esc_attr( $this->option_name ); ?>"> | |
| <?php _e( 'Bloomerang API Key', 'bloomerang-data-exporter' ); ?> | |
| </label> | |
| </th> | |
| <td> | |
| <input type="password" id="<?php echo esc_attr( $this->option_name ); ?>" name="<?php echo esc_attr( $this->option_name ); ?>" value="<?php echo esc_attr( get_option( $this->option_name ) ); ?>" class="regular-text" /> | |
| <p class="description"><?php _e( 'Your v2.0 API key from your Bloomerang user settings.', 'bloomerang-data-exporter' ); ?></p> | |
| </td> | |
| </tr> | |
| </table> | |
| <?php submit_button( 'Save API Key' ); ?> | |
| </form> | |
| </div> | |
| </div> | |
| <div class="postbox"> | |
| <h2 class="hndle"><span><?php _e( 'Run Exporter', 'bloomerang-data-exporter' ); ?></span></h2> | |
| <div class="inside"> | |
| <p><?php _e( 'Click the button below to start the export process. This may take a long time depending on the amount of data in your Bloomerang account. Please do not navigate away from this page until the process is complete.', 'bloomerang-data-exporter' ); ?></p> | |
| <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"> | |
| <input type="hidden" name="action" value="bde_run_export"> | |
| <?php wp_nonce_field( 'bde_run_export_nonce' ); ?> | |
| <?php | |
| $api_key = get_option( $this->option_name ); | |
| submit_button( 'Export All Data', 'primary', 'submit', true, empty( $api_key ) ? [ 'disabled' => 'disabled' ] : null ); | |
| if ( empty( $api_key ) ) { | |
| echo '<p class="description" style="color: red;">' . __( 'You must save an API key before you can run the export.', 'bloomerang-data-exporter' ) . '</p>'; | |
| } | |
| ?> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <br class="clear"> | |
| </div> | |
| </div> | |
| <?php | |
| } | |
| /** | |
| * Handles the export trigger from the admin page button. | |
| */ | |
| public function handle_export_trigger() { | |
| // Verify nonce for security | |
| if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'bde_run_export_nonce' ) ) { | |
| wp_die( 'Security check failed.' ); | |
| } | |
| // Check user capabilities | |
| if ( ! current_user_can( 'manage_options' ) ) { | |
| wp_die( 'You do not have permission to perform this action.' ); | |
| } | |
| $this->run_export(); | |
| // Redirect back to the settings page with a success message | |
| wp_redirect( add_query_arg( [ 'page' => $this->menu_slug, 'message' => 'success' ], admin_url( 'admin.php' ) ) ); | |
| exit; | |
| } | |
| /** | |
| * The main export logic. | |
| */ | |
| private function run_export() { | |
| // Increase execution time limit to prevent timeouts on large exports | |
| @set_time_limit( 300 ); // 5 minutes | |
| $api_key = get_option( $this->option_name ); | |
| if ( empty( $api_key ) ) { | |
| add_settings_error( 'bde_messages', 'bde_api_key_error', __( 'API Key is not set. Cannot run export.', 'bloomerang-data-exporter' ), 'error' ); | |
| return; | |
| } | |
| $total_files_created = 0; | |
| foreach ( $this->endpoints_to_export as $endpoint ) { | |
| $all_data = $this->fetch_all_data_for_endpoint( $endpoint, $api_key ); | |
| if ( ! empty( $all_data ) ) { | |
| $this->save_data_to_file( $all_data, $endpoint ); | |
| $total_files_created++; | |
| } else { | |
| add_settings_error( 'bde_messages', 'bde_export_error_' . $endpoint, sprintf( __( 'No data was returned or an error occurred for the "%s" endpoint.', 'bloomerang-data-exporter' ), $endpoint ), 'warning' ); | |
| } | |
| } | |
| if ($total_files_created > 0) { | |
| add_settings_error( 'bde_messages', 'bde_export_success', sprintf(__( 'Export process completed successfully. %d files were created in your uploads directory.', 'bloomerang-data-exporter' ), $total_files_created), 'success' ); | |
| } else { | |
| add_settings_error( 'bde_messages', 'bde_export_failed', __( 'Export process finished, but no data could be exported.', 'bloomerang-data-exporter' ), 'error' ); | |
| } | |
| } | |
| /** | |
| * Fetches all records for a single endpoint, handling pagination. | |
| * | |
| * @param string $endpoint The API endpoint to fetch from. | |
| * @param string $api_key The Bloomerang API key. | |
| * @return array A list of all records. | |
| */ | |
| private function fetch_all_data_for_endpoint( $endpoint, $api_key ) { | |
| $all_records = []; | |
| $skip = 0; | |
| $take = 50; // Max records per API request | |
| $headers = [ 'X-API-KEY' => $api_key ]; | |
| while ( true ) { | |
| $url = add_query_arg( [ | |
| 'skip' => $skip, | |
| 'take' => $take, | |
| ], "{$this->base_url}/{$endpoint}" ); | |
| $response = wp_remote_get( $url, [ | |
| 'headers' => $headers, | |
| 'timeout' => 30, // 30-second timeout for the request | |
| ] ); | |
| if ( is_wp_error( $response ) ) { | |
| add_settings_error( 'bde_messages', 'bde_wp_error_' . $endpoint, sprintf(__( 'Request failed for endpoint "%s": %s', 'bloomerang-data-exporter' ), $endpoint, $response->get_error_message()), 'error' ); | |
| return []; // Stop on error | |
| } | |
| $body = wp_remote_retrieve_body( $response ); | |
| $data = json_decode( $body, true ); | |
| if ( json_last_error() !== JSON_ERROR_NONE ) { | |
| add_settings_error( 'bde_messages', 'bde_json_error_' . $endpoint, sprintf(__( 'Failed to decode JSON for endpoint "%s".', 'bloomerang-data-exporter' ), $endpoint), 'error' ); | |
| return []; // Stop on error | |
| } | |
| $records = []; | |
| if ( isset( $data['Results'] ) && is_array( $data['Results'] ) ) { | |
| $records = $data['Results']; | |
| } elseif ( isset( $data['results'] ) && is_array( $data['results'] ) ) { | |
| $records = $data['results']; | |
| } elseif ( is_array( $data ) ) { | |
| // Some endpoints might return a simple array | |
| $records = $data; | |
| } | |
| if ( empty( $records ) ) { | |
| break; // No more records to fetch | |
| } | |
| $all_records = array_merge( $all_records, $records ); | |
| $skip += $take; | |
| // Small delay to be a good API citizen | |
| usleep( 200000 ); // 0.2 seconds | |
| } | |
| return $all_records; | |
| } | |
| /** | |
| * Saves the fetched data to a JSON file in the uploads directory. | |
| * | |
| * @param array $data The data to save. | |
| * @param string $endpoint The endpoint name, used for the filename. | |
| */ | |
| private function save_data_to_file( $data, $endpoint ) { | |
| $upload_dir = wp_upload_dir(); | |
| $safe_endpoint_name = str_replace( '/', '_', $endpoint ); | |
| $filename = "bloomerang_export_{$safe_endpoint_name}_" . date( 'Y-m-d_H-i-s' ) . '.json'; | |
| $filepath = trailingslashit( $upload_dir['basedir'] ) . $filename; | |
| $json_data = json_encode( $data, JSON_PRETTY_PRINT ); | |
| if ( file_put_contents( $filepath, $json_data ) === false ) { | |
| add_settings_error( 'bde_messages', 'bde_file_save_error_' . $endpoint, sprintf(__( 'Failed to save file for endpoint "%s". Check file permissions for the uploads directory.', 'bloomerang-data-exporter' ), $endpoint), 'error' ); | |
| } | |
| } | |
| } | |
| // Instantiate the plugin class. | |
| new Bloomerang_Data_Exporter(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment