Plugin Directory

Changeset 3497127


Ignore:
Timestamp:
04/02/2026 01:32:07 AM (4 days ago)
Author:
1platform
Message:

Update to version 2.19.0 from GitHub

Location:
1platform-content-ai
Files:
12 added
10 deleted
40 edited
1 copied

Legend:

Unmodified
Added
Removed
  • 1platform-content-ai/tags/2.19.0/.git/FETCH_HEAD

    r3497124 r3497127  
    1 446726a18086b3134b15ccc1642ba4ba1ae9d91b        branch 'main' of https://github.com/1platformlabs/1platform-content-ai
     1709aa959b84a1cccdfcb95ca325a7dbd8019c290        branch 'main' of https://github.com/1platformlabs/1platform-content-ai
  • 1platform-content-ai/tags/2.19.0/.git/ORIG_HEAD

    r3497124 r3497127  
    1 446726a18086b3134b15ccc1642ba4ba1ae9d91b
     1709aa959b84a1cccdfcb95ca325a7dbd8019c290
  • 1platform-content-ai/tags/2.19.0/.git/config

    r3497124 r3497127  
    1010    auto = 0
    1111[http "https://github.com/"]
    12     extraheader = AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2hzX3lNYzExNkR0OXlDZzRXblQ0SU5URzJuM3BSMmhnVDNVeFdaRw==
     12    extraheader = AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2hzXzA2OVU1NnFWc3l3djM3d1hOcW1nOWlLUm9YNFJqOTRIQkxUNA==
    1313[branch "main"]
    1414    remote = origin
  • 1platform-content-ai/tags/2.19.0/.git/logs/HEAD

    r3497124 r3497127  
    1 0000000000000000000000000000000000000000 446726a18086b3134b15ccc1642ba4ba1ae9d91b runner <runner@runnervm727z3.umhcbtvdydrevbqwdfzdqmnpzb.cx.internal.cloudapp.net> 1775093211 +0000    checkout: moving from master to main
     10000000000000000000000000000000000000000 709aa959b84a1cccdfcb95ca325a7dbd8019c290 runner <runner@runnervmrg6be.zfkur4ax2nlu5onhaw0pux0jqe.ex.internal.cloudapp.net> 1775093477 +0000    checkout: moving from master to main
  • 1platform-content-ai/tags/2.19.0/.git/logs/refs/heads/main

    r3497124 r3497127  
    1 0000000000000000000000000000000000000000 446726a18086b3134b15ccc1642ba4ba1ae9d91b runner <runner@runnervm727z3.umhcbtvdydrevbqwdfzdqmnpzb.cx.internal.cloudapp.net> 1775093211 +0000    branch: Created from refs/remotes/origin/main
     10000000000000000000000000000000000000000 709aa959b84a1cccdfcb95ca325a7dbd8019c290 runner <runner@runnervmrg6be.zfkur4ax2nlu5onhaw0pux0jqe.ex.internal.cloudapp.net> 1775093477 +0000    branch: Created from refs/remotes/origin/main
  • 1platform-content-ai/tags/2.19.0/.git/logs/refs/remotes/origin/chore/sonarcloud-integration

    r3497124 r3497127  
    1 0000000000000000000000000000000000000000 47874cdf78da218e5246afd6c314e0321d2dfd24 runner <runner@runnervm727z3.umhcbtvdydrevbqwdfzdqmnpzb.cx.internal.cloudapp.net> 1775093211 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
     10000000000000000000000000000000000000000 47874cdf78da218e5246afd6c314e0321d2dfd24 runner <runner@runnervmrg6be.zfkur4ax2nlu5onhaw0pux0jqe.ex.internal.cloudapp.net> 1775093477 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
  • 1platform-content-ai/tags/2.19.0/.git/logs/refs/remotes/origin/feature/adsense-integration

    r3497124 r3497127  
    1 0000000000000000000000000000000000000000 a7a64298a2a6e9760b7176bf1c07193d78033a04 runner <runner@runnervm727z3.umhcbtvdydrevbqwdfzdqmnpzb.cx.internal.cloudapp.net> 1775093211 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
     10000000000000000000000000000000000000000 a7a64298a2a6e9760b7176bf1c07193d78033a04 runner <runner@runnervmrg6be.zfkur4ax2nlu5onhaw0pux0jqe.ex.internal.cloudapp.net> 1775093477 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
  • 1platform-content-ai/tags/2.19.0/.git/logs/refs/remotes/origin/hotfix/fix-job-queue-cron-self-healing-issue-39

    r3497124 r3497127  
    1 0000000000000000000000000000000000000000 6a700de81959c5223e1038b4d493b8fa54344cff runner <runner@runnervm727z3.umhcbtvdydrevbqwdfzdqmnpzb.cx.internal.cloudapp.net> 1775093211 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
     10000000000000000000000000000000000000000 6a700de81959c5223e1038b4d493b8fa54344cff runner <runner@runnervmrg6be.zfkur4ax2nlu5onhaw0pux0jqe.ex.internal.cloudapp.net> 1775093477 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
  • 1platform-content-ai/tags/2.19.0/.git/logs/refs/remotes/origin/main

    r3497124 r3497127  
    1 0000000000000000000000000000000000000000 446726a18086b3134b15ccc1642ba4ba1ae9d91b runner <runner@runnervm727z3.umhcbtvdydrevbqwdfzdqmnpzb.cx.internal.cloudapp.net> 1775093211 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
     10000000000000000000000000000000000000000 709aa959b84a1cccdfcb95ca325a7dbd8019c290 runner <runner@runnervmrg6be.zfkur4ax2nlu5onhaw0pux0jqe.ex.internal.cloudapp.net> 1775093477 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
  • 1platform-content-ai/tags/2.19.0/.git/refs/heads/main

    r3497124 r3497127  
    1 446726a18086b3134b15ccc1642ba4ba1ae9d91b
     1709aa959b84a1cccdfcb95ca325a7dbd8019c290
  • 1platform-content-ai/tags/2.19.0/.git/refs/remotes/origin/main

    r3497124 r3497127  
    1 446726a18086b3134b15ccc1642ba4ba1ae9d91b
     1709aa959b84a1cccdfcb95ca325a7dbd8019c290
  • 1platform-content-ai/tags/2.19.0/.git/refs/tags/qa-v2.18.0-rc66

    r3497124 r3497127  
    1 ab8a80691a01228df2d37bcfae7cb944c0d20edf
     1446726a18086b3134b15ccc1642ba4ba1ae9d91b
  • 1platform-content-ai/tags/2.19.0/1platform-content-ai.php

    r3497124 r3497127  
    55 * Plugin URI: https://1platform.pro/
    66 * Description: SaaS client for AI-powered content generation, SEO optimization, and site management. All AI processing happens on 1Platform external servers. Includes free local tools: Table of Contents and Internal Links.
    7  * Version: 2.17.4
     7 * Version: 2.19.0
    88 * Author: 1Platform
    99 * License: GPLv2 or later
     
    6161require_once plugin_dir_path( __FILE__ ) . 'includes/admin/agents/ContaiAgentsAdminPage.php';
    6262require_once plugin_dir_path( __FILE__ ) . 'includes/cron/agent-actions-cron.php';
     63
     64// SEO domain
     65require_once plugin_dir_path(__FILE__) . 'includes/services/seo/SeoHeadService.php';
     66$contai_seo_head = new ContaiSeoHeadService();
     67$contai_seo_head->register();
    6368
    6469// Analytics domain
  • 1platform-content-ai/tags/2.19.0/includes/services/content/ContentGeneratorService.php

    r3483422 r3497127  
    167167            $data['url'] ?? null,
    168168            $seo_metadata['metatitle'] ?? null,
     169            $seo_metadata['meta_description'] ?? null,
    169170            $seo_metadata['post_date'] ?? null
    170171        );
     
    189190    private ?string $slug;
    190191    private ?string $metatitle;
     192    private ?string $meta_description;
    191193    private ?string $post_date;
    192194
     
    199201        ?string $slug = null,
    200202        ?string $metatitle = null,
     203        ?string $meta_description = null,
    201204        ?string $post_date = null,
    202205        ?string $error_message = null,
     
    210213        $this->slug = $slug;
    211214        $this->metatitle = $metatitle;
     215        $this->meta_description = $meta_description;
    212216        $this->post_date = $post_date;
    213217        $this->error_message = $error_message;
     
    222226        ?string $slug = null,
    223227        ?string $metatitle = null,
     228        ?string $meta_description = null,
    224229        ?string $post_date = null
    225230    ): self {
    226         return new self(true, $title, $content, $images, $category, $slug, $metatitle, $post_date, null, 200);
     231        return new self(true, $title, $content, $images, $category, $slug, $metatitle, $meta_description, $post_date, null, 200);
    227232    }
    228233
    229234    public static function failure(string $error_message, ?int $status_code = null): self {
    230         return new self(false, '', '', [], null, null, null, null, $error_message, $status_code);
     235        return new self(false, '', '', [], null, null, null, null, null, $error_message, $status_code);
    231236    }
    232237
     
    261266    public function getMetatitle(): ?string {
    262267        return $this->metatitle;
     268    }
     269
     270    public function getMetaDescription(): ?string {
     271        return $this->meta_description;
    263272    }
    264273
     
    280289            'slug' => $this->slug,
    281290            'metatitle' => $this->metatitle,
     291            'meta_description' => $this->meta_description,
    282292            'post_date' => $this->post_date,
    283293            'error_message' => $this->error_message,
  • 1platform-content-ai/tags/2.19.0/includes/services/post/ContentImageProcessor.php

    r3483422 r3497127  
    1313    }
    1414
    15     public function process(string $content, array $images): string {
    16         if (empty($images)) {
    17             return $content;
     15    public function process(string $content, array $images, string $alt_text = ''): string {
     16        if (!empty($images)) {
     17            $url_map = $this->buildUrlMap($content, $images, $alt_text);
     18            $content = $this->replaceUrls($content, $url_map);
    1819        }
    1920
    20         $url_map = $this->buildUrlMap($content, $images);
     21        if ($alt_text !== '') {
     22            $content = $this->ensureImgAltAttributes($content, $alt_text);
     23        }
    2124
    22         return $this->replaceUrls($content, $url_map);
     25        return $content;
    2326    }
    2427
    25     private function buildUrlMap(string $content, array $images): array {
     28    private function buildUrlMap(string $content, array $images, string $alt_text): array {
    2629        $url_map = [];
    2730
     
    3336            }
    3437
    35             $attachment_id = $this->uploader->uploadFromUrl($external_url);
     38            $image_alt = !empty($image['alt_text']) ? $image['alt_text'] : $alt_text;
     39            $attachment_id = $this->uploader->uploadFromUrl($external_url, $image_alt);
    3640
    3741            if ($attachment_id === null) {
     
    6064        return $content;
    6165    }
     66
     67    private function ensureImgAltAttributes(string $content, string $alt_text): string {
     68        $escaped_alt = esc_attr($alt_text);
     69
     70        // Replace empty alt attributes with the keyword-based alt text
     71        $content = preg_replace(
     72            '/<img([^>]*)\salt=["\']["\']/',
     73            '<img$1 alt="' . $escaped_alt . '"',
     74            $content
     75        );
     76
     77        // Add alt attribute to <img> tags missing it entirely
     78        $content = preg_replace(
     79            '/<img((?![^>]*\salt=)[^>]*)\s*\/?>/',
     80            '<img$1 alt="' . $escaped_alt . '">',
     81            $content
     82        );
     83
     84        return $content;
     85    }
    6286}
  • 1platform-content-ai/tags/2.19.0/includes/services/post/ImageUploader.php

    r3483422 r3497127  
    99    }
    1010
    11     public function uploadFromUrl(string $image_url): ?int {
     11    public function uploadFromUrl(string $image_url, string $alt_text = ''): ?int {
    1212        $temp_file = download_url($image_url);
    1313
     
    2020
    2121        $this->cleanupTempFile($temp_file);
     22
     23        if ($attachment_id !== null && $alt_text !== '') {
     24            update_post_meta($attachment_id, '_wp_attachment_image_alt', sanitize_text_field($alt_text));
     25        }
    2226
    2327        return $attachment_id;
  • 1platform-content-ai/tags/2.19.0/includes/services/post/PostGenerationOrchestrator.php

    r3497124 r3497127  
    117117            $api_result['url'] ?? null,
    118118            $seo_metadata['metatitle'] ?? null,
     119            $seo_metadata['meta_description'] ?? null,
    119120            $seo_metadata['post_date'] ?? null
    120121        );
     
    128129
    129130    private function buildPost(ContaiKeyword $keyword, array $params, ContaiContentGenerationResult $content_result): ContaiPostGenerationResult {
     131        $alt_text = $keyword->getTitle() ?: $keyword->getKeyword();
     132
    130133        $processed_content = $this->image_processor->process(
    131134            $content_result->getContent(),
    132             $content_result->getImages()
     135            $content_result->getImages(),
     136            $alt_text
    133137        );
    134138
     
    139143            $content_result->getSlug(),
    140144            $content_result->getPostDate(),
    141             $content_result->getMetatitle()
     145            $content_result->getMetatitle(),
     146            $content_result->getMetaDescription()
    142147        );
    143148
     
    146151        $this->saveMetadata($post_id, $keyword, $params);
    147152
    148         $this->setFeaturedImageIfExists($post_id, $content_result->getImages());
     153        $this->setFeaturedImageIfExists($post_id, $content_result->getImages(), $alt_text);
    149154
    150155        return new ContaiPostGenerationResult(
     
    186191    }
    187192
    188     private function setFeaturedImageIfExists(int $post_id, array $images): void {
     193    private function setFeaturedImageIfExists(int $post_id, array $images, string $alt_text = ''): void {
    189194        if (empty($images)) {
    190195            return;
     
    202207        update_post_meta($post_id, self::META_FEATURED_IMAGE_SOURCE, $selected_url);
    203208
    204         $attachment_id = $this->image_uploader->uploadFromUrl($selected_url);
     209        $attachment_id = $this->image_uploader->uploadFromUrl($selected_url, $alt_text);
    205210
    206211        if ($attachment_id !== null) {
  • 1platform-content-ai/tags/2.19.0/includes/services/post/WordPressPostCreator.php

    r3483422 r3497127  
    66
    77    private const CUSTOM_FIELD_METATITLE = '_contai_metatitle';
     8    private const META_DESCRIPTION_LENGTH = 155;
    89
    9     public function create(string $title, string $content, ?string $slug = null, ?string $post_date = null, ?string $metatitle = null): int {
     10    public function create(string $title, string $content, ?string $slug = null, ?string $post_date = null, ?string $metatitle = null, ?string $meta_description = null): int {
     11        $excerpt = !empty($meta_description) ? $meta_description : $this->generateExcerpt($content);
     12
    1013        $post_data = [
    1114            'post_title' => sanitize_text_field($title),
    1215            'post_content' => $content,
    1316            'post_status' => 'publish',
    14             'post_type' => 'post'
     17            'post_type' => 'post',
     18            'post_excerpt' => sanitize_text_field($excerpt),
    1519        ];
    1620
     
    5256    }
    5357
     58    private function generateExcerpt(string $content): string {
     59        $text = wp_strip_all_tags($content);
     60        $text = preg_replace('/\s+/', ' ', trim($text));
     61
     62        if (mb_strlen($text) <= self::META_DESCRIPTION_LENGTH) {
     63            return $text;
     64        }
     65
     66        $truncated = mb_substr($text, 0, self::META_DESCRIPTION_LENGTH);
     67        $last_space = mb_strrpos($truncated, ' ');
     68
     69        if ($last_space !== false) {
     70            $truncated = mb_substr($truncated, 0, $last_space);
     71        }
     72
     73        return $truncated . '...';
     74    }
     75
    5476    public function assignCategory(int $post_id, int $category_id): void {
    5577        wp_set_object_terms($post_id, [$category_id], 'category', false);
  • 1platform-content-ai/tags/2.19.0/readme.txt

    r3497124 r3497127  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 2.17.4
     7Stable tag: 2.19.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    161161== Changelog ==
    162162
     163= 2.18.0 =
     164* Added: SEO alt text on all images uploaded by the content pipeline (#49)
     165* Added: Meta description output via wp_head for generated posts (#49)
     166* Added: SEO title tag override using AI-generated metatitle (#49)
     167* Added: Auto-generated post excerpts for meta description support
     168
    163169= 2.17.3 =
    164170* Fix: Featured image dedup no longer falls back to first image when all candidates are used — skips featured image to avoid duplication (#47)
  • 1platform-content-ai/trunk/.git/FETCH_HEAD

    r3497124 r3497127  
    1 446726a18086b3134b15ccc1642ba4ba1ae9d91b        branch 'main' of https://github.com/1platformlabs/1platform-content-ai
     1709aa959b84a1cccdfcb95ca325a7dbd8019c290        branch 'main' of https://github.com/1platformlabs/1platform-content-ai
  • 1platform-content-ai/trunk/.git/ORIG_HEAD

    r3497124 r3497127  
    1 446726a18086b3134b15ccc1642ba4ba1ae9d91b
     1709aa959b84a1cccdfcb95ca325a7dbd8019c290
  • 1platform-content-ai/trunk/.git/config

    r3497124 r3497127  
    1010    auto = 0
    1111[http "https://github.com/"]
    12     extraheader = AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2hzX3lNYzExNkR0OXlDZzRXblQ0SU5URzJuM3BSMmhnVDNVeFdaRw==
     12    extraheader = AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2hzXzA2OVU1NnFWc3l3djM3d1hOcW1nOWlLUm9YNFJqOTRIQkxUNA==
    1313[branch "main"]
    1414    remote = origin
  • 1platform-content-ai/trunk/.git/logs/HEAD

    r3497124 r3497127  
    1 0000000000000000000000000000000000000000 446726a18086b3134b15ccc1642ba4ba1ae9d91b runner <runner@runnervm727z3.umhcbtvdydrevbqwdfzdqmnpzb.cx.internal.cloudapp.net> 1775093211 +0000    checkout: moving from master to main
     10000000000000000000000000000000000000000 709aa959b84a1cccdfcb95ca325a7dbd8019c290 runner <runner@runnervmrg6be.zfkur4ax2nlu5onhaw0pux0jqe.ex.internal.cloudapp.net> 1775093477 +0000    checkout: moving from master to main
  • 1platform-content-ai/trunk/.git/logs/refs/heads/main

    r3497124 r3497127  
    1 0000000000000000000000000000000000000000 446726a18086b3134b15ccc1642ba4ba1ae9d91b runner <runner@runnervm727z3.umhcbtvdydrevbqwdfzdqmnpzb.cx.internal.cloudapp.net> 1775093211 +0000    branch: Created from refs/remotes/origin/main
     10000000000000000000000000000000000000000 709aa959b84a1cccdfcb95ca325a7dbd8019c290 runner <runner@runnervmrg6be.zfkur4ax2nlu5onhaw0pux0jqe.ex.internal.cloudapp.net> 1775093477 +0000    branch: Created from refs/remotes/origin/main
  • 1platform-content-ai/trunk/.git/logs/refs/remotes/origin/chore/sonarcloud-integration

    r3497124 r3497127  
    1 0000000000000000000000000000000000000000 47874cdf78da218e5246afd6c314e0321d2dfd24 runner <runner@runnervm727z3.umhcbtvdydrevbqwdfzdqmnpzb.cx.internal.cloudapp.net> 1775093211 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
     10000000000000000000000000000000000000000 47874cdf78da218e5246afd6c314e0321d2dfd24 runner <runner@runnervmrg6be.zfkur4ax2nlu5onhaw0pux0jqe.ex.internal.cloudapp.net> 1775093477 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
  • 1platform-content-ai/trunk/.git/logs/refs/remotes/origin/feature/adsense-integration

    r3497124 r3497127  
    1 0000000000000000000000000000000000000000 a7a64298a2a6e9760b7176bf1c07193d78033a04 runner <runner@runnervm727z3.umhcbtvdydrevbqwdfzdqmnpzb.cx.internal.cloudapp.net> 1775093211 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
     10000000000000000000000000000000000000000 a7a64298a2a6e9760b7176bf1c07193d78033a04 runner <runner@runnervmrg6be.zfkur4ax2nlu5onhaw0pux0jqe.ex.internal.cloudapp.net> 1775093477 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
  • 1platform-content-ai/trunk/.git/logs/refs/remotes/origin/hotfix/fix-job-queue-cron-self-healing-issue-39

    r3497124 r3497127  
    1 0000000000000000000000000000000000000000 6a700de81959c5223e1038b4d493b8fa54344cff runner <runner@runnervm727z3.umhcbtvdydrevbqwdfzdqmnpzb.cx.internal.cloudapp.net> 1775093211 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
     10000000000000000000000000000000000000000 6a700de81959c5223e1038b4d493b8fa54344cff runner <runner@runnervmrg6be.zfkur4ax2nlu5onhaw0pux0jqe.ex.internal.cloudapp.net> 1775093477 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
  • 1platform-content-ai/trunk/.git/logs/refs/remotes/origin/main

    r3497124 r3497127  
    1 0000000000000000000000000000000000000000 446726a18086b3134b15ccc1642ba4ba1ae9d91b runner <runner@runnervm727z3.umhcbtvdydrevbqwdfzdqmnpzb.cx.internal.cloudapp.net> 1775093211 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
     10000000000000000000000000000000000000000 709aa959b84a1cccdfcb95ca325a7dbd8019c290 runner <runner@runnervmrg6be.zfkur4ax2nlu5onhaw0pux0jqe.ex.internal.cloudapp.net> 1775093477 +0000    fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*: storing head
  • 1platform-content-ai/trunk/.git/refs/heads/main

    r3497124 r3497127  
    1 446726a18086b3134b15ccc1642ba4ba1ae9d91b
     1709aa959b84a1cccdfcb95ca325a7dbd8019c290
  • 1platform-content-ai/trunk/.git/refs/remotes/origin/main

    r3497124 r3497127  
    1 446726a18086b3134b15ccc1642ba4ba1ae9d91b
     1709aa959b84a1cccdfcb95ca325a7dbd8019c290
  • 1platform-content-ai/trunk/.git/refs/tags/qa-v2.18.0-rc66

    r3497124 r3497127  
    1 ab8a80691a01228df2d37bcfae7cb944c0d20edf
     1446726a18086b3134b15ccc1642ba4ba1ae9d91b
  • 1platform-content-ai/trunk/1platform-content-ai.php

    r3497124 r3497127  
    55 * Plugin URI: https://1platform.pro/
    66 * Description: SaaS client for AI-powered content generation, SEO optimization, and site management. All AI processing happens on 1Platform external servers. Includes free local tools: Table of Contents and Internal Links.
    7  * Version: 2.17.4
     7 * Version: 2.19.0
    88 * Author: 1Platform
    99 * License: GPLv2 or later
     
    6161require_once plugin_dir_path( __FILE__ ) . 'includes/admin/agents/ContaiAgentsAdminPage.php';
    6262require_once plugin_dir_path( __FILE__ ) . 'includes/cron/agent-actions-cron.php';
     63
     64// SEO domain
     65require_once plugin_dir_path(__FILE__) . 'includes/services/seo/SeoHeadService.php';
     66$contai_seo_head = new ContaiSeoHeadService();
     67$contai_seo_head->register();
    6368
    6469// Analytics domain
  • 1platform-content-ai/trunk/includes/services/content/ContentGeneratorService.php

    r3483422 r3497127  
    167167            $data['url'] ?? null,
    168168            $seo_metadata['metatitle'] ?? null,
     169            $seo_metadata['meta_description'] ?? null,
    169170            $seo_metadata['post_date'] ?? null
    170171        );
     
    189190    private ?string $slug;
    190191    private ?string $metatitle;
     192    private ?string $meta_description;
    191193    private ?string $post_date;
    192194
     
    199201        ?string $slug = null,
    200202        ?string $metatitle = null,
     203        ?string $meta_description = null,
    201204        ?string $post_date = null,
    202205        ?string $error_message = null,
     
    210213        $this->slug = $slug;
    211214        $this->metatitle = $metatitle;
     215        $this->meta_description = $meta_description;
    212216        $this->post_date = $post_date;
    213217        $this->error_message = $error_message;
     
    222226        ?string $slug = null,
    223227        ?string $metatitle = null,
     228        ?string $meta_description = null,
    224229        ?string $post_date = null
    225230    ): self {
    226         return new self(true, $title, $content, $images, $category, $slug, $metatitle, $post_date, null, 200);
     231        return new self(true, $title, $content, $images, $category, $slug, $metatitle, $meta_description, $post_date, null, 200);
    227232    }
    228233
    229234    public static function failure(string $error_message, ?int $status_code = null): self {
    230         return new self(false, '', '', [], null, null, null, null, $error_message, $status_code);
     235        return new self(false, '', '', [], null, null, null, null, null, $error_message, $status_code);
    231236    }
    232237
     
    261266    public function getMetatitle(): ?string {
    262267        return $this->metatitle;
     268    }
     269
     270    public function getMetaDescription(): ?string {
     271        return $this->meta_description;
    263272    }
    264273
     
    280289            'slug' => $this->slug,
    281290            'metatitle' => $this->metatitle,
     291            'meta_description' => $this->meta_description,
    282292            'post_date' => $this->post_date,
    283293            'error_message' => $this->error_message,
  • 1platform-content-ai/trunk/includes/services/post/ContentImageProcessor.php

    r3483422 r3497127  
    1313    }
    1414
    15     public function process(string $content, array $images): string {
    16         if (empty($images)) {
    17             return $content;
     15    public function process(string $content, array $images, string $alt_text = ''): string {
     16        if (!empty($images)) {
     17            $url_map = $this->buildUrlMap($content, $images, $alt_text);
     18            $content = $this->replaceUrls($content, $url_map);
    1819        }
    1920
    20         $url_map = $this->buildUrlMap($content, $images);
     21        if ($alt_text !== '') {
     22            $content = $this->ensureImgAltAttributes($content, $alt_text);
     23        }
    2124
    22         return $this->replaceUrls($content, $url_map);
     25        return $content;
    2326    }
    2427
    25     private function buildUrlMap(string $content, array $images): array {
     28    private function buildUrlMap(string $content, array $images, string $alt_text): array {
    2629        $url_map = [];
    2730
     
    3336            }
    3437
    35             $attachment_id = $this->uploader->uploadFromUrl($external_url);
     38            $image_alt = !empty($image['alt_text']) ? $image['alt_text'] : $alt_text;
     39            $attachment_id = $this->uploader->uploadFromUrl($external_url, $image_alt);
    3640
    3741            if ($attachment_id === null) {
     
    6064        return $content;
    6165    }
     66
     67    private function ensureImgAltAttributes(string $content, string $alt_text): string {
     68        $escaped_alt = esc_attr($alt_text);
     69
     70        // Replace empty alt attributes with the keyword-based alt text
     71        $content = preg_replace(
     72            '/<img([^>]*)\salt=["\']["\']/',
     73            '<img$1 alt="' . $escaped_alt . '"',
     74            $content
     75        );
     76
     77        // Add alt attribute to <img> tags missing it entirely
     78        $content = preg_replace(
     79            '/<img((?![^>]*\salt=)[^>]*)\s*\/?>/',
     80            '<img$1 alt="' . $escaped_alt . '">',
     81            $content
     82        );
     83
     84        return $content;
     85    }
    6286}
  • 1platform-content-ai/trunk/includes/services/post/ImageUploader.php

    r3483422 r3497127  
    99    }
    1010
    11     public function uploadFromUrl(string $image_url): ?int {
     11    public function uploadFromUrl(string $image_url, string $alt_text = ''): ?int {
    1212        $temp_file = download_url($image_url);
    1313
     
    2020
    2121        $this->cleanupTempFile($temp_file);
     22
     23        if ($attachment_id !== null && $alt_text !== '') {
     24            update_post_meta($attachment_id, '_wp_attachment_image_alt', sanitize_text_field($alt_text));
     25        }
    2226
    2327        return $attachment_id;
  • 1platform-content-ai/trunk/includes/services/post/PostGenerationOrchestrator.php

    r3497124 r3497127  
    117117            $api_result['url'] ?? null,
    118118            $seo_metadata['metatitle'] ?? null,
     119            $seo_metadata['meta_description'] ?? null,
    119120            $seo_metadata['post_date'] ?? null
    120121        );
     
    128129
    129130    private function buildPost(ContaiKeyword $keyword, array $params, ContaiContentGenerationResult $content_result): ContaiPostGenerationResult {
     131        $alt_text = $keyword->getTitle() ?: $keyword->getKeyword();
     132
    130133        $processed_content = $this->image_processor->process(
    131134            $content_result->getContent(),
    132             $content_result->getImages()
     135            $content_result->getImages(),
     136            $alt_text
    133137        );
    134138
     
    139143            $content_result->getSlug(),
    140144            $content_result->getPostDate(),
    141             $content_result->getMetatitle()
     145            $content_result->getMetatitle(),
     146            $content_result->getMetaDescription()
    142147        );
    143148
     
    146151        $this->saveMetadata($post_id, $keyword, $params);
    147152
    148         $this->setFeaturedImageIfExists($post_id, $content_result->getImages());
     153        $this->setFeaturedImageIfExists($post_id, $content_result->getImages(), $alt_text);
    149154
    150155        return new ContaiPostGenerationResult(
     
    186191    }
    187192
    188     private function setFeaturedImageIfExists(int $post_id, array $images): void {
     193    private function setFeaturedImageIfExists(int $post_id, array $images, string $alt_text = ''): void {
    189194        if (empty($images)) {
    190195            return;
     
    202207        update_post_meta($post_id, self::META_FEATURED_IMAGE_SOURCE, $selected_url);
    203208
    204         $attachment_id = $this->image_uploader->uploadFromUrl($selected_url);
     209        $attachment_id = $this->image_uploader->uploadFromUrl($selected_url, $alt_text);
    205210
    206211        if ($attachment_id !== null) {
  • 1platform-content-ai/trunk/includes/services/post/WordPressPostCreator.php

    r3483422 r3497127  
    66
    77    private const CUSTOM_FIELD_METATITLE = '_contai_metatitle';
     8    private const META_DESCRIPTION_LENGTH = 155;
    89
    9     public function create(string $title, string $content, ?string $slug = null, ?string $post_date = null, ?string $metatitle = null): int {
     10    public function create(string $title, string $content, ?string $slug = null, ?string $post_date = null, ?string $metatitle = null, ?string $meta_description = null): int {
     11        $excerpt = !empty($meta_description) ? $meta_description : $this->generateExcerpt($content);
     12
    1013        $post_data = [
    1114            'post_title' => sanitize_text_field($title),
    1215            'post_content' => $content,
    1316            'post_status' => 'publish',
    14             'post_type' => 'post'
     17            'post_type' => 'post',
     18            'post_excerpt' => sanitize_text_field($excerpt),
    1519        ];
    1620
     
    5256    }
    5357
     58    private function generateExcerpt(string $content): string {
     59        $text = wp_strip_all_tags($content);
     60        $text = preg_replace('/\s+/', ' ', trim($text));
     61
     62        if (mb_strlen($text) <= self::META_DESCRIPTION_LENGTH) {
     63            return $text;
     64        }
     65
     66        $truncated = mb_substr($text, 0, self::META_DESCRIPTION_LENGTH);
     67        $last_space = mb_strrpos($truncated, ' ');
     68
     69        if ($last_space !== false) {
     70            $truncated = mb_substr($truncated, 0, $last_space);
     71        }
     72
     73        return $truncated . '...';
     74    }
     75
    5476    public function assignCategory(int $post_id, int $category_id): void {
    5577        wp_set_object_terms($post_id, [$category_id], 'category', false);
  • 1platform-content-ai/trunk/readme.txt

    r3497124 r3497127  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 2.17.4
     7Stable tag: 2.19.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    161161== Changelog ==
    162162
     163= 2.18.0 =
     164* Added: SEO alt text on all images uploaded by the content pipeline (#49)
     165* Added: Meta description output via wp_head for generated posts (#49)
     166* Added: SEO title tag override using AI-generated metatitle (#49)
     167* Added: Auto-generated post excerpts for meta description support
     168
    163169= 2.17.3 =
    164170* Fix: Featured image dedup no longer falls back to first image when all candidates are used — skips featured image to avoid duplication (#47)
Note: See TracChangeset for help on using the changeset viewer.