importers = $importers; add_action( 'admin_menu', array( $this, 'add_menu' ), 100 ); add_filter( 'plugin_action_links_' . AIR_WP_SYNC_BASENAME, array( $this, 'plugin_action_links' ) ); add_filter( 'manage_airwpsync-connection_posts_columns', array( $this, 'admin_table_columns' ), 10, 1 ); add_action( 'manage_airwpsync-connection_posts_custom_column', array( $this, 'admin_table_columns_html' ), 10, 2 ); add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'register_styles_scripts' ) ); add_action( 'in_admin_header', array( $this, 'in_admin_header' ) ); add_action( 'edit_form_top', array( $this, 'add_header' ) ); add_action( 'dbx_post_sidebar', array( $this, 'add_footer' ) ); add_action( 'wp_insert_post_data', array( $this, 'wp_insert_post_data' ), 10, 2 ); add_filter( 'redirect_post_location', array( $this, 'redirect_post_location' ), 10, 2 ); add_action( 'admin_notices', array( $this, 'admin_notices' ), 10 ); add_action( 'wp_ajax_air_wp_sync_trigger_update', array( $this, 'trigger_update' ) ); add_filter( 'post_updated_messages', array( $this, 'post_updated_messages' ) ); add_action( 'wp_ajax_air_wp_sync_check_airtable_settings', array( $this, 'check_airtable_settings' ) ); } /** * Display plugin header */ public function in_admin_header() { $screen = get_current_screen(); if ( 'airwpsync-connection' === $screen->post_type ) { include_once AIR_WP_SYNC_PLUGIN_DIR . 'views/header.php'; } } /** * Output wrapper opening for alpinejs x-data */ public function add_header( $post ) { if ( 'airwpsync-connection' === get_post_type() ) { $config = json_decode( $post->post_content, true ); $json = $config ? wp_json_encode( $config ) : '{}'; // Json output is already encoded // phpcs:ignore echo ''; echo '
'; echo ''; } } /** * Output wrapper closing for alpinejs x-data */ public function add_footer() { if ( 'airwpsync-connection' === get_post_type() ) { echo '
'; } } /** * Add menu */ public function add_menu() { add_menu_page( __( 'Air WP Sync', 'airwpsync' ), __( 'Air WP Sync', 'airwpsync' ), apply_filters( 'airwpsync/manage_options_capability', 'manage_options' ), 'edit.php?post_type=airwpsync-connection', false, 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHZpZXdCb3g9IjAgMCA0MyAzNiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CiAgICA8cGF0aCBkPSJNMTksMC40TDMuMSw3Yy0wLjksMC40LTAuOSwxLjYsMCwyTDE5LDE1LjNjMS40LDAuNiwzLDAuNiw0LjMsMEwzOS4zLDljMC45LTAuNCwwLjktMS42LDAtMkwyMy41LDAuNCBDMjItMC4xLDIwLjQtMC4xLDE5LDAuNCAiIGZpbGw9ImN1cnJlbnRDb2xvciIvPgogICAgPHBhdGggZD0iTTIyLjYsMTguN3YxNS43YzAsMC43LDAuOCwxLjMsMS41LDFsMTcuNy02LjkgYzAuNC0wLjIsMC43LTAuNiwwLjctMVYxMS44YzAtMC43LTAuOC0xLjMtMS41LTFsLTE3LjcsNi45QzIyLjksMTcuOSwyMi42LDE4LjMsMjIuNiwxOC43IiBmaWxsPSJjdXJyZW50Q29sb3IiLz4KICAgIDxwYXRoIGQ9Ik0xOC41LDE5LjVsLTUuMywyLjVsLTAuNSwwLjNMMS42LDI3LjYgQzAuOSwyOCwwLDI3LjUsMCwyNi43VjExLjljMC0wLjMsMC4xLTAuNSwwLjMtMC43QzAuNCwxMS4xLDAuNSwxMSwwLjYsMTFjMC4zLTAuMiwwLjYtMC4yLDEtMC4xbDE2LjgsNi43IEMxOS4zLDE3LjksMTkuMywxOS4xLDE4LjUsMTkuNSIgZmlsbD0iY3VycmVudENvbG9yIi8+Cjwvc3ZnPg==' ); add_submenu_page( 'edit.php?post_type=airwpsync-connection', __( 'All Connections', 'airwpsync' ), __( 'All Connections', 'airwpsync' ), apply_filters( 'airwpsync/manage_options_capability', 'manage_options' ), 'edit.php?post_type=airwpsync-connection' ); add_submenu_page( 'edit.php?post_type=airwpsync-connection', __( 'Add New', 'airwpsync' ), __( 'Add New', 'airwpsync' ), apply_filters( 'airwpsync/manage_options_capability', 'manage_options' ), 'post-new.php?post_type=airwpsync-connection' ); } /** * Show action links on the plugin screen */ public function plugin_action_links( $links ) { return array_merge( $links, array( 'settings' => '' . esc_html__( 'Settings', 'airwpsync' ) . '', ) ); } /** * Customizes the admin table columns. */ public function admin_table_columns( $_columns ) { $columns = array( 'cb' => $_columns['cb'], 'title' => $_columns['title'], 'modified-date' => __( 'Last Modified On', 'airwpsync' ), 'post-type' => __( 'Post Type', 'airwpsync' ), 'sync-date' => __( 'Last Synced On', 'airwpsync' ), 'sync-trigger' => __( 'Trigger', 'airwpsync' ), ); return $columns; } /** * Renders the admin table column HTML * * @param string $column_name The name of the column to display. * @param int $post_id The current post ID. * @return void */ public function admin_table_columns_html( $column_name, $post_id ) { $importer = $this->get_importer_by_id( $post_id ); if ( ! $importer ) { return; } switch ( $column_name ) { case 'post-type': $post_type = $importer->config()->get( 'post_type' ); $post_type_object = get_post_type_object( $post_type ); if ( $post_type_object ) { echo ' '; echo esc_html( $post_type_object->labels->name ); } break; case 'sync-trigger': if ( 'manual' === $importer->config()->get( 'scheduled_sync.type' ) ) { esc_html_e( 'Manual only', 'airwpsync' ); } if ( 'cron' === $importer->config()->get( 'scheduled_sync.type' ) ) { esc_html_e( 'Recurring', 'airwpsync' ); if ( $importer->config()->get( 'scheduled_sync.recurrence' ) ) { $schedules = wp_get_schedules(); foreach ( $schedules as $slug => $schedule ) { if ( $slug === $importer->config()->get( 'scheduled_sync.recurrence' ) ) { echo esc_html( ' (' . $schedule['display'] . ')' ); } } } } break; case 'sync-date': $last_updated = get_post_meta( $post_id, 'last_updated', true ); echo esc_html( $last_updated ? Air_WP_Sync_Helpers::get_formatted_date_time( $last_updated ) : '--' ); break; case 'modified-date': $last_modified = $importer->infos()->get( 'modified' ); echo esc_html( $last_modified ? Air_WP_Sync_Helpers::get_formatted_date_time( $last_modified ) : '--' ); break; } } /** * Add metaboxes for importer post type */ public function add_meta_boxes() { // Settings add_meta_box( 'air-wp-sync-global-settings', __( 'Airtable Settings', 'airwpsync' ), array( $this, 'display_airtable_settings_metabox' ), 'airwpsync-connection', 'normal', 'high' ); // Post Settings add_meta_box( 'air-wp-sync-post-settings', __( 'Import As...', 'airwpsync' ), array( $this, 'display_post_settings_metabox' ), 'airwpsync-connection', 'normal', 'high' ); // Mapping $tooltip_html = '?'; add_meta_box( 'air-wp-sync-mapping', __( 'Field Mapping', 'airwpsync' ) . $tooltip_html, array( $this, 'display_mapping_metabox' ), 'airwpsync-connection', 'normal', 'high' ); // Import Infos add_meta_box( 'air-wp-sync-infos', __( 'Actions', 'airwpsync' ), array( $this, 'display_side_metabox' ), 'airwpsync-connection', 'side' ); // Sync Settings add_meta_box( 'air-wp-sync-sync-settings', __( 'Sync Settings', 'airwpsync' ), array( $this, 'display_sync_metabox' ), 'airwpsync-connection', 'normal' ); } /** * Output global settings metabox HTML */ public function display_airtable_settings_metabox( $post ) { include_once AIR_WP_SYNC_PLUGIN_DIR . 'views/metabox-airtable-settings.php'; } /** * Output mapping metabox HTML */ public function display_mapping_metabox( $post ) { $wp_fields = apply_filters( 'airwpsync/get_wp_fields', array() ); include_once AIR_WP_SYNC_PLUGIN_DIR . 'views/metabox-mapping.php'; } /** * Output post settings metabox HTML */ public function display_post_settings_metabox( $post ) { $post_types = $this->get_post_types(); $post_stati = $this->get_post_stati(); $post_authors = $this->get_post_authors(); include_once AIR_WP_SYNC_PLUGIN_DIR . 'views/metabox-post-settings.php'; } /** * Output side metabox HTML */ public function display_side_metabox( $post ) { $importer = $this->get_importer_by_id( $post->ID ); include_once AIR_WP_SYNC_PLUGIN_DIR . 'views/metabox-side.php'; } /** * Output sync metabox HTML */ public function display_sync_metabox( $post ) { $sync_strategies = $this->get_sync_strategies(); $schedules = $this->get_schedules(); include_once AIR_WP_SYNC_PLUGIN_DIR . 'views/metabox-sync.php'; } /** * Register admin styles and scripts */ public function register_styles_scripts() { wp_enqueue_style( 'air-wp-sync-admin', plugins_url( 'assets/css/admin-page.css', __DIR__ ), false, AIR_WP_SYNC_VERSION ); $screen = get_current_screen(); if ( is_object( $screen ) && 'airwpsync-connection' === $screen->id ) { wp_enqueue_script( 'air-wp-sync-alpine', plugins_url( 'assets/js/alpinejs@3.10.2.min.js', __DIR__ ), false, AIR_WP_SYNC_VERSION, false ); wp_enqueue_script( 'air-wp-sync-admin', plugins_url( 'assets/js/admin-page.js', __DIR__ ), array( 'air-wp-sync-alpine' ), AIR_WP_SYNC_VERSION, false ); } } /** * Save post */ public function wp_insert_post_data( $data, $postarr ) { if ( 'airwpsync-connection' !== $postarr['post_type'] ) { return $data; } $post_id = $postarr['ID'] ?? 0; // Limit active importers if ( ! isset( $data['post_status'] ) || $data['post_status'] === 'publish' ) { $importers = get_posts( array( 'post_type' => 'airwpsync-connection', 'post_status' => 'publish', 'posts_per_page' => -1, 'post__not_in' => array( $post_id ), ) ); if ( count( $importers ) > 0 ) { $data['post_status'] = 'draft'; $this->display_max_connection = true; } } // Update slug if ( array_key_exists( 'post_title', $data ) ) { $data['post_name'] = sanitize_title( $data['post_title'] ); } // Clear schedule if ( ! empty( $post_id ) ) { wp_clear_scheduled_hook( 'air_wp_sync_importer_' . $post_id ); } return $data; } public function redirect_post_location( $location, $post_id ) { if ( $this->display_max_connection ) { $location = add_query_arg( 'display_max_connection', 1, $location ); } return $location; } public function admin_notices() { // phpcs:ignore if ( ! empty( $_GET['display_max_connection'] ) && '1' === $_GET['display_max_connection'] ) { printf( '

%2$s

', esc_attr( 'notice notice-error' ), wp_kses( __( 'Thank you for using the free version of our plugin! To be able to create as many connections as you want, upgrade to Pro version.', 'airwpsync' ), array( 'a' => array( 'href' => array(), 'target' => array(), ), ) ) ); } } /** * Add back link to message when connection is updated. */ public function post_updated_messages( $messages ) { $back_link_html = sprintf( ' %2$s', esc_url( admin_url( 'edit.php?post_type=airwpsync-connection' ) ), __( 'Back to list', 'airwpsync' ) ); $messages['post'][1] .= $back_link_html; return $messages; } /** * Manual sync AJAX function */ public function trigger_update() { // AJAX check if ( ( empty( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) === 'xmlhttprequest' ) ) { wp_die(); } // Nonce check check_ajax_referer( 'air-wp-sync-trigger-update', 'nonce' ); $importer_id = (int) $_POST['importer'] ?? 0; $nonce = wp_create_nonce( 'air-wp-sync-trigger-update' ); try { $importer = $this->get_importer_by_id( $importer_id ); if ( ! $importer ) { throw new Exception( 'No connection found.' ); } $result = $importer->run(); if ( is_wp_error( $result ) ) { throw new Exception( $result->get_error_message() ); } wp_send_json_success( array( 'nonce' => $nonce, 'infosHtml' => $this->get_stats_html( $importer_id ), 'feedback' => __( 'Finished!', 'airwpsync' ), ) ); } catch ( Exception $e ) { wp_send_json_error( array( 'nonce' => $nonce, 'infosHtml' => $this->get_stats_html( $importer_id, $e->getMessage() ), 'feedback' => __( 'Finished with errors.', 'airwpsync' ), ) ); } } public function check_airtable_settings() { // AJAX check if ( empty( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) === 'xmlhttprequest' ) { wp_die(); } // Data check if ( empty( $_POST['apiKey'] ) || empty( $_POST['appId'] ) || empty( $_POST['table'] ) ) { wp_die(); } // Nonce check check_ajax_referer( 'air-wp-sync-check-airtable-settings', 'nonce' ); // Get data $api_key = sanitize_text_field( $_POST['apiKey'] ); $app_id = sanitize_text_field( $_POST['appId'] ); $table = sanitize_text_field( $_POST['table'] ); $nonce = wp_create_nonce( 'air-wp-sync-check-airtable-settings' ); try { $client = new Air_WP_Sync_Airtable_Api_Client( $app_id, $api_key ); $client->list( $table ); wp_send_json_success( array( 'nonce' => $nonce, 'feedback' => __( 'Finished!', 'airwpsync' ), ) ); } catch ( Exception $e ) { $response = array( 'nonce' => $nonce, ); if ( strpos( $e->getMessage(), 'Could not find table' ) > -1 || strpos( $e->getMessage(), 'NOT_FOUND' ) > -1 ) { $response['fields'] = array( 'appId', 'table' ); $response['message'] = $e->getMessage(); } if ( strpos( $e->getMessage(), 'Authentication required' ) > -1 ) { $response['fields'] = array( 'apiKey' ); $response['message'] = __( 'Wrong API Key', 'airwpsync' ); } wp_send_json_error( $response ); } } /** * Get importer instance from id */ protected function get_importer_by_id( $id ) { return array_reduce( $this->importers, function( $result, $importer ) use ( $id ) { return $importer->infos()->get( 'id' ) === (int) $id ? $importer : $result; }, false ); } /** * Get importer statistics html */ protected function get_stats_html( $importer_id, $last_error = null ) { ob_start(); $status = $importer_id ? get_post_meta( $importer_id, 'status', true ) : ''; $last_updated = $importer_id ? get_post_meta( $importer_id, 'last_updated', true ) : ''; $next_sync = $importer_id ? wp_next_scheduled( 'air_wp_sync_importer_' . $importer_id ) : ''; $last_error = $last_error ? $last_error : ( $importer_id ? get_post_meta( $importer_id, 'last_error', true ) : '' ); include AIR_WP_SYNC_PLUGIN_DIR . 'views/metabox-side/infos.php'; return ob_get_clean(); } /** * Get available post types */ protected function get_post_types() { $excluded = array( 'attachment' ); $enabled = array( 'page', 'post' ); $post_types = array(); $wp_post_types = get_post_types( array( 'public' => true, ), 'objects' ); foreach ( $wp_post_types as $wp_post_type ) { if ( ! in_array( $wp_post_type->name, $excluded, true ) ) { $post_types[] = array( 'value' => $wp_post_type->name, 'label' => $wp_post_type->labels->singular_name . ( ! in_array( $wp_post_type->name, $enabled, true ) ? ' ' . __( '(Pro version, coming soon)', 'airwpsync' ) : '' ), 'enabled' => in_array( $wp_post_type->name, $enabled, true ), ); } } $post_types[] = array( 'value' => 'custom', 'label' => __( 'Custom Post Type (Pro version, coming soon)', 'airwpsync' ), 'enabled' => false, ); return apply_filters( 'airwpsync/get_post_types', $post_types ); } /** * Get post stati */ protected function get_post_stati() { $whitelist = array( 'publish', 'future', 'draft', 'pending', 'trash', 'private' ); $post_stati = array(); $wp_post_stati = get_post_stati( array( 'internal' => false ), 'objects' ); foreach ( $wp_post_stati as $wp_post_status ) { $enabled = in_array( $wp_post_status->name, $whitelist, true ); $post_stati[] = array( 'value' => $wp_post_status->name, 'label' => $wp_post_status->label . ( ! $enabled ? ' ' . __( '(Pro version, coming soon)', 'airwpsync' ) : '' ), 'enabled' => $enabled, ); } return apply_filters( 'airwpsync/get_post_stati', $post_stati ); } /** * Get post authors */ protected function get_post_authors() { $authors = array(); $wp_authors = get_users( array( 'role__in' => array( 'administrator', 'editor', 'author', 'contributor' ) ) ); foreach ( $wp_authors as $wp_author ) { $authors[] = array( 'value' => $wp_author->ID, 'label' => $wp_author->display_name, 'enabled' => true, ); } return apply_filters( 'airwpsync/get_post_authors', $authors ); } /** * Get sync strategies */ protected function get_sync_strategies() { return array( 'add' => __( 'Add', 'airwpsync' ), 'add_update' => __( 'Add & Update', 'airwpsync' ), 'add_update_delete' => __( 'Add, Update & Delete', 'airwpsync' ), ); } /** * Get schedules */ protected function get_schedules() { $whitelist = array( 'hourly', 'twicedaily', 'daily', 'weekly' ); $schedules = array(); foreach ( wp_get_schedules() as $key => $schedule ) { if ( in_array( $key, $whitelist, true ) || strpos( $key, 'airwpsync_' ) === 0 ) { $enabled = $schedule['interval'] >= DAY_IN_SECONDS; $schedules[ $schedule['interval'] ] = array( 'value' => $key, 'label' => $schedule['display'] . ( ! $enabled ? ' ' . __( '(Pro version, coming soon)', 'airwpsync' ) : '' ), 'enabled' => $enabled, ); } } ksort( $schedules ); return array_reverse( $schedules ); } }