Changeset 3461526
- Timestamp:
- 02/14/2026 09:59:09 PM (6 weeks ago)
- Location:
- draftseo-ai
- Files:
-
- 26 added
- 5 edited
-
tags/1.0.2 (added)
-
tags/1.0.2/LICENSE.txt (added)
-
tags/1.0.2/README.md (added)
-
tags/1.0.2/admin (added)
-
tags/1.0.2/admin/css (added)
-
tags/1.0.2/admin/css/admin-styles.css (added)
-
tags/1.0.2/admin/css/index.php (added)
-
tags/1.0.2/admin/index.php (added)
-
tags/1.0.2/admin/js (added)
-
tags/1.0.2/admin/js/admin-scripts.js (added)
-
tags/1.0.2/admin/js/index.php (added)
-
tags/1.0.2/admin/settings-page.php (added)
-
tags/1.0.2/draftseo-ai.php (added)
-
tags/1.0.2/includes (added)
-
tags/1.0.2/includes/class-api-client.php (added)
-
tags/1.0.2/includes/class-content-processor.php (added)
-
tags/1.0.2/includes/class-image-handler.php (added)
-
tags/1.0.2/includes/class-rest-api.php (added)
-
tags/1.0.2/includes/class-seo-handler.php (added)
-
tags/1.0.2/includes/class-settings.php (added)
-
tags/1.0.2/includes/index.php (added)
-
tags/1.0.2/index.php (added)
-
tags/1.0.2/languages (added)
-
tags/1.0.2/languages/index.php (added)
-
tags/1.0.2/readme.txt (added)
-
tags/1.0.2/uninstall.php (added)
-
trunk/README.md (modified) (1 diff)
-
trunk/draftseo-ai.php (modified) (3 diffs)
-
trunk/includes/class-content-processor.php (modified) (5 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
r3461460 r3461526 173 173 ## Changelog 174 174 175 ### 1.0.2 176 177 Content formatting and SEO structured data hotfix release. 178 179 - **In-text citations** — `[1]`, `[2]` markers now render as clickable superscript links that scroll to the matching reference in the References section 180 - **References section** — Converted to a styled numbered list with anchor IDs (`#ref-1`, `#ref-2`) for citation linking; supports all reference styles (url_title, harvard_apa6, mla9, etc.) 181 - **External links** — All external links now open in a new tab with `target="_blank"` and `rel="noopener noreferrer"` for security and better UX 182 - **FAQ Schema (JSON-LD)** — FAQ question-answer pairs are extracted from blog content and injected as structured data in the WordPress post `<head>` for Google FAQ rich results 183 - **Front-end CSS** — Plugin now injects lightweight CSS for consistent styling of citations, references, and tables across all WordPress themes 184 - **Content sanitization** — Updated `wp_kses` allowlist to support `<sup>`, `<ol>`/`<li>` with `id`/`value`/`class`, and `<a>` with `target`/`rel` attributes 185 - **Active sites filter** — WordPress site dropdown now only shows active/connected sites 186 175 187 ### 1.0.1 176 188 -
draftseo-ai/trunk/draftseo-ai.php
r3461460 r3461526 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.0. 16 * Version: 1.0.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. 0');40 define('DRAFTSEO_VERSION', '1.0.2'); 41 41 define('DRAFTSEO_PLUGIN_DIR', plugin_dir_path(__FILE__)); 42 42 define('DRAFTSEO_PLUGIN_URL', plugin_dir_url(__FILE__)); … … 109 109 // Add Settings link on Plugins page 110 110 add_filter('plugin_action_links_' . DRAFTSEO_PLUGIN_BASENAME, array($this, 'add_plugin_action_links')); 111 112 // Output FAQ Schema JSON-LD in post head 113 add_action('wp_head', array('DraftSEO_REST_API', 'output_faq_schema')); 114 115 // Output front-end CSS for citations, references, and tables 116 add_action('wp_head', array('DraftSEO_REST_API', 'output_frontend_styles')); 111 117 } 112 118 -
draftseo-ai/trunk/includes/class-content-processor.php
r3461460 r3461526 30 30 */ 31 31 public static function process($content) { 32 $content = self:: format_tables($content);32 $content = self::convert_youtube_oembed_to_embeds($content); 33 33 $content = self::format_quotes($content); 34 34 $content = self::sanitize_content($content); … … 43 43 * - All standard post HTML tags (via wp_kses_allowed_html('post')) 44 44 * - <table>, <thead>, <tbody>, <tfoot>, <tr>, <th>, <td> for data tables 45 * - <iframe> restricted to trusted video providers (YouTube, Vimeo) 45 * - <figure>, <figcaption> for images with captions 46 * - <div> with class attribute for responsive wrappers 46 47 * 47 48 * @param string $content HTML content … … 51 52 $allowed = wp_kses_allowed_html('post'); 52 53 53 $ table_tags = array(54 $extra_tags = array( 54 55 'table' => array( 55 56 'class' => true, … … 73 74 'rowspan' => true, 74 75 ), 76 'figure' => array( 77 'class' => true, 78 'id' => true, 79 ), 80 'figcaption' => array( 81 'class' => true, 82 ), 83 'div' => array( 84 'class' => true, 85 'id' => true, 86 ), 87 'img' => array( 88 'src' => true, 89 'alt' => true, 90 'class' => true, 91 'width' => true, 92 'height' => true, 93 'loading' => true, 94 ), 95 'sup' => array( 96 'class' => true, 97 ), 98 'ol' => array( 99 'class' => true, 100 'id' => true, 101 ), 102 'li' => array( 103 'class' => true, 104 'id' => true, 105 'value' => true, 106 ), 107 'a' => array( 108 'href' => true, 109 'target' => true, 110 'rel' => true, 111 'title' => true, 112 'class' => true, 113 ), 75 114 ); 76 115 77 $allowed = array_merge($allowed, $ table_tags);116 $allowed = array_merge($allowed, $extra_tags); 78 117 79 118 return wp_kses($content, $allowed); … … 81 120 82 121 /** 83 * Format tables for WordPress122 * Convert YouTube oEmbed URLs to WordPress embed shortcodes 84 123 * 85 * Wraps <table> elements with a responsive container div. 124 * YouTube URLs on their own line are converted to WordPress [embed] shortcodes 125 * which WordPress processes into responsive video players. 86 126 * 87 * @param string $content HTML content 88 * @return string Formatted content127 * @param string $content HTML content with YouTube URLs 128 * @return string Content with YouTube URLs wrapped in [embed] shortcodes 89 129 */ 90 private static function format_tables($content) {130 private static function convert_youtube_oembed_to_embeds($content) { 91 131 $content = preg_replace( 92 '/ <table([^>]*)>/i',93 '<div class="table-responsive"><table$1>',132 '/^\s*(https:\/\/www\.youtube\.com\/watch\?v=[a-zA-Z0-9_-]+)\s*$/m', 133 "\n" . '[embed]$1[/embed]' . "\n", 94 134 $content 95 135 ); 96 136 97 $content = str_replace('</table>', '</table></div>', $content); 137 $content = preg_replace( 138 '/<p>\s*(https:\/\/www\.youtube\.com\/watch\?v=[a-zA-Z0-9_-]+)\s*<\/p>/i', 139 "\n" . '[embed]$1[/embed]' . "\n", 140 $content 141 ); 98 142 99 143 return $content; -
draftseo-ai/trunk/includes/class-rest-api.php
r3461357 r3461526 418 418 } 419 419 420 // Store FAQ Schema data if provided 421 if (isset($params['faqItems']) && is_array($params['faqItems']) && !empty($params['faqItems'])) { 422 $faq_schema = self::build_faq_schema($params['faqItems']); 423 update_post_meta($post_id, 'draftseo_faq_schema', wp_json_encode($faq_schema)); 424 } 425 420 426 // Log publication 421 427 self::log_publication($post_id, $params['id'] ?? null, 'success'); … … 527 533 } 528 534 535 // Update FAQ Schema if provided 536 if (isset($params['faqItems']) && is_array($params['faqItems']) && !empty($params['faqItems'])) { 537 $faq_schema = self::build_faq_schema($params['faqItems']); 538 update_post_meta($post_id, 'draftseo_faq_schema', wp_json_encode($faq_schema)); 539 } 540 529 541 // Update modified timestamp 530 542 update_post_meta($post_id, 'draftseo_last_updated', current_time('mysql')); … … 613 625 ); 614 626 } 615 } 627 628 /** 629 * Build FAQ Schema (JSON-LD) from FAQ items 630 * 631 * Creates a Google-compatible FAQPage structured data object 632 * following schema.org specification. 633 * 634 * @param array $faq_items Array of FAQ items with 'question' and 'answer' keys 635 * @return array JSON-LD structured data array 636 */ 637 private static function build_faq_schema($faq_items) { 638 $main_entity = array(); 639 640 foreach ($faq_items as $item) { 641 if (empty($item['question']) || empty($item['answer'])) { 642 continue; 643 } 644 645 $main_entity[] = array( 646 '@type' => 'Question', 647 'name' => sanitize_text_field($item['question']), 648 'acceptedAnswer' => array( 649 '@type' => 'Answer', 650 'text' => wp_kses_post($item['answer']) 651 ) 652 ); 653 } 654 655 if (empty($main_entity)) { 656 return array(); 657 } 658 659 return array( 660 '@context' => 'https://schema.org', 661 '@type' => 'FAQPage', 662 'mainEntity' => $main_entity 663 ); 664 } 665 666 /** 667 * Output FAQ Schema JSON-LD in the <head> of single posts 668 * 669 * Hooked to wp_head, outputs the FAQ structured data 670 * for posts that have FAQ schema stored in post meta. 671 */ 672 public static function output_faq_schema() { 673 if (!is_singular('post')) { 674 return; 675 } 676 677 $post_id = get_the_ID(); 678 $faq_schema_json = get_post_meta($post_id, 'draftseo_faq_schema', true); 679 680 if (empty($faq_schema_json)) { 681 return; 682 } 683 684 $faq_schema = json_decode($faq_schema_json, true); 685 686 if (empty($faq_schema) || !isset($faq_schema['@type'])) { 687 return; 688 } 689 690 echo '<script type="application/ld+json">' . wp_json_encode($faq_schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . '</script>' . "\n"; 691 } 692 693 /** 694 * Output front-end CSS for DraftSEO content elements 695 * 696 * Styles citations, references, tables, and blockquotes for consistent 697 * display across WordPress themes. Only loads on single posts. 698 */ 699 public static function output_frontend_styles() { 700 if (!is_singular('post')) { 701 return; 702 } 703 704 echo '<style id="draftseo-content-styles"> 705 /* DraftSEO.AI - In-text Citations */ 706 .draftseo-citation { 707 font-size: 0.75em; 708 line-height: 1; 709 vertical-align: super; 710 margin: 0 1px; 711 } 712 .draftseo-citation a { 713 color: #4f46e5; 714 text-decoration: none; 715 font-weight: 600; 716 transition: color 0.2s ease; 717 } 718 .draftseo-citation a:hover { 719 color: #3730a3; 720 text-decoration: underline; 721 } 722 723 /* DraftSEO.AI - References List */ 724 .draftseo-references-heading { 725 margin-top: 2.5em; 726 padding-top: 1.5em; 727 border-top: 2px solid #e5e7eb; 728 } 729 .draftseo-references-list { 730 list-style: none; 731 padding-left: 0; 732 margin: 1em 0; 733 counter-reset: none; 734 font-size: 0.9em; 735 line-height: 1.7; 736 } 737 .draftseo-reference-item { 738 padding: 0.5em 0 0.5em 2.5em; 739 position: relative; 740 border-bottom: 1px solid #f3f4f6; 741 scroll-margin-top: 80px; 742 } 743 .draftseo-reference-item::before { 744 content: "[" attr(value) "]"; 745 position: absolute; 746 left: 0; 747 font-weight: 700; 748 color: #4f46e5; 749 min-width: 2em; 750 } 751 .draftseo-reference-item:target { 752 background-color: #eef2ff; 753 border-radius: 4px; 754 padding-left: 2.8em; 755 padding-right: 0.3em; 756 } 757 .draftseo-reference-item a { 758 color: #4f46e5; 759 word-break: break-all; 760 } 761 .draftseo-reference-item a:hover { 762 color: #3730a3; 763 text-decoration: underline; 764 } 765 .draftseo-reference-item em { 766 font-style: italic; 767 } 768 769 /* DraftSEO.AI - Tables */ 770 .table-responsive { 771 overflow-x: auto; 772 margin: 1.5em 0; 773 -webkit-overflow-scrolling: touch; 774 } 775 .wp-table { 776 width: 100%; 777 border-collapse: collapse; 778 border: 1px solid #e5e7eb; 779 font-size: 0.95em; 780 } 781 .wp-table th { 782 background-color: #f9fafb; 783 font-weight: 600; 784 text-align: left; 785 padding: 0.75em 1em; 786 border: 1px solid #e5e7eb; 787 } 788 .wp-table td { 789 padding: 0.75em 1em; 790 border: 1px solid #e5e7eb; 791 } 792 .wp-table tbody tr:nth-child(even) { 793 background-color: #f9fafb; 794 } 795 </style>' . "\n"; 796 } 797 } -
draftseo-ai/trunk/readme.txt
r3461460 r3461526 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 1.0. 17 Stable tag: 1.0.2 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 106 106 107 107 == Changelog == 108 109 = 1.0.2 = 110 111 Content formatting and SEO structured data hotfix release. 112 113 * In-text citations `[1]`, `[2]` now render as clickable superscript links that scroll to the matching reference 114 * References section converted to a styled numbered list with anchor IDs for citation linking 115 * All external links now open in a new tab with `rel="noopener noreferrer"` for security 116 * FAQ Schema (JSON-LD) structured data extracted from content and injected into WordPress post `<head>` for Google rich results 117 * WordPress plugin now injects front-end CSS for consistent styling of citations, references, and tables across themes 118 * Updated content sanitization allowlist to support `<sup>`, `<ol>`/`<li>` with IDs, and `<a>` with `target`/`rel` attributes 119 * WordPress site dropdown now only shows active/connected sites 108 120 109 121 = 1.0.1 = … … 195 207 == Upgrade Notice == 196 208 209 = 1.0.2 = 210 Hotfix: Fixes in-text citations, references list, external links, and adds FAQ structured data (JSON-LD) for SEO rich results. 211 197 212 = 1.0.1 = 198 213 Hotfix: Fixes YouTube video embeds and data tables not rendering correctly in published posts.
Note: See TracChangeset
for help on using the changeset viewer.