Plugin Directory

Changeset 3461526


Ignore:
Timestamp:
02/14/2026 09:59:09 PM (6 weeks ago)
Author:
klimentp
Message:

Version 1.0.2 - Hotfix Formatting: Lists, Citations, FAQ, Videos, External Links etc.

Location:
draftseo-ai
Files:
26 added
5 edited

Legend:

Unmodified
Added
Removed
  • draftseo-ai/trunk/README.md

    r3461460 r3461526  
    173173## Changelog
    174174
     175### 1.0.2
     176
     177Content 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
    175187### 1.0.1
    176188
  • draftseo-ai/trunk/draftseo-ai.php

    r3461460 r3461526  
    44 * Plugin URI: https://draftseo.ai/wp-plugin
    55 * 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.1
     6 * Version: 1.0.2
    77 * Author: DraftSEO.AI
    88 * Author URI: https://draftseo.ai
     
    3838
    3939// Define plugin constants
    40 define('DRAFTSEO_VERSION', '1.0.0');
     40define('DRAFTSEO_VERSION', '1.0.2');
    4141define('DRAFTSEO_PLUGIN_DIR', plugin_dir_path(__FILE__));
    4242define('DRAFTSEO_PLUGIN_URL', plugin_dir_url(__FILE__));
     
    109109        // Add Settings link on Plugins page
    110110        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'));
    111117    }
    112118   
  • draftseo-ai/trunk/includes/class-content-processor.php

    r3461460 r3461526  
    3030     */
    3131    public static function process($content) {
    32         $content = self::format_tables($content);
     32        $content = self::convert_youtube_oembed_to_embeds($content);
    3333        $content = self::format_quotes($content);
    3434        $content = self::sanitize_content($content);
     
    4343     * - All standard post HTML tags (via wp_kses_allowed_html('post'))
    4444     * - <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
    4647     *
    4748     * @param string $content HTML content
     
    5152        $allowed = wp_kses_allowed_html('post');
    5253       
    53         $table_tags = array(
     54        $extra_tags = array(
    5455            'table' => array(
    5556                'class' => true,
     
    7374                'rowspan' => true,
    7475            ),
     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            ),
    75114        );
    76115       
    77         $allowed = array_merge($allowed, $table_tags);
     116        $allowed = array_merge($allowed, $extra_tags);
    78117       
    79118        return wp_kses($content, $allowed);
     
    81120   
    82121    /**
    83      * Format tables for WordPress
     122     * Convert YouTube oEmbed URLs to WordPress embed shortcodes
    84123     *
    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.
    86126     *
    87      * @param string $content HTML content
    88      * @return string Formatted content
     127     * @param string $content HTML content with YouTube URLs
     128     * @return string Content with YouTube URLs wrapped in [embed] shortcodes
    89129     */
    90     private static function format_tables($content) {
     130    private static function convert_youtube_oembed_to_embeds($content) {
    91131        $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",
    94134            $content
    95135        );
    96136       
    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        );
    98142       
    99143        return $content;
  • draftseo-ai/trunk/includes/class-rest-api.php

    r3461357 r3461526  
    418418        }
    419419       
     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       
    420426        // Log publication
    421427        self::log_publication($post_id, $params['id'] ?? null, 'success');
     
    527533        }
    528534       
     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       
    529541        // Update modified timestamp
    530542        update_post_meta($post_id, 'draftseo_last_updated', current_time('mysql'));
     
    613625        );
    614626    }
    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  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.0.1
     7Stable tag: 1.0.2
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    106106
    107107== Changelog ==
     108
     109= 1.0.2 =
     110
     111Content 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
    108120
    109121= 1.0.1 =
     
    195207== Upgrade Notice ==
    196208
     209= 1.0.2 =
     210Hotfix: Fixes in-text citations, references list, external links, and adds FAQ structured data (JSON-LD) for SEO rich results.
     211
    197212= 1.0.1 =
    198213Hotfix: Fixes YouTube video embeds and data tables not rendering correctly in published posts.
Note: See TracChangeset for help on using the changeset viewer.