Changeset 3491200
- Timestamp:
- 03/25/2026 07:54:36 PM (7 days ago)
- Location:
- draftseo-ai
- Files:
-
- 26 added
- 4 edited
-
tags/1.1.2 (added)
-
tags/1.1.2/LICENSE.txt (added)
-
tags/1.1.2/README.md (added)
-
tags/1.1.2/admin (added)
-
tags/1.1.2/admin/css (added)
-
tags/1.1.2/admin/css/admin-styles.css (added)
-
tags/1.1.2/admin/css/index.php (added)
-
tags/1.1.2/admin/index.php (added)
-
tags/1.1.2/admin/js (added)
-
tags/1.1.2/admin/js/admin-scripts.js (added)
-
tags/1.1.2/admin/js/index.php (added)
-
tags/1.1.2/admin/settings-page.php (added)
-
tags/1.1.2/draftseo-ai.php (added)
-
tags/1.1.2/includes (added)
-
tags/1.1.2/includes/class-api-client.php (added)
-
tags/1.1.2/includes/class-content-processor.php (added)
-
tags/1.1.2/includes/class-image-handler.php (added)
-
tags/1.1.2/includes/class-rest-api.php (added)
-
tags/1.1.2/includes/class-seo-handler.php (added)
-
tags/1.1.2/includes/class-settings.php (added)
-
tags/1.1.2/includes/index.php (added)
-
tags/1.1.2/index.php (added)
-
tags/1.1.2/languages (added)
-
tags/1.1.2/languages/index.php (added)
-
tags/1.1.2/readme.txt (added)
-
tags/1.1.2/uninstall.php (added)
-
trunk/README.md (modified) (1 diff)
-
trunk/draftseo-ai.php (modified) (2 diffs)
-
trunk/includes/class-rest-api.php (modified) (3 diffs)
-
trunk/readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
draftseo-ai/trunk/README.md
r3478994 r3491200 173 173 ## Changelog 174 174 175 ### 1.1.1 176 Plugin images update 175 ### 1.1.2 176 177 Security update: fixes API token authentication on WordPress sites where the Application Passwords feature or a security plugin was blocking server-to-server requests from DraftSEO.AI before they could be validated. 177 178 178 179 ### 1.1.0 -
draftseo-ai/trunk/draftseo-ai.php
r3479027 r3491200 4 4 * Plugin URI: https://draftseo.ai/wp-plugin 5 5 * Description: Publish AI-generated blogs from DraftSEO.AI platform directly to WordPress. Transfers images from Nebius CDN to WordPress media library while maintaining SEO optimization. 6 * Version: 1.1. 16 * Version: 1.1.2 7 7 * Author: DraftSEO.AI 8 8 * Author URI: https://draftseo.ai … … 38 38 39 39 // Define plugin constants 40 define('DRAFTSEO_VERSION', '1. 0.4');40 define('DRAFTSEO_VERSION', '1.1.2'); 41 41 define('DRAFTSEO_PLUGIN_DIR', plugin_dir_path(__FILE__)); 42 42 define('DRAFTSEO_PLUGIN_URL', plugin_dir_url(__FILE__)); -
draftseo-ai/trunk/includes/class-rest-api.php
r3478117 r3491200 22 22 23 23 /** 24 * Prevent WordPress's own authentication layer from rejecting DraftSEO 25 * Bearer token requests before our permission_callback can run. 26 * 27 * WordPress 5.6+ intercepts "Authorization: Bearer <token>" headers at the 28 * REST authentication layer (via Application Passwords). When the token does 29 * not match any WordPress Application Password, WordPress returns 30 * "restx_logged_out" (401) before our verify_api_key permission_callback is 31 * ever called — making every server-to-server call from DraftSEO.AI fail on 32 * sites where no WordPress user is logged in, regardless of whether the API 33 * key itself is valid. 34 * 35 * The fix: hook into rest_authentication_errors and, when the incoming 36 * request targets a /draftseo/v1/ endpoint and carries a "Bearer ds_" token, 37 * return null (no opinion) so WordPress hands control to our 38 * permission_callback. The actual key validation still happens there. 39 * 40 * This pattern is used by WooCommerce, Jetpack, and other plugins that 41 * implement their own REST auth alongside WordPress core. 42 * 43 * @param WP_Error|null|bool $result Current authentication error, null, or true. 44 * @return WP_Error|null|bool 45 */ 46 public static function bypass_wp_auth_for_draftseo_endpoints($result) { 47 // If WordPress has already positively authenticated the user, leave it alone. 48 if (true === $result || is_user_logged_in()) { 49 return $result; 50 } 51 52 // Only intervene for requests to our own namespace. 53 $request_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; 54 if (strpos($request_uri, '/draftseo/v1/') === false) { 55 return $result; 56 } 57 58 // Extract the Authorization header (Apache may move it to REDIRECT_*). 59 $auth_header = ''; 60 if (!empty($_SERVER['HTTP_AUTHORIZATION'])) { 61 $auth_header = $_SERVER['HTTP_AUTHORIZATION']; 62 } elseif (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { 63 $auth_header = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; 64 } 65 66 // Only bypass for DraftSEO Bearer tokens (prefixed ds_). 67 // Any other token (Application Passwords, cookies, etc.) goes through 68 // WordPress's normal auth flow unchanged. 69 if (strpos($auth_header, 'Bearer ds_') === 0) { 70 return null; // No opinion — let permission_callback decide. 71 } 72 73 return $result; 74 } 75 76 /** 24 77 * Register REST API routes 25 78 */ 26 79 public static function register_routes() { 80 // Prevent WordPress's Application Passwords layer from rejecting our 81 // Bearer tokens before verify_api_key has a chance to run. 82 add_filter('rest_authentication_errors', array(__CLASS__, 'bypass_wp_auth_for_draftseo_endpoints'), 10, 1); 83 27 84 // Get WordPress users 28 85 register_rest_route(self::NAMESPACE, '/users', array( … … 98 155 'methods' => 'POST', 99 156 'callback' => array(__CLASS__, 'remote_disconnect'), 157 'permission_callback' => array(__CLASS__, 'verify_api_key') 158 )); 159 160 // Site info endpoint — for Autopilot: language, site name, locale 161 register_rest_route(self::NAMESPACE, '/site-info', array( 162 'methods' => 'GET', 163 'callback' => array(__CLASS__, 'get_site_info'), 164 'permission_callback' => array(__CLASS__, 'verify_api_key') 165 )); 166 167 // Posts endpoint — for Autopilot: authenticated paginated content index 168 register_rest_route(self::NAMESPACE, '/posts', array( 169 'methods' => 'GET', 170 'callback' => array(__CLASS__, 'get_posts'), 100 171 'permission_callback' => array(__CLASS__, 'verify_api_key') 101 172 )); … … 800 871 </style>' . "\n"; 801 872 } 802 } 873 874 /** 875 * Get site metadata for Autopilot: language, name, locale. 876 * 877 * @param WP_REST_Request $request 878 * @return WP_REST_Response 879 */ 880 public static function get_site_info($request) { 881 $locale = get_locale(); // e.g. en_US, fr_FR, de_DE 882 return rest_ensure_response(array( 883 'success' => true, 884 'site_url' => get_site_url(), 885 'site_name' => get_bloginfo('name'), 886 'site_description' => get_bloginfo('description'), 887 'locale' => $locale, 888 'language_code' => substr($locale, 0, 2), // e.g. en, fr, de 889 'charset' => get_bloginfo('charset'), 890 'woocommerce' => class_exists('WooCommerce'), 891 )); 892 } 893 894 /** 895 * Get paginated published posts/pages for Autopilot content indexing. 896 * Returns slug, URL, title, and SEO metadata (Yoast / RankMath / AIOSEO). 897 * 898 * Query params: 899 * per_page int Items per page (1–250, default 100) 900 * page int Page number (default 1) 901 * 902 * @param WP_REST_Request $request 903 * @return WP_REST_Response 904 */ 905 public static function get_posts($request) { 906 $per_page = max(1, min(250, absint($request->get_param('per_page') ?: 100))); 907 $page = max(1, absint($request->get_param('page') ?: 1)); 908 909 $post_types = array('post', 'page'); 910 if (class_exists('WooCommerce')) { 911 $post_types[] = 'product'; 912 } 913 914 $query = new WP_Query(array( 915 'post_type' => $post_types, 916 'post_status' => 'publish', 917 'posts_per_page' => $per_page, 918 'paged' => $page, 919 'fields' => 'ids', 920 'no_found_rows' => false, 921 )); 922 923 $total = (int) $query->found_posts; 924 $total_pages = (int) ceil($total / max(1, $per_page)); 925 926 $items = array(); 927 foreach ($query->posts as $post_id) { 928 $post = get_post($post_id); 929 $permalink = get_permalink($post_id); 930 if (!$post || !$permalink) continue; 931 932 // SEO plugin support: Yoast SEO → RankMath → AIO SEO → fallback empty 933 $seo_title = get_post_meta($post_id, '_yoast_wpseo_title', true) 934 ?: get_post_meta($post_id, 'rank_math_title', true) 935 ?: get_post_meta($post_id, '_aioseop_title', true) 936 ?: ''; 937 938 $seo_description = get_post_meta($post_id, '_yoast_wpseo_metadesc', true) 939 ?: get_post_meta($post_id, 'rank_math_description', true) 940 ?: get_post_meta($post_id, '_aioseop_description', true) 941 ?: ''; 942 943 $items[] = array( 944 'id' => $post_id, 945 'slug' => $post->post_name, 946 'title' => get_the_title($post_id), 947 'url' => $permalink, 948 'post_type' => $post->post_type, 949 'seo_title' => $seo_title, 950 'seo_description' => $seo_description, 951 ); 952 } 953 954 return rest_ensure_response(array( 955 'success' => true, 956 'total' => $total, 957 'total_pages' => $total_pages, 958 'page' => $page, 959 'per_page' => $per_page, 960 'items' => $items, 961 )); 962 } 963 } -
draftseo-ai/trunk/readme.txt
r3478994 r3491200 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 1.1. 17 Stable tag: 1.1.2 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 107 107 == Changelog == 108 108 109 = 1.1. 1=110 111 Plugin images update 109 = 1.1.2 = 110 111 Security update: fixes API token authentication on WordPress sites where the Application Passwords feature or a security plugin was blocking server-to-server requests from DraftSEO.AI before they could be validated. 112 112 113 113 = 1.1.0 = … … 235 235 == Upgrade Notice == 236 236 237 = 1.1. 1=238 readme update 237 = 1.1.2 = 238 Security update. Fixes API authentication failures on certain WordPress configurations that prevented DraftSEO.AI from syncing disconnect and deactivation events with your site. 239 239 240 240 = 1.1.0 =
Note: See TracChangeset
for help on using the changeset viewer.