Changeset 3490608
- Timestamp:
- 03/25/2026 07:25:29 AM (8 days ago)
- Location:
- outrank
- Files:
-
- 19 added
- 2 deleted
- 3 edited
-
assets/banner-icon-128x128.png (deleted)
-
assets/banner-icon-256х258.png (deleted)
-
tags/1.0.8 (added)
-
tags/1.0.8/css (added)
-
tags/1.0.8/css/article.css (added)
-
tags/1.0.8/css/home.css (added)
-
tags/1.0.8/css/manage.css (added)
-
tags/1.0.8/images (added)
-
tags/1.0.8/images/icon.svg (added)
-
tags/1.0.8/includes (added)
-
tags/1.0.8/includes/image-functions.php (added)
-
tags/1.0.8/index.php (added)
-
tags/1.0.8/libs (added)
-
tags/1.0.8/libs/api.php (added)
-
tags/1.0.8/outrank.php (added)
-
tags/1.0.8/pages (added)
-
tags/1.0.8/pages/home.php (added)
-
tags/1.0.8/pages/manage.php (added)
-
tags/1.0.8/readme.txt (added)
-
tags/1.0.8/script (added)
-
tags/1.0.8/script/manage.js (added)
-
trunk/libs/api.php (modified) (10 diffs)
-
trunk/outrank.php (modified) (3 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
outrank/trunk/libs/api.php
r3476368 r3490608 10 10 'methods' => 'POST', 11 11 'callback' => 'outrank_receive_article', 12 'permission_callback' => function ($request) { 13 $secretKey = $request->get_header('X-Secret-Key'); 14 if (!$secretKey) { 15 $secretKey = $request->get_header('x-secret-key'); 16 } 17 return $secretKey && hash_equals(OUTRANK_API_SECRET, $secretKey); 18 } 19 ]); 20 21 register_rest_route('outrank/v1', '/edit', [ 22 'methods' => 'PUT', 23 'callback' => 'outrank_edit_article', 12 24 'permission_callback' => function ($request) { 13 25 $secretKey = $request->get_header('X-Secret-Key'); … … 50 62 'permission_callback' => '__return_true' 51 63 ]); 64 65 register_rest_route('outrank/v1', '/capabilities', [ 66 'methods' => 'GET', 67 'callback' => 'outrank_get_capabilities', 68 'permission_callback' => '__return_true' 69 ]); 52 70 }); 53 71 … … 70 88 71 89 return new WP_REST_Response(['success' => true], 200); 90 } 91 92 function outrank_get_capabilities() { 93 $capabilities = ['publish', 'edit', 'list']; 94 95 $plugin_file = OUTRANK_PLUGIN_PATH . 'outrank.php'; 96 $plugin_data = get_file_data($plugin_file, ['Version' => 'Version']); 97 $version = !empty($plugin_data['Version']) ? $plugin_data['Version'] : '0.0.0'; 98 99 return new WP_REST_Response([ 100 'version' => $version, 101 'capabilities' => $capabilities, 102 ], 200); 72 103 } 73 104 … … 182 213 } 183 214 215 function outrank_edit_article($request) { 216 global $wpdb; 217 218 outrank_ensure_table_exists(); 219 220 $params = $request->get_json_params(); 221 if (!is_array($params)) { 222 $params = []; 223 } 224 225 $secret = sanitize_text_field($params['secret'] ?? ''); 226 $storedSecret = get_option('outrank_api_key'); 227 228 if (!$secret || !$storedSecret || !hash_equals($storedSecret, $secret)) { 229 return new WP_REST_Response(['error' => 'Invalid or missing secret'], 403); 230 } 231 232 $post_id = isset($params['id']) ? absint($params['id']) : 0; 233 $current_slug = isset($params['current_slug']) ? sanitize_title($params['current_slug']) : ''; 234 235 if (!$post_id && $current_slug === '') { 236 return new WP_REST_Response(['error' => 'Either id or current_slug is required'], 400); 237 } 238 239 $post = $post_id ? get_post($post_id) : get_page_by_path($current_slug, OBJECT, 'post'); 240 if (!$post || $post->post_type !== 'post') { 241 return new WP_REST_Response(['error' => 'Post not found'], 404); 242 } 243 244 $original_slug = $post->post_name; 245 $original_thumbnail_id = (int) get_post_thumbnail_id($post->ID); 246 $table_name = $wpdb->prefix . 'outrank_manage'; 247 248 $has_title = array_key_exists('title', $params); 249 $has_content = array_key_exists('content', $params); 250 $has_slug = array_key_exists('slug', $params); 251 $has_status = array_key_exists('status', $params); 252 $has_author = array_key_exists('author', $params); 253 $has_category = array_key_exists('category', $params); 254 $has_tags = array_key_exists('tags', $params); 255 $has_image_url = array_key_exists('image_url', $params) && !empty($params['image_url']); 256 $has_meta_description = array_key_exists('meta_description', $params) && $params['meta_description'] !== ''; 257 $has_focus_keyword = array_key_exists('focus_keyword', $params) && $params['focus_keyword'] !== ''; 258 $has_focus_keyphrase = array_key_exists('focus_keyphrase', $params) && $params['focus_keyphrase'] !== ''; 259 260 if ( 261 !$has_title && 262 !$has_content && 263 !$has_slug && 264 !$has_status && 265 !$has_author && 266 !$has_category && 267 !$has_tags && 268 !$has_image_url && 269 !$has_meta_description && 270 !$has_focus_keyword && 271 !$has_focus_keyphrase 272 ) { 273 return new WP_REST_Response(['error' => 'No update fields provided'], 400); 274 } 275 276 $post_update = [ 277 'ID' => $post->ID, 278 ]; 279 280 if ($has_title) { 281 $title = sanitize_text_field($params['title']); 282 if ($title === '') { 283 return new WP_REST_Response(['error' => 'Invalid title'], 400); 284 } 285 $post_update['post_title'] = $title; 286 } 287 288 if ($has_content) { 289 $post_update['post_content'] = outrank_sanitize_content((string) $params['content']); 290 } 291 292 if ($has_status) { 293 $status = sanitize_key($params['status']); 294 $allowed_statuses = ['publish', 'draft', 'pending', 'private', 'future', 'trash']; 295 if (!in_array($status, $allowed_statuses, true)) { 296 return new WP_REST_Response(['error' => 'Invalid status'], 400); 297 } 298 $post_update['post_status'] = $status; 299 } 300 301 if ($has_author) { 302 $author = $params['author']; 303 $author_id = 0; 304 305 if (is_numeric($author)) { 306 $author_id = absint($author); 307 if ($author_id && !get_userdata($author_id)) { 308 return new WP_REST_Response(['error' => 'Invalid author'], 400); 309 } 310 } else { 311 $user = get_user_by('login', sanitize_user((string) $author, true)); 312 if (!$user) { 313 return new WP_REST_Response(['error' => 'Invalid author'], 400); 314 } 315 $author_id = (int) $user->ID; 316 } 317 318 if (!$author_id) { 319 return new WP_REST_Response(['error' => 'Invalid author'], 400); 320 } 321 322 $post_update['post_author'] = $author_id; 323 } 324 325 if ($has_category) { 326 $post_update['post_category'] = outrank_resolve_category_ids($params['category']); 327 } 328 329 if ($has_tags) { 330 $tags = is_array($params['tags']) ? $params['tags'] : [$params['tags']]; 331 $post_update['tags_input'] = array_map('sanitize_text_field', $tags); 332 } 333 334 if ($has_slug) { 335 $new_slug = sanitize_title($params['slug']); 336 if ($new_slug === '') { 337 return new WP_REST_Response(['error' => 'Invalid slug'], 400); 338 } 339 340 if ($new_slug !== $original_slug && outrank_slug_exists_for_other_post($new_slug, $post->ID, $original_slug)) { 341 return new WP_REST_Response(['error' => 'Slug already exists'], 409); 342 } 343 344 $post_update['post_name'] = $new_slug; 345 } 346 347 $new_thumbnail_id = 0; 348 $existing_attachment_id = 0; 349 if ($has_image_url) { 350 $image_url = esc_url_raw((string) $params['image_url']); 351 if (!$image_url) { 352 return new WP_REST_Response(['error' => 'Invalid image_url'], 400); 353 } 354 355 $existing_attachment_id = outrank_find_attachment_by_source_url($image_url); 356 if ($existing_attachment_id) { 357 $new_thumbnail_id = $existing_attachment_id; 358 } else { 359 $new_thumbnail_id = (int) outrank_upload_image_from_url($image_url, $post->ID); 360 if (!$new_thumbnail_id) { 361 return new WP_REST_Response(['error' => 'Failed to upload image'], 500); 362 } 363 } 364 } 365 366 $warnings = []; 367 368 kses_remove_filters(); 369 try { 370 $updated_post_id = wp_update_post($post_update, true); 371 if (is_wp_error($updated_post_id)) { 372 throw new RuntimeException($updated_post_id->get_error_message()); 373 } 374 } catch (RuntimeException $e) { 375 kses_init_filters(); 376 if ($new_thumbnail_id && !$existing_attachment_id) { 377 wp_delete_attachment($new_thumbnail_id, true); 378 } 379 return new WP_REST_Response(['error' => 'Failed to update post: ' . $e->getMessage()], 500); 380 } 381 382 if ($has_content) { 383 $saved_content = isset($post_update['post_content']) ? $post_update['post_content'] : get_post_field('post_content', $post->ID); 384 $localized_content = outrank_download_content_images($saved_content, $post->ID); 385 386 if ($localized_content !== $saved_content) { 387 $content_update_result = wp_update_post([ 388 'ID' => $post->ID, 389 'post_content' => $localized_content, 390 ], true); 391 392 if (is_wp_error($content_update_result)) { 393 $warnings[] = 'Content images could not be localized.'; 394 } 395 } 396 } 397 398 kses_init_filters(); 399 400 if ($new_thumbnail_id) { 401 if (!set_post_thumbnail($post->ID, $new_thumbnail_id)) { 402 $warnings[] = 'Featured image could not be assigned.'; 403 } 404 405 $final_post = get_post($post->ID); 406 if ($final_post && !$existing_attachment_id) { 407 $attachment_update_result = wp_update_post([ 408 'ID' => $new_thumbnail_id, 409 'post_author' => (int) $final_post->post_author, 410 'post_parent' => $post->ID, 411 ], true); 412 413 if (is_wp_error($attachment_update_result)) { 414 $warnings[] = 'Featured image metadata could not be updated.'; 415 } 416 } 417 418 if ( 419 $original_thumbnail_id && 420 $original_thumbnail_id !== $new_thumbnail_id && 421 get_post_meta($original_thumbnail_id, '_outrank_source_url', true) && 422 !outrank_attachment_is_featured_elsewhere($original_thumbnail_id, $post->ID) 423 ) { 424 wp_delete_attachment($original_thumbnail_id, true); 425 } 426 } 427 428 $final_post = get_post($post->ID); 429 if (!$final_post) { 430 return new WP_REST_Response(['error' => 'Post not found after update'], 500); 431 } 432 433 $final_title = $final_post->post_title; 434 $final_status = $final_post->post_status; 435 $final_slug = $final_post->post_name; 436 $tracking_slug = outrank_normalize_tracking_slug($final_slug); 437 $final_thumbnail_id = (int) get_post_thumbnail_id($post->ID); 438 439 if ($has_title || $has_meta_description || $has_focus_keyword || $has_focus_keyphrase) { 440 $meta_description = $has_meta_description ? sanitize_text_field($params['meta_description']) : null; 441 $focus_keyword = null; 442 443 if ($has_focus_keyword) { 444 $focus_keyword = sanitize_text_field($params['focus_keyword']); 445 } elseif ($has_focus_keyphrase) { 446 $focus_keyword = sanitize_text_field($params['focus_keyphrase']); 447 } 448 449 outrank_set_seo_meta($post->ID, $has_title ? $final_title : null, $meta_description, $focus_keyword); 450 outrank_set_squirrly_seo($post->ID, $has_title ? $final_title : null, $meta_description, $focus_keyword); 451 } 452 453 $original_tracking_slug = outrank_normalize_tracking_slug($original_slug); 454 455 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 456 $tracking_row = $wpdb->get_row($wpdb->prepare( 457 "SELECT id FROM {$table_name} WHERE slug = %s OR slug = %s LIMIT 1", 458 $original_slug, 459 $original_tracking_slug 460 )); 461 462 if ($tracking_row) { 463 $tracking_update = [ 464 'slug' => $tracking_slug, 465 'title' => $final_title, 466 'status' => $final_status, 467 'image' => $final_thumbnail_id ? (string) $final_thumbnail_id : '', 468 ]; 469 470 if ($has_meta_description) { 471 $tracking_update['meta_description'] = sanitize_text_field($params['meta_description']); 472 } 473 474 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 475 $wpdb->update( 476 $table_name, 477 $tracking_update, 478 ['id' => (int) $tracking_row->id] 479 ); 480 } 481 482 outrank_clear_articles_cache(); 483 484 $response = [ 485 'success' => true, 486 'post_id' => $post->ID, 487 ]; 488 489 if (!empty($warnings)) { 490 $response['warnings'] = array_values(array_unique($warnings)); 491 } 492 493 return new WP_REST_Response($response, 200); 494 } 495 184 496 function outrank_test_integration($request) { 185 497 // 1. Get integration key from request … … 238 550 'message' => 'Integration test successful' 239 551 ], 200); 552 } 553 554 function outrank_find_attachment_by_source_url($image_url) { 555 $existing = get_posts([ 556 'post_type' => 'attachment', 557 'meta_key' => '_outrank_source_url', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 558 'meta_value' => $image_url, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value 559 'numberposts' => 1, 560 'fields' => 'ids', 561 ]); 562 563 if (empty($existing)) { 564 return 0; 565 } 566 567 return (int) $existing[0]; 568 } 569 570 function outrank_attachment_is_featured_elsewhere($attachment_id, $exclude_post_id = 0) { 571 global $wpdb; 572 573 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 574 $count = $wpdb->get_var($wpdb->prepare( 575 "SELECT COUNT(*) FROM {$wpdb->postmeta} pm 576 INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id 577 WHERE pm.meta_key = '_thumbnail_id' 578 AND pm.meta_value = %d 579 AND p.post_type = 'post' 580 AND p.ID != %d", 581 $attachment_id, 582 $exclude_post_id 583 )); 584 585 return (int) $count > 0; 586 } 587 588 function outrank_normalize_tracking_slug($slug) { 589 return preg_replace('/__trashed$/', '', (string) $slug); 590 } 591 592 function outrank_slug_exists_for_other_post($slug, $post_id, $original_slug) { 593 global $wpdb; 594 595 $table_name = $wpdb->prefix . 'outrank_manage'; 596 $original_tracking_slug = outrank_normalize_tracking_slug($original_slug); 597 598 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 599 $wp_conflict = $wpdb->get_var($wpdb->prepare( 600 "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_name = %s AND post_type = 'post' AND ID != %d", 601 $slug, 602 $post_id 603 )); 604 605 if ((int) $wp_conflict > 0) { 606 return true; 607 } 608 609 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 610 $table_conflict = $wpdb->get_var($wpdb->prepare( 611 "SELECT COUNT(*) FROM {$table_name} WHERE slug = %s AND slug NOT IN (%s, %s)", 612 $slug, 613 $original_slug, 614 $original_tracking_slug 615 )); 616 617 return (int) $table_conflict > 0; 240 618 } 241 619 … … 401 779 } 402 780 403 function outrank_set_seo_meta($post_id, $title , $meta_description = '', $focus_keyword = '') {404 if ( !empty($meta_description)) {781 function outrank_set_seo_meta($post_id, $title = null, $meta_description = null, $focus_keyword = null) { 782 if ($meta_description !== null && $meta_description !== '') { 405 783 update_post_meta($post_id, '_yoast_wpseo_metadesc', $meta_description); 406 784 update_post_meta($post_id, 'rank_math_description', $meta_description); … … 408 786 update_post_meta($post_id, '_seopress_titles_desc', $meta_description); 409 787 } 410 if ( !empty($focus_keyword)) {788 if ($focus_keyword !== null && $focus_keyword !== '') { 411 789 update_post_meta($post_id, '_yoast_wpseo_focuskw', $focus_keyword); 412 790 update_post_meta($post_id, 'rank_math_focus_keyword', $focus_keyword); … … 416 794 update_post_meta($post_id, '_seopress_analysis_target_kw', $focus_keyword); 417 795 } 418 if ( !empty($title)) {796 if ($title !== null && $title !== '') { 419 797 update_post_meta($post_id, '_yoast_wpseo_title', $title); 420 798 update_post_meta($post_id, 'rank_math_title', $title); … … 424 802 } 425 803 426 function outrank_set_squirrly_seo($post_id, $title , $meta_description = '', $focus_keyword = '') {804 function outrank_set_squirrly_seo($post_id, $title = null, $meta_description = null, $focus_keyword = null) { 427 805 global $wpdb; 428 806 $sq_table = $wpdb->prefix . 'qss'; … … 460 838 } 461 839 462 $seo_data['title'] = $title ?? ''; 463 $seo_data['description'] = $meta_description; 464 $seo_data['keywords'] = $focus_keyword; 840 if ($title !== null) { 841 $seo_data['title'] = $title; 842 } 843 if ($meta_description !== null) { 844 $seo_data['description'] = $meta_description; 845 } 846 if ($focus_keyword !== null) { 847 $seo_data['keywords'] = $focus_keyword; 848 } 465 849 $seo_data['doseo'] = 1; 466 850 -
outrank/trunk/outrank.php
r3476368 r3490608 6 6 * Plugin URI: https://outrank.so 7 7 * Description: Get traffic and outrank competitors with automatic SEO-optimized content generation published to your WordPress site. 8 * Version: 1.0. 78 * Version: 1.0.8 9 9 * Author: Outrank 10 10 * License: GPLv2 or later … … 258 258 if (strpos($hook_suffix, 'outrank') === false) return; // Only enqueue on outrank pages 259 259 260 wp_enqueue_style('outrank-style', OUTRANK_PLUGIN_URL . 'css/manage.css', [], '1.0. 7');261 wp_enqueue_style('outrank-home-style', OUTRANK_PLUGIN_URL . 'css/home.css', [], '1.0. 7');262 263 wp_enqueue_script('outrank-script', OUTRANK_PLUGIN_URL . 'script/manage.js', ['jquery'], '1.0. 7', true);260 wp_enqueue_style('outrank-style', OUTRANK_PLUGIN_URL . 'css/manage.css', [], '1.0.8'); 261 wp_enqueue_style('outrank-home-style', OUTRANK_PLUGIN_URL . 'css/home.css', [], '1.0.8'); 262 263 wp_enqueue_script('outrank-script', OUTRANK_PLUGIN_URL . 'script/manage.js', ['jquery'], '1.0.8', true); 264 264 } 265 265 … … 339 339 outrank_clear_articles_cache(); 340 340 } 341 -
outrank/trunk/readme.txt
r3476368 r3490608 5 5 Tested up to: 6.9 6 6 Requires PHP: 8.0 7 Stable tag: 1.0. 77 Stable tag: 1.0.8 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 71 71 == Changelog == 72 72 73 = 1.0.8 = 74 * Added support for updating existing synced articles from Outrank 75 * Improved integration compatibility by exposing available plugin capabilities 76 * Internal maintenance and packaging improvements 77 73 78 = 1.0.7 = 74 79 * External images in articles are now downloaded to the WordPress media library for better performance and SEO
Note: See TracChangeset
for help on using the changeset viewer.