Plugin Directory

Changeset 3482396


Ignore:
Timestamp:
03/14/2026 07:36:07 AM (2 weeks ago)
Author:
zeicaro
Message:

v2.0.0 — AI content generation (BYOK), MCP Server for AI agents, JWT Auth, Webhooks, Bulk create, Multi-SEO support

Location:
wpraiz-content-api-tool
Files:
56 added
2 deleted
6 edited
1 copied

Legend:

Unmodified
Added
Removed
  • wpraiz-content-api-tool/tags/2.0.0/readme.txt

    r3265377 r3482396  
    11=== WPRaiz Content API Tool ===
    2 Contributors: zeicaro
    3 Donate link: https://ai.wpraiz.com.br
    4 Tags: REST API, Content, Post Creation, SEO, Image Upload, Automation
    5 Requires at least: 5.0
    6 Tested up to: 6.7.2
    7 Stable tag: 1.5
    8 License: GPLv3
    9 License URI: https://www.gnu.org/licenses/gpl-3.0.html
     2Contributors: wpraiz, joseicaro
     3Tags: rest-api, content-automation, ai-content, mcp, claude
     4Requires at least: 5.8
     5Tested up to: 6.7
     6Requires PHP: 7.4
     7Stable tag: 2.0.0
     8License: GPLv2 or later
     9License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 Create WordPress posts via REST API with custom SEO fields, image uploads, primary category assignment, and integration with major SEO plugins (SEOPress, Yoast SEO, Rank Math).
     11REST API + MCP Server for WordPress. Create, update, and manage posts programmatically. AI content generation with your own API keys (BYOK).
    1212
    1313== Description ==
    1414
    15 The WPRaiz Content API Tool is a powerful plugin that enables the creation of WordPress posts programmatically through a REST API. Ideal for developers and websites that need seamless content integration from external systems, this plugin supports:
     15**WPRaiz Content API Tool** turns your WordPress site into a powerful content API. Create posts, manage categories, generate AI content, and connect AI agents — all via REST API or Model Context Protocol (MCP).
    1616
    17 - **Integration with Major SEO Plugins**: Set custom SEO fields for titles and descriptions, compatible with SEOPress, Yoast SEO, and Rank Math.
    18 - **Flexible Image Uploads**: Accept images via URL, with automatic attachment as the featured image.
    19 - **Automatic Category Management**: Assign an existing category or create a new one based on supplied category names.
    20 - **Installation Check Endpoint**: Verify plugin installation, authentication, and detect the installed SEO plugin.
    21 - **Post Similarity Search**: Find and return the top 10 most similar published posts by title for better content linking and duplication checks.
    22 - **Admin Interface**: Dashboard section with copyable API endpoints for easy testing.
     17= What You Can Do =
     18
     19* **Create & Update Posts** — Full control over title, content, status, categories, tags, excerpt, featured images, and custom meta fields via REST API.
     20* **Bulk Creation** — Create up to 50 posts in a single request (Pro).
     21* **AI Content Generation** — Generate full articles from a topic using Claude or OpenAI with your own API keys (Pro).
     22* **AI Rewrite** — Improve SEO, fix grammar, change tone, expand, or summarize existing posts (Pro).
     23* **Auto-SEO** — Automatically generate SEO titles and meta descriptions when not provided. Supports SEOPress, Yoast SEO, and Rank Math.
     24* **MCP Server** — Connect AI agents (Claude Desktop, Cursor, Windsurf) directly to your site via Model Context Protocol.
     25* **Similar Post Search** — Find duplicate or related content using intelligent Levenshtein-based scoring.
     26* **Webhooks** — Get notified when posts are created or bulk operations complete, with HMAC signature verification.
     27* **JWT Authentication** — Secure token-based auth with configurable rate limiting.
     28
     29= Free vs Pro =
     30
     31**Free** (this plugin):
     32
     33* Create and update single posts via REST API
     34* Search similar posts
     35* List and manage categories
     36* JWT and Basic Auth (Application Passwords)
     37* SEO plugin auto-detection and meta writing
     38* Featured image upload from URL
     39* Rate limiting
     40* Legacy v1 endpoint compatibility
     41
     42**Pro** ($49/year at [wpraiz.com.br/pro](https://wpraiz.com.br/pro)):
     43
     44* Everything in Free, plus:
     45* Bulk post creation (up to 50 per batch)
     46* AI content generation (BYOK — Claude or OpenAI)
     47* AI post rewriting (5 modes)
     48* Auto-SEO via AI
     49* MCP Server (HTTP + STDIO transports)
     50* Webhook notifications with HMAC signing
     51* Priority support
     52
     53= MCP Server =
     54
     55The Model Context Protocol server lets AI agents interact with your WordPress site natively. Available via HTTP (REST API) or STDIO (WP-CLI).
     56
     57**Tools:** create_post, update_post, search_similar, get_categories, generate_content, rewrite_post, bulk_create
     58
     59**Resources:** site-info, recent-posts, categories, content-stats, seo-config
     60
     61**Prompts:** publish_seo_article, content_series, seo_audit, refresh_old_content, internal_linking
     62
     63Add to your `claude_desktop_config.json`:
     64
     65`
     66{
     67    "mcpServers": {
     68        "wpraiz": {
     69            "command": "wp",
     70            "args": ["wpraiz-mcp", "serve", "--path=/path/to/wordpress", "--user=1"]
     71        }
     72    }
     73}
     74`
     75
     76= REST API Endpoints =
     77
     78Base URL: `https://yoursite.com/wp-json/wpraiz/v2/`
     79
     80| Endpoint | Method | Auth | Tier |
     81|---|---|---|---|
     82| create-post | POST | JWT/Basic | Free |
     83| update-post | POST | JWT/Basic | Free |
     84| create-posts | POST | JWT/Basic | Pro |
     85| generate-content | POST | JWT/Basic | Pro |
     86| rewrite-post | POST | JWT/Basic | Pro |
     87| search-similar | GET | Public | Free |
     88| categories | GET | Public | Free |
     89| check-status | GET | Public | Free |
     90| auth/token | POST | Credentials | Free |
     91
     92= Authentication =
     93
     94**JWT Token:**
     951. POST to `auth/token` with `username` and `password`
     962. Use the returned token as `Authorization: Bearer <token>`
     97
     98**Basic Auth:**
     99Use WordPress Application Passwords with standard HTTP Basic authentication.
     100
     101= Requirements =
     102
     103* WordPress 5.8+
     104* PHP 7.4+
     105* For AI features: Claude API key or OpenAI API key
     106* For MCP STDIO: WP-CLI installed
    23107
    24108== Installation ==
    25109
    26 1. Download the plugin zip file.
    27 2. In your WordPress dashboard, go to **Plugins > Add New**.
    28 3. Click on **Upload Plugin** and choose the zip file you downloaded.
    29 4. Click **Install Now** and activate the plugin.
    30 5. Use the REST API endpoint `/wp-json/api-post-creator/v1/create-post` to start creating posts programmatically.
    31 
    32 == Authentication ==
    33 
    34 To authenticate API requests, you must use an application password, which can be generated from your user profile in the WordPress admin dashboard.
    35 
    36 1. **Generate Application Password**: Go to **Users > Profile** in the WordPress dashboard and create an application password.
    37 2. **Authorization Header**: Pass the application password in a Base64-encoded `Authorization` header in the format `Basic {base64_encode(username:application_password)}`.
    38 
    39 == REST API Endpoints ==
    40 
    41 1. **Create Post Endpoint** 
    42    - **URL**: `/wp-json/api-post-creator/v1/create-post` 
    43    - **Method**: POST 
    44    - **Parameters**:
    45      - `title` (required): Title of the post. 
    46      - `content` (required): Content of the post. 
    47      - `status` (optional): Post status (default: draft). 
    48      - `primary_category` (optional): Primary category name. 
    49      - `seo_title` (optional): SEO title. 
    50      - `seo_desc` (optional): SEO description. 
    51      - `image_url` (optional): URL for the featured image.
    52 
    53 2. **Check Installation Endpoint** 
    54    - **URL**: `/wp-json/api-post-creator/v1/check-status` 
    55    - **Method**: GET 
    56    - **Purpose**: Verifies plugin status and active SEO plugin.
    57 
    58 3. **List Categories Endpoint** 
    59    - **URL**: `/wp-json/api-post-creator/v1/get-categories` 
    60    - **Method**: GET 
    61    - **Purpose**: Returns all registered post categories.
    62 
    63 4. **Similar Titles Search Endpoint** 
    64    - **URL**: `/wp-json/api-post-creator/v1/search-similar-posts?title=Your+Title+Here` 
    65    - **Method**: GET 
    66    - **Purpose**: Returns the top 10 most similar published posts by title.
     1101. Upload the plugin files to `/wp-content/plugins/wpraiz-content-api-tool/` or install through the WordPress plugins screen.
     1112. Activate the plugin through the 'Plugins' screen in WordPress.
     1123. Go to **Tools > WPRaiz Content API** to configure settings.
     1134. (Optional) Add your AI API keys for content generation features.
     1145. (Optional) Configure webhook URL for notifications.
    67115
    68116== Frequently Asked Questions ==
    69117
    70 = What is the REST API endpoint to create posts? =
    71 Use `/wp-json/api-post-creator/v1/create-post` with a POST request and appropriate parameters.
     118= Do I need an AI API key to use the plugin? =
    72119
    73 = How do I authenticate requests? =
    74 Use application passwords and pass them using the Basic Auth header.
     120No. The free version works without any API keys. AI features (content generation, rewriting, auto-SEO) require a Claude or OpenAI API key and a Pro license.
    75121
    76 = What SEO plugins are supported? =
    77 SEOPress, Yoast SEO, and Rank Math. The plugin auto-updates the correct meta fields.
     122= Which SEO plugins are supported? =
    78123
    79 = Can I search for similar post titles before publishing? =
    80 Yes! Use the `/search-similar-posts` endpoint to retrieve related posts for internal linking or avoiding duplicates.
     124SEOPress, Yoast SEO, and Rank Math. The plugin auto-detects which one is active and writes meta data accordingly. It also writes to all three for maximum compatibility.
     125
     126= Is the REST API secure? =
     127
     128Yes. All write endpoints require JWT or Basic Auth. Rate limiting is configurable. Read-only endpoints (search, categories, status) are public by design.
     129
     130= Can I use custom post types? =
     131
     132Yes. Both `create-post` and `search-similar` accept a `post_type` parameter. Any registered public post type is supported.
     133
     134= What is MCP? =
     135
     136Model Context Protocol is an open standard for connecting AI models to external tools. With our MCP server, Claude Desktop, Cursor, and other AI agents can create posts, search content, and generate articles directly on your WordPress site.
     137
     138= Are v1 endpoints still supported? =
     139
     140Yes. Legacy endpoints under `api-post-creator/v1/` continue to work for backward compatibility.
    81141
    82142== Screenshots ==
    83143
    84 1. **Admin Page Interface** – Displays all endpoints with copy buttons.
    85 2. **API Example Payload** – Demonstrates JSON structure to create a post.
    86 3. **Similarity Response** – JSON listing similar titles by relevance.
     1441. Admin page — Endpoints tab with copy-to-clipboard URLs
     1452. Admin page — Settings with AI provider configuration
     1463. Admin page — MCP Server setup with Claude Desktop config
     1474. Admin page — License activation
    87148
    88149== Changelog ==
    89150
     151= 2.0.0 =
     152* **Major rewrite** — Complete architecture overhaul with PSR-4 namespacing
     153* **New:** AI content generation with Claude and OpenAI (BYOK)
     154* **New:** AI post rewriting (improve SEO, fix grammar, change tone, expand, summarize)
     155* **New:** Auto-SEO — generate title and meta description via AI
     156* **New:** MCP Server with HTTP and STDIO transports
     157* **New:** MCP Tools, Resources, and Prompts for AI agents
     158* **New:** WP-CLI command `wp wpraiz-mcp serve` for Claude Desktop
     159* **New:** Bulk post creation (up to 50 per batch)
     160* **New:** Post update endpoint
     161* **New:** JWT authentication with configurable rate limiting
     162* **New:** Webhook system with HMAC signing and retry logic
     163* **New:** Admin UI with tabs (Endpoints, Settings, MCP, License)
     164* **Improved:** Similar post search using Levenshtein distance scoring
     165* **Improved:** SEO handler supports SEOPress, Yoast SEO, and Rank Math simultaneously
     166* **Improved:** Media handler with content-type validation
     167* **Improved:** Category auto-creation
     168* Backward compatible with v1 endpoints
     169
    90170= 1.5 =
    91 * Adicionada interface mais organizada para endpoints na página do admin.
    92 * Novo endpoint `search-similar-posts` para sugerir conteúdos semelhantes com score.
    93 * Otimizações de compatibilidade com WordPress 6.7.2.
    94 * SEO fields agora são gravados diretamente em todos os plugins suportados.
     171* Added organized endpoints interface on admin page.
     172* New `search-similar-posts` endpoint for content similarity scoring.
     173* Compatibility with WordPress 6.7.2.
     174* SEO fields now written directly to all supported plugins.
    95175
    96176= 1.4 =
    97177* Added SEO metadata integration for SEOPress, Yoast SEO, and Rank Math.
    98 * Introduced `/check-status` endpoint to verify installation and authentication.
     178* Introduced `/check-status` endpoint.
    99179* Enhanced error handling and response messages.
    100180
    101181= 1.3 =
    102182* Improved compatibility with WordPress 6.6.
    103 * Proper enqueuing of admin JavaScript and CSS files.
    104183* Added support for automatic category creation.
    105184* Enhanced image upload functionality.
     
    114193== Upgrade Notice ==
    115194
    116 = 1.5 =
    117 Recomendado para todos os usuários. Adiciona recursos importantes de verificação de similaridade e melhoria de usabilidade no painel administrativo.
    118 
    119 == License ==
    120 Este plugin é licenciado sob a GPLv3. Veja [GNU's official site](https://www.gnu.org/licenses/gpl-3.0.html) para detalhes.
     195= 2.0.0 =
     196Major update with AI features, MCP server, and complete rewrite. All v1 endpoints remain compatible. Review the new Settings page after upgrading.
    121197
    122198== Support ==
    123 Para dúvidas ou suporte, visite [WPRaiz Support](https://ai.wpraiz.com.br).
     199Visit [wpraiz.com.br](https://wpraiz.com.br) or open an issue on [GitHub](https://github.com/wpraiz/wpraiz-content-api-tool).
  • wpraiz-content-api-tool/tags/2.0.0/wpraiz-content.php

    r3265375 r3482396  
    11<?php
    2 
    3 /*
     2/**
    43 * Plugin Name: WPRaiz Content API Tool
    54 * Plugin URI: https://wpraiz.com.br
    6  * Donate link: https://wpraiz.com.br
    7  * Description: Plugin para criar postagens via API REST com campos personalizados de SEO, upload de imagens e categoria principal.
    8  * Version: 1.5
    9  * Author: José caro
     5 * Description: Create WordPress posts via REST API with SEO integration, AI content generation, and MCP server for AI agents.
     6 * Version: 2.0.0
     7 * Author: José Ícaro – WPRaiz
     8 * Author URI: https://wpraiz.com.br
    109 * License: GPLv3
     10 * License URI: https://www.gnu.org/licenses/gpl-3.0.html
     11 * Text Domain: wpraiz-content-api
     12 * Domain Path: /languages
     13 * Requires at least: 5.0
     14 * Tested up to: 7.0
     15 * Requires PHP: 7.4
    1116 */
    1217
    13 class API_Post_Creator_With_Image {
     18if ( ! defined( 'ABSPATH' ) ) {
     19    exit;
     20}
    1421
    15     public function __construct() {
    16         // Registra o endpoint na API REST
    17         add_action('rest_api_init', [$this, 'register_routes']);
    18         // Garante que os arquivos necessários para manipulação de mídia estejam disponíveis
    19         require_once(ABSPATH . 'wp-admin/includes/file.php');
    20         require_once(ABSPATH . 'wp-admin/includes/media.php');
    21         require_once(ABSPATH . 'wp-admin/includes/image.php');
     22// Plugin constants
     23define( 'WPRAIZ_VERSION', '2.0.0' );
     24define( 'WPRAIZ_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
     25define( 'WPRAIZ_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
     26define( 'WPRAIZ_PLUGIN_FILE', __FILE__ );
     27define( 'WPRAIZ_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
     28
     29/**
     30 * PSR-4-style autoloader for WPRaiz classes.
     31 */
     32spl_autoload_register( function ( $class ) {
     33    $prefix = 'WPRaiz\\ContentAPI\\';
     34    $len    = strlen( $prefix );
     35
     36    if ( strncmp( $prefix, $class, $len ) !== 0 ) {
     37        return;
    2238    }
    2339
    24     public function register_routes() {
    25         register_rest_route('api-post-creator/v1', '/create-post', [
    26             'methods'  => 'POST',
    27             'callback' => [$this, 'handle_create_post'],
    28             'permission_callback' => [$this, 'authenticate_user'],
    29         ]);
     40    $relative = substr( $class, $len );
     41    $parts    = explode( '\\', $relative );
     42    $filename = 'class-' . strtolower( str_replace( '_', '-', array_pop( $parts ) ) ) . '.php';
    3043
    31         // Novo endpoint GET para verificação
    32         register_rest_route('api-post-creator/v1', '/check-status', [
    33             'methods' => 'GET',
    34             'callback' => [$this, 'check_status'],
    35             //'permission_callback' => [$this, 'authenticate_user'],
    36         ]);
     44    $subdir = '';
     45    if ( ! empty( $parts ) ) {
     46        $subdir = strtolower( implode( '/', $parts ) ) . '/';
    3747    }
    3848
    39     // Função para validar autenticação e plugins de SEO ativos
    40     public function check_status() {
    41         $seo_plugin = $this->detect_seo_plugin();
    42        
    43         return new WP_REST_Response([
    44             'message' => 'Plugin ativo e autenticado com sucesso.',
    45             'seo_plugin' => $seo_plugin ? $seo_plugin : 'Nenhum plugin de SEO detectado',
    46         ], 200);
     49    $file = WPRAIZ_PLUGIN_DIR . 'includes/' . $subdir . $filename;
     50
     51    if ( file_exists( $file ) ) {
     52        require_once $file;
    4753    }
     54});
    4855
    49     // Função para detectar o plugin de SEO ativo
    50     private function detect_seo_plugin() {
    51         if (defined('SEOPRESS_VERSION')) {
    52             return 'seopress';
    53         } elseif (defined('WPSEO_VERSION')) {
    54             return 'yoastseo';
    55         } elseif (defined('RANK_MATH_VERSION')) {
    56             return 'rankmath';
    57         }
    58         return null;
    59     }
     56/**
     57 * Initialize the plugin.
     58 */
     59function wpraiz_init() {
     60    // Load textdomain
     61    load_plugin_textdomain( 'wpraiz-content-api', false, dirname( WPRAIZ_PLUGIN_BASENAME ) . '/languages' );
    6062
    61     public function handle_create_post($request) {
    62     $params = $request->get_json_params();
     63    // Core
     64    new WPRaiz\ContentAPI\Auth();
     65    new WPRaiz\ContentAPI\Content_Manager();
     66    new WPRaiz\ContentAPI\Search_Engine();
     67    new WPRaiz\ContentAPI\SEO_Handler();
     68    new WPRaiz\ContentAPI\Media_Handler();
     69    new WPRaiz\ContentAPI\Webhooks();
    6370
    64     if (empty($params['title']) || empty($params['content'])) {
    65         return new WP_Error('missing_data', 'Título e conteúdo são obrigatórios.', ['status' => 400]);
    66     }
    67            
    68     remove_filter('content_save_pre', 'wp_filter_post_kses');
     71    // AI
     72    new WPRaiz\ContentAPI\AI\AI_Manager();
    6973
    70     $post_id = wp_insert_post([
    71         'post_title'   => sanitize_text_field($params['title']),
    72         'post_content' => $params['content'],
    73         'post_status'  => sanitize_text_field($params['status'] ?? 'draft'),
    74         'post_author'  => get_current_user_id(),
    75     ]);
     74    // MCP
     75    new WPRaiz\ContentAPI\MCP\MCP_Server();
    7676
    77     add_filter('content_save_pre', 'wp_filter_post_kses');
    78 
    79     if (is_wp_error($post_id)) {
    80         return new WP_Error('post_creation_failed', 'Falha ao criar o post.', ['status' => 500]);
    81     }
    82 
    83     // Verifica ou cria a categoria principal
    84     if (!empty($params['primary_category'])) {
    85         $primary_category_id = $this->get_or_create_category(trim($params['primary_category']));
    86         if (!is_wp_error($primary_category_id)) {
    87             wp_set_post_terms($post_id, [$primary_category_id], 'category');
    88         }
    89     }
    90 
    91     // Faz o upload da imagem se a URL for fornecida
    92     if (!empty($params['image_url'])) {
    93         $image_id = $this->upload_image_from_url($params['image_url'], $post_id);
    94         if (!is_wp_error($image_id)) {
    95             set_post_thumbnail($post_id, $image_id);
    96         }
    97     }
    98 
    99     // Adiciona os campos de SEO com base no plugin ativo
    100     // Adiciona os campos de SEO diretamente
    101     if (!empty($params['seo_title'])) {
    102         update_post_meta($post_id, '_seopress_titles_title', sanitize_text_field($params['seo_title']));
    103         update_post_meta($post_id, '_yoast_wpseo_title', sanitize_text_field($params['seo_title']));
    104         update_post_meta($post_id, 'rank_math_title', sanitize_text_field($params['seo_title']));
    105     }
    106 
    107     if (!empty($params['seo_desc'])) {
    108         update_post_meta($post_id, '_seopress_titles_desc', sanitize_text_field($params['seo_desc']));
    109         update_post_meta($post_id, '_yoast_wpseo_metadesc', sanitize_text_field($params['seo_desc']));
    110         update_post_meta($post_id, 'rank_math_description', sanitize_text_field($params['seo_desc']));
    111     }
    112 
    113     return new WP_REST_Response([
    114         'message' => 'Post criado com sucesso!',
    115         'post_id' => $post_id,
    116         'post_url' => get_permalink($post_id),
    117     ], 201);
    118     }
    119 
    120     /**
    121      * Função para verificar ou criar uma categoria.
    122      */
    123     public function get_or_create_category($category_name) {
    124         $term = get_term_by('name', $category_name, 'category');
    125         if ($term) {
    126             return $term->term_id;
    127         } else {
    128             $new_term = wp_insert_term($category_name, 'category');
    129             if (is_wp_error($new_term)) {
    130                 return $new_term;
    131             }
    132             return $new_term['term_id'];
    133         }
    134     }
    135 
    136     public function upload_image_from_url($image_url, $post_id) {
    137         // Usa wp_remote_get() para obter o conteúdo da imagem da URL
    138         $response = wp_remote_get($image_url);
    139    
    140         if (is_wp_error($response)) {
    141             return new WP_Error('image_download_failed', 'Falha ao baixar a imagem.');
    142         }
    143    
    144         $image_data = wp_remote_retrieve_body($response);
    145         $http_code = wp_remote_retrieve_response_code($response);
    146    
    147         // Verifica se a resposta foi bem-sucedida
    148         if ($http_code !== 200 || !$image_data) {
    149             return new WP_Error('image_download_failed', 'Falha ao baixar a imagem.');
    150         }
    151    
    152         // Cria um arquivo temporário
    153         $tmp_file = wp_tempnam($image_url);
    154         global $wp_filesystem;
    155    
    156         if (!function_exists('WP_Filesystem')) {
    157             require_once(ABSPATH . 'wp-admin/includes/file.php');
    158         }
    159        
    160         WP_Filesystem();
    161         $wp_filesystem->put_contents($tmp_file, $image_data);
    162    
    163         // Define as informações do arquivo para o upload
    164         $file_array = array();
    165         $file_array['name'] = basename(wp_parse_url($image_url, PHP_URL_PATH)); // Usando wp_parse_url()
    166         $file_array['tmp_name'] = $tmp_file;
    167    
    168         // Faz o upload da imagem para o WordPress
    169         $image_id = media_handle_sideload($file_array, $post_id);
    170    
    171         if (is_wp_error($image_id)) {
    172             wp_delete_file($tmp_file);  // Remove o arquivo temporário
    173             return $image_id;
    174         }
    175    
    176         return $image_id;
    177     }
    178 
    179     public function authenticate_user($request) {
    180         return current_user_can('edit_posts');
     77    // Admin
     78    if ( is_admin() ) {
     79        new WPRaiz\ContentAPI\Admin\Admin_Page();
     80        new WPRaiz\ContentAPI\Admin\License();
    18181    }
    18282}
     83add_action( 'plugins_loaded', 'wpraiz_init' );
    18384
    184 class API_Post_Creator_With_Menu {
     85/**
     86 * Activation hook — create options and flush rewrite rules.
     87 */
     88function wpraiz_activate() {
     89    add_option( 'wpraiz_settings', [
     90        'ai_provider'    => '',
     91        'openai_api_key' => '',
     92        'claude_api_key' => '',
     93        'auto_seo'       => true,
     94        'webhook_url'    => '',
     95        'webhook_events' => [ 'post_created' ],
     96        'rate_limit'     => 60,
     97    ]);
     98    flush_rewrite_rules();
     99}
     100register_activation_hook( __FILE__, 'wpraiz_activate' );
    185101
    186     public function __construct() {
    187         // Adiciona o sub-menu na página de Ferramentas
    188         add_action('admin_menu', [$this, 'add_plugin_menu']);
    189         add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
    190     }
     102/**
     103 * Deactivation hook.
     104 */
     105function wpraiz_deactivate() {
     106    flush_rewrite_rules();
     107}
     108register_deactivation_hook( __FILE__, 'wpraiz_deactivate' );
    191109
    192     public function add_plugin_menu() {
    193         add_submenu_page(
    194             'tools.php', // O menu principal "Ferramentas"
    195             'WPRaiz Content API', // Título da página
    196             'WPRaiz Content API', // Texto do menu
    197             'manage_options', // Capability (permissão)
    198             'wpraiz-content-api', // Slug da página
    199             [$this, 'render_admin_page'] // Função que renderiza a página
    200         );
    201     }
    202 
    203     public function render_admin_page() {
    204         $site_url = get_site_url();
    205         $endpoint_url = $site_url . '/wp-json/api-post-creator/v1/create-post';
    206         $check_url = $site_url . '/wp-json/api-post-creator/v1/check-status';
    207         $categories_url = $site_url . '/wp-json/api-post-creator/v1/get-categories';
    208         $search_url = $site_url . '/wp-json/api-post-creator/v1/search-similar-posts?title=Exemplo';
    209         ?>
    210         <div class="wrap">
    211             <h1>WPRaiz Content API</h1>
    212             <img src="<?php echo esc_url(plugins_url('assets/images/logo_wpraiz.png', __FILE__)); ?>" width="300" alt="WPRaiz Logo" />
    213    
    214             <table class="widefat fixed" style="max-width: 900px; margin-top: 20px;">
    215                 <thead>
    216                     <tr>
    217                         <th>Descrição</th>
    218                         <th>Endpoint</th>
    219                         <th>Ação</th>
    220                     </tr>
    221                 </thead>
    222                 <tbody>
    223                     <tr>
    224                         <td><strong>Criação de Post</strong><br><small>Envia um post com imagem e SEO.</small></td>
    225                         <td><input type="text" id="api-endpoint" value="<?php echo esc_url($endpoint_url); ?>" readonly style="width: 100%;" /></td>
    226                         <td><button onclick="copyToClipboard('api-endpoint')" class="button">Copiar</button></td>
    227                     </tr>
    228                     <tr>
    229                         <td><strong>Status do Plugin</strong><br><small>Verifica se o plugin e o SEO estão ativos.</small></td>
    230                         <td><input type="text" id="check-endpoint" value="<?php echo esc_url($check_url); ?>" readonly style="width: 100%;" /></td>
    231                         <td><button onclick="copyToClipboard('check-endpoint')" class="button">Copiar</button></td>
    232                     </tr>
    233                     <tr>
    234                         <td><strong>Listar Categorias</strong><br><small>Retorna categorias existentes no site.</small></td>
    235                         <td><input type="text" id="categories-endpoint" value="<?php echo esc_url($categories_url); ?>" readonly style="width: 100%;" /></td>
    236                         <td><button onclick="copyToClipboard('categories-endpoint')" class="button">Copiar</button></td>
    237                     </tr>
    238                     <tr>
    239                         <td><strong>Buscar Títulos Semelhantes</strong><br><small>Retorna posts parecidos com base no título.</small></td>
    240                         <td><input type="text" id="search-endpoint" value="<?php echo esc_url($search_url); ?>" readonly style="width: 100%;" /></td>
    241                         <td><button onclick="copyToClipboard('search-endpoint')" class="button">Copiar</button></td>
    242                     </tr>
    243                 </tbody>
    244             </table>
    245    
    246             <h2 style="margin-top: 30px;">Exemplo de Requisição</h2>
    247             <pre>{
    248         "title": "Título do Post",
    249         "content": "Este é o conteúdo do post",
    250         "status": "publish",
    251         "primary_category": "Geral",
    252         "seo_title": "Título SEO",
    253         "seo_desc": "Descrição SEO",
    254         "image_url": "https://seu-site.com/imagem.jpg"
    255     }</pre>
    256             <p><strong>Importante:</strong> Use a senha de aplicativo do WordPress para autenticação via API.</p>
    257    
    258             <h2>Links Rápidos</h2>
    259             <a href="https://wpraiz.com.br" target="_blank" class="button button-primary">Visitar WPRaiz</a>
    260             <a href="https://youtube.com/wpraiz" target="_blank" class="button button-primary">Canal no Youtube</a>
    261         </div>
    262    
    263         <script type="text/javascript">
    264             function copyToClipboard(id) {
    265                 const el = document.getElementById(id);
    266                 el.select();
    267                 el.setSelectionRange(0, 99999);
    268                 document.execCommand("copy");
    269                 alert("Endpoint copiado: " + el.value);
    270             }
    271         </script>
    272         <?php
    273     }
    274 
    275     public function enqueue_admin_scripts($hook_suffix) {
    276         // Certifique-se de que você só adiciona os scripts na página correta
    277         if ($hook_suffix === 'tools_page_wpraiz-content-api') {
    278             wp_enqueue_script(
    279                 'wpraiz-admin-script',
    280                 plugins_url('assets/js/admin-scripts.js', __FILE__),
    281                 array('jquery'),
    282                 '1.3', // Definindo a versão do script
    283                 true
    284             );
    285         }
    286     }
     110/**
     111 * Helper: get plugin settings.
     112 */
     113function wpraiz_get_settings() {
     114    return wp_parse_args( get_option( 'wpraiz_settings', [] ), [
     115        'ai_provider'    => '',
     116        'openai_api_key' => '',
     117        'claude_api_key' => '',
     118        'auto_seo'       => true,
     119        'webhook_url'    => '',
     120        'webhook_events' => [ 'post_created' ],
     121        'rate_limit'     => 60,
     122    ]);
    287123}
    288124
    289 
    290 function api_post_creator_with_image_and_menu_init() {
    291     new API_Post_Creator_With_Image();
    292     new API_Post_Creator_With_Menu();
    293     }
    294     add_action('plugins_loaded', 'api_post_creator_with_image_and_menu_init');
    295     class API_Post_Creator_With_Categories {
    296 
    297         public function __construct() {
    298             add_action('rest_api_init', [$this, 'register_routes']);
    299         }
    300 
    301         public function register_routes() {
    302             register_rest_route('api-post-creator/v1', '/get-categories', [
    303                 'methods' => 'GET',
    304                 'callback' => [$this, 'get_categories'],
    305                 //'permission_callback' => [$this, 'authenticate_user'],
    306             ]);
    307             register_rest_route('api-post-creator/v1', '/search-similar-posts', [
    308                 'methods' => 'GET',
    309                 'callback' => [$this, 'search_similar_posts'],
    310                 'permission_callback' => '__return_true',
    311             ]);
    312         }
    313 
    314         public function get_categories() {
    315             $categories = get_categories([
    316                 'taxonomy' => 'category',
    317                 'hide_empty' => false,
    318             ]);
    319 
    320             $category_list = [];
    321             foreach ($categories as $category) {
    322                 $category_list[] = [
    323                     'id' => $category->term_id,
    324                     'name' => $category->name,
    325                     'slug' => $category->slug,
    326                     'description' => $category->description,
    327                     'count' => $category->count,
    328                 ];
    329             }
    330 
    331             return new WP_REST_Response($category_list, 200);
    332         }
    333 
    334         public function search_similar_posts($request) {
    335             $query_title = sanitize_text_field($request->get_param('title'));
    336 
    337             if (empty($query_title)) {
    338                 return new WP_REST_Response(['error' => 'Parâmetro "title" é obrigatório.'], 400);
    339             }
    340 
    341             $posts = get_posts([
    342                 'numberposts' => 100,
    343                 'post_status' => 'publish',
    344                 'post_type'   => 'post',
    345             ]);
    346 
    347             $resultados = [];
    348 
    349             foreach ($posts as $post) {
    350                 similar_text(strtolower($query_title), strtolower($post->post_title), $percent);
    351                 $categorias = get_the_category($post->ID);
    352                 $categoria_principal = $categorias[0]->name ?? '';
    353                 $categoria_secundaria = $categorias[1]->name ?? '';
    354 
    355                 $resultados[] = [
    356                     'title' => $post->post_title,
    357                     'score' => round($percent, 2),
    358                     'primary_category' => $categoria_principal,
    359                     'secondary_category' => $categoria_secundaria,
    360                     'url' => get_permalink($post->ID),
    361                 ];
    362             }
    363 
    364             usort($resultados, function($a, $b) {
    365                 return $b['score'] <=> $a['score'];
    366             });
    367 
    368             return new WP_REST_Response(array_slice($resultados, 0, 10), 200);
    369         }
    370 
    371 
    372         public function authenticate_user($request) {
    373             return current_user_can('edit_posts');
    374         }
    375     }
    376 
    377     new API_Post_Creator_With_Categories();
     125/**
     126 * Helper: check if Pro license is active.
     127 */
     128function wpraiz_is_pro(): bool {
     129    return \WPRaiz\ContentAPI\Admin\License::is_pro();
     130}
  • wpraiz-content-api-tool/trunk/readme.txt

    r3265377 r3482396  
    11=== WPRaiz Content API Tool ===
    2 Contributors: zeicaro
    3 Donate link: https://ai.wpraiz.com.br
    4 Tags: REST API, Content, Post Creation, SEO, Image Upload, Automation
    5 Requires at least: 5.0
    6 Tested up to: 6.7.2
    7 Stable tag: 1.5
    8 License: GPLv3
    9 License URI: https://www.gnu.org/licenses/gpl-3.0.html
     2Contributors: wpraiz, joseicaro
     3Tags: rest-api, content-automation, ai-content, mcp, claude
     4Requires at least: 5.8
     5Tested up to: 6.7
     6Requires PHP: 7.4
     7Stable tag: 2.0.0
     8License: GPLv2 or later
     9License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 Create WordPress posts via REST API with custom SEO fields, image uploads, primary category assignment, and integration with major SEO plugins (SEOPress, Yoast SEO, Rank Math).
     11REST API + MCP Server for WordPress. Create, update, and manage posts programmatically. AI content generation with your own API keys (BYOK).
    1212
    1313== Description ==
    1414
    15 The WPRaiz Content API Tool is a powerful plugin that enables the creation of WordPress posts programmatically through a REST API. Ideal for developers and websites that need seamless content integration from external systems, this plugin supports:
     15**WPRaiz Content API Tool** turns your WordPress site into a powerful content API. Create posts, manage categories, generate AI content, and connect AI agents — all via REST API or Model Context Protocol (MCP).
    1616
    17 - **Integration with Major SEO Plugins**: Set custom SEO fields for titles and descriptions, compatible with SEOPress, Yoast SEO, and Rank Math.
    18 - **Flexible Image Uploads**: Accept images via URL, with automatic attachment as the featured image.
    19 - **Automatic Category Management**: Assign an existing category or create a new one based on supplied category names.
    20 - **Installation Check Endpoint**: Verify plugin installation, authentication, and detect the installed SEO plugin.
    21 - **Post Similarity Search**: Find and return the top 10 most similar published posts by title for better content linking and duplication checks.
    22 - **Admin Interface**: Dashboard section with copyable API endpoints for easy testing.
     17= What You Can Do =
     18
     19* **Create & Update Posts** — Full control over title, content, status, categories, tags, excerpt, featured images, and custom meta fields via REST API.
     20* **Bulk Creation** — Create up to 50 posts in a single request (Pro).
     21* **AI Content Generation** — Generate full articles from a topic using Claude or OpenAI with your own API keys (Pro).
     22* **AI Rewrite** — Improve SEO, fix grammar, change tone, expand, or summarize existing posts (Pro).
     23* **Auto-SEO** — Automatically generate SEO titles and meta descriptions when not provided. Supports SEOPress, Yoast SEO, and Rank Math.
     24* **MCP Server** — Connect AI agents (Claude Desktop, Cursor, Windsurf) directly to your site via Model Context Protocol.
     25* **Similar Post Search** — Find duplicate or related content using intelligent Levenshtein-based scoring.
     26* **Webhooks** — Get notified when posts are created or bulk operations complete, with HMAC signature verification.
     27* **JWT Authentication** — Secure token-based auth with configurable rate limiting.
     28
     29= Free vs Pro =
     30
     31**Free** (this plugin):
     32
     33* Create and update single posts via REST API
     34* Search similar posts
     35* List and manage categories
     36* JWT and Basic Auth (Application Passwords)
     37* SEO plugin auto-detection and meta writing
     38* Featured image upload from URL
     39* Rate limiting
     40* Legacy v1 endpoint compatibility
     41
     42**Pro** ($49/year at [wpraiz.com.br/pro](https://wpraiz.com.br/pro)):
     43
     44* Everything in Free, plus:
     45* Bulk post creation (up to 50 per batch)
     46* AI content generation (BYOK — Claude or OpenAI)
     47* AI post rewriting (5 modes)
     48* Auto-SEO via AI
     49* MCP Server (HTTP + STDIO transports)
     50* Webhook notifications with HMAC signing
     51* Priority support
     52
     53= MCP Server =
     54
     55The Model Context Protocol server lets AI agents interact with your WordPress site natively. Available via HTTP (REST API) or STDIO (WP-CLI).
     56
     57**Tools:** create_post, update_post, search_similar, get_categories, generate_content, rewrite_post, bulk_create
     58
     59**Resources:** site-info, recent-posts, categories, content-stats, seo-config
     60
     61**Prompts:** publish_seo_article, content_series, seo_audit, refresh_old_content, internal_linking
     62
     63Add to your `claude_desktop_config.json`:
     64
     65`
     66{
     67    "mcpServers": {
     68        "wpraiz": {
     69            "command": "wp",
     70            "args": ["wpraiz-mcp", "serve", "--path=/path/to/wordpress", "--user=1"]
     71        }
     72    }
     73}
     74`
     75
     76= REST API Endpoints =
     77
     78Base URL: `https://yoursite.com/wp-json/wpraiz/v2/`
     79
     80| Endpoint | Method | Auth | Tier |
     81|---|---|---|---|
     82| create-post | POST | JWT/Basic | Free |
     83| update-post | POST | JWT/Basic | Free |
     84| create-posts | POST | JWT/Basic | Pro |
     85| generate-content | POST | JWT/Basic | Pro |
     86| rewrite-post | POST | JWT/Basic | Pro |
     87| search-similar | GET | Public | Free |
     88| categories | GET | Public | Free |
     89| check-status | GET | Public | Free |
     90| auth/token | POST | Credentials | Free |
     91
     92= Authentication =
     93
     94**JWT Token:**
     951. POST to `auth/token` with `username` and `password`
     962. Use the returned token as `Authorization: Bearer <token>`
     97
     98**Basic Auth:**
     99Use WordPress Application Passwords with standard HTTP Basic authentication.
     100
     101= Requirements =
     102
     103* WordPress 5.8+
     104* PHP 7.4+
     105* For AI features: Claude API key or OpenAI API key
     106* For MCP STDIO: WP-CLI installed
    23107
    24108== Installation ==
    25109
    26 1. Download the plugin zip file.
    27 2. In your WordPress dashboard, go to **Plugins > Add New**.
    28 3. Click on **Upload Plugin** and choose the zip file you downloaded.
    29 4. Click **Install Now** and activate the plugin.
    30 5. Use the REST API endpoint `/wp-json/api-post-creator/v1/create-post` to start creating posts programmatically.
    31 
    32 == Authentication ==
    33 
    34 To authenticate API requests, you must use an application password, which can be generated from your user profile in the WordPress admin dashboard.
    35 
    36 1. **Generate Application Password**: Go to **Users > Profile** in the WordPress dashboard and create an application password.
    37 2. **Authorization Header**: Pass the application password in a Base64-encoded `Authorization` header in the format `Basic {base64_encode(username:application_password)}`.
    38 
    39 == REST API Endpoints ==
    40 
    41 1. **Create Post Endpoint** 
    42    - **URL**: `/wp-json/api-post-creator/v1/create-post` 
    43    - **Method**: POST 
    44    - **Parameters**:
    45      - `title` (required): Title of the post. 
    46      - `content` (required): Content of the post. 
    47      - `status` (optional): Post status (default: draft). 
    48      - `primary_category` (optional): Primary category name. 
    49      - `seo_title` (optional): SEO title. 
    50      - `seo_desc` (optional): SEO description. 
    51      - `image_url` (optional): URL for the featured image.
    52 
    53 2. **Check Installation Endpoint** 
    54    - **URL**: `/wp-json/api-post-creator/v1/check-status` 
    55    - **Method**: GET 
    56    - **Purpose**: Verifies plugin status and active SEO plugin.
    57 
    58 3. **List Categories Endpoint** 
    59    - **URL**: `/wp-json/api-post-creator/v1/get-categories` 
    60    - **Method**: GET 
    61    - **Purpose**: Returns all registered post categories.
    62 
    63 4. **Similar Titles Search Endpoint** 
    64    - **URL**: `/wp-json/api-post-creator/v1/search-similar-posts?title=Your+Title+Here` 
    65    - **Method**: GET 
    66    - **Purpose**: Returns the top 10 most similar published posts by title.
     1101. Upload the plugin files to `/wp-content/plugins/wpraiz-content-api-tool/` or install through the WordPress plugins screen.
     1112. Activate the plugin through the 'Plugins' screen in WordPress.
     1123. Go to **Tools > WPRaiz Content API** to configure settings.
     1134. (Optional) Add your AI API keys for content generation features.
     1145. (Optional) Configure webhook URL for notifications.
    67115
    68116== Frequently Asked Questions ==
    69117
    70 = What is the REST API endpoint to create posts? =
    71 Use `/wp-json/api-post-creator/v1/create-post` with a POST request and appropriate parameters.
     118= Do I need an AI API key to use the plugin? =
    72119
    73 = How do I authenticate requests? =
    74 Use application passwords and pass them using the Basic Auth header.
     120No. The free version works without any API keys. AI features (content generation, rewriting, auto-SEO) require a Claude or OpenAI API key and a Pro license.
    75121
    76 = What SEO plugins are supported? =
    77 SEOPress, Yoast SEO, and Rank Math. The plugin auto-updates the correct meta fields.
     122= Which SEO plugins are supported? =
    78123
    79 = Can I search for similar post titles before publishing? =
    80 Yes! Use the `/search-similar-posts` endpoint to retrieve related posts for internal linking or avoiding duplicates.
     124SEOPress, Yoast SEO, and Rank Math. The plugin auto-detects which one is active and writes meta data accordingly. It also writes to all three for maximum compatibility.
     125
     126= Is the REST API secure? =
     127
     128Yes. All write endpoints require JWT or Basic Auth. Rate limiting is configurable. Read-only endpoints (search, categories, status) are public by design.
     129
     130= Can I use custom post types? =
     131
     132Yes. Both `create-post` and `search-similar` accept a `post_type` parameter. Any registered public post type is supported.
     133
     134= What is MCP? =
     135
     136Model Context Protocol is an open standard for connecting AI models to external tools. With our MCP server, Claude Desktop, Cursor, and other AI agents can create posts, search content, and generate articles directly on your WordPress site.
     137
     138= Are v1 endpoints still supported? =
     139
     140Yes. Legacy endpoints under `api-post-creator/v1/` continue to work for backward compatibility.
    81141
    82142== Screenshots ==
    83143
    84 1. **Admin Page Interface** – Displays all endpoints with copy buttons.
    85 2. **API Example Payload** – Demonstrates JSON structure to create a post.
    86 3. **Similarity Response** – JSON listing similar titles by relevance.
     1441. Admin page — Endpoints tab with copy-to-clipboard URLs
     1452. Admin page — Settings with AI provider configuration
     1463. Admin page — MCP Server setup with Claude Desktop config
     1474. Admin page — License activation
    87148
    88149== Changelog ==
    89150
     151= 2.0.0 =
     152* **Major rewrite** — Complete architecture overhaul with PSR-4 namespacing
     153* **New:** AI content generation with Claude and OpenAI (BYOK)
     154* **New:** AI post rewriting (improve SEO, fix grammar, change tone, expand, summarize)
     155* **New:** Auto-SEO — generate title and meta description via AI
     156* **New:** MCP Server with HTTP and STDIO transports
     157* **New:** MCP Tools, Resources, and Prompts for AI agents
     158* **New:** WP-CLI command `wp wpraiz-mcp serve` for Claude Desktop
     159* **New:** Bulk post creation (up to 50 per batch)
     160* **New:** Post update endpoint
     161* **New:** JWT authentication with configurable rate limiting
     162* **New:** Webhook system with HMAC signing and retry logic
     163* **New:** Admin UI with tabs (Endpoints, Settings, MCP, License)
     164* **Improved:** Similar post search using Levenshtein distance scoring
     165* **Improved:** SEO handler supports SEOPress, Yoast SEO, and Rank Math simultaneously
     166* **Improved:** Media handler with content-type validation
     167* **Improved:** Category auto-creation
     168* Backward compatible with v1 endpoints
     169
    90170= 1.5 =
    91 * Adicionada interface mais organizada para endpoints na página do admin.
    92 * Novo endpoint `search-similar-posts` para sugerir conteúdos semelhantes com score.
    93 * Otimizações de compatibilidade com WordPress 6.7.2.
    94 * SEO fields agora são gravados diretamente em todos os plugins suportados.
     171* Added organized endpoints interface on admin page.
     172* New `search-similar-posts` endpoint for content similarity scoring.
     173* Compatibility with WordPress 6.7.2.
     174* SEO fields now written directly to all supported plugins.
    95175
    96176= 1.4 =
    97177* Added SEO metadata integration for SEOPress, Yoast SEO, and Rank Math.
    98 * Introduced `/check-status` endpoint to verify installation and authentication.
     178* Introduced `/check-status` endpoint.
    99179* Enhanced error handling and response messages.
    100180
    101181= 1.3 =
    102182* Improved compatibility with WordPress 6.6.
    103 * Proper enqueuing of admin JavaScript and CSS files.
    104183* Added support for automatic category creation.
    105184* Enhanced image upload functionality.
     
    114193== Upgrade Notice ==
    115194
    116 = 1.5 =
    117 Recomendado para todos os usuários. Adiciona recursos importantes de verificação de similaridade e melhoria de usabilidade no painel administrativo.
    118 
    119 == License ==
    120 Este plugin é licenciado sob a GPLv3. Veja [GNU's official site](https://www.gnu.org/licenses/gpl-3.0.html) para detalhes.
     195= 2.0.0 =
     196Major update with AI features, MCP server, and complete rewrite. All v1 endpoints remain compatible. Review the new Settings page after upgrading.
    121197
    122198== Support ==
    123 Para dúvidas ou suporte, visite [WPRaiz Support](https://ai.wpraiz.com.br).
     199Visit [wpraiz.com.br](https://wpraiz.com.br) or open an issue on [GitHub](https://github.com/wpraiz/wpraiz-content-api-tool).
  • wpraiz-content-api-tool/trunk/wpraiz-content.php

    r3265375 r3482396  
    11<?php
    2 
    3 /*
     2/**
    43 * Plugin Name: WPRaiz Content API Tool
    54 * Plugin URI: https://wpraiz.com.br
    6  * Donate link: https://wpraiz.com.br
    7  * Description: Plugin para criar postagens via API REST com campos personalizados de SEO, upload de imagens e categoria principal.
    8  * Version: 1.5
    9  * Author: José caro
     5 * Description: Create WordPress posts via REST API with SEO integration, AI content generation, and MCP server for AI agents.
     6 * Version: 2.0.0
     7 * Author: José Ícaro – WPRaiz
     8 * Author URI: https://wpraiz.com.br
    109 * License: GPLv3
     10 * License URI: https://www.gnu.org/licenses/gpl-3.0.html
     11 * Text Domain: wpraiz-content-api
     12 * Domain Path: /languages
     13 * Requires at least: 5.0
     14 * Tested up to: 7.0
     15 * Requires PHP: 7.4
    1116 */
    1217
    13 class API_Post_Creator_With_Image {
     18if ( ! defined( 'ABSPATH' ) ) {
     19    exit;
     20}
    1421
    15     public function __construct() {
    16         // Registra o endpoint na API REST
    17         add_action('rest_api_init', [$this, 'register_routes']);
    18         // Garante que os arquivos necessários para manipulação de mídia estejam disponíveis
    19         require_once(ABSPATH . 'wp-admin/includes/file.php');
    20         require_once(ABSPATH . 'wp-admin/includes/media.php');
    21         require_once(ABSPATH . 'wp-admin/includes/image.php');
     22// Plugin constants
     23define( 'WPRAIZ_VERSION', '2.0.0' );
     24define( 'WPRAIZ_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
     25define( 'WPRAIZ_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
     26define( 'WPRAIZ_PLUGIN_FILE', __FILE__ );
     27define( 'WPRAIZ_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
     28
     29/**
     30 * PSR-4-style autoloader for WPRaiz classes.
     31 */
     32spl_autoload_register( function ( $class ) {
     33    $prefix = 'WPRaiz\\ContentAPI\\';
     34    $len    = strlen( $prefix );
     35
     36    if ( strncmp( $prefix, $class, $len ) !== 0 ) {
     37        return;
    2238    }
    2339
    24     public function register_routes() {
    25         register_rest_route('api-post-creator/v1', '/create-post', [
    26             'methods'  => 'POST',
    27             'callback' => [$this, 'handle_create_post'],
    28             'permission_callback' => [$this, 'authenticate_user'],
    29         ]);
     40    $relative = substr( $class, $len );
     41    $parts    = explode( '\\', $relative );
     42    $filename = 'class-' . strtolower( str_replace( '_', '-', array_pop( $parts ) ) ) . '.php';
    3043
    31         // Novo endpoint GET para verificação
    32         register_rest_route('api-post-creator/v1', '/check-status', [
    33             'methods' => 'GET',
    34             'callback' => [$this, 'check_status'],
    35             //'permission_callback' => [$this, 'authenticate_user'],
    36         ]);
     44    $subdir = '';
     45    if ( ! empty( $parts ) ) {
     46        $subdir = strtolower( implode( '/', $parts ) ) . '/';
    3747    }
    3848
    39     // Função para validar autenticação e plugins de SEO ativos
    40     public function check_status() {
    41         $seo_plugin = $this->detect_seo_plugin();
    42        
    43         return new WP_REST_Response([
    44             'message' => 'Plugin ativo e autenticado com sucesso.',
    45             'seo_plugin' => $seo_plugin ? $seo_plugin : 'Nenhum plugin de SEO detectado',
    46         ], 200);
     49    $file = WPRAIZ_PLUGIN_DIR . 'includes/' . $subdir . $filename;
     50
     51    if ( file_exists( $file ) ) {
     52        require_once $file;
    4753    }
     54});
    4855
    49     // Função para detectar o plugin de SEO ativo
    50     private function detect_seo_plugin() {
    51         if (defined('SEOPRESS_VERSION')) {
    52             return 'seopress';
    53         } elseif (defined('WPSEO_VERSION')) {
    54             return 'yoastseo';
    55         } elseif (defined('RANK_MATH_VERSION')) {
    56             return 'rankmath';
    57         }
    58         return null;
    59     }
     56/**
     57 * Initialize the plugin.
     58 */
     59function wpraiz_init() {
     60    // Load textdomain
     61    load_plugin_textdomain( 'wpraiz-content-api', false, dirname( WPRAIZ_PLUGIN_BASENAME ) . '/languages' );
    6062
    61     public function handle_create_post($request) {
    62     $params = $request->get_json_params();
     63    // Core
     64    new WPRaiz\ContentAPI\Auth();
     65    new WPRaiz\ContentAPI\Content_Manager();
     66    new WPRaiz\ContentAPI\Search_Engine();
     67    new WPRaiz\ContentAPI\SEO_Handler();
     68    new WPRaiz\ContentAPI\Media_Handler();
     69    new WPRaiz\ContentAPI\Webhooks();
    6370
    64     if (empty($params['title']) || empty($params['content'])) {
    65         return new WP_Error('missing_data', 'Título e conteúdo são obrigatórios.', ['status' => 400]);
    66     }
    67            
    68     remove_filter('content_save_pre', 'wp_filter_post_kses');
     71    // AI
     72    new WPRaiz\ContentAPI\AI\AI_Manager();
    6973
    70     $post_id = wp_insert_post([
    71         'post_title'   => sanitize_text_field($params['title']),
    72         'post_content' => $params['content'],
    73         'post_status'  => sanitize_text_field($params['status'] ?? 'draft'),
    74         'post_author'  => get_current_user_id(),
    75     ]);
     74    // MCP
     75    new WPRaiz\ContentAPI\MCP\MCP_Server();
    7676
    77     add_filter('content_save_pre', 'wp_filter_post_kses');
    78 
    79     if (is_wp_error($post_id)) {
    80         return new WP_Error('post_creation_failed', 'Falha ao criar o post.', ['status' => 500]);
    81     }
    82 
    83     // Verifica ou cria a categoria principal
    84     if (!empty($params['primary_category'])) {
    85         $primary_category_id = $this->get_or_create_category(trim($params['primary_category']));
    86         if (!is_wp_error($primary_category_id)) {
    87             wp_set_post_terms($post_id, [$primary_category_id], 'category');
    88         }
    89     }
    90 
    91     // Faz o upload da imagem se a URL for fornecida
    92     if (!empty($params['image_url'])) {
    93         $image_id = $this->upload_image_from_url($params['image_url'], $post_id);
    94         if (!is_wp_error($image_id)) {
    95             set_post_thumbnail($post_id, $image_id);
    96         }
    97     }
    98 
    99     // Adiciona os campos de SEO com base no plugin ativo
    100     // Adiciona os campos de SEO diretamente
    101     if (!empty($params['seo_title'])) {
    102         update_post_meta($post_id, '_seopress_titles_title', sanitize_text_field($params['seo_title']));
    103         update_post_meta($post_id, '_yoast_wpseo_title', sanitize_text_field($params['seo_title']));
    104         update_post_meta($post_id, 'rank_math_title', sanitize_text_field($params['seo_title']));
    105     }
    106 
    107     if (!empty($params['seo_desc'])) {
    108         update_post_meta($post_id, '_seopress_titles_desc', sanitize_text_field($params['seo_desc']));
    109         update_post_meta($post_id, '_yoast_wpseo_metadesc', sanitize_text_field($params['seo_desc']));
    110         update_post_meta($post_id, 'rank_math_description', sanitize_text_field($params['seo_desc']));
    111     }
    112 
    113     return new WP_REST_Response([
    114         'message' => 'Post criado com sucesso!',
    115         'post_id' => $post_id,
    116         'post_url' => get_permalink($post_id),
    117     ], 201);
    118     }
    119 
    120     /**
    121      * Função para verificar ou criar uma categoria.
    122      */
    123     public function get_or_create_category($category_name) {
    124         $term = get_term_by('name', $category_name, 'category');
    125         if ($term) {
    126             return $term->term_id;
    127         } else {
    128             $new_term = wp_insert_term($category_name, 'category');
    129             if (is_wp_error($new_term)) {
    130                 return $new_term;
    131             }
    132             return $new_term['term_id'];
    133         }
    134     }
    135 
    136     public function upload_image_from_url($image_url, $post_id) {
    137         // Usa wp_remote_get() para obter o conteúdo da imagem da URL
    138         $response = wp_remote_get($image_url);
    139    
    140         if (is_wp_error($response)) {
    141             return new WP_Error('image_download_failed', 'Falha ao baixar a imagem.');
    142         }
    143    
    144         $image_data = wp_remote_retrieve_body($response);
    145         $http_code = wp_remote_retrieve_response_code($response);
    146    
    147         // Verifica se a resposta foi bem-sucedida
    148         if ($http_code !== 200 || !$image_data) {
    149             return new WP_Error('image_download_failed', 'Falha ao baixar a imagem.');
    150         }
    151    
    152         // Cria um arquivo temporário
    153         $tmp_file = wp_tempnam($image_url);
    154         global $wp_filesystem;
    155    
    156         if (!function_exists('WP_Filesystem')) {
    157             require_once(ABSPATH . 'wp-admin/includes/file.php');
    158         }
    159        
    160         WP_Filesystem();
    161         $wp_filesystem->put_contents($tmp_file, $image_data);
    162    
    163         // Define as informações do arquivo para o upload
    164         $file_array = array();
    165         $file_array['name'] = basename(wp_parse_url($image_url, PHP_URL_PATH)); // Usando wp_parse_url()
    166         $file_array['tmp_name'] = $tmp_file;
    167    
    168         // Faz o upload da imagem para o WordPress
    169         $image_id = media_handle_sideload($file_array, $post_id);
    170    
    171         if (is_wp_error($image_id)) {
    172             wp_delete_file($tmp_file);  // Remove o arquivo temporário
    173             return $image_id;
    174         }
    175    
    176         return $image_id;
    177     }
    178 
    179     public function authenticate_user($request) {
    180         return current_user_can('edit_posts');
     77    // Admin
     78    if ( is_admin() ) {
     79        new WPRaiz\ContentAPI\Admin\Admin_Page();
     80        new WPRaiz\ContentAPI\Admin\License();
    18181    }
    18282}
     83add_action( 'plugins_loaded', 'wpraiz_init' );
    18384
    184 class API_Post_Creator_With_Menu {
     85/**
     86 * Activation hook — create options and flush rewrite rules.
     87 */
     88function wpraiz_activate() {
     89    add_option( 'wpraiz_settings', [
     90        'ai_provider'    => '',
     91        'openai_api_key' => '',
     92        'claude_api_key' => '',
     93        'auto_seo'       => true,
     94        'webhook_url'    => '',
     95        'webhook_events' => [ 'post_created' ],
     96        'rate_limit'     => 60,
     97    ]);
     98    flush_rewrite_rules();
     99}
     100register_activation_hook( __FILE__, 'wpraiz_activate' );
    185101
    186     public function __construct() {
    187         // Adiciona o sub-menu na página de Ferramentas
    188         add_action('admin_menu', [$this, 'add_plugin_menu']);
    189         add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
    190     }
     102/**
     103 * Deactivation hook.
     104 */
     105function wpraiz_deactivate() {
     106    flush_rewrite_rules();
     107}
     108register_deactivation_hook( __FILE__, 'wpraiz_deactivate' );
    191109
    192     public function add_plugin_menu() {
    193         add_submenu_page(
    194             'tools.php', // O menu principal "Ferramentas"
    195             'WPRaiz Content API', // Título da página
    196             'WPRaiz Content API', // Texto do menu
    197             'manage_options', // Capability (permissão)
    198             'wpraiz-content-api', // Slug da página
    199             [$this, 'render_admin_page'] // Função que renderiza a página
    200         );
    201     }
    202 
    203     public function render_admin_page() {
    204         $site_url = get_site_url();
    205         $endpoint_url = $site_url . '/wp-json/api-post-creator/v1/create-post';
    206         $check_url = $site_url . '/wp-json/api-post-creator/v1/check-status';
    207         $categories_url = $site_url . '/wp-json/api-post-creator/v1/get-categories';
    208         $search_url = $site_url . '/wp-json/api-post-creator/v1/search-similar-posts?title=Exemplo';
    209         ?>
    210         <div class="wrap">
    211             <h1>WPRaiz Content API</h1>
    212             <img src="<?php echo esc_url(plugins_url('assets/images/logo_wpraiz.png', __FILE__)); ?>" width="300" alt="WPRaiz Logo" />
    213    
    214             <table class="widefat fixed" style="max-width: 900px; margin-top: 20px;">
    215                 <thead>
    216                     <tr>
    217                         <th>Descrição</th>
    218                         <th>Endpoint</th>
    219                         <th>Ação</th>
    220                     </tr>
    221                 </thead>
    222                 <tbody>
    223                     <tr>
    224                         <td><strong>Criação de Post</strong><br><small>Envia um post com imagem e SEO.</small></td>
    225                         <td><input type="text" id="api-endpoint" value="<?php echo esc_url($endpoint_url); ?>" readonly style="width: 100%;" /></td>
    226                         <td><button onclick="copyToClipboard('api-endpoint')" class="button">Copiar</button></td>
    227                     </tr>
    228                     <tr>
    229                         <td><strong>Status do Plugin</strong><br><small>Verifica se o plugin e o SEO estão ativos.</small></td>
    230                         <td><input type="text" id="check-endpoint" value="<?php echo esc_url($check_url); ?>" readonly style="width: 100%;" /></td>
    231                         <td><button onclick="copyToClipboard('check-endpoint')" class="button">Copiar</button></td>
    232                     </tr>
    233                     <tr>
    234                         <td><strong>Listar Categorias</strong><br><small>Retorna categorias existentes no site.</small></td>
    235                         <td><input type="text" id="categories-endpoint" value="<?php echo esc_url($categories_url); ?>" readonly style="width: 100%;" /></td>
    236                         <td><button onclick="copyToClipboard('categories-endpoint')" class="button">Copiar</button></td>
    237                     </tr>
    238                     <tr>
    239                         <td><strong>Buscar Títulos Semelhantes</strong><br><small>Retorna posts parecidos com base no título.</small></td>
    240                         <td><input type="text" id="search-endpoint" value="<?php echo esc_url($search_url); ?>" readonly style="width: 100%;" /></td>
    241                         <td><button onclick="copyToClipboard('search-endpoint')" class="button">Copiar</button></td>
    242                     </tr>
    243                 </tbody>
    244             </table>
    245    
    246             <h2 style="margin-top: 30px;">Exemplo de Requisição</h2>
    247             <pre>{
    248         "title": "Título do Post",
    249         "content": "Este é o conteúdo do post",
    250         "status": "publish",
    251         "primary_category": "Geral",
    252         "seo_title": "Título SEO",
    253         "seo_desc": "Descrição SEO",
    254         "image_url": "https://seu-site.com/imagem.jpg"
    255     }</pre>
    256             <p><strong>Importante:</strong> Use a senha de aplicativo do WordPress para autenticação via API.</p>
    257    
    258             <h2>Links Rápidos</h2>
    259             <a href="https://wpraiz.com.br" target="_blank" class="button button-primary">Visitar WPRaiz</a>
    260             <a href="https://youtube.com/wpraiz" target="_blank" class="button button-primary">Canal no Youtube</a>
    261         </div>
    262    
    263         <script type="text/javascript">
    264             function copyToClipboard(id) {
    265                 const el = document.getElementById(id);
    266                 el.select();
    267                 el.setSelectionRange(0, 99999);
    268                 document.execCommand("copy");
    269                 alert("Endpoint copiado: " + el.value);
    270             }
    271         </script>
    272         <?php
    273     }
    274 
    275     public function enqueue_admin_scripts($hook_suffix) {
    276         // Certifique-se de que você só adiciona os scripts na página correta
    277         if ($hook_suffix === 'tools_page_wpraiz-content-api') {
    278             wp_enqueue_script(
    279                 'wpraiz-admin-script',
    280                 plugins_url('assets/js/admin-scripts.js', __FILE__),
    281                 array('jquery'),
    282                 '1.3', // Definindo a versão do script
    283                 true
    284             );
    285         }
    286     }
     110/**
     111 * Helper: get plugin settings.
     112 */
     113function wpraiz_get_settings() {
     114    return wp_parse_args( get_option( 'wpraiz_settings', [] ), [
     115        'ai_provider'    => '',
     116        'openai_api_key' => '',
     117        'claude_api_key' => '',
     118        'auto_seo'       => true,
     119        'webhook_url'    => '',
     120        'webhook_events' => [ 'post_created' ],
     121        'rate_limit'     => 60,
     122    ]);
    287123}
    288124
    289 
    290 function api_post_creator_with_image_and_menu_init() {
    291     new API_Post_Creator_With_Image();
    292     new API_Post_Creator_With_Menu();
    293     }
    294     add_action('plugins_loaded', 'api_post_creator_with_image_and_menu_init');
    295     class API_Post_Creator_With_Categories {
    296 
    297         public function __construct() {
    298             add_action('rest_api_init', [$this, 'register_routes']);
    299         }
    300 
    301         public function register_routes() {
    302             register_rest_route('api-post-creator/v1', '/get-categories', [
    303                 'methods' => 'GET',
    304                 'callback' => [$this, 'get_categories'],
    305                 //'permission_callback' => [$this, 'authenticate_user'],
    306             ]);
    307             register_rest_route('api-post-creator/v1', '/search-similar-posts', [
    308                 'methods' => 'GET',
    309                 'callback' => [$this, 'search_similar_posts'],
    310                 'permission_callback' => '__return_true',
    311             ]);
    312         }
    313 
    314         public function get_categories() {
    315             $categories = get_categories([
    316                 'taxonomy' => 'category',
    317                 'hide_empty' => false,
    318             ]);
    319 
    320             $category_list = [];
    321             foreach ($categories as $category) {
    322                 $category_list[] = [
    323                     'id' => $category->term_id,
    324                     'name' => $category->name,
    325                     'slug' => $category->slug,
    326                     'description' => $category->description,
    327                     'count' => $category->count,
    328                 ];
    329             }
    330 
    331             return new WP_REST_Response($category_list, 200);
    332         }
    333 
    334         public function search_similar_posts($request) {
    335             $query_title = sanitize_text_field($request->get_param('title'));
    336 
    337             if (empty($query_title)) {
    338                 return new WP_REST_Response(['error' => 'Parâmetro "title" é obrigatório.'], 400);
    339             }
    340 
    341             $posts = get_posts([
    342                 'numberposts' => 100,
    343                 'post_status' => 'publish',
    344                 'post_type'   => 'post',
    345             ]);
    346 
    347             $resultados = [];
    348 
    349             foreach ($posts as $post) {
    350                 similar_text(strtolower($query_title), strtolower($post->post_title), $percent);
    351                 $categorias = get_the_category($post->ID);
    352                 $categoria_principal = $categorias[0]->name ?? '';
    353                 $categoria_secundaria = $categorias[1]->name ?? '';
    354 
    355                 $resultados[] = [
    356                     'title' => $post->post_title,
    357                     'score' => round($percent, 2),
    358                     'primary_category' => $categoria_principal,
    359                     'secondary_category' => $categoria_secundaria,
    360                     'url' => get_permalink($post->ID),
    361                 ];
    362             }
    363 
    364             usort($resultados, function($a, $b) {
    365                 return $b['score'] <=> $a['score'];
    366             });
    367 
    368             return new WP_REST_Response(array_slice($resultados, 0, 10), 200);
    369         }
    370 
    371 
    372         public function authenticate_user($request) {
    373             return current_user_can('edit_posts');
    374         }
    375     }
    376 
    377     new API_Post_Creator_With_Categories();
     125/**
     126 * Helper: check if Pro license is active.
     127 */
     128function wpraiz_is_pro(): bool {
     129    return \WPRaiz\ContentAPI\Admin\License::is_pro();
     130}
Note: See TracChangeset for help on using the changeset viewer.