ID); error_log('[Bluesky Connector] Post type: ' . $post->post_type); error_log('[Bluesky Connector] Has featured image: ' . (has_post_thumbnail($post->ID) ? 'Yes' : 'No')); error_log('[Bluesky Connector] Theme supports thumbnails: ' . (current_theme_supports('post-thumbnails') ? 'Yes' : 'No')); if ($new_status === 'publish' && $old_status !== 'publish') { $this->handle_post_save($post->ID, $post); } } /** * Get plugin instance * * @return Bluesky_Connector */ public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } /** * Constructor */ private function __construct() { $this->debug_mode = defined('WP_DEBUG') && WP_DEBUG; // Load plugin text domain add_action('init', array($this, 'load_textdomain')); // Initialize plugin add_action('init', array($this, 'init')); add_action('transition_post_status', array($this, 'handle_post_transition'), 10, 3); // Admin hooks if (is_admin()) { add_action('admin_init', array($this, 'admin_init')); add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_notices', array($this, 'admin_notices')); add_action('add_meta_boxes', array($this, 'add_post_meta_box')); add_action('save_post', array($this, 'handle_post_save'), 10, 2); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); add_filter('plugin_action_links_' . plugin_basename(__FILE__), array($this, 'add_settings_link')); } // Register activation/deactivation hooks register_activation_hook(__FILE__, array($this, 'activate')); register_deactivation_hook(__FILE__, array($this, 'deactivate')); // AJAX handlers add_action('wp_ajax_bluesky_post_now', array($this, 'handle_immediate_post')); } /** * Load plugin text domain */ public function load_textdomain() { load_plugin_textdomain('bluesky-connctor', false, dirname(plugin_basename(__FILE__)) . '/languages'); } /** * Initialize plugin */ public function init() { add_theme_support('post-thumbnails'); $this->settings = array( 'domain' => get_option('bluesky_domain', BLUESKY_API_DOMAIN), 'identifier' => get_option('bluesky_identifier', ''), 'did' => get_option('bluesky_did', ''), 'access_jwt' => get_option('bluesky_access_jwt', ''), ); // Handle form submissions if (is_admin() && !empty($_POST['action']) && $_POST['action'] === 'update_bluesky_settings') { $this->handle_settings_update(); } } private function handle_settings_update() { if (!current_user_can('manage_options')) { return; } check_admin_referer('bluesky_connector_settings'); if (empty($_POST['action']) || $_POST['action'] !== 'update_bluesky_settings') { return; } // Get the settings from the form $settings = isset($_POST['bluesky_connector_settings']) ? $_POST['bluesky_connector_settings'] : array(); // Validate and sanitize inputs $identifier = isset($settings['identifier']) ? trim(sanitize_text_field(wp_unslash($settings['identifier']))) : ''; $password = isset($settings['password']) ? trim(sanitize_text_field(wp_unslash($settings['password']))) : ''; // 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', sanitize_text_field($result['error'])); add_settings_error( 'bluesky_connector_settings', 'auth_failed', sprintf( esc_html__('Authentication failed: %s', 'bluesky-connctor'), esc_html($result['error']) ) ); } else { // Save the credentials in the settings array $current_settings = get_option('bluesky_connector_settings', array()); $settings_to_save = array_merge($current_settings, array( 'domain' => BLUESKY_API_DOMAIN, 'identifier' => $identifier, 'password' => $password, 'post_format' => $current_settings['post_format'] ?? 'title-excerpt-link', 'include_title' => $current_settings['include_title'] ?? true, 'title_separator' => $current_settings['title_separator'] ?? "\n\n" )); // Update all settings update_option('bluesky_connector_settings', $settings_to_save); update_option('bluesky_identifier', $identifier); // Keep for backward compatibility update_option('bluesky_connection_status', 'connected'); update_option('bluesky_last_error', ''); add_settings_error( 'bluesky_connector_settings', 'settings_updated', esc_html__('Settings saved and connected successfully.', 'bluesky-connctor'), 'success' ); } } /** * Register plugin settings */ public function admin_init() { // Add the settings page to allowed options add_allowed_options(array( 'bluesky-connector' => array( 'bluesky_connector_settings' ) )); // Register the main settings register_setting( 'bluesky-connector', 'bluesky_connector_settings', array( 'type' => 'object', 'description' => esc_html__('Bluesky Connector Settings', 'bluesky-connctor'), 'sanitize_callback' => array($this, 'sanitize_settings'), 'default' => array( 'domain' => BLUESKY_API_DOMAIN, 'identifier' => '', 'password' => '', 'post_format' => 'title-excerpt-link', 'include_title' => true, 'title_separator' => "\n\n" ), 'show_in_rest' => false, ) ); // Add settings sections add_settings_section( 'bluesky_connection_section', esc_html__('Connection Settings', 'bluesky-connctor'), array($this, 'render_connection_section'), 'bluesky-connector' ); add_settings_section( 'bluesky_post_format_section', esc_html__('Post Format Settings', 'bluesky-connctor'), array($this, 'render_format_section'), 'bluesky-connector' ); } public function sanitize_settings($input) { $sanitized = array(); // Domain is fixed $sanitized['domain'] = BLUESKY_API_DOMAIN; // Sanitize identifier if (isset($input['identifier'])) { $sanitized['identifier'] = sanitize_text_field($input['identifier']); } // Handle password - note we don't store this if (isset($input['password'])) { $sanitized['password'] = sanitize_text_field($input['password']); } // Sanitize post format $sanitized['post_format'] = isset($input['post_format']) ? sanitize_text_field($input['post_format']) : 'title-excerpt-link'; // Sanitize include_title $sanitized['include_title'] = isset($input['include_title']); // Sanitize title separator $sanitized['title_separator'] = isset($input['title_separator']) ? sanitize_text_field($input['title_separator']) : "\n\n"; return $sanitized; } public function render_connection_section() { echo '
' . esc_html__('Configure your Bluesky connection settings.', 'bluesky-connctor') . '
'; } public function render_format_section() { echo '' . esc_html__('Configure how your posts appear on Bluesky.', 'bluesky-connctor') . '
'; } /** * Enqueue admin scripts and styles * * @param string $hook The current admin page. */ public function enqueue_admin_scripts($hook) { // Only load on post edit screens and settings page if (!in_array($hook, array('post.php', 'post-new.php', 'settings_page_bluesky-settings'))) { return; } // Enqueue WordPress media uploader 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( 'ajaxUrl' => admin_url('admin-ajax.php'), 'strings' => array( 'selectImage' => esc_html__('Select Fallback Image', 'bluesky-connctor'), 'useImage' => esc_html__('Use this image', 'bluesky-connctor'), 'publishing' => esc_html__('Publishing...', 'bluesky-connctor'), 'retrying' => esc_html__('Retrying...', 'bluesky-connctor'), 'reposting' => esc_html__('Reposting...', 'bluesky-connctor'), 'retry' => esc_html__('Retry Post', 'bluesky-connctor'), 'repost' => esc_html__('Post Again', 'bluesky-connctor'), 'error' => esc_html__('An error occurred. Please try again.', 'bluesky-connctor'), 'confirmRepost' => esc_html__('Are you sure you want to post this content again?', 'bluesky-connctor') ), 'nonce' => wp_create_nonce('bluesky_post_now'), )); wp_enqueue_style( 'bluesky-admin', BLUESKY_CONNECTOR_URL . 'assets/css/admin.css', array(), BLUESKY_CONNECTOR_VERSION ); } /** * Handle post saving and immediate posting to Bluesky * * @param int $post_id Post ID. * @param WP_Post $post Post object. */ public function handle_post_save($post_id, $post) { error_log('[Bluesky Connector] handle_post_save called for post ' . $post_id); // Skip autosaves if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { error_log('[Bluesky Connector] Skipping - autosave'); return; } // Check post type if ('post' !== $post->post_type) { error_log('[Bluesky Connector] Skipping - not a post: ' . $post->post_type); return; } // Only proceed for published posts if ('publish' !== $post->post_status) { error_log('[Bluesky Connector] Skipping - not published: ' . $post->post_status); return; } $already_posted = get_post_meta($post_id, '_bluesky_posted', true); $had_image = get_post_meta($post_id, '_bluesky_had_image', true); $has_image_now = has_post_thumbnail($post->ID); error_log('[Bluesky Connector] Post status check:'); error_log('[Bluesky Connector] - Already posted: ' . ($already_posted ? 'yes' : 'no')); error_log('[Bluesky Connector] - Had image: ' . ($had_image ? 'yes' : 'no')); error_log('[Bluesky Connector] - Has image now: ' . ($has_image_now ? 'yes' : 'no')); // If already posted, only proceed if image status has changed if ($already_posted) { if ($had_image === $has_image_now) { error_log('[Bluesky Connector] Skipping - already posted and image status unchanged'); return; } error_log('[Bluesky Connector] Image status changed, proceeding with update'); } // Only check nonce if this is a manual save from the editor if (isset($_POST['bluesky_post_meta_box_nonce'])) { if (!wp_verify_nonce( sanitize_text_field(wp_unslash($_POST['bluesky_post_meta_box_nonce'])), 'bluesky_post_meta_box' )) { error_log('[Bluesky Connector] Skipping - invalid nonce on manual save'); return; } } error_log('[Bluesky Connector] Calling post_to_bluesky'); // Post to Bluesky $response = $this->post_to_bluesky($post); // If successful, update image status if (!isset($response['error'])) { update_post_meta($post_id, '_bluesky_had_image', $has_image_now); } } /** * Post content to Bluesky * * @param WP_Post $post Post object. * @return array Response from Bluesky API. */ private function post_to_bluesky($post) { try { error_log('[Bluesky Connector] Starting post_to_bluesky for post ' . $post->ID); update_post_meta($post->ID, '_bluesky_status', 'pending'); $identifier = get_option('bluesky_identifier'); $password = get_option('bluesky_password'); if (empty($identifier) || empty($password)) { throw new Exception('Bluesky identifier or password is missing.'); } // Log settings (without password) error_log('[Bluesky Connector] Using identifier: ' . $identifier); // Authenticate and get access token $auth = new Bluesky_Auth($identifier, $password); $token = $auth->get_access_token(); if (isset($token['error'])) { throw new Exception($token['error']); } // Create Post_Formatter instance $formatter = new Post_Formatter($token, get_option('bluesky_did')); $response = $formatter->format_and_post($post); // Update meta data if successful if (!isset($response['error'])) { $record_key = $response['uri']; if (strpos($response['uri'], 'at://') === 0) { $parts = explode('/', $response['uri']); $record_key = end($parts); } update_post_meta($post->ID, '_bluesky_posted', current_time('mysql')); update_post_meta($post->ID, '_bluesky_post_id', sanitize_text_field($record_key)); update_post_meta($post->ID, '_bluesky_status', 'success'); update_option('bluesky_last_post_time', time()); } return $response; } catch (Exception $e) { update_post_meta($post->ID, '_bluesky_status', 'error'); update_post_meta($post->ID, '_bluesky_error', sanitize_text_field($e->getMessage())); $this->log_error('Bluesky posting failed: ' . $e->getMessage()); return array('error' => $e->getMessage()); } } /** * Log error message if debug mode is enabled * * @param string $message Error message to log. * @param array $data Optional data to log. */ // In bluesky-connector.php, inside the Bluesky_Connector class private function log_error($message, $data = array()) { if (defined('WP_DEBUG') && WP_DEBUG) { if (is_array($data) || is_object($data)) { $data = wp_json_encode($data); } error_log(sprintf('[Bluesky Connector] %s | Data: %s', $message, $data)); } } /** * Handle AJAX immediate post request */ public function handle_immediate_post() { check_ajax_referer('bluesky_post_now', 'nonce'); if (!current_user_can('edit_posts')) { wp_send_json_error(array( 'message' => esc_html__('Permission denied.', 'bluesky-connctor') )); } $post_id = filter_input(INPUT_POST, 'post_id', FILTER_VALIDATE_INT); if (!$post_id) { wp_send_json_error(array( 'message' => esc_html__('Invalid post ID.', 'bluesky-connctor') )); } $post = get_post($post_id); if (!$post) { wp_send_json_error(array( 'message' => esc_html__('Post not found.', 'bluesky-connctor') )); } $response = $this->post_to_bluesky($post); if (isset($response['error'])) { wp_send_json_error(array( 'message' => esc_html($response['error']) )); } else { wp_send_json_success(array( 'message' => esc_html__('Posted successfully to Bluesky.', 'bluesky-connctor'), 'post_id' => esc_html($response['uri']) )); } } /** * Add admin menu items */ public function add_admin_menu() { add_options_page( esc_html__('Bluesky Settings', 'bluesky-connctor'), esc_html__('Bluesky', 'bluesky-connctor'), 'manage_options', 'bluesky-settings', array($this, 'render_settings_page') ); } /** * Add post meta box */ public function add_post_meta_box() { add_meta_box( 'bluesky_post_meta', esc_html__('Bluesky Status', 'bluesky-connctor'), array($this, 'render_post_meta_box'), 'post', 'side', 'default' ); } /** * Render post meta box * * @param WP_Post $post Post object. */ public function render_post_meta_box($post) { // Add this line at the very start of the function wp_nonce_field('bluesky_post_meta_box', 'bluesky_post_meta_box_nonce'); $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); require BLUESKY_CONNECTOR_DIR . 'templates/post-meta-box.php'; } /** * Add settings link to plugins page * * @param array $links Plugin action links. * @return array Modified plugin action links. */ public function add_settings_link($links) { $settings_link = sprintf( '%s', esc_url(admin_url('options-general.php?page=bluesky-settings')), esc_html__('Settings', 'bluesky-connctor') ); array_unshift($links, $settings_link); return $links; } /** * Render settings page */ public function render_settings_page() { if (!current_user_can('manage_options')) { return; } $settings = array( 'identifier' => esc_html(get_option('bluesky_identifier', '')), 'connection_status' => get_option('bluesky_connection_status', ''), 'last_error' => esc_html(get_option('bluesky_last_error', '')) ); require BLUESKY_CONNECTOR_DIR . 'templates/settings-page.php'; } /** * Display admin notices */ public function admin_notices() { if (!current_user_can('manage_options')) { return; } $screen = get_current_screen(); if ($screen->id !== 'settings_page_bluesky-settings') { return; } settings_errors('bluesky_connector_settings'); } /** * Plugin activation */ public function activate() { // Create necessary options with default values add_option('bluesky_domain', BLUESKY_API_DOMAIN); add_option('bluesky_post_format', 'title-excerpt-link'); // Ensure languages directory exists $languages_dir = dirname(plugin_basename(__FILE__)) . '/languages/'; if (!file_exists($languages_dir)) { wp_mkdir_p($languages_dir); } // Clear any existing caches wp_cache_delete('bluesky_settings', 'options'); } /** * Plugin deactivation */ public function deactivate() { // Clean up transients and caches delete_transient('bluesky_auth_check'); wp_cache_delete('bluesky_settings', 'options'); // Log deactivation if in debug mode $this->log_error('Plugin deactivated'); } } /** * Clean up plugin data * Used during uninstall */ function bluesky_connector_uninstall() { if (!defined('WP_UNINSTALL_PLUGIN')) { return; } // Remove all plugin options $options = array( 'bluesky_domain', 'bluesky_identifier', 'bluesky_password', 'bluesky_access_jwt', 'bluesky_refresh_jwt', 'bluesky_did', 'bluesky_post_queue', 'bluesky_connection_status', 'bluesky_last_error', 'bluesky_post_format', 'bluesky_connector_settings', 'bluesky_token_created', 'bluesky_last_post_time' ); foreach ($options as $option) { delete_option($option); } // Remove all post meta $meta_keys = array( '_bluesky_posted', '_bluesky_status', '_bluesky_error', '_bluesky_post_id', '_bluesky_had_image' // Added new meta key ); // Get all posts $posts = get_posts(array( 'posts_per_page' => -1, 'post_type' => 'any', 'fields' => 'ids' )); // Delete meta and clear cache for each post foreach ($posts as $post_id) { foreach ($meta_keys as $meta_key) { delete_post_meta($post_id, $meta_key); } clean_post_cache($post_id); } // Clear settings cache wp_cache_delete('bluesky_settings', 'options'); } // Initialize plugin function bluesky_connector_init() { return Bluesky_Connector::get_instance(); } add_action('plugins_loaded', 'bluesky_connector_init'); // Register uninstall hook register_uninstall_hook(__FILE__, 'bluesky_connector_uninstall');