'string', 'default' => 'https://bsky.social', 'sanitize_callback' => function($input) { // Force the default if empty if (empty($input)) { return 'https://bsky.social'; } // Ensure https:// is present if (strpos($input, 'https://') !== 0) { $input = 'https://' . $input; } // Remove any trailing slashes return rtrim($input, '/'); } )); register_setting('bluesky_connector_settings', 'bluesky_identifier', array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' )); register_setting('bluesky_connector_settings', 'bluesky_password', array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' )); register_setting('bluesky_connector_settings', 'bluesky_post_format', array( 'type' => 'string', 'default' => 'title-excerpt-image-link', 'sanitize_callback' => 'sanitize_text_field' )); register_setting('bluesky_connector_settings', 'bluesky_include_title', array( 'type' => 'boolean', 'default' => true )); register_setting('bluesky_connector_settings', 'bluesky_title_separator', array( 'type' => 'string', 'default' => "\n\n", 'sanitize_callback' => 'sanitize_text_field' )); // Add settings sections and fields add_settings_section( 'bluesky_connector_main', __('Bluesky Connection Settings', 'bluesky-connector'), array($this, 'render_settings_section'), 'bluesky_connector_settings' ); add_settings_section( 'bluesky_format_section', __('Post Format Settings', 'bluesky-connector'), array($this, 'render_format_section'), 'bluesky_connector_settings' ); add_settings_field( 'bluesky_domain', __('Bluesky Domain', 'bluesky-connector'), array($this, 'render_domain_field'), 'bluesky_connector_settings', 'bluesky_connector_main' ); add_settings_field( 'bluesky_identifier', __('Bluesky Handle', 'bluesky-connector'), array($this, 'render_identifier_field'), 'bluesky_connector_settings', 'bluesky_connector_main' ); add_settings_field( 'bluesky_password', __('App Password', 'bluesky-connector'), array($this, 'render_password_field'), 'bluesky_connector_settings', 'bluesky_connector_main' ); add_settings_field( 'bluesky_post_format', __('Post Layout', 'bluesky-connector'), array($this, 'render_post_format_field'), 'bluesky_connector_settings', 'bluesky_format_section' ); add_settings_field( 'bluesky_include_title', __('Include Post Title', 'bluesky-connector'), array($this, 'render_include_title_field'), 'bluesky_connector_settings', 'bluesky_format_section' ); add_settings_field( 'bluesky_title_separator', __('Title Separator', 'bluesky-connector'), array($this, 'render_title_separator_field'), 'bluesky_connector_settings', 'bluesky_format_section' ); } /** * Add admin menu */ public function add_admin_menu() { add_options_page( __('Bluesky Connector Settings', 'bluesky-connector'), __('Bluesky Connector', 'bluesky-connector'), 'manage_options', 'bluesky-connector', array($this, 'render_settings_page') ); } /** * Render settings page */ public function render_settings_page() { if (!current_user_can('manage_options')) { return; } // Add process queue button if (isset($_POST['process_queue_now']) && check_admin_referer('bluesky_process_queue')) { $this->process_post_queue(); add_settings_error( 'bluesky_connector_settings', 'queue_processed', __('Queue processed.', 'bluesky-connector'), 'success' ); } // Check for form submission if (isset($_POST['action']) && $_POST['action'] === 'update_bluesky_settings') { check_admin_referer('bluesky_connector_settings'); $this->handle_settings_update(); } // Get current settings $settings = array( 'domain' => get_option('bluesky_domain', 'https://bsky.social'), 'identifier' => get_option('bluesky_identifier', ''), 'connection_status' => get_option('bluesky_connection_status', ''), 'last_error' => get_option('bluesky_last_error', '') ); include BLUESKY_CONNECTOR_DIR . 'templates/settings-page.php'; } /** * Render settings section description */ public function render_settings_section() { echo '
' . esc_html__('Configure your Bluesky connection settings below.', 'bluesky-connector') . '
'; } /** * Render domain field */ public function render_domain_field() { $value = get_option('bluesky_domain', 'https://bsky.social'); echo ''; echo '' . esc_html__('The Bluesky API domain (default: https://bsky.social)', 'bluesky-connector') . '
'; } /** * Render identifier field */ public function render_identifier_field() { $value = get_option('bluesky_identifier', ''); echo ''; echo '' . esc_html__('Your Bluesky handle (e.g., username.bsky.social)', 'bluesky-connector') . '
'; } /** * Render password field */ public function render_password_field() { echo ''; echo '' . esc_html__('Your Bluesky app password (will not be stored)', 'bluesky-connector') . '
'; } /** * Handle settings update */ private function handle_settings_update() { $identifier = sanitize_text_field($_POST['bluesky_identifier']); $password = sanitize_text_field($_POST['bluesky_password']); $domain = esc_url_raw($_POST['bluesky_domain']); if (empty($identifier) || empty($password)) { add_settings_error( 'bluesky_connector_settings', 'missing_credentials', __('Both identifier and password are required.', 'bluesky-connector') ); return; } // Try to authenticate $auth = new Bluesky_Auth($identifier, $password); $result = $auth->get_access_token(); if (isset($result['error'])) { update_option('bluesky_connection_status', 'error'); update_option('bluesky_last_error', $result['error']); add_settings_error( 'bluesky_connector_settings', 'auth_failed', sprintf(__('Authentication failed: %s', 'bluesky-connector'), $result['error']) ); } else { update_option('bluesky_connection_status', 'connected'); update_option('bluesky_last_error', ''); add_settings_error( 'bluesky_connector_settings', 'settings_updated', __('Settings saved and connected successfully.', 'bluesky-connector'), 'success' ); } } /** * Handle post publish */ public function handle_post_publish($post_id, $post) { // Skip if not a public post if ($post->post_status !== 'publish' || $post->post_type !== 'post') { return; } // Skip if already posted if (get_post_meta($post_id, '_bluesky_posted', true)) { return; } // Add to queue $queue = get_option('bluesky_post_queue', array()); $queue[] = $post_id; update_option('bluesky_post_queue', array_unique($queue)); } /** * Process post queue */ public function process_post_queue() { $queue = get_option('bluesky_post_queue', array()); if (empty($queue)) { return; } $access_token = get_option('bluesky_access_jwt'); $did = get_option('bluesky_did'); if (!$access_token || !$did) { error_log('Bluesky Connector: Missing access token or DID'); return; } foreach ($queue as $key => $post_id) { $post = get_post($post_id); if (!$post) { unset($queue[$key]); continue; } try { $formatter = new Post_Formatter($access_token, $did); $response = $formatter->format_and_post($post); if (!isset($response['error'])) { unset($queue[$key]); update_post_meta($post_id, '_bluesky_post_id', $response['uri']); update_post_meta($post_id, '_bluesky_posted', current_time('mysql')); update_post_meta($post_id, '_bluesky_status', 'success'); } else { update_post_meta($post_id, '_bluesky_status', 'error'); update_post_meta($post_id, '_bluesky_error', $response['error']); error_log('Bluesky Post Error: ' . print_r($response['error'], true)); } } catch (Exception $e) { error_log('Bluesky Connector Exception: ' . $e->getMessage()); update_post_meta($post_id, '_bluesky_status', 'error'); update_post_meta($post_id, '_bluesky_error', $e->getMessage()); } } update_option('bluesky_post_queue', array_values($queue)); } /** * Add post meta box */ public function add_post_meta_box() { add_meta_box( 'bluesky_post_status', __('Bluesky Status', 'bluesky-connector'), array($this, 'render_post_meta_box'), 'post', 'side', 'default' ); } /** * Render post meta box */ public function render_post_meta_box($post) { $status = get_post_meta($post->ID, '_bluesky_status', true); $posted_date = get_post_meta($post->ID, '_bluesky_posted', true); $error = get_post_meta($post->ID, '_bluesky_error', true); $post_id = get_post_meta($post->ID, '_bluesky_post_id', true); include BLUESKY_CONNECTOR_DIR . 'templates/post-meta-box.php'; } /** * Save post meta box */ public function save_post_meta_box($post_id) { if (!current_user_can('edit_post', $post_id)) { return; } if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } // Add any custom meta box saving logic here } /** * Handle retry post AJAX request */ public function handle_retry_post() { check_ajax_referer('bluesky_admin', 'nonce'); if (!current_user_can('edit_posts')) { wp_send_json_error(['message' => __('Permission denied.', 'bluesky-connector')]); } $post_id = intval($_POST['post_id']); $post = get_post($post_id); if (!$post) { wp_send_json_error(['message' => __('Post not found.', 'bluesky-connector')]); } // Add to queue $queue = get_option('bluesky_post_queue', array()); $queue[] = $post_id; update_option('bluesky_post_queue', array_unique($queue)); // Update post meta update_post_meta($post_id, '_bluesky_status', 'queued'); delete_post_meta($post_id, '_bluesky_error'); wp_send_json_success(['message' => __('Post queued for retry.', 'bluesky-connector')]); } /** * Handle share post AJAX request */ public function handle_share_post() { check_ajax_referer('bluesky_admin', 'nonce'); if (!current_user_can('edit_posts')) { wp_send_json_error(['message' => __('Permission denied.', 'bluesky-connector')]); } $post_id = intval($_POST['post_id']); $post = get_post($post_id); if (!$post) { wp_send_json_error(['message' => __('Post not found.', 'bluesky-connector')]); } // Add to queue $queue = get_option('bluesky_post_queue', array()); $queue[] = $post_id; update_option('bluesky_post_queue', array_unique($queue)); // Update post meta update_post_meta($post_id, '_bluesky_status', 'queued'); wp_send_json_success(['message' => __('Post queued for sharing.', 'bluesky-connector')]); } /** * Enqueue admin scripts and styles */ public function enqueue_admin_scripts($hook) { if ('post.php' !== $hook && 'post-new.php' !== $hook) { return; } wp_enqueue_script( 'bluesky-admin', BLUESKY_CONNECTOR_URL . 'assets/js/admin.js', array('jquery'), BLUESKY_CONNECTOR_VERSION, true ); wp_localize_script('bluesky-admin', 'blueskyAdmin', array( 'nonce' => wp_create_nonce('bluesky_admin'), 'ajaxUrl' => admin_url('admin-ajax.php'), 'strings' => array( 'error' => __('An error occurred. Please try again.', 'bluesky-connector'), 'success' => __('Operation completed successfully.', 'bluesky-connector') ) )); wp_enqueue_style( 'bluesky-admin', BLUESKY_CONNECTOR_URL . 'assets/css/admin.css', array(), BLUESKY_CONNECTOR_VERSION ); } /** * Plugin activation */ public function activate() { // Create necessary options with default values add_option('bluesky_domain', 'https://bsky.social'); add_option('bluesky_post_queue', array()); add_option('bluesky_connection_status', ''); // Schedule cron job if (!wp_next_scheduled('process_bluesky_post_queue')) { wp_schedule_event(time(), 'hourly', 'process_bluesky_post_queue'); } // Create custom capabilities $role = get_role('administrator'); if ($role) { $role->add_cap('manage_bluesky_settings'); } // Flush rewrite rules flush_rewrite_rules(); } /** * Plugin deactivation */ public function deactivate() { // Clear scheduled events wp_clear_scheduled_hook('process_bluesky_post_queue'); // Remove custom capabilities $role = get_role('administrator'); if ($role) { $role->remove_cap('manage_bluesky_settings'); } } /** * Display admin notices */ public function admin_notices() { if (!current_user_can('manage_options')) { return; } $screen = get_current_screen(); if ($screen->id !== 'settings_page_bluesky-connector') { return; } settings_errors('bluesky_connector_settings'); } /** * Add settings link to plugins page */ public function add_settings_link($links) { $settings_link = sprintf( '%s', admin_url('options-general.php?page=bluesky-connector'), __('Settings', 'bluesky-connector') ); array_unshift($links, $settings_link); return $links; } /** * Log plugin errors */ private function log_error($message, $data = array()) { if (defined('WP_DEBUG') && WP_DEBUG) { error_log(sprintf( '[Bluesky Connector] %s | Data: %s', $message, print_r($data, true) )); } } /** * Filter for customizing post content before sending to Bluesky */ public function filter_post_content($content, $post) { return apply_filters('bluesky_post_content', $content, $post); } /** * Check if post should be shared to Bluesky */ private function should_share_post($post) { // Skip if post is not published if ($post->post_status !== 'publish') { return false; } // Skip if post type is not supported $supported_post_types = apply_filters('bluesky_supported_post_types', array('post')); if (!in_array($post->post_type, $supported_post_types)) { return false; } // Skip if already shared if (get_post_meta($post->ID, '_bluesky_posted', true)) { return false; } // Allow custom filtering return apply_filters('bluesky_should_share_post', true, $post); } public function render_format_section() { echo '' . esc_html__('Customize how your posts appear on Bluesky.', 'bluesky-connector') . '
'; } public function render_post_format_field() { $format = get_option('bluesky_post_format', 'title-excerpt-image-link'); $options = array( 'title-excerpt-image-link' => __('Title → Excerpt → Image → Link', 'bluesky-connector'), 'title-image-excerpt-link' => __('Title → Image → Excerpt → Link', 'bluesky-connector'), 'image-title-excerpt-link' => __('Image → Title → Excerpt → Link', 'bluesky-connector'), 'excerpt-image-link' => __('Excerpt → Image → Link', 'bluesky-connector'), 'title-excerpt-link' => __('Title → Excerpt → Link (No Image)', 'bluesky-connector'), ); echo ''; echo '' . esc_html__('Choose how you want your posts to be formatted on Bluesky.', 'bluesky-connector') . '
'; } public function render_include_title_field() { $include_title = get_option('bluesky_include_title', true); printf( '', checked($include_title, true, false), __('Include post title at the beginning', 'bluesky-connector') ); echo '' . esc_html__('Add the post title to the beginning of each Bluesky post.', 'bluesky-connector') . '
'; } public function render_title_separator_field() { $separator = get_option('bluesky_title_separator', "\n\n"); $options = array( "\n\n" => __('Double Line Break', 'bluesky-connector'), "\n" => __('Single Line Break', 'bluesky-connector'), " - " => __('Dash', 'bluesky-connector'), ": " => __('Colon', 'bluesky-connector'), ); echo ''; echo '' . esc_html__('Choose how to separate the title from the excerpt.', 'bluesky-connector') . '
'; } } /** * Initialize the plugin */ function bluesky_connector_init() { return Bluesky_Connector::get_instance(); } // Initialize the plugin add_action('plugins_loaded', 'bluesky_connector_init'); /** * Register uninstall hook */ register_uninstall_hook(__FILE__, 'bluesky_connector_uninstall'); /** * Clean up plugin data on uninstall */ function bluesky_connector_uninstall() { // Only run if explicitly uninstalling if (!defined('WP_UNINSTALL_PLUGIN')) { return; } // Remove options delete_option('bluesky_domain'); delete_option('bluesky_identifier'); delete_option('bluesky_access_jwt'); delete_option('bluesky_refresh_jwt'); delete_option('bluesky_did'); delete_option('bluesky_post_queue'); delete_option('bluesky_connection_status'); delete_option('bluesky_last_error'); // Remove capabilities $role = get_role('administrator'); if ($role) { $role->remove_cap('manage_bluesky_settings'); } // Clear scheduled hooks wp_clear_scheduled_hook('process_bluesky_post_queue'); // Remove post meta global $wpdb; $wpdb->query("DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE '_bluesky_%'"); }