Plugin Directory

Changeset 3464721


Ignore:
Timestamp:
02/19/2026 03:59:11 AM (6 weeks ago)
Author:
contentee
Message:

Update to version 2.1.0

Location:
contentee-ai/trunk
Files:
1 added
5 edited

Legend:

Unmodified
Added
Removed
  • contentee-ai/trunk/README.txt

    r3452669 r3464721  
    22Contributors: contentee
    33Tags: content, automation, seo, social media, contentee
    4 Requires at least: 5.0
     4Requires at least: 5.1
    55Tested up to: 6.9
    6 Stable tag: 2.0.0
     6Stable tag: 2.1.0
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    3535* Publish multilingual content with automatic language detection
    3636* Integrate with popular translation plugins including Polylang, WPML, TranslatePress, Weglot, GTranslate, MultilingualPress, and Loco Translate
     37* Full WordPress Multisite support with network-wide activation and a Network Admin settings page for managing all sites
    3738
    3839= External Service Requirement =
     
    5960
    6061== Installation ==
     62
     63= Single Site =
    6164
    62651. Upload the `contentee-ai` folder to the `/wp-content/plugins/` directory
     
    68717. Test the connection to ensure everything is working
    6972
     73= WordPress Multisite =
     74
     751. Upload the `contentee-ai` folder to the `/wp-content/plugins/` directory
     762. Network Activate the plugin from the Network Admin > Plugins page, or activate it on individual sites
     773. Each site admin can configure their own API key from the Contentee.ai menu in their site admin
     784. Super admins can also manage API keys for all sites from the Network Admin > Contentee.ai page
     79
    7080== Frequently Asked Questions ==
    7181
     
    8999
    90100The plugin automatically detects and works with: Polylang, WPML, TranslatePress, Weglot, GTranslate, MultilingualPress, and Loco Translate. If you have one of these plugins installed, Contentee.ai can publish content in the appropriate language.
     101
     102= Does this plugin work with WordPress Multisite? =
     103
     104Yes! The plugin fully supports WordPress Multisite. You can network-activate it for all sites at once, or activate it on individual sites. Each site maintains its own independent API key and connection to Contentee.ai.
     105
     106= Can a super admin manage API keys for all sites? =
     107
     108Yes. When the plugin is active on a multisite network, a Network Admin settings page is available under the Contentee.ai menu in the Network Admin dashboard. Super admins can configure and test API keys for any site in the network from this centralized page.
     109
     110= Do I need separate Contentee.ai accounts for each site? =
     111
     112Each site connects to Contentee.ai independently using its own API key. Depending on your Contentee.ai subscription, you may be able to use the same account for multiple sites.
    91113
    92114== Screenshots ==
     
    98120
    99121== Changelog ==
     122
     123= 2.1.0 =
     124* Added WordPress Multisite support with both network-wide and per-site activation
     125* Added Network Admin settings page for super admins to manage API keys across all sites
     126* Added automatic plugin setup for new sites created in a multisite network
     127* Added activation and deactivation hooks with multisite-aware lifecycle management
     128* Fixed post author assignment for multisite compatibility (no longer hardcoded to user ID 1)
     129* Added multisite context (blog ID, network name) to connection verification response
    100130
    101131= 2.0.0 =
     
    147177== Upgrade Notice ==
    148178
     179= 2.1.0 =
     180Multisite support! Network admins can now activate the plugin across all sites and manage API keys from a centralized Network Admin page. Each site maintains independent settings and connections. Also fixes post author assignment for multisite environments.
     181
    149182= 2.0.0 =
    150183Major update with multilingual support! Now compatible with 7 popular translation plugins including Polylang, WPML, TranslatePress, Weglot, GTranslate, MultilingualPress, and Loco Translate. Improved authentication system with WordPress-specific API keys for enhanced security.
  • contentee-ai/trunk/admin/css/admin-styles.css

    r3452669 r3464721  
    7272}
    7373
     74/* Network admin styles */
     75
     76.contentee-network-sites-table {
     77    margin-top: 15px;
     78}
     79
     80.contentee-network-sites-table .column-site-name {
     81    width: 20%;
     82}
     83
     84.contentee-network-sites-table .column-site-url {
     85    width: 20%;
     86}
     87
     88.contentee-network-sites-table .column-status {
     89    width: 12%;
     90}
     91
     92.contentee-network-sites-table .column-api-key {
     93    width: 25%;
     94}
     95
     96.contentee-network-sites-table .column-actions {
     97    width: 23%;
     98}
     99
     100.contentee-network-sites-table .contentee-network-api-key {
     101    width: 100%;
     102}
     103
     104.contentee-site-id {
     105    color: #999;
     106    font-size: 12px;
     107    margin-top: 2px;
     108}
     109
     110.contentee-status {
     111    display: inline-block;
     112    padding: 3px 10px;
     113    border-radius: 12px;
     114    font-size: 12px;
     115    font-weight: 600;
     116    line-height: 1.4;
     117}
     118
     119.contentee-status-connected {
     120    background-color: #d5e8d4;
     121    color: #1e7e34;
     122}
     123
     124.contentee-status-not-configured {
     125    background-color: #f0f0f0;
     126    color: #666;
     127}
     128
     129.contentee-network-status {
     130    display: inline-block;
     131    margin-left: 8px;
     132    font-weight: 600;
     133    font-size: 13px;
     134}
     135
     136.contentee-inline-success {
     137    color: #00a32a;
     138}
     139
     140.contentee-inline-error {
     141    color: #d63638;
     142}
     143
     144.contentee-network-sites-table .column-actions .button {
     145    margin-right: 4px;
     146}
     147
  • contentee-ai/trunk/admin/js/admin-scripts.js

    r3452669 r3464721  
    11jQuery(document).ready(function($) {
    2     // Test connection
     2
     3    // --- Per-site settings page ---
     4
    35    $('#contentee_test_connection').on('click', function() {
    46        var apiKey = $('#contentee_api_key').val();
    5        
     7
    68        if (!apiKey) {
    79            $('#contentee_connection_status').html('<span style="color: #d63638;">✗ Please enter your Profile API key first</span>');
    810            return;
    911        }
    10        
     12
    1113        $(this).prop('disabled', true).text('Testing...');
    1214        $('#contentee_connection_status').html('');
    13        
     15
    1416        $.ajax({
    1517            url: contenteeAdmin.ajaxurl,
     
    3537        });
    3638    });
     39
     40    // --- Network admin settings page ---
     41
     42    if (typeof contenteeAdmin !== 'undefined' && contenteeAdmin.isNetworkAdmin) {
     43
     44        $('.contentee-network-save').on('click', function() {
     45            var $btn = $(this);
     46            var blogId = $btn.data('blog-id');
     47            var $row = $btn.closest('tr');
     48            var apiKey = $row.find('.contentee-network-api-key').val();
     49            var $status = $row.find('.contentee-network-status');
     50
     51            $btn.prop('disabled', true).text('Saving...');
     52            $status.html('');
     53
     54            $.ajax({
     55                url: contenteeAdmin.ajaxurl,
     56                type: 'POST',
     57                data: {
     58                    action: 'contentee_network_save_site',
     59                    blog_id: blogId,
     60                    api_key: apiKey,
     61                    nonce: contenteeAdmin.networkNonce
     62                },
     63                success: function(response) {
     64                    if (response.success) {
     65                        $status.html('<span class="contentee-inline-success">✓ ' + response.data.message + '</span>');
     66                        var $statusCol = $row.find('.column-status');
     67                        if (apiKey) {
     68                            $statusCol.html('<span class="contentee-status contentee-status-connected">Connected</span>');
     69                        } else {
     70                            $statusCol.html('<span class="contentee-status contentee-status-not-configured">Not Configured</span>');
     71                        }
     72                    } else {
     73                        $status.html('<span class="contentee-inline-error">✗ ' + response.data.message + '</span>');
     74                    }
     75                },
     76                error: function() {
     77                    $status.html('<span class="contentee-inline-error">✗ Save failed</span>');
     78                },
     79                complete: function() {
     80                    $btn.prop('disabled', false).text('Save');
     81                }
     82            });
     83        });
     84
     85        $('.contentee-network-test').on('click', function() {
     86            var $btn = $(this);
     87            var blogId = $btn.data('blog-id');
     88            var $row = $btn.closest('tr');
     89            var apiKey = $row.find('.contentee-network-api-key').val();
     90            var $status = $row.find('.contentee-network-status');
     91
     92            $btn.prop('disabled', true).text('Testing...');
     93            $status.html('');
     94
     95            $.ajax({
     96                url: contenteeAdmin.ajaxurl,
     97                type: 'POST',
     98                data: {
     99                    action: 'contentee_network_test_connection',
     100                    blog_id: blogId,
     101                    api_key: apiKey,
     102                    nonce: contenteeAdmin.networkNonce
     103                },
     104                success: function(response) {
     105                    if (response.success) {
     106                        $status.html('<span class="contentee-inline-success">✓ ' + response.data.message + '</span>');
     107                    } else {
     108                        $status.html('<span class="contentee-inline-error">✗ ' + response.data.message + '</span>');
     109                    }
     110                },
     111                error: function() {
     112                    $status.html('<span class="contentee-inline-error">✗ Connection test failed</span>');
     113                },
     114                complete: function() {
     115                    $btn.prop('disabled', false).text('Test');
     116                }
     117            });
     118        });
     119    }
     120
    37121});
  • contentee-ai/trunk/contentee-ai.php

    r3452669 r3464721  
    33 * Plugin Name: Contentee.ai
    44 * Description: Connect your WordPress site to Contentee.ai for seamless content publishing from your content management platform.
    5  * Version: 2.0.0
     5 * Version: 2.1.0
    66 * Author: Contentee.ai
    77 * Author URI: https://contentee.ai
     
    1717
    1818// Define plugin constants
    19 define('CONTENTEE_VERSION', '2.0.0');
     19define('CONTENTEE_VERSION', '2.1.0');
    2020define('CONTENTEE_PLUGIN_DIR', plugin_dir_path(__FILE__));
    2121define('CONTENTEE_PLUGIN_URL', plugin_dir_url(__FILE__));
     
    2525
    2626/**
    27  * Add admin menu with custom icon
    28  */
    29 function contentee_add_admin_menu() {
    30     // Create SVG with padding
     27 * Plugin activation - handles both single-site and network-wide activation
     28 */
     29function contentee_activate($network_wide) {
     30    if (is_multisite() && $network_wide) {
     31        $sites = get_sites(array('number' => 0, 'fields' => 'ids'));
     32        foreach ($sites as $blog_id) {
     33            switch_to_blog($blog_id);
     34            contentee_activate_single_site();
     35            restore_current_blog();
     36        }
     37    } else {
     38        contentee_activate_single_site();
     39    }
     40}
     41register_activation_hook(__FILE__, 'contentee_activate');
     42
     43function contentee_activate_single_site() {
     44    if (empty(get_option('contentee_wordpress_api_key'))) {
     45        update_option('contentee_wordpress_api_key', wp_generate_password(32, false));
     46    }
     47}
     48
     49/**
     50 * Plugin deactivation - handles both single-site and network-wide deactivation
     51 */
     52function contentee_deactivate($network_wide) {
     53    if (is_multisite() && $network_wide) {
     54        $sites = get_sites(array('number' => 0, 'fields' => 'ids'));
     55        foreach ($sites as $blog_id) {
     56            switch_to_blog($blog_id);
     57            contentee_deactivate_single_site();
     58            restore_current_blog();
     59        }
     60    } else {
     61        contentee_deactivate_single_site();
     62    }
     63}
     64register_deactivation_hook(__FILE__, 'contentee_deactivate');
     65
     66function contentee_deactivate_single_site() {
     67    global $wpdb;
     68    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     69    $wpdb->query(
     70        $wpdb->prepare(
     71            "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
     72            '_transient_contentee_media_%'
     73        )
     74    );
     75    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     76    $wpdb->query(
     77        $wpdb->prepare(
     78            "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
     79            '_transient_timeout_contentee_media_%'
     80        )
     81    );
     82}
     83
     84/**
     85 * Auto-setup new sites created while plugin is network-activated
     86 */
     87function contentee_on_new_site($new_site) {
     88    if (!is_plugin_active_for_network(plugin_basename(__FILE__))) {
     89        return;
     90    }
     91    switch_to_blog($new_site->blog_id);
     92    contentee_activate_single_site();
     93    restore_current_blog();
     94}
     95add_action('wp_initialize_site', 'contentee_on_new_site', 900);
     96
     97/**
     98 * Get the base64-encoded SVG icon used for admin menus
     99 */
     100function contentee_get_icon_svg() {
    31101    $svg = '<svg width="20" height="20" viewBox="0 0 767.44 1276.74" xmlns="http://www.w3.org/2000/svg" style="padding: 2px;">
    32102        <path fill="white" d="M711.63,0H55.81C24.99,0,0,24.99,0,55.81v1165.12c0,30.83,24.99,55.81,55.81,55.81h655.81c30.83,0,55.81-24.99,55.81-55.81V55.81c0-30.83-24.99-55.81-55.81-55.81ZM623.28,583.89l-389.84,575.9c-4.43,6.65-11.07,8.86-17.72,8.86-2.22,0-6.64,0-8.86-2.21-8.86-4.43-15.51-15.51-11.08-26.58l137.33-480.65h-170.55c-8.86,0-15.51-4.43-19.93-11.08s-4.43-15.51,2.22-22.15L494.81,114.31c6.64-8.86,17.72-11.07,26.58-8.86,8.86,4.43,15.51,15.5,13.29,26.58l-99.67,416.42h170.55c8.86,0,15.51,4.43,19.93,11.08,4.43,8.86,2.21,17.72-2.22,24.36Z"/>
    33103    </svg>';
    34    
    35     $icon_svg = 'data:image/svg+xml;base64,' . base64_encode($svg);
    36    
     104    return 'data:image/svg+xml;base64,' . base64_encode($svg);
     105}
     106
     107/**
     108 * Add admin menu with custom icon (per-site)
     109 */
     110function contentee_add_admin_menu() {
    37111    add_menu_page(
    38         'Contentee.ai',           // Page title
    39         'Contentee.ai',           // Menu title
    40         'manage_options',         // Capability
    41         'contentee-settings',     // Menu slug
    42         'contentee_settings_page', // Callback function
    43         $icon_svg,                // Icon
    44         30                        // Position
     112        'Contentee.ai',
     113        'Contentee.ai',
     114        'manage_options',
     115        'contentee-settings',
     116        'contentee_settings_page',
     117        contentee_get_icon_svg(),
     118        30
    45119    );
    46120}
    47121add_action('admin_menu', 'contentee_add_admin_menu');
     122
     123/**
     124 * Add network admin menu for super admins (multisite only)
     125 */
     126function contentee_add_network_admin_menu() {
     127    add_menu_page(
     128        __('Contentee.ai Network Settings', 'contentee-ai'),
     129        'Contentee.ai',
     130        'manage_network_options',
     131        'contentee-network-settings',
     132        'contentee_network_settings_page',
     133        contentee_get_icon_svg(),
     134        30
     135    );
     136}
     137add_action('network_admin_menu', 'contentee_add_network_admin_menu');
     138
     139/**
     140 * Network settings page callback
     141 */
     142function contentee_network_settings_page() {
     143    require_once CONTENTEE_PLUGIN_DIR . 'admin/network-settings-page.php';
     144}
    48145
    49146/**
     
    67164
    68165/**
    69  * Enqueue admin styles and scripts
     166 * Enqueue admin styles and scripts (per-site and network admin)
    70167 */
    71168function contentee_admin_styles($hook) {
    72     if ($hook !== 'toplevel_page_contentee-settings') {
    73         return;
    74     }
    75    
    76     // Enqueue CSS
     169    $is_site_page = ($hook === 'toplevel_page_contentee-settings');
     170    $is_network_page = ($hook === 'toplevel_page_contentee-network-settings');
     171
     172    if (!$is_site_page && !$is_network_page) {
     173        return;
     174    }
     175
    77176    wp_enqueue_style('contentee-admin', CONTENTEE_PLUGIN_URL . 'admin/css/admin-styles.css', array(), CONTENTEE_VERSION);
    78    
    79     // Enqueue JavaScript
    80177    wp_enqueue_script('contentee-admin', CONTENTEE_PLUGIN_URL . 'admin/js/admin-scripts.js', array('jquery'), CONTENTEE_VERSION, true);
    81    
    82     // Pass data to JavaScript
    83     wp_localize_script('contentee-admin', 'contenteeAdmin', array(
     178
     179    $localized_data = array(
    84180        'ajaxurl' => admin_url('admin-ajax.php'),
    85         'nonce' => wp_create_nonce('contentee_nonce')
    86     ));
     181        'nonce' => wp_create_nonce('contentee_nonce'),
     182        'isNetworkAdmin' => $is_network_page,
     183    );
     184
     185    if ($is_network_page) {
     186        $localized_data['networkNonce'] = wp_create_nonce('contentee_network_nonce');
     187    }
     188
     189    wp_localize_script('contentee-admin', 'contenteeAdmin', $localized_data);
    87190}
    88191add_action('admin_enqueue_scripts', 'contentee_admin_styles');
     
    188291}
    189292add_action('wp_ajax_contentee_test_connection_temp', 'contentee_test_connection_temp');
     293
     294/**
     295 * Network AJAX handler: save API key for a specific site
     296 */
     297function contentee_network_save_site() {
     298    check_ajax_referer('contentee_network_nonce', 'nonce');
     299
     300    if (!current_user_can('manage_network_options')) {
     301        wp_send_json_error(array('message' => __('Permission denied.', 'contentee-ai')));
     302        return;
     303    }
     304
     305    $blog_id = isset($_POST['blog_id']) ? intval($_POST['blog_id']) : 0;
     306    $api_key = isset($_POST['api_key']) ? sanitize_text_field(wp_unslash($_POST['api_key'])) : '';
     307
     308    if ($blog_id < 1) {
     309        wp_send_json_error(array('message' => __('Invalid site ID.', 'contentee-ai')));
     310        return;
     311    }
     312
     313    switch_to_blog($blog_id);
     314
     315    update_option('contentee_api_key', $api_key);
     316
     317    $wordpress_api_key = get_option('contentee_wordpress_api_key');
     318    if (empty($wordpress_api_key)) {
     319        $wordpress_api_key = wp_generate_password(32, false);
     320        update_option('contentee_wordpress_api_key', $wordpress_api_key);
     321    }
     322
     323    if (!empty($api_key)) {
     324        $site_url = home_url();
     325        $site_name = get_bloginfo('name');
     326
     327        $register_response = wp_remote_post('https://api.contentee.ai/api/integrations/wordpress/register', array(
     328            'headers' => array(
     329                'Content-Type' => 'application/json',
     330                'x-api-key' => $api_key,
     331            ),
     332            'body' => wp_json_encode(array(
     333                'siteUrl' => $site_url,
     334                'siteName' => $site_name,
     335                'apiKey' => $wordpress_api_key,
     336            )),
     337            'timeout' => 15,
     338        ));
     339
     340        restore_current_blog();
     341
     342        if (is_wp_error($register_response)) {
     343            wp_send_json_error(array('message' => __('API key saved, but registration with Contentee.ai failed: ', 'contentee-ai') . $register_response->get_error_message()));
     344            return;
     345        }
     346
     347        wp_send_json_success(array('message' => __('API key saved and site registered with Contentee.ai.', 'contentee-ai')));
     348    } else {
     349        restore_current_blog();
     350        wp_send_json_success(array('message' => __('API key cleared.', 'contentee-ai')));
     351    }
     352}
     353add_action('wp_ajax_contentee_network_save_site', 'contentee_network_save_site');
     354
     355/**
     356 * Network AJAX handler: test connection for a specific site
     357 */
     358function contentee_network_test_connection() {
     359    check_ajax_referer('contentee_network_nonce', 'nonce');
     360
     361    if (!current_user_can('manage_network_options')) {
     362        wp_send_json_error(array('message' => __('Permission denied.', 'contentee-ai')));
     363        return;
     364    }
     365
     366    $blog_id = isset($_POST['blog_id']) ? intval($_POST['blog_id']) : 0;
     367    $api_key = isset($_POST['api_key']) ? sanitize_text_field(wp_unslash($_POST['api_key'])) : '';
     368
     369    if ($blog_id < 1) {
     370        wp_send_json_error(array('message' => __('Invalid site ID.', 'contentee-ai')));
     371        return;
     372    }
     373
     374    if (empty($api_key)) {
     375        switch_to_blog($blog_id);
     376        $api_key = get_option('contentee_api_key', '');
     377        restore_current_blog();
     378    }
     379
     380    if (empty($api_key)) {
     381        wp_send_json_error(array('message' => __('No API key configured for this site.', 'contentee-ai')));
     382        return;
     383    }
     384
     385    $response = wp_remote_get('https://api.contentee.ai/api/integrations/wordpress', array(
     386        'headers' => array('x-api-key' => $api_key),
     387        'timeout' => 15,
     388    ));
     389
     390    if (is_wp_error($response)) {
     391        wp_send_json_error(array('message' => $response->get_error_message()));
     392        return;
     393    }
     394
     395    $status_code = wp_remote_retrieve_response_code($response);
     396
     397    if ($status_code === 200) {
     398        wp_send_json_success(array('message' => __('Connection successful!', 'contentee-ai')));
     399    } else {
     400        wp_send_json_error(array('message' => __('Connection failed. Status: ', 'contentee-ai') . $status_code));
     401    }
     402}
     403add_action('wp_ajax_contentee_network_test_connection', 'contentee_network_test_connection');
  • contentee-ai/trunk/includes/api.php

    r3452669 r3464721  
    6565
    6666/**
     67 * Get the default post author for the current site (first administrator)
     68 */
     69function contentee_get_default_author() {
     70    $admins = get_users(array(
     71        'role'    => 'administrator',
     72        'number'  => 1,
     73        'orderby' => 'ID',
     74        'order'   => 'ASC',
     75        'blog_id' => get_current_blog_id(),
     76    ));
     77    return !empty($admins) ? $admins[0]->ID : 1;
     78}
     79
     80/**
    6781 * Verify API key from header
    6882 */
     
    8498 */
    8599function contentee_rest_verify_connection($request) {
    86     $site_url = get_site_url();
    87     $site_name = get_bloginfo('name');
    88    
    89     return new WP_REST_Response(array(
     100    $response_data = array(
    90101        'success' => true,
    91         'site_url' => $site_url,
    92         'site_name' => $site_name,
     102        'site_url' => get_site_url(),
     103        'site_name' => get_bloginfo('name'),
    93104        'wordpress_version' => get_bloginfo('version'),
    94         'plugin_version' => CONTENTEE_VERSION
    95     ), 200);
     105        'plugin_version' => CONTENTEE_VERSION,
     106        'is_multisite' => is_multisite(),
     107    );
     108
     109    if (is_multisite()) {
     110        $response_data['blog_id'] = get_current_blog_id();
     111        $response_data['network_name'] = get_network()->site_name;
     112    }
     113
     114    return new WP_REST_Response($response_data, 200);
    96115}
    97116
     
    215234        'post_status' => $status,
    216235        'post_type' => $post_type,
    217         'post_author' => 1, // Use admin user
     236        'post_author' => contentee_get_default_author(),
    218237    );
    219238   
Note: See TracChangeset for help on using the changeset viewer.