Plugin Directory

Changeset 3471895


Ignore:
Timestamp:
03/01/2026 06:33:08 AM (4 weeks ago)
Author:
rifaldye
Message:

Version 1.4.4: API status hero, status endpoint validation, single related article display

Location:
apicoid-ghostwriter/trunk
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • apicoid-ghostwriter/trunk/CHANGELOG.md

    r3466712 r3471895  
    55The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
    66and 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
    717
    818## [1.4.3] - 2026-02-22
     
    162172- Secure API key validation
    163173
     174[1.4.4]: https://github.com/apicoid/ghostwriter/compare/v1.4.3...v1.4.4
    164175[1.4.3]: https://github.com/apicoid/ghostwriter/compare/v1.4.2...v1.4.3
    165176[1.4.2]: https://github.com/apicoid/ghostwriter/compare/v1.4.1...v1.4.2
  • apicoid-ghostwriter/trunk/apicoid-ghostwriter.php

    r3466712 r3471895  
    44 * Plugin URI:  https://wordpress.org/plugins/apicoid-ghostwriter/
    55 * 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.3
     6 * Version:     1.4.4
    77 * Author:      Api.co.id
    88 * Author URI:  https://api.co.id
     
    2121
    2222// Define plugin constants
    23 define( 'APICOID_GW_VERSION', '1.4.3' );
     23define( 'APICOID_GW_VERSION', '1.4.4' );
    2424define( 'APICOID_GW_DIR', plugin_dir_path( __FILE__ ) );
    2525define( 'APICOID_GW_URL', plugin_dir_url( __FILE__ ) );
     
    793793     * Validate API key
    794794     *
     795     * Calls GET {host}/article-generator/status. 401 = invalid; 200 with is_success = true returns mode and points.
     796     *
    795797     * @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'.
    797799     */
    798800    public function validate_api_key( $api_key ) {
     
    803805            );
    804806        }
    805        
    806         // Use hardcoded base URL
     807
    807808        $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
    811811        $response = wp_remote_request(
    812812            $endpoint,
     
    814814                'method'  => 'GET',
    815815                'headers' => array(
    816                     'x-api-co-id' => $api_key,
    817816                    'Content-Type' => 'application/json',
     817                    'x-api-co-id'  => $api_key,
    818818                ),
    819                 'timeout' => 600, // 10 minutes timeout
    820             )
    821         );
    822        
    823         // Check for request errors
     819                'timeout' => 30,
     820            )
     821        );
     822
    824823        if ( is_wp_error( $response ) ) {
    825824            return array(
    826                 'valid' => false,
     825                'valid'   => false,
    827826                'message' => sprintf(
    828827                    /* translators: %s: Error message */
     
    832831            );
    833832        }
    834        
     833
    835834        $response_code = wp_remote_retrieve_response_code( $response );
    836835        $response_body = wp_remote_retrieve_body( $response );
    837        
    838         // Check for 401 or other error status codes
     836
    839837        if ( 401 === $response_code ) {
    840838            return array(
    841                 'valid' => false,
     839                'valid'   => false,
    842840                'message' => __( 'Invalid API key. Please check your API key and try again.', 'apicoid-ghostwriter' ),
    843841            );
    844842        }
    845        
     843
    846844        if ( $response_code < 200 || $response_code >= 300 ) {
    847845            return array(
    848                 'valid' => false,
     846                'valid'   => false,
    849847                'message' => sprintf(
    850848                    /* translators: %d: HTTP status code */
     
    854852            );
    855853        }
    856        
    857         // Parse response
     854
    858855        $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'] ) ) {
    862857            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' ),
    865860            );
    866861        }
    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
    876873        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,
    879879        );
    880880    }
     
    908908            update_option( 'apicoid_gw_api_key', $api_key );
    909909            update_option( 'apicoid_gw_api_key_valid', true );
    910            
     910
    911911            wp_send_json_success(
    912912                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',
    914916                )
    915917            );
     
    959961            // Mark as valid
    960962            update_option( 'apicoid_gw_api_key_valid', true );
    961            
     963
    962964            wp_send_json_success(
    963965                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',
    965969                )
    966970            );
     
    968972            // Mark as invalid
    969973            update_option( 'apicoid_gw_api_key_valid', false );
    970            
     974
    971975            wp_send_json_error(
    972976                array(
     
    29332937        }
    29342938       
    2935         // Only load validation script on main settings page
    2936         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 ) {
    29372941            wp_enqueue_script(
    29382942                'apicoid-gw-admin-script',
     
    29572961                        'success'    => __( 'Valid', 'apicoid-ghostwriter' ),
    29582962                        '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' ),
    29592967                    ),
    29602968                )
  • apicoid-ghostwriter/trunk/assets/css/admin.css

    r3466696 r3471895  
    5555    opacity: 0.6;
    5656    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%);
    57187}
    58188
  • apicoid-ghostwriter/trunk/assets/js/admin.js

    r3466696 r3471895  
    1515       
    1616        /**
     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        /**
    1746         * Check API key status on page load
    1847         */
     
    3463                success: function(response) {
    3564                    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
    3768                        $status.html(
    3869                            '<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> ' +
    3970                            '<span style="color: #46b450;">' + (apicoidGwAdminAjax.strings.success || 'Valid') + '</span>'
    4071                        );
    41                        
     72
     73                        updateStatusHero(mode, pts);
     74
    4275                        // Enable save button
    4376                        $('#submit').prop('disabled', false);
     
    4982                            '<span style="color: #dc3232;">' + errorMsg + '</span>'
    5083                        );
     84                        $('#apicoid-gw-status-hero').hide();
    5185                       
    5286                        // Disable save button
     
    6094                        '<span style="color: #dc3232;">Failed to check status</span>'
    6195                    );
     96                    $('#apicoid-gw-status-hero').hide();
    6297                   
    6398                    // Disable save button
     
    98133                success: function(response) {
    99134                    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
    101138                        $status.html(
    102139                            '<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> ' +
    103140                            '<span style="color: #46b450;">' + (apicoidGwAdminAjax.strings.success || 'Valid') + '</span>'
    104141                        );
    105                        
     142
     143                        updateStatusHero(mode, pts);
     144
    106145                        // Enable save button
    107146                        $submitBtn.prop('disabled', false);
    108                        
    109                         // Reload page after 1 second to show updated status
     147
     148                        // Reload page after 1.5 seconds to show updated status
    110149                        setTimeout(function() {
    111150                            location.reload();
    112                         }, 1000);
     151                        }, 1500);
    113152                    } else {
    114153                        // Show error message
     
    118157                            '<span style="color: #dc3232;">' + errorMsg + '</span>'
    119158                        );
     159                        $('#apicoid-gw-status-hero').hide();
    120160                       
    121161                        // Disable save button
  • apicoid-ghostwriter/trunk/includes/admin-page.php

    r3457596 r3471895  
    3030        </div>
    3131       
     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
    3251        <div class="apicoid-gw-content">
    3352            <?php
     
    5675           
    5776            <?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">
    5978                    <p>
    6079                        <strong><?php esc_html_e( 'Activation Required:', 'apicoid-ghostwriter' ); ?></strong>
     
    6382                </div>
    6483            <?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;">
    6685                    <p>
    6786                        <strong><?php esc_html_e( 'Plugin Activated:', 'apicoid-ghostwriter' ); ?></strong>
  • apicoid-ghostwriter/trunk/includes/article-optimizer-page.php

    r3466696 r3471895  
    9090<div class="wrap">
    9191    <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
    93112    <div class="apicoid-gw-admin">
    94113        <div class="apicoid-gw-header">
  • apicoid-ghostwriter/trunk/readme.txt

    r3466712 r3471895  
    55Requires at least: 6.2
    66Tested up to: 6.9
    7 Stable tag: 1.4.3
     7Stable tag: 1.4.4
    88Requires PHP: 7.4
    99License: GPLv2 or later
     
    131131
    132132== 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
    133139
    134140= 1.4.3 =
     
    234240== Upgrade Notice ==
    235241
     242= 1.4.4 =
     243API 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
    236245= 1.4.3 =
    237246FAQ accordion questions are now bold for better readability.
Note: See TracChangeset for help on using the changeset viewer.