Plugin Already Installed

You have already installed this plugin. It might have a different name but it has the same functionality - it might be called something like "Adaptify SEO One-Click Publishing", "SEO One-Click Publishing", or a custom-branded SEO publishing plugin.

If you would like to replace the existing plugin with this version, please remove the old one first. Keep in mind you might have to set up the integration again.

Go to Plugins Page

'GET', 'callback' => 'adpty_rest_plugin_info', 'permission_callback' => 'adpty_rest_manage_options_permission' )); // The dashboard asks for a homepage-tag sync right after Technical SEO // Fixes are applied, instead of waiting for the 15-minute cron. Admin // only: the backend authenticates with the site's application password. register_rest_route('adaptify/v1', '/sync-now', array( 'methods' => 'POST', 'callback' => 'adpty_rest_sync_now', 'permission_callback' => 'adpty_rest_manage_options_permission' )); }); if (!function_exists('adpty_rest_manage_options_permission')) { function adpty_rest_manage_options_permission() { return current_user_can('manage_options'); } } if (!function_exists('adpty_autofix_option_is_set')) { function adpty_autofix_option_is_set() { $bot_id = get_option('adpty_seo_autofix_bot_id', ''); return is_string($bot_id) && preg_match('/^[A-Za-z0-9_-]{1,64}$/', $bot_id) === 1; } } if (!function_exists('adpty_rest_sync_now')) { function adpty_rest_sync_now() { // One sync per minute per site, whoever asks. if (get_transient('adpty_seo_sync_now_lock')) { return new WP_REST_Response(array( 'synced' => false, 'reason' => 'rate_limited', 'autofix_active' => adpty_autofix_option_is_set(), ), 200); } set_transient('adpty_seo_sync_now_lock', 1, 60); // The cron's own 20-second lock must not swallow this call. delete_transient('adpty_seo_msvalidate_sync_lock'); adpty_seo_log('[adpty-sync-now] Sync requested by backend'); $before = intval(get_option('adpty_seo_msvalidate_last_sync', 0)); adpty_sync_home_meta_tag(); $after = intval(get_option('adpty_seo_msvalidate_last_sync', 0)); $synced = $after > $before || $after >= (time() - 30); return new WP_REST_Response(array( 'synced' => $synced, 'reason' => $synced ? 'ok' : 'backend_unreachable', 'autofix_active' => adpty_autofix_option_is_set(), ), 200); } } if (!function_exists('adpty_rest_plugin_info')) { function adpty_rest_plugin_info() { global $wp_version; $plugin_data = get_file_data(__FILE__, array( 'Version' => 'Version', 'Name' => 'Plugin Name' )); return array( 'name' => $plugin_data['Name'], 'version' => $plugin_data['Version'], 'status' => 'active', 'wp_version' => $wp_version, 'php_version' => phpversion(), 'permalink_structure' => get_option('permalink_structure'), 'is_multisite' => is_multisite(), 'home_url' => home_url() ); } } // Notify backend of plugin lifecycle events (activation/deactivation) if (!function_exists('adpty_notify_plugin_event')) { function adpty_notify_plugin_event($event) { $post_data = array( 'site_url' => get_site_url(), 'event' => $event, 'plugin_version' => ADPTY_SEO_PLUGIN_VERSION, 'wp_version' => get_bloginfo('version') ); wp_remote_post('https://api.ttsp4712.ai/open/wp-plugin-event', array( 'method' => 'POST', 'headers' => array('Content-Type' => 'application/json; charset=utf-8'), 'body' => wp_json_encode($post_data), 'data_format' => 'body', 'timeout' => 10, 'blocking' => false )); } } // Activation function if (!function_exists('adpty_seo_plugin_activate')) { function adpty_seo_plugin_activate() { // Notify backend of activation adpty_notify_plugin_event('activated'); // Prime homepage verification tag cache on activation if (function_exists('adpty_sync_home_meta_tag')) { adpty_sync_home_meta_tag(); } } } // Activation hook register_activation_hook( __FILE__, 'adpty_seo_plugin_activate' ); // Register the settings page add_action('admin_menu', 'adpty_seo_settings_page'); if (!function_exists('adpty_seo_settings_page')) { function adpty_seo_settings_page() { add_options_page( 'Adaptify SEO One-Click Publishing Settings', 'Adaptify SEO', 'manage_options', 'one-click-seo-settings', 'adpty_seo_settings_page_html' ); } } add_action('admin_enqueue_scripts', 'adpty_seo_enqueue_admin_assets'); if (!function_exists('adpty_seo_enqueue_admin_assets')) { function adpty_seo_enqueue_admin_assets($hook_suffix) { if ($hook_suffix !== 'settings_page_one-click-seo-settings') { return; } wp_enqueue_style( 'adpty-seo-admin-settings', plugin_dir_url(__FILE__) . 'assets/admin-settings.css', array(), ADPTY_SEO_PLUGIN_VERSION ); } } if (!function_exists('adpty_seo_settings_page_html')) { function adpty_seo_settings_page_html() { if (!current_user_can('manage_options')) { return; } global $adpty_plugin_error_message; if (isset($_POST['adpty_seo_settings_submitted']) && $_POST['adpty_seo_settings_submitted'] == 'submitted') { check_admin_referer('adpty_seo_save_settings', 'adpty_seo_settings_nonce'); update_option('adpty_seo_enable_meta_sync', isset($_POST['adpty_seo_enable_meta_sync']) ? '1' : '0'); echo '

Settings saved.

'; } if (isset($_POST['adpty_seo_connect_site'])) { check_admin_referer('adpty_seo_connect_site', 'adpty_seo_connect_nonce'); $adpty_connection_code = isset($_POST['adpty_connection_code']) ? sanitize_text_field(wp_unslash($_POST['adpty_connection_code'])) : ''; if (adpty_create_application_password_for_user($adpty_connection_code)) { echo '

Connection request sent to Adaptify. Return to Adaptify; it will show connected once the request is saved.

'; } else if (!empty($adpty_plugin_error_message)) { echo '

' . esc_html($adpty_plugin_error_message) . '

'; } } if (isset($_POST['delete_application_password'])) { check_admin_referer('adpty_seo_delete_passwords', 'adpty_seo_delete_nonce'); if (adpty_delete_application_password_for_user()) { echo '

Adaptify application passwords deleted for this WordPress user.

'; } else if (!empty($adpty_plugin_error_message)) { echo '

' . esc_html($adpty_plugin_error_message) . '

'; } } $enable_meta_sync = adpty_get_first_option(array('adpty_seo_enable_meta_sync', 'adaptify_seo_enable_meta_sync'), '1'); $adpty_connected_at = adpty_get_first_option(array('adpty_seo_connected_at', 'adaptify_seo_connected_at'), ''); $adpty_connected_user = adpty_get_first_option(array('adpty_seo_connected_user', 'adaptify_seo_connected_user'), ''); $adpty_connected_site = adpty_get_first_option(array('adpty_seo_connected_site', 'adaptify_seo_connected_site'), ''); $adpty_application_password_found = false; if (class_exists('WP_Application_Passwords')) { $current_user_id = get_current_user_id(); $current_user = get_userdata($current_user_id); $app_passwords = WP_Application_Passwords::get_user_application_passwords($current_user_id); if (!empty($app_passwords)) { foreach ($app_passwords as $app_password) { $app_name = isset($app_password['name']) ? $app_password['name'] : ''; if (adpty_is_plugin_application_password_name($app_name)) { $adpty_application_password_found = true; $adpty_connected_user = $current_user ? $current_user->user_login : $adpty_connected_user; break; } } } } $adpty_is_connected = $adpty_application_password_found; $adpty_connected_at_display = ''; if (!empty($adpty_connected_at)) { $adpty_connected_at_display = date_i18n(get_option('date_format') . ' ' . get_option('time_format'), intval($adpty_connected_at)); } $adpty_connect_button_label = $adpty_is_connected ? 'Reconnect to Adaptify' : 'Connect to Adaptify'; ?>

Adaptify SEO One-Click Publishing

Connect this WordPress site to Adaptify so scheduled content can be published through the WordPress REST API.

Connect to Adaptify

Paste the connection code from Adaptify to explicitly authorize this WordPress site.

Connected to Adaptify

Last connected . This WordPress user has an Adaptify application password.

You do not need to reconnect unless Adaptify asks for a new connection code.

What happens when you connect

  • WordPress creates a native Application Password for your current administrator account.
  • This plugin securely sends the site URL, WordPress username, generated Application Password, and Adaptify connection code to Adaptify.
  • Adaptify uses that access to publish scheduled content through the WordPress REST API.

Find this code in Adaptify under Website Settings → One-Click Content. Choose the Adaptify Plugin option, copy the connection code, paste it here, then click Connect to Adaptify.

Publishing settings

Revoke Adaptify access

You can revoke this access from your WordPress user profile or delete Adaptify application passwords for this WordPress user here.

get_param('yoast_meta'); if (is_array($yoast_meta)) { if (isset($yoast_meta['yoast_wpseo_metadesc'])) { update_post_meta($post->ID, 'rank_math_description', $yoast_meta['yoast_wpseo_metadesc']); } if (isset($yoast_meta['yoast_wpseo_metakeywords'])) { update_post_meta($post->ID, 'rank_math_focus_keyword', $yoast_meta['yoast_wpseo_metakeywords']); } if (isset($yoast_meta['yoast_wpseo_title'])) { update_post_meta($post->ID, 'rank_math_title', $yoast_meta['yoast_wpseo_title']); } } } }, 10, 3); if (!class_exists('Adpty_Yoast_To_REST_API')) { class Adpty_Yoast_To_REST_API { protected $keys = array( 'yoast_wpseo_focuskw', 'yoast_wpseo_title', 'yoast_wpseo_metadesc', 'yoast_wpseo_linkdex', 'yoast_wpseo_metakeywords', 'yoast_wpseo_meta-robots-noindex', 'yoast_wpseo_meta-robots-nofollow', 'yoast_wpseo_meta-robots-adv', 'yoast_wpseo_canonical', 'yoast_wpseo_redirect', 'yoast_wpseo_opengraph-title', 'yoast_wpseo_opengraph-description', 'yoast_wpseo_opengraph-image', 'yoast_wpseo_twitter-title', 'yoast_wpseo_twitter-description', 'yoast_wpseo_twitter-image' ); function __construct() { add_action( 'rest_api_init', array( $this, 'add_yoast_data' ) ); } function add_yoast_data() { // Posts register_rest_field( 'post', 'yoast_meta', array( 'get_callback' => array( $this, 'wp_api_encode_yoast' ), 'update_callback' => array( $this, 'wp_api_update_yoast' ), 'schema' => null, ) ); // Pages register_rest_field( 'page', 'yoast_meta', array( 'get_callback' => array( $this, 'wp_api_encode_yoast' ), 'update_callback' => array( $this, 'wp_api_update_yoast' ), 'schema' => null, ) ); // Category register_rest_field( 'category', 'yoast_meta', array( 'get_callback' => array( $this, 'wp_api_encode_yoast_category' ), 'update_callback' => null, 'schema' => null, ) ); // Tag register_rest_field( 'tag', 'yoast_meta', array( 'get_callback' => array( $this, 'wp_api_encode_yoast_tag' ), 'update_callback' => null, 'schema' => null, ) ); // Public custom post types $types = get_post_types( array( 'public' => true, '_builtin' => false ) ); foreach ( $types as $key => $type ) { register_rest_field( $type, 'yoast_meta', array( 'get_callback' => array( $this, 'wp_api_encode_yoast' ), 'update_callback' => array( $this, 'wp_api_update_yoast' ), 'schema' => null, ) ); } } /** * Updates post meta with values from post/put request. * * @param array $value * @param object $data * @param string $field_name * * @return array */ function wp_api_update_yoast( $value, $data, $field_name ) { foreach ( $value as $k => $v ) { if ( in_array( $k, $this->keys ) ) { ! empty( $k ) ? update_post_meta( $data->ID, '_' . $k, $v ) : null; } } return $this->wp_api_encode_yoast( $data->ID, null, null ); } function wp_api_encode_yoast( $p, $field_name, $request ) { $wpseo_frontend = Adpty_WPSEO_Frontend_To_REST_API::get_instance(); $wpseo_frontend->reset(); $post_id = is_array( $p ) && isset( $p['id'] ) ? intval( $p['id'] ) : intval( $p ); $adpty_yoast_query = new WP_Query( array( 'p' => $post_id, // ID of a page, post, or custom type 'post_type' => 'any' ) ); if ( $adpty_yoast_query->have_posts() ) { $adpty_yoast_query->the_post(); } $yoast_meta = array( 'yoast_wpseo_title' => $wpseo_frontend->get_content_title(), 'yoast_wpseo_metadesc' => $wpseo_frontend->metadesc( false ), 'yoast_wpseo_canonical' => $wpseo_frontend->canonical( false ), ); /** * Filter the returned yoast meta. * * @since 1.4.2 * @param array $yoast_meta Array of metadata to return from Yoast. * @param \WP_Post $p The current post object. * @param \WP_REST_Request $request The REST request. * @return array $yoast_meta Filtered meta array. */ $yoast_meta = apply_filters( 'adpty_wpseo_to_api_yoast_meta', $yoast_meta, $p, $request ); wp_reset_postdata(); return (array) $yoast_meta; } private function wp_api_encode_taxonomy() { $wpseo_frontend = Adpty_WPSEO_Frontend_To_REST_API::get_instance(); $wpseo_frontend->reset(); $yoast_meta = array( 'yoast_wpseo_title' => $wpseo_frontend->get_taxonomy_title(), 'yoast_wpseo_metadesc' => $wpseo_frontend->metadesc( false ), ); /** * Filter the returned yoast meta for a taxonomy. * * @since 1.4.2 * @param array $yoast_meta Array of metadata to return from Yoast. * @return array $yoast_meta Filtered meta array. */ $yoast_meta = apply_filters( 'adpty_wpseo_to_api_yoast_taxonomy_meta', $yoast_meta ); return (array) $yoast_meta; } function wp_api_encode_yoast_category( $category ) { $adpty_yoast_query = new WP_Query( array( 'cat' => $category['id'], 'posts_per_page' => 1, ) ); if ( $adpty_yoast_query->have_posts() ) { $adpty_yoast_query->the_post(); } $res = $this->wp_api_encode_taxonomy(); wp_reset_postdata(); return $res; } function wp_api_encode_yoast_tag( $tag ) { $adpty_yoast_query = new WP_Query( array( 'tag_id' => $tag['id'], 'posts_per_page' => 1, ) ); if ( $adpty_yoast_query->have_posts() ) { $adpty_yoast_query->the_post(); } $res = $this->wp_api_encode_taxonomy(); wp_reset_postdata(); return $res; } } } if (!function_exists('adpty_wpapi_yoast_init')) { function adpty_wpapi_yoast_init() { if ( class_exists( 'WPSEO_Frontend' ) ) { include __DIR__ . '/classes/class-wpseo-frontend-to-rest-api.php'; $adpty_yoast_to_rest_api = new Adpty_Yoast_To_REST_API(); } } } if (!function_exists('adpty_create_application_password_for_user')) { function adpty_create_application_password_for_user($adpty_connection_code = '') { global $adpty_plugin_error_message; if ( ! current_user_can( 'manage_options' ) ) { return false; } $adpty_connection_code = trim($adpty_connection_code); if (empty($adpty_connection_code)) { $adpty_plugin_error_message = "Paste the Adaptify connection code before connecting."; add_action( 'admin_notices', 'adpty_application_password_fail' ); return false; } $user_id = get_current_user_id(); $custom_date = gmdate('YmdHis'); $app_name = 'adpty-connect-' . $custom_date; $user = get_userdata($user_id); if (!$user) { $adpty_plugin_error_message = "Can't access your username, please contact support."; add_action( 'admin_notices', 'adpty_application_password_fail' ); return false; } $new_password = WP_Application_Passwords::create_new_application_password( $user_id, array( 'name' => $app_name ) ); if ( is_wp_error( $new_password ) ) { $adpty_plugin_error_message = "Error creating application password."; add_action( 'admin_notices', 'adpty_application_password_fail' ); return false; } $post_data = array( 'user_name' => $user->user_login, 'application_pw' => $new_password[0], 'site_url' => get_site_url(), 'connection_code' => $adpty_connection_code ); $response = wp_remote_post( 'https://api.ttsp4712.ai/add-wp-pw', array( 'method' => 'POST', 'headers' => array('Content-Type' => 'application/json; charset=utf-8'), 'body' => wp_json_encode($post_data), 'data_format' => 'body' )); if ( is_wp_error( $response ) ) { $adpty_plugin_error_message = "Adaptify connection request did not finish: " . $response->get_error_message() . ". Return to Adaptify and check whether the site connected before retrying."; add_action( 'admin_notices', 'adpty_application_password_fail' ); return false; } $response_code = wp_remote_retrieve_response_code( $response ); $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); $connection_accepted = ( $response_code >= 200 && $response_code < 300 && is_array( $response_body ) && ! empty( $response_body['bot_id'] ) && ! empty( $response_body['status'] ) && $response_body['status'] === 'connected' ); if ( ! $connection_accepted ) { if (!empty($new_password[1]['uuid'])) { $delete_result = WP_Application_Passwords::delete_application_password( $user_id, $new_password[1]['uuid'] ); if ( is_wp_error( $delete_result ) ) { $adpty_plugin_error_message = "Adaptify connection failed and WordPress could not remove the new Application Password: " . $delete_result->get_error_message(); add_action( 'admin_notices', 'adpty_application_password_fail' ); return false; } } $adpty_plugin_error_message = "Adaptify connection failed. Please try again or contact support."; add_action( 'admin_notices', 'adpty_application_password_fail' ); return false; } update_option('adpty_seo_connected_at', current_time('timestamp')); update_option('adpty_seo_connected_user', $user->user_login); update_option('adpty_seo_connected_site', get_site_url()); return true; } } if (!function_exists('adpty_delete_application_password_for_user')) { function adpty_delete_application_password_for_user() { global $adpty_plugin_error_message; if ( ! current_user_can( 'manage_options' ) ) { return false; } $user_id = get_current_user_id(); $user = get_userdata($user_id); if (!$user) { $adpty_plugin_error_message = "Can't access your username, please contact support."; add_action( 'admin_notices', 'adpty_application_password_fail' ); return false; } $app_passwords = WP_Application_Passwords::get_user_application_passwords( $user_id ); $deleted_count = 0; $has_stored_connection = ( adpty_get_first_option(array('adpty_seo_connected_at', 'adaptify_seo_connected_at'), '') !== '' || adpty_get_first_option(array('adpty_seo_connected_user', 'adaptify_seo_connected_user'), '') !== '' || adpty_get_first_option(array('adpty_seo_connected_site', 'adaptify_seo_connected_site'), '') !== '' ); if ( !empty($app_passwords) ) { foreach ( $app_passwords as $app_password ) { $app_name = isset($app_password['name']) ? $app_password['name'] : ''; if (adpty_is_plugin_application_password_name($app_name)) { if (empty($app_password['uuid'])) { continue; } $delete_result = WP_Application_Passwords::delete_application_password( $user_id, $app_password['uuid'] ); if ( is_wp_error( $delete_result ) ) { $adpty_plugin_error_message = "Could not delete an Adaptify application password: " . $delete_result->get_error_message(); add_action( 'admin_notices', 'adpty_application_password_fail' ); return false; } $deleted_count++; } } } if ($deleted_count === 0 && !$has_stored_connection) { $adpty_plugin_error_message = "No Adaptify application passwords found to delete."; add_action( 'admin_notices', 'adpty_application_password_fail' ); return false; } adpty_delete_options(array('adpty_seo_connected_at', 'adaptify_seo_connected_at')); adpty_delete_options(array('adpty_seo_connected_user', 'adaptify_seo_connected_user')); adpty_delete_options(array('adpty_seo_connected_site', 'adaptify_seo_connected_site')); return true; } } if (!function_exists('adpty_application_password_fail')) { function adpty_application_password_fail() { global $adpty_plugin_error_message; printf( '

%s

', esc_html( $adpty_plugin_error_message ) ); } } // ============================================ // WP-CRON FALLBACK PUBLISHING (429 Recovery) // ============================================ // Add custom cron interval (15 minutes) add_filter('cron_schedules', function($schedules) { $schedules['fifteen_minutes'] = array( 'interval' => 900, 'display' => __('Every 15 Minutes', 'ada-seo-by-adaptify') ); return $schedules; }); // Ensure cron is always scheduled (self-heals if cron was cleared by DB issues, migrations, etc.) // This is the recommended pattern per WordPress Plugin Handbook if (!function_exists('adpty_schedule_fallback_cron')) { function adpty_schedule_fallback_cron() { $legacy_timestamp = wp_next_scheduled('adaptify_fallback_publish_hook'); if ($legacy_timestamp) { wp_unschedule_event($legacy_timestamp, 'adaptify_fallback_publish_hook'); } if (!wp_next_scheduled('adpty_fallback_publish_hook')) { wp_schedule_event(time(), 'fifteen_minutes', 'adpty_fallback_publish_hook'); adpty_seo_log('[adpty-fallback] Scheduled fallback cron job'); } } } add_action('init', 'adpty_schedule_fallback_cron'); // Unschedule fallback cron and notify backend on plugin deactivation if (!function_exists('adpty_unschedule_fallback_cron')) { function adpty_unschedule_fallback_cron() { // Notify backend before unscheduling (so the request can still go out) adpty_notify_plugin_event('deactivated'); $timestamp = wp_next_scheduled('adpty_fallback_publish_hook'); if ($timestamp) { wp_unschedule_event($timestamp, 'adpty_fallback_publish_hook'); adpty_seo_log('[adpty-fallback] Unscheduled fallback cron job'); } $legacy_timestamp = wp_next_scheduled('adaptify_fallback_publish_hook'); if ($legacy_timestamp) { wp_unschedule_event($legacy_timestamp, 'adaptify_fallback_publish_hook'); } $legacy_sync_timestamp = wp_next_scheduled('adaptify_sync_home_meta_tag_hook'); if ($legacy_sync_timestamp) { wp_unschedule_event($legacy_sync_timestamp, 'adaptify_sync_home_meta_tag_hook'); } // Clean up cached verification tags adpty_delete_options(array('adpty_seo_verification_tags_html', 'adaptify_seo_verification_tags_html', 'seo_plugin_verification_tags_html')); adpty_delete_options(array('adpty_seo_msvalidate_content', 'adaptify_seo_msvalidate_content', 'seo_plugin_msvalidate_content')); adpty_delete_options(array('adpty_seo_msvalidate_last_sync', 'adaptify_seo_msvalidate_last_sync', 'seo_plugin_msvalidate_last_sync')); delete_transient('adpty_seo_msvalidate_sync_lock'); delete_transient('adaptify_seo_msvalidate_sync_lock'); delete_transient('seo_plugin_msvalidate_sync_lock'); delete_transient('adpty_seo_sync_now_lock'); delete_option('adpty_seo_autofix_bot_id'); } } register_deactivation_hook(__FILE__, 'adpty_unschedule_fallback_cron'); // Hook the fallback processing function add_action('adpty_fallback_publish_hook', 'adpty_process_fallback_articles'); add_action('adpty_fallback_publish_hook', 'adpty_sync_home_meta_tag'); add_action('adpty_sync_home_meta_tag_hook', 'adpty_sync_home_meta_tag'); // Main function to process fallback articles if (!function_exists('adpty_process_fallback_articles')) { function adpty_process_fallback_articles() { adpty_seo_log('[adpty-fallback] Starting fallback article check'); $site_url = get_site_url(); // Fetch pending articles from Adaptify API. Report plugin_version so the // backend knows this site can perform update-in-place republishes and // only then dispatches them (older plugins omit the field). $response = wp_remote_post('https://api.ttsp4712.ai/open/wp-pending-articles', array( 'method' => 'POST', 'headers' => array('Content-Type' => 'application/json'), 'body' => wp_json_encode(array('site_url' => $site_url, 'plugin_version' => ADPTY_SEO_PLUGIN_VERSION)), 'timeout' => 30 )); if (is_wp_error($response)) { adpty_seo_log('[adpty-fallback] Error fetching pending articles: ' . $response->get_error_message()); return; } $body = json_decode(wp_remote_retrieve_body($response), true); if (empty($body['articles']) || !is_array($body['articles'])) { adpty_seo_log('[adpty-fallback] No pending articles found'); return; } adpty_seo_log('[adpty-fallback] Found ' . count($body['articles']) . ' pending articles'); foreach ($body['articles'] as $article) { adpty_publish_article_locally($article, $site_url); } } } // Publish a single article locally using wp_insert_post if (!function_exists('adpty_publish_article_locally')) { function adpty_publish_article_locally($article, $site_url) { adpty_seo_log('[adpty-fallback] Publishing: ' . $article['title']); // Prepare post data $post_data = array( 'post_title' => sanitize_text_field($article['title']), 'post_content' => wp_kses_post($article['content']), 'post_status' => isset($article['status']) ? $article['status'] : 'publish', 'post_type' => 'post', 'comment_status'=> isset($article['comment_status']) ? $article['comment_status'] : 'closed', 'ping_status' => 'closed' ); // Set excerpt if provided if (!empty($article['excerpt'])) { $post_data['post_excerpt'] = sanitize_text_field($article['excerpt']); } // Set category if provided if (!empty($article['category_id'])) { $post_data['post_category'] = array(intval($article['category_id'])); } elseif (!empty($article['category_instruction'])) { // Fallback when upstream category selection was unavailable (REST blocked by security plugin, etc.) // Extract a literal category name from the instruction, look it up locally via get_term_by. // If not found, fall through to site default — no auto-create. $category_instruction = sanitize_text_field($article['category_instruction']); $cat_name = null; // Tier 1: quoted extraction — straight double, straight single, or curly (typographic) quotes if (preg_match('/"([^"]+)"/', $category_instruction, $matches)) { $cat_name = $matches[1]; } elseif (preg_match("/\x27([^\x27]+)\x27/", $category_instruction, $matches)) { $cat_name = $matches[1]; } elseif (preg_match('/\x{201C}(.+?)\x{201D}/u', $category_instruction, $matches)) { $cat_name = $matches[1]; } elseif (preg_match('/\x{2018}(.+?)\x{2019}/u', $category_instruction, $matches)) { $cat_name = $matches[1]; } elseif (str_word_count($category_instruction) <= 3 && strlen($category_instruction) <= 60) { // Tier 2: bare-name fallback — instruction itself is short enough to be a literal category name $cat_name = $category_instruction; } if ($cat_name) { $cat_name = trim($cat_name); adpty_seo_log('[seo-fallback] Category name extracted: "' . $cat_name . '" from instruction: ' . $category_instruction); $term = get_term_by('name', $cat_name, 'category'); if ($term && !is_wp_error($term)) { $post_data['post_category'] = array($term->term_id); adpty_seo_log('[seo-fallback] Matched category by name: "' . $cat_name . '" (id=' . $term->term_id . ')'); } else { adpty_seo_log('[seo-fallback] Category not found by name "' . $cat_name . '" - leaving post_category unset (will fall back to site default).'); } } else { adpty_seo_log('[seo-fallback] Could not extract category name from instruction: ' . $category_instruction); } } // Set author if provided if (!empty($article['author_id'])) { $post_data['post_author'] = intval($article['author_id']); } elseif (!empty($article['custom_author_name'])) { // Fallback: try simple name matching when LLM matching failed $author_name = sanitize_text_field($article['custom_author_name']); $users = get_users(array( 'role__in' => array('administrator', 'editor', 'author', 'contributor'), 'fields' => array('ID', 'display_name', 'user_login') )); $found_author = null; // Try exact match first foreach ($users as $user) { if (strcasecmp($user->display_name, $author_name) === 0 || strcasecmp($user->user_login, $author_name) === 0) { $found_author = $user->ID; break; } } // Try partial match if no exact match if (!$found_author) { foreach ($users as $user) { if (stripos($user->display_name, $author_name) !== false || stripos($author_name, $user->display_name) !== false) { $found_author = $user->ID; break; } } } if ($found_author) { $post_data['post_author'] = $found_author; adpty_seo_log('[adpty-fallback] Matched author "' . $author_name . '" to user ID: ' . $found_author); } else { // Final fallback: use first administrator $admins = get_users(array('role' => 'administrator', 'number' => 1, 'fields' => array('ID'))); if (!empty($admins)) { $post_data['post_author'] = $admins[0]->ID; adpty_seo_log('[adpty-fallback] No author match for "' . $author_name . '", using first admin: ' . $admins[0]->ID); } else { adpty_seo_log('[adpty-fallback] No author match and no admins found for: ' . $author_name); } } } // Set slug if provided if (!empty($article['slug'])) { $post_data['post_name'] = sanitize_title($article['slug']); } // Set tags if provided if (!empty($article['tag_ids']) && is_array($article['tag_ids'])) { $post_data['tags_input'] = array_map('intval', $article['tag_ids']); } // Update-in-place republish: the backend sends a post_id ONLY for a job // that must land on an existing post (a republish that failed over REST). // Contract (Di review round 3, concern 2): "update the requested post or // change nothing." We therefore NEVER fall through to creating a new post // when a post_id is supplied — that would duplicate the live article, // recreate content the customer deleted, or edit the wrong object. $is_update = false; if (!empty($article['post_id'])) { $republish_target_id = intval($article['post_id']); $target_post = $republish_target_id > 0 ? get_post($republish_target_id) : null; // Fail closed unless the target is a real, live post of the right type. if (!$target_post || $target_post->post_type !== 'post' || $target_post->post_status === 'trash') { $reason = 'republish target post_id ' . $republish_target_id . ' invalid (' . ($target_post ? ('type=' . $target_post->post_type . ' status=' . $target_post->post_status) : 'not_found') . ') — refusing to create a new post'; adpty_seo_log('[adpty-fallback] ' . $reason); adpty_send_callback($article, $site_url, 'failed', 0, '', $reason); return; } $post_data['ID'] = $republish_target_id; $is_update = true; adpty_seo_log('[adpty-fallback] Updating existing post ID: ' . $republish_target_id); } if ($is_update) { // Preserve fields a content/image republish does NOT intend to change // and that the customer may have set in WordPress: author, slug, // categories and tags (Di review round 3, concern 1). Publish date, // parent, menu order and password protection are never set in // $post_data, so wp_update_post leaves them untouched too — whereas // wp_insert_post would reset every omitted field to a creation default // on a SUCCESSFUL update. Only title, content, excerpt and status // carry through. unset( $post_data['post_author'], $post_data['post_name'], $post_data['post_category'], $post_data['tags_input'], // Also preserve discussion settings the customer may have opened // in WordPress — the backend payload always says 'closed', which // would silently flip comments/pings off on every republish. $post_data['comment_status'], $post_data['ping_status'] ); $post_id = wp_update_post($post_data, true); } else { $post_id = wp_insert_post($post_data, true); } if (is_wp_error($post_id)) { adpty_seo_log('[adpty-fallback] Failed to ' . ($is_update ? 'update' : 'create') . ' post: ' . $post_id->get_error_message()); adpty_send_callback($article, $site_url, 'failed', 0, '', $post_id->get_error_message()); return; } adpty_seo_log('[adpty-fallback] ' . ($is_update ? 'Updated' : 'Created') . ' post ID: ' . $post_id); // Handle featured image if provided if (!empty($article['featured_image_url'])) { require_once(ABSPATH . 'wp-admin/includes/media.php'); require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/image.php'); $image_alt = !empty($article['featured_image_alt']) ? sanitize_text_field($article['featured_image_alt']) : sanitize_text_field($article['title']); $image_id = media_sideload_image($article['featured_image_url'], $post_id, $article['title'], 'id'); if (!is_wp_error($image_id)) { set_post_thumbnail($post_id, $image_id); // Set alt text on the attachment update_post_meta($image_id, '_wp_attachment_image_alt', $image_alt); adpty_seo_log('[adpty-fallback] Set featured image: ' . $image_id); } else { adpty_seo_log('[adpty-fallback] Failed to set featured image: ' . $image_id->get_error_message()); } } // Set SEO meta fields for all major SEO plugins $meta_description = !empty($article['meta_description']) ? sanitize_text_field($article['meta_description']) : ''; $focus_keyword = !empty($article['focus_keyword']) ? sanitize_text_field($article['focus_keyword']) : ''; $meta_title = !empty($article['meta_title']) ? sanitize_text_field($article['meta_title']) : sanitize_text_field($article['title']); $secondary_keywords = !empty($article['secondary_keywords']) && is_array($article['secondary_keywords']) ? implode(',', array_map('sanitize_text_field', $article['secondary_keywords'])) : $focus_keyword; $og_article_section = !empty($article['og_article_section']) ? sanitize_text_field($article['og_article_section']) : ''; $og_article_tags = !empty($article['og_article_tags']) ? sanitize_text_field($article['og_article_tags']) : $focus_keyword; $og_image = !empty($article['og_image']) ? esc_url_raw($article['og_image']) : ''; $twitter_image = !empty($article['twitter_image']) ? esc_url_raw($article['twitter_image']) : ''; // Yoast SEO meta if ($meta_description) { update_post_meta($post_id, '_yoast_wpseo_metadesc', $meta_description); } if ($focus_keyword) { update_post_meta($post_id, '_yoast_wpseo_focuskw', $focus_keyword); } if ($meta_title) { update_post_meta($post_id, '_yoast_wpseo_title', $meta_title); } if ($secondary_keywords) { update_post_meta($post_id, '_yoast_wpseo_metakeywords', $secondary_keywords); } // RankMath SEO meta if ($meta_description) { update_post_meta($post_id, 'rank_math_description', $meta_description); } if ($focus_keyword) { update_post_meta($post_id, 'rank_math_focus_keyword', $focus_keyword); } if ($meta_title) { update_post_meta($post_id, 'rank_math_title', $meta_title); } // AIOSEO meta if ($meta_title) { update_post_meta($post_id, '_aioseo_title', $meta_title); update_post_meta($post_id, '_aioseo_og_title', $meta_title); update_post_meta($post_id, '_aioseo_twitter_title', $meta_title); } if ($meta_description) { update_post_meta($post_id, '_aioseo_description', $meta_description); update_post_meta($post_id, '_aioseo_og_description', $meta_description); update_post_meta($post_id, '_aioseo_twitter_description', $meta_description); } if ($og_article_section) { update_post_meta($post_id, '_aioseo_og_article_section', $og_article_section); } if ($og_article_tags) { update_post_meta($post_id, '_aioseo_og_article_tags', $og_article_tags); } if ($og_image) { update_post_meta($post_id, '_aioseo_og_image', $og_image); } if ($twitter_image) { update_post_meta($post_id, '_aioseo_twitter_image', $twitter_image); } // SEOPress meta if ($meta_title) { update_post_meta($post_id, '_seopress_titles_title', $meta_title); update_post_meta($post_id, '_seopress_social_fb_title', $meta_title); update_post_meta($post_id, '_seopress_social_twitter_title', $meta_title); } if ($meta_description) { update_post_meta($post_id, '_seopress_titles_desc', $meta_description); update_post_meta($post_id, '_seopress_social_fb_desc', $meta_description); update_post_meta($post_id, '_seopress_social_twitter_desc', $meta_description); } if ($focus_keyword) { update_post_meta($post_id, '_seopress_analysis_target_kw', $focus_keyword); } if ($og_image) { update_post_meta($post_id, '_seopress_social_fb_img', $og_image); } if ($twitter_image) { update_post_meta($post_id, '_seopress_social_twitter_img', $twitter_image); } // Custom author name meta (used by some themes). Skipped for update-mode // republish: authorship (post_author AND this byline meta) is preserved, // never overwritten by the backend's payload. if (!$is_update && !empty($article['custom_author_name'])) { update_post_meta($post_id, 'custom_author_name', sanitize_text_field($article['custom_author_name'])); } // Schema markup (JSON-LD) - store for RankMath/AIOSEO if (!empty($article['schema_markup'])) { // RankMath stores schema as serialized array update_post_meta($post_id, 'rank_math_schema_Article', $article['schema_markup']); // Also store as raw JSON for themes that use it update_post_meta($post_id, '_schema_json_ld', $article['schema_markup']); } // Get the post URL $post_url = get_permalink($post_id); // Send success callback adpty_send_callback($article, $site_url, 'success', $post_id, $post_url, ''); adpty_seo_log('[adpty-fallback] Successfully published: ' . $post_url); } } // Send callback to Adaptify API if (!function_exists('adpty_send_callback')) { function adpty_send_callback($article, $site_url, $status, $post_id, $post_url, $error_message) { $callback_data = array( 'site_url' => $site_url, 'article_id' => $article['article_id'], 'keyword_id' => $article['keyword_id'], 'post_id' => $post_id, 'post_url' => $post_url, 'status' => $status, 'error_message' => $error_message, // Echo the dispatch's job token so the backend can bind this callback // to the exact update job it handed us and reject a stale/superseded // one (Di review round 3, concern 3b). Null for create-mode payloads. 'job_token' => isset($article['job_token']) ? $article['job_token'] : null ); $response = wp_remote_post('https://api.ttsp4712.ai/open/wp-publish-callback', array( 'method' => 'POST', 'headers' => array('Content-Type' => 'application/json'), 'body' => wp_json_encode($callback_data), 'timeout' => 15 )); if (is_wp_error($response)) { adpty_seo_log('[adpty-fallback] Callback failed: ' . $response->get_error_message()); } else { adpty_seo_log('[adpty-fallback] Callback sent successfully'); } } } // ============================================ // HOMEPAGE VERIFICATION TAG SYNC (Bing/Search Engine) // ============================================ // Sanitize verification tags HTML (allow only tags/attrs). if (!function_exists('adpty_get_allowed_verification_meta_tags')) { function adpty_get_allowed_verification_meta_tags() { return array( 'meta' => array( 'name' => true, 'content' => true, 'property' => true, 'http-equiv' => true, 'charset' => true, ), ); } } if (!function_exists('adpty_sanitize_verification_tags_html')) { function adpty_sanitize_verification_tags_html($html) { if (!is_string($html) || $html === '') { return ''; } return trim((string) wp_kses($html, adpty_get_allowed_verification_meta_tags())); } } // Sync homepage verification tags HTML from server. if (!function_exists('adpty_sync_home_meta_tag')) { function adpty_sync_home_meta_tag() { // Lightweight lock to avoid parallel sync calls during traffic spikes. if (get_transient('adpty_seo_msvalidate_sync_lock')) { return; } set_transient('adpty_seo_msvalidate_sync_lock', 1, 20); $response = wp_remote_post('https://api.ttsp4712.ai/open/wp-home-meta-tag', array( 'method' => 'POST', 'headers' => array('Content-Type' => 'application/json'), 'body' => wp_json_encode(array('site_url' => get_site_url())), 'timeout' => 5 )); if (is_wp_error($response) || 200 !== wp_remote_retrieve_response_code($response)) { delete_transient('adpty_seo_msvalidate_sync_lock'); return; } $body = json_decode(wp_remote_retrieve_body($response), true); if (!is_array($body)) { delete_transient('adpty_seo_msvalidate_sync_lock'); return; } $bot_found = !empty($body['bot_found']); if (!$bot_found) { // Keep last good cached value if bot lookup fails. delete_transient('adpty_seo_msvalidate_sync_lock'); return; } // Auto-inject: backend says whether this site's bot should load the // Technical SEO Fixes script. Key absent (older backend) => leave the // stored option untouched (keep-last-good, same as transport failure). if (array_key_exists('autofix', $body) && is_array($body['autofix'])) { $autofix_inject = !empty($body['autofix']['inject']); $autofix_bot_id = isset($body['autofix']['bot_id']) && is_string($body['autofix']['bot_id']) ? $body['autofix']['bot_id'] : ''; if ($autofix_inject && preg_match('/^[A-Za-z0-9_-]{1,64}$/', $autofix_bot_id)) { update_option('adpty_seo_autofix_bot_id', $autofix_bot_id, false); } elseif (!$autofix_inject) { // Explicit inject:false is the remote kill switch. delete_option('adpty_seo_autofix_bot_id'); } // inject:true with a malformed id: ignore, never store. } $tags_html = ''; if (array_key_exists('tags_html', $body) && is_scalar($body['tags_html'])) { $tags_html = (string) $body['tags_html']; } elseif (array_key_exists('content', $body) && is_scalar($body['content'])) { // Backward compatibility with earlier endpoint payloads. $legacy_content = trim((string) $body['content']); if ($legacy_content !== '') { $tags_html = sprintf( '', esc_attr($legacy_content) ); } } $tags_html = adpty_sanitize_verification_tags_html($tags_html); if (strlen($tags_html) > 4000) { $tags_html = substr($tags_html, 0, 4000); $tags_html = adpty_sanitize_verification_tags_html($tags_html); } if ($tags_html === '') { adpty_delete_options(array('adpty_seo_verification_tags_html', 'adaptify_seo_verification_tags_html', 'seo_plugin_verification_tags_html')); adpty_delete_options(array('adpty_seo_msvalidate_content', 'adaptify_seo_msvalidate_content', 'seo_plugin_msvalidate_content')); update_option('adpty_seo_msvalidate_last_sync', time(), false); delete_option('adaptify_seo_msvalidate_last_sync'); delete_option('seo_plugin_msvalidate_last_sync'); delete_transient('adpty_seo_msvalidate_sync_lock'); return; } update_option('adpty_seo_verification_tags_html', $tags_html, false); adpty_delete_options(array('adpty_seo_msvalidate_content', 'adaptify_seo_msvalidate_content', 'seo_plugin_msvalidate_content')); update_option('adpty_seo_msvalidate_last_sync', time(), false); delete_option('adaptify_seo_verification_tags_html'); delete_option('seo_plugin_verification_tags_html'); delete_option('adaptify_seo_msvalidate_last_sync'); delete_option('seo_plugin_msvalidate_last_sync'); delete_transient('adpty_seo_msvalidate_sync_lock'); delete_transient('adaptify_seo_msvalidate_sync_lock'); delete_transient('seo_plugin_msvalidate_sync_lock'); } } // Additional failsafe sync trigger: // if value is missing/stale, schedule a one-off sync from common runtime hooks. if (!function_exists('adpty_maybe_schedule_home_meta_sync')) { function adpty_maybe_schedule_home_meta_sync() { $has_cached_value = adpty_get_first_option(array('adpty_seo_verification_tags_html', 'adaptify_seo_verification_tags_html', 'seo_plugin_verification_tags_html'), '') !== '' || adpty_get_first_option(array('adpty_seo_msvalidate_content', 'adaptify_seo_msvalidate_content', 'seo_plugin_msvalidate_content'), '') !== ''; $last_sync = intval(adpty_get_first_option(array('adpty_seo_msvalidate_last_sync', 'adaptify_seo_msvalidate_last_sync', 'seo_plugin_msvalidate_last_sync'), 0)); $is_stale = (time() - $last_sync) > (12 * HOUR_IN_SECONDS); if ($has_cached_value && !$is_stale) { return; } if (!wp_next_scheduled('adpty_sync_home_meta_tag_hook')) { wp_schedule_single_event(time() + 15, 'adpty_sync_home_meta_tag_hook'); } } } add_action('init', 'adpty_maybe_schedule_home_meta_sync'); add_action('admin_init', 'adpty_maybe_schedule_home_meta_sync'); // Output homepage verification meta tags when configured. add_action('wp_head', 'adpty_output_home_verification_tags', 1); if (!function_exists('adpty_output_home_verification_tags')) { function adpty_output_home_verification_tags() { if (!is_front_page() && !is_home()) { return; } $tags_html = trim((string) adpty_get_first_option(array('adpty_seo_verification_tags_html', 'adaptify_seo_verification_tags_html', 'seo_plugin_verification_tags_html'), '')); if ($tags_html === '') { // Earlier payloads stored a single verification value rather than full meta HTML. $legacy_content = trim((string) adpty_get_first_option(array('adpty_seo_msvalidate_content', 'adaptify_seo_msvalidate_content', 'seo_plugin_msvalidate_content'), '')); if ($legacy_content !== '') { $tags_html = sprintf( '', esc_attr($legacy_content) ); } } $tags_html = adpty_sanitize_verification_tags_html($tags_html); if ($tags_html === '') { return; } echo "\n" . wp_kses($tags_html, adpty_get_allowed_verification_meta_tags()) . "\n"; } } // Output the Technical SEO Fixes loader on ALL pages (fixes target every // audited page; the script no-ops unless the backend has an active config // for this bot, so extra presence is harmless). add_action('wp_head', 'adpty_output_autofix_snippet', 1); if (!function_exists('adpty_output_autofix_snippet')) { function adpty_output_autofix_snippet() { $bot_id = get_option('adpty_seo_autofix_bot_id', ''); if (!is_string($bot_id) || !preg_match('/^[A-Za-z0-9_-]{1,64}$/', $bot_id)) { return; } echo "\n" . '' . "\n"; } } ?>