Changeset 3471895
- Timestamp:
- 03/01/2026 06:33:08 AM (4 weeks ago)
- Location:
- apicoid-ghostwriter/trunk
- Files:
-
- 7 edited
-
CHANGELOG.md (modified) (2 diffs)
-
apicoid-ghostwriter.php (modified) (12 diffs)
-
assets/css/admin.css (modified) (1 diff)
-
assets/js/admin.js (modified) (6 diffs)
-
includes/admin-page.php (modified) (3 diffs)
-
includes/article-optimizer-page.php (modified) (1 diff)
-
readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
apicoid-ghostwriter/trunk/CHANGELOG.md
r3466712 r3471895 5 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 8 ## [1.4.4] - 2026-02-08 9 10 ### Added 11 - **API Status Hero Card**: Eye-catching hero card on the main plugin page and Article Generator page showing "API Active" badge, account mode (Pay as you go / Bring your own key), and points (formatted count or "Unlimited") 12 - **Status Endpoint Validation**: API key validation now uses GET `{host}/article-generator/status` with `x-api-co-id` header; 401 = invalid, 200 = valid with mode and points in response 13 14 ### Changed 15 - **Related Article Preview**: Settings page "Preview (real links from your posts)" now shows a single related article link (one post, no comma-separated list) 16 - **Related Article in Content**: Each related-article block in generated content now displays only one title/link instead of multiple comma-separated links 7 17 8 18 ## [1.4.3] - 2026-02-22 … … 162 172 - Secure API key validation 163 173 174 [1.4.4]: https://github.com/apicoid/ghostwriter/compare/v1.4.3...v1.4.4 164 175 [1.4.3]: https://github.com/apicoid/ghostwriter/compare/v1.4.2...v1.4.3 165 176 [1.4.2]: https://github.com/apicoid/ghostwriter/compare/v1.4.1...v1.4.2 -
apicoid-ghostwriter/trunk/apicoid-ghostwriter.php
r3466712 r3471895 4 4 * Plugin URI: https://wordpress.org/plugins/apicoid-ghostwriter/ 5 5 * Description: Connects your WordPress site to Api.co.id to generate content and rewrite content automatically using AI. Features include article generation, content rewriting, automatic related article linking, SEO integration, and image generation. 6 * Version: 1.4. 36 * Version: 1.4.4 7 7 * Author: Api.co.id 8 8 * Author URI: https://api.co.id … … 21 21 22 22 // Define plugin constants 23 define( 'APICOID_GW_VERSION', '1.4. 3' );23 define( 'APICOID_GW_VERSION', '1.4.4' ); 24 24 define( 'APICOID_GW_DIR', plugin_dir_path( __FILE__ ) ); 25 25 define( 'APICOID_GW_URL', plugin_dir_url( __FILE__ ) ); … … 793 793 * Validate API key 794 794 * 795 * Calls GET {host}/article-generator/status. 401 = invalid; 200 with is_success = true returns mode and points. 796 * 795 797 * @param string $api_key The API key to validate. 796 * @return array Validation result with 'valid' and 'message' keys.798 * @return array Validation result with 'valid', 'message', and on success 'mode', 'points_display'. 797 799 */ 798 800 public function validate_api_key( $api_key ) { … … 803 805 ); 804 806 } 805 806 // Use hardcoded base URL 807 807 808 $base_url = rtrim( APICOID_GW_API_BASE_URL, '/' ); 808 $endpoint = $base_url . '/ping'; 809 810 // Make API request 809 $endpoint = $base_url . '/article-generator/status'; 810 811 811 $response = wp_remote_request( 812 812 $endpoint, … … 814 814 'method' => 'GET', 815 815 'headers' => array( 816 'x-api-co-id' => $api_key,817 816 'Content-Type' => 'application/json', 817 'x-api-co-id' => $api_key, 818 818 ), 819 'timeout' => 600, // 10 minutes timeout 820 ) 821 ); 822 823 // Check for request errors 819 'timeout' => 30, 820 ) 821 ); 822 824 823 if ( is_wp_error( $response ) ) { 825 824 return array( 826 'valid' => false,825 'valid' => false, 827 826 'message' => sprintf( 828 827 /* translators: %s: Error message */ … … 832 831 ); 833 832 } 834 833 835 834 $response_code = wp_remote_retrieve_response_code( $response ); 836 835 $response_body = wp_remote_retrieve_body( $response ); 837 838 // Check for 401 or other error status codes 836 839 837 if ( 401 === $response_code ) { 840 838 return array( 841 'valid' => false,839 'valid' => false, 842 840 'message' => __( 'Invalid API key. Please check your API key and try again.', 'apicoid-ghostwriter' ), 843 841 ); 844 842 } 845 843 846 844 if ( $response_code < 200 || $response_code >= 300 ) { 847 845 return array( 848 'valid' => false,846 'valid' => false, 849 847 'message' => sprintf( 850 848 /* translators: %d: HTTP status code */ … … 854 852 ); 855 853 } 856 857 // Parse response 854 858 855 $data = json_decode( $response_body, true ); 859 860 // Check if response is valid 861 if ( ! is_array( $data ) ) { 856 if ( ! is_array( $data ) || empty( $data['is_success'] ) || empty( $data['data'] ) ) { 862 857 return array( 863 'valid' => false,864 'message' => __( 'Invalid response from API server.', 'apicoid-ghostwriter' ),858 'valid' => false, 859 'message' => isset( $data['message'] ) ? $data['message'] : __( 'API key validation failed.', 'apicoid-ghostwriter' ), 865 860 ); 866 861 } 867 868 // Check for success response 869 if ( isset( $data['is_success'] ) && true === $data['is_success'] ) { 870 return array( 871 'valid' => true, 872 'message' => isset( $data['data']['message'] ) ? $data['data']['message'] : __( 'API key is valid.', 'apicoid-ghostwriter' ), 873 ); 874 } 875 862 863 $payload = $data['data']; 864 $mode = isset( $payload['mode'] ) ? $payload['mode'] : 'pay_as_you_go'; 865 $points = isset( $payload['points_left'] ) ? (int) $payload['points_left'] : 0; 866 867 if ( 'bring_your_own_key' === $mode ) { 868 $points_display = __( 'Unlimited', 'apicoid-ghostwriter' ); 869 } else { 870 $points_display = number_format_i18n( $points ); 871 } 872 876 873 return array( 877 'valid' => false, 878 'message' => isset( $data['message'] ) ? $data['message'] : __( 'API key validation failed.', 'apicoid-ghostwriter' ), 874 'valid' => true, 875 'message' => isset( $data['message'] ) ? $data['message'] : __( 'API key is valid.', 'apicoid-ghostwriter' ), 876 'mode' => $mode, 877 'points_left' => $points, 878 'points_display' => $points_display, 879 879 ); 880 880 } … … 908 908 update_option( 'apicoid_gw_api_key', $api_key ); 909 909 update_option( 'apicoid_gw_api_key_valid', true ); 910 910 911 911 wp_send_json_success( 912 912 array( 913 'message' => $result['message'], 913 'message' => $result['message'], 914 'mode' => isset( $result['mode'] ) ? $result['mode'] : 'pay_as_you_go', 915 'points_display' => isset( $result['points_display'] ) ? $result['points_display'] : '0', 914 916 ) 915 917 ); … … 959 961 // Mark as valid 960 962 update_option( 'apicoid_gw_api_key_valid', true ); 961 963 962 964 wp_send_json_success( 963 965 array( 964 'message' => $result['message'], 966 'message' => $result['message'], 967 'mode' => isset( $result['mode'] ) ? $result['mode'] : 'pay_as_you_go', 968 'points_display' => isset( $result['points_display'] ) ? $result['points_display'] : '0', 965 969 ) 966 970 ); … … 968 972 // Mark as invalid 969 973 update_option( 'apicoid_gw_api_key_valid', false ); 970 974 971 975 wp_send_json_error( 972 976 array( … … 2933 2937 } 2934 2938 2935 // Only load validation script on main settingspage2936 if ( 'apicoid-ghostwriter' === $current_page ) {2939 // Load validation/status script on main settings page AND article generator page 2940 if ( 'apicoid-ghostwriter' === $current_page || $is_article_generator_page ) { 2937 2941 wp_enqueue_script( 2938 2942 'apicoid-gw-admin-script', … … 2957 2961 'success' => __( 'Valid', 'apicoid-ghostwriter' ), 2958 2962 'error' => __( 'Not validated', 'apicoid-ghostwriter' ), 2963 'pay_as_you_go' => __( 'Pay as you go', 'apicoid-ghostwriter' ), 2964 'bring_your_own_key' => __( 'Bring your own key', 'apicoid-ghostwriter' ), 2965 'points_left' => __( 'points left', 'apicoid-ghostwriter' ), 2966 'points_unlimited' => __( 'Unlimited', 'apicoid-ghostwriter' ), 2959 2967 ), 2960 2968 ) -
apicoid-ghostwriter/trunk/assets/css/admin.css
r3466696 r3471895 55 55 opacity: 0.6; 56 56 cursor: not-allowed; 57 } 58 59 /* API Status Hero Card */ 60 .apicoid-gw-status-hero { 61 margin-bottom: 20px; 62 border-radius: 14px; 63 overflow: hidden; 64 } 65 66 .apicoid-gw-status-hero-inner { 67 position: relative; 68 display: flex; 69 align-items: center; 70 gap: 24px; 71 padding: 28px 32px; 72 background: linear-gradient(135deg, #1a1a2e 0%, #16213e 40%, #0f3460 100%); 73 border-radius: 14px; 74 overflow: hidden; 75 box-shadow: 0 8px 32px rgba(15, 52, 96, 0.45); 76 } 77 78 .apicoid-gw-status-hero-icon { 79 flex-shrink: 0; 80 width: 64px; 81 height: 64px; 82 border-radius: 50%; 83 background: rgba(255, 255, 255, 0.1); 84 border: 2px solid rgba(255, 255, 255, 0.2); 85 display: flex; 86 align-items: center; 87 justify-content: center; 88 backdrop-filter: blur(4px); 89 z-index: 1; 90 } 91 92 .apicoid-gw-status-hero-icon .dashicons { 93 font-size: 34px; 94 width: 34px; 95 height: 34px; 96 color: #4ade80; 97 filter: drop-shadow(0 0 8px rgba(74, 222, 128, 0.6)); 98 } 99 100 .apicoid-gw-status-hero-body { 101 flex: 1; 102 z-index: 1; 103 } 104 105 .apicoid-gw-status-hero-badge { 106 display: inline-flex; 107 align-items: center; 108 gap: 6px; 109 background: rgba(74, 222, 128, 0.15); 110 border: 1px solid rgba(74, 222, 128, 0.4); 111 color: #4ade80; 112 font-size: 11px; 113 font-weight: 700; 114 letter-spacing: 1.2px; 115 text-transform: uppercase; 116 padding: 3px 10px; 117 border-radius: 20px; 118 margin-bottom: 8px; 119 } 120 121 .apicoid-gw-status-hero-badge::before { 122 content: ''; 123 display: inline-block; 124 width: 6px; 125 height: 6px; 126 border-radius: 50%; 127 background: #4ade80; 128 box-shadow: 0 0 6px #4ade80; 129 animation: apicoid-gw-pulse 2s ease-in-out infinite; 130 } 131 132 @keyframes apicoid-gw-pulse { 133 0%, 100% { opacity: 1; transform: scale(1); } 134 50% { opacity: 0.5; transform: scale(0.85); } 135 } 136 137 .apicoid-gw-status-hero-mode { 138 color: #fff; 139 font-size: 22px; 140 font-weight: 700; 141 line-height: 1.2; 142 letter-spacing: -0.3px; 143 margin-bottom: 4px; 144 } 145 146 .apicoid-gw-status-hero-points { 147 display: flex; 148 align-items: baseline; 149 gap: 6px; 150 } 151 152 .apicoid-gw-status-hero-points-number { 153 color: #93c5fd; 154 font-size: 28px; 155 font-weight: 800; 156 letter-spacing: -0.5px; 157 line-height: 1; 158 } 159 160 .apicoid-gw-status-hero-points-label { 161 color: rgba(255, 255, 255, 0.55); 162 font-size: 13px; 163 font-weight: 500; 164 } 165 166 /* Decorative orbs */ 167 .apicoid-gw-status-hero-orb { 168 position: absolute; 169 border-radius: 50%; 170 pointer-events: none; 171 } 172 173 .apicoid-gw-status-hero-orb-1 { 174 width: 160px; 175 height: 160px; 176 top: -60px; 177 right: 60px; 178 background: radial-gradient(circle, rgba(99, 102, 241, 0.35) 0%, transparent 70%); 179 } 180 181 .apicoid-gw-status-hero-orb-2 { 182 width: 120px; 183 height: 120px; 184 bottom: -40px; 185 right: 20px; 186 background: radial-gradient(circle, rgba(15, 148, 210, 0.3) 0%, transparent 70%); 57 187 } 58 188 -
apicoid-ghostwriter/trunk/assets/js/admin.js
r3466696 r3471895 15 15 16 16 /** 17 * Populate the status hero card with mode and points info 18 */ 19 function updateStatusHero(mode, pointsDisplay) { 20 var $hero = $('#apicoid-gw-status-hero'); 21 var $notice = $('#apicoid-gw-activation-notice'); 22 23 if (!mode) { 24 $hero.hide(); 25 return; 26 } 27 28 var modeLabel = mode === 'bring_your_own_key' 29 ? (apicoidGwAdminAjax.strings.bring_your_own_key || 'Bring your own key') 30 : (apicoidGwAdminAjax.strings.pay_as_you_go || 'Pay as you go'); 31 32 var isUnlimited = (mode === 'bring_your_own_key'); 33 var pts = pointsDisplay || (isUnlimited ? (apicoidGwAdminAjax.strings.points_unlimited || 'Unlimited') : '0'); 34 35 $('#apicoid-gw-hero-mode').text(modeLabel); 36 $('#apicoid-gw-hero-points').text(pts); 37 $('#apicoid-gw-hero-points-label').text( 38 isUnlimited ? '' : (apicoidGwAdminAjax.strings.points_left || 'points left') 39 ); 40 41 $hero.fadeIn(300); 42 $notice.hide(); 43 } 44 45 /** 17 46 * Check API key status on page load 18 47 */ … … 34 63 success: function(response) { 35 64 if (response.success) { 36 // Show success message 65 var mode = (response.data && response.data.mode) ? response.data.mode : ''; 66 var pts = (response.data && response.data.points_display != null) ? response.data.points_display : ''; 67 37 68 $status.html( 38 69 '<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> ' + 39 70 '<span style="color: #46b450;">' + (apicoidGwAdminAjax.strings.success || 'Valid') + '</span>' 40 71 ); 41 72 73 updateStatusHero(mode, pts); 74 42 75 // Enable save button 43 76 $('#submit').prop('disabled', false); … … 49 82 '<span style="color: #dc3232;">' + errorMsg + '</span>' 50 83 ); 84 $('#apicoid-gw-status-hero').hide(); 51 85 52 86 // Disable save button … … 60 94 '<span style="color: #dc3232;">Failed to check status</span>' 61 95 ); 96 $('#apicoid-gw-status-hero').hide(); 62 97 63 98 // Disable save button … … 98 133 success: function(response) { 99 134 if (response.success) { 100 // Show success message 135 var mode = (response.data && response.data.mode) ? response.data.mode : ''; 136 var pts = (response.data && response.data.points_display != null) ? response.data.points_display : ''; 137 101 138 $status.html( 102 139 '<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> ' + 103 140 '<span style="color: #46b450;">' + (apicoidGwAdminAjax.strings.success || 'Valid') + '</span>' 104 141 ); 105 142 143 updateStatusHero(mode, pts); 144 106 145 // Enable save button 107 146 $submitBtn.prop('disabled', false); 108 109 // Reload page after 1 secondto show updated status147 148 // Reload page after 1.5 seconds to show updated status 110 149 setTimeout(function() { 111 150 location.reload(); 112 }, 1 000);151 }, 1500); 113 152 } else { 114 153 // Show error message … … 118 157 '<span style="color: #dc3232;">' + errorMsg + '</span>' 119 158 ); 159 $('#apicoid-gw-status-hero').hide(); 120 160 121 161 // Disable save button -
apicoid-ghostwriter/trunk/includes/admin-page.php
r3457596 r3471895 30 30 </div> 31 31 32 <!-- API Status Hero Card --> 33 <div id="apicoid-gw-status-hero" class="apicoid-gw-status-hero" style="display:none;"> 34 <div class="apicoid-gw-status-hero-inner"> 35 <div class="apicoid-gw-status-hero-icon"> 36 <span class="dashicons dashicons-yes-alt"></span> 37 </div> 38 <div class="apicoid-gw-status-hero-body"> 39 <div class="apicoid-gw-status-hero-badge"><?php esc_html_e( 'API Active', 'apicoid-ghostwriter' ); ?></div> 40 <div class="apicoid-gw-status-hero-mode" id="apicoid-gw-hero-mode">–</div> 41 <div class="apicoid-gw-status-hero-points"> 42 <span class="apicoid-gw-status-hero-points-number" id="apicoid-gw-hero-points">–</span> 43 <span class="apicoid-gw-status-hero-points-label" id="apicoid-gw-hero-points-label"><?php esc_html_e( 'points left', 'apicoid-ghostwriter' ); ?></span> 44 </div> 45 </div> 46 <div class="apicoid-gw-status-hero-orb apicoid-gw-status-hero-orb-1"></div> 47 <div class="apicoid-gw-status-hero-orb apicoid-gw-status-hero-orb-2"></div> 48 </div> 49 </div> 50 32 51 <div class="apicoid-gw-content"> 33 52 <?php … … 56 75 57 76 <?php if ( ! $is_valid ) : ?> 58 <div class="apicoid-gw-notice notice notice-warning inline" >77 <div class="apicoid-gw-notice notice notice-warning inline" id="apicoid-gw-activation-notice"> 59 78 <p> 60 79 <strong><?php esc_html_e( 'Activation Required:', 'apicoid-ghostwriter' ); ?></strong> … … 63 82 </div> 64 83 <?php else : ?> 65 <div class="apicoid-gw-notice notice notice-success inline" >84 <div class="apicoid-gw-notice notice notice-success inline" id="apicoid-gw-activation-notice" style="display:none;"> 66 85 <p> 67 86 <strong><?php esc_html_e( 'Plugin Activated:', 'apicoid-ghostwriter' ); ?></strong> -
apicoid-ghostwriter/trunk/includes/article-optimizer-page.php
r3466696 r3471895 90 90 <div class="wrap"> 91 91 <h1><?php esc_html_e( 'Article Generator', 'apicoid-ghostwriter' ); ?></h1> 92 92 93 <!-- API Status Hero Card --> 94 <div id="apicoid-gw-status-hero" class="apicoid-gw-status-hero" style="display:none;"> 95 <div class="apicoid-gw-status-hero-inner"> 96 <div class="apicoid-gw-status-hero-icon"> 97 <span class="dashicons dashicons-yes-alt"></span> 98 </div> 99 <div class="apicoid-gw-status-hero-body"> 100 <div class="apicoid-gw-status-hero-badge"><?php esc_html_e( 'API Active', 'apicoid-ghostwriter' ); ?></div> 101 <div class="apicoid-gw-status-hero-mode" id="apicoid-gw-hero-mode">–</div> 102 <div class="apicoid-gw-status-hero-points"> 103 <span class="apicoid-gw-status-hero-points-number" id="apicoid-gw-hero-points">–</span> 104 <span class="apicoid-gw-status-hero-points-label" id="apicoid-gw-hero-points-label"><?php esc_html_e( 'points left', 'apicoid-ghostwriter' ); ?></span> 105 </div> 106 </div> 107 <div class="apicoid-gw-status-hero-orb apicoid-gw-status-hero-orb-1"></div> 108 <div class="apicoid-gw-status-hero-orb apicoid-gw-status-hero-orb-2"></div> 109 </div> 110 </div> 111 93 112 <div class="apicoid-gw-admin"> 94 113 <div class="apicoid-gw-header"> -
apicoid-ghostwriter/trunk/readme.txt
r3466712 r3471895 5 5 Requires at least: 6.2 6 6 Tested up to: 6.9 7 Stable tag: 1.4. 37 Stable tag: 1.4.4 8 8 Requires PHP: 7.4 9 9 License: GPLv2 or later … … 131 131 132 132 == Changelog == 133 134 = 1.4.4 = 135 * API Status Hero Card: New hero card on main plugin page and Article Generator page showing "API Active" badge, account mode (Pay as you go / Bring your own key), and points (formatted or Unlimited) 136 * Status endpoint validation: API key validation now uses GET /article-generator/status with x-api-co-id header (401 = invalid, 200 = valid with mode and points) 137 * Related article preview: Settings page preview now shows a single related article link (one post, no comma list) 138 * Related article in content: Each related-article block in generated content now displays one title/link instead of multiple comma-separated links 133 139 134 140 = 1.4.3 = … … 234 240 == Upgrade Notice == 235 241 242 = 1.4.4 = 243 API key validation now uses the status endpoint and shows your account mode and points in a new hero card on the main page and Article Generator. Related article display is now one link per block. 244 236 245 = 1.4.3 = 237 246 FAQ accordion questions are now bold for better readability.
Note: See TracChangeset
for help on using the changeset viewer.