Changeset 3464721
- Timestamp:
- 02/19/2026 03:59:11 AM (6 weeks ago)
- Location:
- contentee-ai/trunk
- Files:
-
- 1 added
- 5 edited
-
README.txt (modified) (7 diffs)
-
admin/css/admin-styles.css (modified) (1 diff)
-
admin/js/admin-scripts.js (modified) (2 diffs)
-
admin/network-settings-page.php (added)
-
contentee-ai.php (modified) (5 diffs)
-
includes/api.php (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
contentee-ai/trunk/README.txt
r3452669 r3464721 2 2 Contributors: contentee 3 3 Tags: content, automation, seo, social media, contentee 4 Requires at least: 5. 04 Requires at least: 5.1 5 5 Tested up to: 6.9 6 Stable tag: 2. 0.06 Stable tag: 2.1.0 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later … … 35 35 * Publish multilingual content with automatic language detection 36 36 * 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 37 38 38 39 = External Service Requirement = … … 59 60 60 61 == Installation == 62 63 = Single Site = 61 64 62 65 1. Upload the `contentee-ai` folder to the `/wp-content/plugins/` directory … … 68 71 7. Test the connection to ensure everything is working 69 72 73 = WordPress Multisite = 74 75 1. Upload the `contentee-ai` folder to the `/wp-content/plugins/` directory 76 2. Network Activate the plugin from the Network Admin > Plugins page, or activate it on individual sites 77 3. Each site admin can configure their own API key from the Contentee.ai menu in their site admin 78 4. Super admins can also manage API keys for all sites from the Network Admin > Contentee.ai page 79 70 80 == Frequently Asked Questions == 71 81 … … 89 99 90 100 The 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 104 Yes! 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 108 Yes. 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 112 Each 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. 91 113 92 114 == Screenshots == … … 98 120 99 121 == 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 100 130 101 131 = 2.0.0 = … … 147 177 == Upgrade Notice == 148 178 179 = 2.1.0 = 180 Multisite 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 149 182 = 2.0.0 = 150 183 Major 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 72 72 } 73 73 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 1 1 jQuery(document).ready(function($) { 2 // Test connection 2 3 // --- Per-site settings page --- 4 3 5 $('#contentee_test_connection').on('click', function() { 4 6 var apiKey = $('#contentee_api_key').val(); 5 7 6 8 if (!apiKey) { 7 9 $('#contentee_connection_status').html('<span style="color: #d63638;">✗ Please enter your Profile API key first</span>'); 8 10 return; 9 11 } 10 12 11 13 $(this).prop('disabled', true).text('Testing...'); 12 14 $('#contentee_connection_status').html(''); 13 15 14 16 $.ajax({ 15 17 url: contenteeAdmin.ajaxurl, … … 35 37 }); 36 38 }); 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 37 121 }); -
contentee-ai/trunk/contentee-ai.php
r3452669 r3464721 3 3 * Plugin Name: Contentee.ai 4 4 * Description: Connect your WordPress site to Contentee.ai for seamless content publishing from your content management platform. 5 * Version: 2. 0.05 * Version: 2.1.0 6 6 * Author: Contentee.ai 7 7 * Author URI: https://contentee.ai … … 17 17 18 18 // Define plugin constants 19 define('CONTENTEE_VERSION', '2. 0.0');19 define('CONTENTEE_VERSION', '2.1.0'); 20 20 define('CONTENTEE_PLUGIN_DIR', plugin_dir_path(__FILE__)); 21 21 define('CONTENTEE_PLUGIN_URL', plugin_dir_url(__FILE__)); … … 25 25 26 26 /** 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 */ 29 function 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 } 41 register_activation_hook(__FILE__, 'contentee_activate'); 42 43 function 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 */ 52 function 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 } 64 register_deactivation_hook(__FILE__, 'contentee_deactivate'); 65 66 function 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 */ 87 function 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 } 95 add_action('wp_initialize_site', 'contentee_on_new_site', 900); 96 97 /** 98 * Get the base64-encoded SVG icon used for admin menus 99 */ 100 function contentee_get_icon_svg() { 31 101 $svg = '<svg width="20" height="20" viewBox="0 0 767.44 1276.74" xmlns="http://www.w3.org/2000/svg" style="padding: 2px;"> 32 102 <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"/> 33 103 </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 */ 110 function contentee_add_admin_menu() { 37 111 add_menu_page( 38 'Contentee.ai', // Page title39 'Contentee.ai', // Menu title40 'manage_options', // Capability41 'contentee-settings', // Menu slug42 'contentee_settings_page', // Callback function43 $icon_svg, // Icon44 30 // Position112 'Contentee.ai', 113 'Contentee.ai', 114 'manage_options', 115 'contentee-settings', 116 'contentee_settings_page', 117 contentee_get_icon_svg(), 118 30 45 119 ); 46 120 } 47 121 add_action('admin_menu', 'contentee_add_admin_menu'); 122 123 /** 124 * Add network admin menu for super admins (multisite only) 125 */ 126 function 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 } 137 add_action('network_admin_menu', 'contentee_add_network_admin_menu'); 138 139 /** 140 * Network settings page callback 141 */ 142 function contentee_network_settings_page() { 143 require_once CONTENTEE_PLUGIN_DIR . 'admin/network-settings-page.php'; 144 } 48 145 49 146 /** … … 67 164 68 165 /** 69 * Enqueue admin styles and scripts 166 * Enqueue admin styles and scripts (per-site and network admin) 70 167 */ 71 168 function 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 77 176 wp_enqueue_style('contentee-admin', CONTENTEE_PLUGIN_URL . 'admin/css/admin-styles.css', array(), CONTENTEE_VERSION); 78 79 // Enqueue JavaScript80 177 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( 84 180 '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); 87 190 } 88 191 add_action('admin_enqueue_scripts', 'contentee_admin_styles'); … … 188 291 } 189 292 add_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 */ 297 function 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 } 353 add_action('wp_ajax_contentee_network_save_site', 'contentee_network_save_site'); 354 355 /** 356 * Network AJAX handler: test connection for a specific site 357 */ 358 function 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 } 403 add_action('wp_ajax_contentee_network_test_connection', 'contentee_network_test_connection'); -
contentee-ai/trunk/includes/api.php
r3452669 r3464721 65 65 66 66 /** 67 * Get the default post author for the current site (first administrator) 68 */ 69 function 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 /** 67 81 * Verify API key from header 68 82 */ … … 84 98 */ 85 99 function 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( 90 101 'success' => true, 91 'site_url' => $site_url,92 'site_name' => $site_name,102 'site_url' => get_site_url(), 103 'site_name' => get_bloginfo('name'), 93 104 '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); 96 115 } 97 116 … … 215 234 'post_status' => $status, 216 235 'post_type' => $post_type, 217 'post_author' => 1, // Use admin user236 'post_author' => contentee_get_default_author(), 218 237 ); 219 238
Note: See TracChangeset
for help on using the changeset viewer.