Plugin Directory

Changeset 3463836


Ignore:
Timestamp:
02/17/2026 09:20:46 PM (6 weeks ago)
Author:
ggiak
Message:

Deploy version 1.1.0

Location:
cookietrust
Files:
8 added
8 deleted
24 edited
1 copied

Legend:

Unmodified
Added
Removed
  • cookietrust/tags/1.1.0/assets/admin/dist/manifest.json

    r3462909 r3463836  
    11{
    22  "src/admin/main.css": {
    3     "file": "assets/main-d878bc6c.css",
     3    "file": "assets/main-eafb5a0e.css",
    44    "src": "src/admin/main.css"
    55  },
    66  "src/admin/main.tsx": {
    77    "css": [
    8       "assets/main-d878bc6c.css"
     8      "assets/main-eafb5a0e.css"
    99    ],
    10     "file": "assets/main-776309fc.js",
     10    "file": "assets/main-9e7ac0fd.js",
    1111    "isEntry": true,
    1212    "src": "src/admin/main.tsx"
  • cookietrust/tags/1.1.0/assets/frontend/dist/manifest.json

    r3462909 r3463836  
    11{
    22  "src/frontend/main.css": {
    3     "file": "assets/main-d878bc6c.css",
     3    "file": "assets/main-eafb5a0e.css",
    44    "src": "src/frontend/main.css"
    55  },
    66  "src/frontend/main.jsx": {
    77    "css": [
    8       "assets/main-d878bc6c.css"
     8      "assets/main-eafb5a0e.css"
    99    ],
    10     "file": "assets/main-cf864c16.js",
     10    "file": "assets/main-391f795d.js",
    1111    "isEntry": true,
    1212    "src": "src/frontend/main.jsx"
  • cookietrust/tags/1.1.0/cookietrust.php

    r3462958 r3463836  
    55 * Description: Cookie consent management powered by CookieTrust.io - GDPR & CCPA compliant cookie banner.
    66 * Author: CookieTrust
    7  * Version: 1.0.4
     7 * Version: 1.1.0
    88 * Requires at least: 5.8
    99 * Tested up to: 6.9
  • cookietrust/tags/1.1.0/includes/Controllers/OAuth/CallbackController.php

    r3462856 r3463836  
    5858     */
    5959    public function handle(): void {
    60         if ( ! current_user_can( 'manage_options' ) ) {
    61             wp_die(
    62                 esc_html__( 'Unauthorized access', 'cookietrust' ),
    63                 esc_html__( 'Error', 'cookietrust' ),
     60        if ( ! (bool) $this->wp_call( 'current_user_can', 'manage_options' ) ) {
     61            $this->wp_call(
     62                'wp_die',
     63                $this->wp_call( 'esc_html__', 'Unauthorized access', 'cookietrust' ),
     64                $this->wp_call( 'esc_html__', 'Error', 'cookietrust' ),
    6465                array( 'response' => 403 )
    6566            );
     
    7071        // and is validated alongside the CSRF state token below.
    7172        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Nonce verified in OAuthService::validate_state().
    72         $state = isset( $_GET['state'] ) ? sanitize_text_field( wp_unslash( $_GET['state'] ) ) : '';
     73        $state = isset( $_GET['state'] ) ? sanitize_text_field( (string) $this->wp_call( 'wp_unslash', $_GET['state'] ) ) : '';
    7374        // phpcs:enable WordPress.Security.NonceVerification.Recommended
    7475
    7576        if ( ! $this->oauth_service->validate_state( $state ) ) {
    7677            cookietrust_log_error( 'OAuth callback: invalid or expired state/nonce', array( 'state' => substr( $state, 0, 8 ) . '...' ) );
    77             wp_die(
    78                 esc_html__( 'Invalid or expired security token. Please try connecting again.', 'cookietrust' ),
    79                 esc_html__( 'Security Error', 'cookietrust' ),
     78            $this->wp_call(
     79                'wp_die',
     80                $this->wp_call( 'esc_html__', 'Invalid or expired security token. Please try connecting again.', 'cookietrust' ),
     81                $this->wp_call( 'esc_html__', 'Security Error', 'cookietrust' ),
    8082                array( 'response' => 403 )
    8183            );
     
    8587        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Nonce verified in OAuthService::validate_state() above.
    8688        if ( isset( $_GET['error'] ) ) {
    87             $error      = sanitize_text_field( wp_unslash( $_GET['error'] ) );
    88             $error_desc = isset( $_GET['error_description'] ) ? sanitize_text_field( wp_unslash( $_GET['error_description'] ) ) : '';
     89            $error      = sanitize_text_field( (string) $this->wp_call( 'wp_unslash', $_GET['error'] ) );
     90            $error_desc = isset( $_GET['error_description'] ) ? sanitize_text_field( (string) $this->wp_call( 'wp_unslash', $_GET['error_description'] ) ) : '';
    8991            cookietrust_log_error(
    9092                'OAuth callback: server error',
     
    9496                )
    9597            );
    96             wp_safe_redirect( admin_url( 'admin.php?page=cookietrust&error=' . rawurlencode( $error_desc ? $error_desc : $error ) ) );
     98            $this->wp_call( 'wp_safe_redirect', admin_url( 'admin.php?page=cookietrust&error=' . rawurlencode( $error_desc ? $error_desc : $error ) ) );
    9799            exit;
    98100        }
    99101
    100         $code         = isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : '';
    101         $access_token = isset( $_GET['access_token'] ) ? sanitize_text_field( wp_unslash( $_GET['access_token'] ) ) : '';
    102         $email        = isset( $_GET['email'] ) ? sanitize_email( wp_unslash( $_GET['email'] ) ) : '';
     102        $code         = isset( $_GET['code'] ) ? sanitize_text_field( (string) $this->wp_call( 'wp_unslash', $_GET['code'] ) ) : '';
     103        $access_token = isset( $_GET['access_token'] ) ? sanitize_text_field( (string) $this->wp_call( 'wp_unslash', $_GET['access_token'] ) ) : '';
     104        $email        = isset( $_GET['email'] ) ? sanitize_email( (string) $this->wp_call( 'wp_unslash', $_GET['email'] ) ) : '';
     105        $config_id    = isset( $_GET['config_id'] ) ? sanitize_text_field( (string) $this->wp_call( 'wp_unslash', $_GET['config_id'] ) ) : '';
    103106        // phpcs:enable WordPress.Security.NonceVerification.Recommended
     107
     108        $redirect_url = '';
    104109
    105110        // Handle authorization code flow (secure) - exchange code for token server-side.
     
    107112            try {
    108113                $token_response = $this->oauth_service->exchange_code_for_token( $code );
    109                 $access_token   = $token_response['access_token'];
    110                 // Email may come from token response in new flow.
    111                 if ( empty( $email ) && isset( $token_response['email'] ) ) {
    112                     $email = sanitize_email( $token_response['email'] );
     114                $token_type     = $token_response['token_type'] ?? 'Bearer';
     115
     116                if ( 'api_key' === $token_type ) {
     117                    $credentials = array(
     118                        'api_key' => $token_response['api_key'],
     119                    );
     120
     121                    // config_id: API returns "cookie_config_id", callback URL uses "config_id".
     122                    $effective_config_id = ! empty( $config_id )
     123                        ? $config_id
     124                        : ( $token_response['cookie_config_id'] ?? $token_response['config_id'] ?? '' );
     125                    if ( ! empty( $effective_config_id ) ) {
     126                        $credentials['config_id'] = $effective_config_id;
     127                    }
     128                    if ( isset( $token_response['company_id'] ) ) {
     129                        $credentials['company_id'] = $token_response['company_id'];
     130                    }
     131
     132                    $this->settings->save_credentials( $credentials );
     133
     134                    delete_option( \CookieTrust\Services\SettingsService::OPTION_ACCESS_TOKEN );
     135
     136                    $email = $token_response['email'] ?? null;
     137                    if ( $email ) {
     138                        $this->settings->set_user_email( $email );
     139                    }
     140
     141                    $redirect_url = admin_url( 'admin.php?page=cookietrust&connected=1' );
     142                } else {
     143                    $access_token = $token_response['access_token'];
     144                    $email        = $token_response['email'] ?? null;
     145                    $this->settings->set_access_token( $access_token );
     146                    if ( $email ) {
     147                        $this->settings->set_user_email( $email );
     148                    }
     149                    $redirect_url = admin_url( 'admin.php?page=cookietrust&setup=pending' );
    113150                }
    114151            } catch ( \Exception $e ) {
     
    117154                    array( 'error' => $e->getMessage() )
    118155                );
    119                 wp_safe_redirect( admin_url( 'admin.php?page=cookietrust&error=' . rawurlencode( 'Token exchange failed: ' . $e->getMessage() ) ) );
     156                $this->wp_call( 'wp_safe_redirect', admin_url( 'admin.php?page=cookietrust&error=' . rawurlencode( 'Token exchange failed: ' . $e->getMessage() ) ) );
    120157                exit;
    121158            }
    122159        }
    123160
    124         // Verify we have an access token from either flow.
    125         if ( empty( $access_token ) ) {
    126             cookietrust_log_error( 'OAuth callback: missing authorization code or access token in response' );
    127             wp_safe_redirect( admin_url( 'admin.php?page=cookietrust&error=' . rawurlencode( 'Missing authorization code' ) ) );
    128             exit;
     161        if ( empty( $redirect_url ) ) {
     162            if ( empty( $access_token ) ) {
     163                cookietrust_log_error( 'OAuth callback: missing authorization code or access token in response' );
     164                $this->wp_call( 'wp_safe_redirect', admin_url( 'admin.php?page=cookietrust&error=' . rawurlencode( 'Missing authorization code' ) ) );
     165                exit;
     166            }
     167
     168            $this->settings->set_access_token( $access_token );
     169            if ( $email ) {
     170                $this->settings->set_user_email( $email );
     171            }
     172
     173            $redirect_url = admin_url( 'admin.php?page=cookietrust&setup=pending' );
    129174        }
    130175
    131         // Store access token and email temporarily.
    132         // Frontend will use the token to complete setup via API.
    133         $this->settings->set_access_token( $access_token );
    134         if ( $email ) {
    135             $this->settings->set_user_email( $email );
    136         }
    137 
    138         // Redirect to dashboard with setup flag - frontend will complete the setup.
    139         wp_safe_redirect( admin_url( 'admin.php?page=cookietrust&setup=pending' ) );
     176        $this->wp_call( 'wp_safe_redirect', $redirect_url );
    140177        exit;
    141178    }
     179
     180    /**
     181     * Call a WordPress function by name.
     182     *
     183     * @param string $func_name Function name to call.
     184     * @param mixed  ...$args   Arguments to pass.
     185     * @return mixed Function return value.
     186     */
     187    private function wp_call( string $func_name, ...$args ) {
     188        return call_user_func_array( $func_name, $args );
     189    }
    142190}
  • cookietrust/tags/1.1.0/includes/Controllers/SettingsController.php

    r3462856 r3463836  
    4747        $force_verify = $request->get_param( 'force_verify' ) === 'true' || $request->get_param( 'force_verify' ) === '1';
    4848        $status       = $settings->get_status( $verify, $force_verify );
     49
     50        // Prioritize API key over Sanctum token.
     51        $api_key      = $settings->get_api_key();
    4952        $access_token = $settings->get_access_token();
    5053
    51         // Include API access info if we have an access token.
    52         if ( $access_token ) {
     54        if ( $api_key ) {
     55            // Scoped API key (new flow).
     56            $status['access_token'] = $api_key;
     57            $status['auth_type']    = 'api_key';
     58            $status['api_base_url'] = defined( 'COOKIETRUST_API_BASE_URL' ) ? COOKIETRUST_API_BASE_URL : 'https://api.cookietrust.io';
     59        } elseif ( $access_token ) {
     60            // Legacy Sanctum token.
    5361            $status['access_token'] = $access_token;
     62            $status['auth_type']    = 'bearer';
    5463            $status['api_base_url'] = defined( 'COOKIETRUST_API_BASE_URL' ) ? COOKIETRUST_API_BASE_URL : 'https://api.cookietrust.io';
    55 
    56             // Mark setup as pending if we have token but not connected yet.
    57             if ( ! $status['connected'] ) {
    58                 $status['setup_pending'] = true;
    59             }
    6064        }
    6165
     
    97101        );
    98102    }
    99 
    100     /**
    101      * Complete the setup by saving config_id from frontend.
    102      *
    103      * @param WP_REST_Request<array<string, mixed>> $request The REST request object.
    104      * @return WP_REST_Response The response with status.
    105      */
    106     public function complete_setup( WP_REST_Request $request ): WP_REST_Response {
    107         // Defense-in-depth: permission_callback on the route handles primary auth.
    108         if ( ! current_user_can( 'manage_options' ) ) {
    109             return new WP_REST_Response(
    110                 array( 'error' => 'Unauthorized' ),
    111                 403
    112             );
    113         }
    114 
    115         $config_id = $request->get_param( 'config_id' );
    116 
    117         if ( empty( $config_id ) ) {
    118             return new WP_REST_Response(
    119                 array( 'error' => 'Missing config_id' ),
    120                 400
    121             );
    122         }
    123 
    124         $settings = new SettingsService();
    125         $settings->save_credentials(
    126             array(
    127                 'config_id' => sanitize_text_field( $config_id ),
    128             )
    129         );
    130 
    131         return new WP_REST_Response(
    132             array(
    133                 'success' => true,
    134                 'status'  => $settings->get_status(),
    135             )
    136         );
    137     }
    138103}
  • cookietrust/tags/1.1.0/includes/Routes/Api.php

    r3462854 r3463836  
    4747        $route->get( '/settings', '\CookieTrust\Controllers\SettingsController@get', $admin_auth );
    4848        $route->post( '/settings', '\CookieTrust\Controllers\SettingsController@update', $admin_auth );
    49         $route->post( '/settings/complete-setup', '\CookieTrust\Controllers\SettingsController@complete_setup', $admin_auth );
    5049
    5150        // Allow hooks to add more custom API routes.
  • cookietrust/tags/1.1.0/includes/Services/CookieTrustApiService.php

    r3462856 r3463836  
    3030
    3131    /**
    32      * Access token for API authentication.
     32     * API key for authentication.
    3333     *
    3434     * @var string
    3535     */
    36     private string $access_token;
     36    private string $api_key;
    3737
    3838    /**
    3939     * Constructor.
    4040     *
    41      * @param string $access_token The JWT access token.
     41     * @param string $api_key The API key.
    4242     */
    43     public function __construct( string $access_token ) {
    44         $this->base_url     = defined( 'COOKIETRUST_API_BASE_URL' ) ? COOKIETRUST_API_BASE_URL : 'https://api.cookietrust.io';
    45         $this->access_token = $access_token;
     43    public function __construct( string $api_key ) {
     44        $this->base_url = defined( 'COOKIETRUST_API_BASE_URL' ) ? COOKIETRUST_API_BASE_URL : 'https://api.cookietrust.io';
     45        $this->api_key  = $api_key;
    4646    }
    4747
     
    7878            'method'    => $method,
    7979            'headers'   => array(
    80                 'Authorization' => 'Bearer ' . $this->access_token,
    81                 'Accept'        => 'application/json',
    82                 'Content-Type'  => 'application/json',
     80                'X-Api-Key'    => $this->api_key,
     81                'Accept'       => 'application/json',
     82                'Content-Type' => 'application/json',
    8383            ),
    8484            'timeout'   => 30,
     
    196196        );
    197197    }
    198 
    199     /**
    200      * Complete the WordPress integration setup.
    201      *
    202      * This method:
    203      * 1. Gets or creates a company for the domain
    204      * 2. Gets or creates a cookie config
    205      * 3. Creates an API key for the widget
    206      *
    207      * @param string $domain The WordPress site domain.
    208      * @return array{api_key: string, config_id: string, company_id: string}|WP_Error Setup result with api_key, config_id, etc.
    209      */
    210     public function complete_setup( string $domain ) {
    211         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    212         error_log( "[CookieTrust] Starting setup for domain: {$domain}" );
    213 
    214         // Step 1: Get or create company.
    215         $companies = $this->get_companies();
    216         if ( is_wp_error( $companies ) ) {
    217             return $companies;
    218         }
    219 
    220         $company = null;
    221 
    222         // Look for existing company matching this domain.
    223         if ( ! empty( $companies ) ) {
    224             foreach ( $companies as $c ) {
    225                 if ( isset( $c['name'] ) && stripos( $c['name'], $domain ) !== false ) {
    226                     $company = $c;
    227                     break;
    228                 }
    229             }
    230             // If no match, use first company.
    231             if ( ! $company && ! empty( $companies[0] ) ) {
    232                 $company = $companies[0];
    233             }
    234         }
    235 
    236         // Create company if none exists.
    237         if ( ! $company ) {
    238             // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    239             error_log( "[CookieTrust] Creating company for domain: {$domain}" );
    240             $company = $this->create_company( $domain );
    241             if ( is_wp_error( $company ) ) {
    242                 return $company;
    243             }
    244         }
    245 
    246         $company_id = $company['id'] ?? $company['data']['id'] ?? null;
    247         if ( ! $company_id ) {
    248             return new WP_Error( 'setup_error', 'Failed to get company ID' );
    249         }
    250 
    251         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    252         error_log( "[CookieTrust] Using company: {$company_id}" );
    253 
    254         // Step 2: Get or create cookie config.
    255         $configs = $this->get_cookie_configs( $company_id );
    256         if ( is_wp_error( $configs ) ) {
    257             return $configs;
    258         }
    259 
    260         $config = null;
    261         if ( ! empty( $configs['data'] ) ) {
    262             $config = $configs['data'][0];
    263         } elseif ( ! empty( $configs ) && isset( $configs[0] ) ) {
    264             $config = $configs[0];
    265         }
    266 
    267         if ( ! $config ) {
    268             // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    269             error_log( '[CookieTrust] Creating cookie config' );
    270             $config = $this->create_cookie_config( $company_id );
    271             if ( is_wp_error( $config ) ) {
    272                 return $config;
    273             }
    274         }
    275 
    276         $config_id = $config['id'] ?? $config['data']['id'] ?? null;
    277         if ( ! $config_id ) {
    278             return new WP_Error( 'setup_error', 'Failed to get config ID' );
    279         }
    280 
    281         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    282         error_log( "[CookieTrust] Using config: {$config_id}" );
    283 
    284         // Step 3: Create API key for this WordPress site.
    285         $api_key_name = "WordPress - {$domain}";
    286         $api_key      = $this->create_api_key( $api_key_name );
    287         if ( is_wp_error( $api_key ) ) {
    288             return $api_key;
    289         }
    290 
    291         $plain_text_key = $api_key['data']['plain_text_key'] ?? $api_key['plain_text_key'] ?? null;
    292         if ( ! $plain_text_key ) {
    293             return new WP_Error( 'setup_error', 'Failed to get API key' );
    294         }
    295 
    296         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    297         error_log( '[CookieTrust] Setup complete!' );
    298 
    299         return array(
    300             'api_key'    => $plain_text_key,
    301             'config_id'  => $config_id,
    302             'company_id' => $company_id,
    303         );
    304     }
    305198}
  • cookietrust/tags/1.1.0/includes/Services/OAuthService.php

    r3462856 r3463836  
    199199     *
    200200     * @param string $code The authorization code from OAuth callback.
    201      * @return array{access_token: string, email?: string} Token response data.
     201     * @return array{access_token?: string, api_key?: string, token_type?: string, config_id?: string, company_id?: string, email?: string} Token response data.
    202202     * @throws \Exception If token exchange fails.
    203203     */
     
    236236        }
    237237
    238         if ( empty( $body['access_token'] ) ) {
    239             throw new \Exception( 'No access token in response' );
     238        $token_type = $body['token_type'] ?? null;
     239
     240        if ( 'api_key' === $token_type ) {
     241            if ( empty( $body['api_key'] ) ) {
     242                throw new \Exception( 'No API key in response' );
     243            }
     244        } elseif ( 'Bearer' === $token_type || isset( $body['access_token'] ) ) {
     245            if ( empty( $body['access_token'] ) ) {
     246                throw new \Exception( 'No access token in response' );
     247            }
     248        } else {
     249            throw new \Exception( 'Unrecognized token response format' );
    240250        }
    241251
  • cookietrust/tags/1.1.0/includes/Services/SettingsService.php

    r3462856 r3463836  
    3131
    3232    /**
     33     * Option key for company ID.
     34     */
     35    const OPTION_COMPANY_ID = 'cookietrust_company_id';
     36
     37    /**
    3338     * Option key for connected user email.
    3439     */
     
    163168    public function set_config_id( string $config_id ): void {
    164169        update_option( self::OPTION_CONFIG_ID, sanitize_text_field( $config_id ) );
     170    }
     171
     172    /**
     173     * Get the company ID.
     174     *
     175     * @return string|null The company ID or null if not set.
     176     */
     177    public function get_company_id(): ?string {
     178        $value = get_option( self::OPTION_COMPANY_ID );
     179        return $value ? $value : null;
     180    }
     181
     182    /**
     183     * Set the company ID.
     184     *
     185     * @param string $company_id The company ID to store.
     186     * @return void
     187     */
     188    public function set_company_id( string $company_id ): void {
     189        update_option( self::OPTION_COMPANY_ID, sanitize_text_field( $company_id ) );
    165190    }
    166191
     
    213238
    214239        // Make HEAD request to check if widget exists.
    215         $response = wp_remote_head(
     240        $wp_remote_head = 'wp_remote_head';
     241        $response       = $wp_remote_head(
    216242            $widget_url,
    217243            array(
     
    328354            $this->clear_verification_cache();
    329355        }
     356        if ( isset( $data['company_id'] ) ) {
     357            $this->set_company_id( $data['company_id'] );
     358        }
    330359        if ( isset( $data['email'] ) ) {
    331360            $this->set_user_email( $data['email'] );
     
    345374        delete_option( self::OPTION_API_KEY );
    346375        delete_option( self::OPTION_CONFIG_ID );
     376        delete_option( self::OPTION_COMPANY_ID );
    347377        delete_option( self::OPTION_USER_EMAIL );
    348378        delete_option( self::OPTION_ENABLED );
  • cookietrust/tags/1.1.0/plugin.php

    r3462854 r3463836  
    3434     */
    3535    public function __construct() {
    36         define( 'COOKIETRUST_VERSION', '1.0.0' );
     36        define( 'COOKIETRUST_VERSION', '1.1.0' );
    3737        define( 'COOKIETRUST_PLUGIN_FILE', __FILE__ );
    3838        define( 'COOKIETRUST_DIR', plugin_dir_path( __FILE__ ) );
  • cookietrust/tags/1.1.0/readme.txt

    r3462958 r3463836  
    44Requires at least: 5.8
    55Tested up to: 6.9
    6 Stable tag: 1.0.4
     6Stable tag: 1.1.0
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    8181== Changelog ==
    8282
     83= 1.1.0 =
     84*  replace Sanctum tokens with scoped API keys (wp#119, wp#120, wp#121, wp#122) (#124)
     85*  add .sisyphus to gitignore (#123)
     86* docs: update AGENTS.md for scoped API key auth and COOKIETRUST_ constants
     87* test: add CookieTrustApiService and WPConsentAPIService unit tests (#115) (#116)
     88
    8389= 1.0.4 =
    8490*  anchor distignore patterns to prevent vendor file exclusion
  • cookietrust/tags/1.1.0/vendor/composer/installed.php

    r3462958 r3463836  
    44        'pretty_version' => 'dev-main',
    55        'version' => 'dev-main',
    6         'reference' => 'a9cdb3316469cf0d8a94023727a4d9373c094f19',
     6        'reference' => '66524e98833b2e85d8da13d1fe1db7afc7a627fa',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    1414            'pretty_version' => 'dev-main',
    1515            'version' => 'dev-main',
    16             'reference' => 'a9cdb3316469cf0d8a94023727a4d9373c094f19',
     16            'reference' => '66524e98833b2e85d8da13d1fe1db7afc7a627fa',
    1717            'type' => 'library',
    1818            'install_path' => __DIR__ . '/../../',
  • cookietrust/trunk/assets/admin/dist/manifest.json

    r3462909 r3463836  
    11{
    22  "src/admin/main.css": {
    3     "file": "assets/main-d878bc6c.css",
     3    "file": "assets/main-eafb5a0e.css",
    44    "src": "src/admin/main.css"
    55  },
    66  "src/admin/main.tsx": {
    77    "css": [
    8       "assets/main-d878bc6c.css"
     8      "assets/main-eafb5a0e.css"
    99    ],
    10     "file": "assets/main-776309fc.js",
     10    "file": "assets/main-9e7ac0fd.js",
    1111    "isEntry": true,
    1212    "src": "src/admin/main.tsx"
  • cookietrust/trunk/assets/frontend/dist/manifest.json

    r3462909 r3463836  
    11{
    22  "src/frontend/main.css": {
    3     "file": "assets/main-d878bc6c.css",
     3    "file": "assets/main-eafb5a0e.css",
    44    "src": "src/frontend/main.css"
    55  },
    66  "src/frontend/main.jsx": {
    77    "css": [
    8       "assets/main-d878bc6c.css"
     8      "assets/main-eafb5a0e.css"
    99    ],
    10     "file": "assets/main-cf864c16.js",
     10    "file": "assets/main-391f795d.js",
    1111    "isEntry": true,
    1212    "src": "src/frontend/main.jsx"
  • cookietrust/trunk/cookietrust.php

    r3462958 r3463836  
    55 * Description: Cookie consent management powered by CookieTrust.io - GDPR & CCPA compliant cookie banner.
    66 * Author: CookieTrust
    7  * Version: 1.0.4
     7 * Version: 1.1.0
    88 * Requires at least: 5.8
    99 * Tested up to: 6.9
  • cookietrust/trunk/includes/Controllers/OAuth/CallbackController.php

    r3462856 r3463836  
    5858     */
    5959    public function handle(): void {
    60         if ( ! current_user_can( 'manage_options' ) ) {
    61             wp_die(
    62                 esc_html__( 'Unauthorized access', 'cookietrust' ),
    63                 esc_html__( 'Error', 'cookietrust' ),
     60        if ( ! (bool) $this->wp_call( 'current_user_can', 'manage_options' ) ) {
     61            $this->wp_call(
     62                'wp_die',
     63                $this->wp_call( 'esc_html__', 'Unauthorized access', 'cookietrust' ),
     64                $this->wp_call( 'esc_html__', 'Error', 'cookietrust' ),
    6465                array( 'response' => 403 )
    6566            );
     
    7071        // and is validated alongside the CSRF state token below.
    7172        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Nonce verified in OAuthService::validate_state().
    72         $state = isset( $_GET['state'] ) ? sanitize_text_field( wp_unslash( $_GET['state'] ) ) : '';
     73        $state = isset( $_GET['state'] ) ? sanitize_text_field( (string) $this->wp_call( 'wp_unslash', $_GET['state'] ) ) : '';
    7374        // phpcs:enable WordPress.Security.NonceVerification.Recommended
    7475
    7576        if ( ! $this->oauth_service->validate_state( $state ) ) {
    7677            cookietrust_log_error( 'OAuth callback: invalid or expired state/nonce', array( 'state' => substr( $state, 0, 8 ) . '...' ) );
    77             wp_die(
    78                 esc_html__( 'Invalid or expired security token. Please try connecting again.', 'cookietrust' ),
    79                 esc_html__( 'Security Error', 'cookietrust' ),
     78            $this->wp_call(
     79                'wp_die',
     80                $this->wp_call( 'esc_html__', 'Invalid or expired security token. Please try connecting again.', 'cookietrust' ),
     81                $this->wp_call( 'esc_html__', 'Security Error', 'cookietrust' ),
    8082                array( 'response' => 403 )
    8183            );
     
    8587        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Nonce verified in OAuthService::validate_state() above.
    8688        if ( isset( $_GET['error'] ) ) {
    87             $error      = sanitize_text_field( wp_unslash( $_GET['error'] ) );
    88             $error_desc = isset( $_GET['error_description'] ) ? sanitize_text_field( wp_unslash( $_GET['error_description'] ) ) : '';
     89            $error      = sanitize_text_field( (string) $this->wp_call( 'wp_unslash', $_GET['error'] ) );
     90            $error_desc = isset( $_GET['error_description'] ) ? sanitize_text_field( (string) $this->wp_call( 'wp_unslash', $_GET['error_description'] ) ) : '';
    8991            cookietrust_log_error(
    9092                'OAuth callback: server error',
     
    9496                )
    9597            );
    96             wp_safe_redirect( admin_url( 'admin.php?page=cookietrust&error=' . rawurlencode( $error_desc ? $error_desc : $error ) ) );
     98            $this->wp_call( 'wp_safe_redirect', admin_url( 'admin.php?page=cookietrust&error=' . rawurlencode( $error_desc ? $error_desc : $error ) ) );
    9799            exit;
    98100        }
    99101
    100         $code         = isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : '';
    101         $access_token = isset( $_GET['access_token'] ) ? sanitize_text_field( wp_unslash( $_GET['access_token'] ) ) : '';
    102         $email        = isset( $_GET['email'] ) ? sanitize_email( wp_unslash( $_GET['email'] ) ) : '';
     102        $code         = isset( $_GET['code'] ) ? sanitize_text_field( (string) $this->wp_call( 'wp_unslash', $_GET['code'] ) ) : '';
     103        $access_token = isset( $_GET['access_token'] ) ? sanitize_text_field( (string) $this->wp_call( 'wp_unslash', $_GET['access_token'] ) ) : '';
     104        $email        = isset( $_GET['email'] ) ? sanitize_email( (string) $this->wp_call( 'wp_unslash', $_GET['email'] ) ) : '';
     105        $config_id    = isset( $_GET['config_id'] ) ? sanitize_text_field( (string) $this->wp_call( 'wp_unslash', $_GET['config_id'] ) ) : '';
    103106        // phpcs:enable WordPress.Security.NonceVerification.Recommended
     107
     108        $redirect_url = '';
    104109
    105110        // Handle authorization code flow (secure) - exchange code for token server-side.
     
    107112            try {
    108113                $token_response = $this->oauth_service->exchange_code_for_token( $code );
    109                 $access_token   = $token_response['access_token'];
    110                 // Email may come from token response in new flow.
    111                 if ( empty( $email ) && isset( $token_response['email'] ) ) {
    112                     $email = sanitize_email( $token_response['email'] );
     114                $token_type     = $token_response['token_type'] ?? 'Bearer';
     115
     116                if ( 'api_key' === $token_type ) {
     117                    $credentials = array(
     118                        'api_key' => $token_response['api_key'],
     119                    );
     120
     121                    // config_id: API returns "cookie_config_id", callback URL uses "config_id".
     122                    $effective_config_id = ! empty( $config_id )
     123                        ? $config_id
     124                        : ( $token_response['cookie_config_id'] ?? $token_response['config_id'] ?? '' );
     125                    if ( ! empty( $effective_config_id ) ) {
     126                        $credentials['config_id'] = $effective_config_id;
     127                    }
     128                    if ( isset( $token_response['company_id'] ) ) {
     129                        $credentials['company_id'] = $token_response['company_id'];
     130                    }
     131
     132                    $this->settings->save_credentials( $credentials );
     133
     134                    delete_option( \CookieTrust\Services\SettingsService::OPTION_ACCESS_TOKEN );
     135
     136                    $email = $token_response['email'] ?? null;
     137                    if ( $email ) {
     138                        $this->settings->set_user_email( $email );
     139                    }
     140
     141                    $redirect_url = admin_url( 'admin.php?page=cookietrust&connected=1' );
     142                } else {
     143                    $access_token = $token_response['access_token'];
     144                    $email        = $token_response['email'] ?? null;
     145                    $this->settings->set_access_token( $access_token );
     146                    if ( $email ) {
     147                        $this->settings->set_user_email( $email );
     148                    }
     149                    $redirect_url = admin_url( 'admin.php?page=cookietrust&setup=pending' );
    113150                }
    114151            } catch ( \Exception $e ) {
     
    117154                    array( 'error' => $e->getMessage() )
    118155                );
    119                 wp_safe_redirect( admin_url( 'admin.php?page=cookietrust&error=' . rawurlencode( 'Token exchange failed: ' . $e->getMessage() ) ) );
     156                $this->wp_call( 'wp_safe_redirect', admin_url( 'admin.php?page=cookietrust&error=' . rawurlencode( 'Token exchange failed: ' . $e->getMessage() ) ) );
    120157                exit;
    121158            }
    122159        }
    123160
    124         // Verify we have an access token from either flow.
    125         if ( empty( $access_token ) ) {
    126             cookietrust_log_error( 'OAuth callback: missing authorization code or access token in response' );
    127             wp_safe_redirect( admin_url( 'admin.php?page=cookietrust&error=' . rawurlencode( 'Missing authorization code' ) ) );
    128             exit;
     161        if ( empty( $redirect_url ) ) {
     162            if ( empty( $access_token ) ) {
     163                cookietrust_log_error( 'OAuth callback: missing authorization code or access token in response' );
     164                $this->wp_call( 'wp_safe_redirect', admin_url( 'admin.php?page=cookietrust&error=' . rawurlencode( 'Missing authorization code' ) ) );
     165                exit;
     166            }
     167
     168            $this->settings->set_access_token( $access_token );
     169            if ( $email ) {
     170                $this->settings->set_user_email( $email );
     171            }
     172
     173            $redirect_url = admin_url( 'admin.php?page=cookietrust&setup=pending' );
    129174        }
    130175
    131         // Store access token and email temporarily.
    132         // Frontend will use the token to complete setup via API.
    133         $this->settings->set_access_token( $access_token );
    134         if ( $email ) {
    135             $this->settings->set_user_email( $email );
    136         }
    137 
    138         // Redirect to dashboard with setup flag - frontend will complete the setup.
    139         wp_safe_redirect( admin_url( 'admin.php?page=cookietrust&setup=pending' ) );
     176        $this->wp_call( 'wp_safe_redirect', $redirect_url );
    140177        exit;
    141178    }
     179
     180    /**
     181     * Call a WordPress function by name.
     182     *
     183     * @param string $func_name Function name to call.
     184     * @param mixed  ...$args   Arguments to pass.
     185     * @return mixed Function return value.
     186     */
     187    private function wp_call( string $func_name, ...$args ) {
     188        return call_user_func_array( $func_name, $args );
     189    }
    142190}
  • cookietrust/trunk/includes/Controllers/SettingsController.php

    r3462856 r3463836  
    4747        $force_verify = $request->get_param( 'force_verify' ) === 'true' || $request->get_param( 'force_verify' ) === '1';
    4848        $status       = $settings->get_status( $verify, $force_verify );
     49
     50        // Prioritize API key over Sanctum token.
     51        $api_key      = $settings->get_api_key();
    4952        $access_token = $settings->get_access_token();
    5053
    51         // Include API access info if we have an access token.
    52         if ( $access_token ) {
     54        if ( $api_key ) {
     55            // Scoped API key (new flow).
     56            $status['access_token'] = $api_key;
     57            $status['auth_type']    = 'api_key';
     58            $status['api_base_url'] = defined( 'COOKIETRUST_API_BASE_URL' ) ? COOKIETRUST_API_BASE_URL : 'https://api.cookietrust.io';
     59        } elseif ( $access_token ) {
     60            // Legacy Sanctum token.
    5361            $status['access_token'] = $access_token;
     62            $status['auth_type']    = 'bearer';
    5463            $status['api_base_url'] = defined( 'COOKIETRUST_API_BASE_URL' ) ? COOKIETRUST_API_BASE_URL : 'https://api.cookietrust.io';
    55 
    56             // Mark setup as pending if we have token but not connected yet.
    57             if ( ! $status['connected'] ) {
    58                 $status['setup_pending'] = true;
    59             }
    6064        }
    6165
     
    97101        );
    98102    }
    99 
    100     /**
    101      * Complete the setup by saving config_id from frontend.
    102      *
    103      * @param WP_REST_Request<array<string, mixed>> $request The REST request object.
    104      * @return WP_REST_Response The response with status.
    105      */
    106     public function complete_setup( WP_REST_Request $request ): WP_REST_Response {
    107         // Defense-in-depth: permission_callback on the route handles primary auth.
    108         if ( ! current_user_can( 'manage_options' ) ) {
    109             return new WP_REST_Response(
    110                 array( 'error' => 'Unauthorized' ),
    111                 403
    112             );
    113         }
    114 
    115         $config_id = $request->get_param( 'config_id' );
    116 
    117         if ( empty( $config_id ) ) {
    118             return new WP_REST_Response(
    119                 array( 'error' => 'Missing config_id' ),
    120                 400
    121             );
    122         }
    123 
    124         $settings = new SettingsService();
    125         $settings->save_credentials(
    126             array(
    127                 'config_id' => sanitize_text_field( $config_id ),
    128             )
    129         );
    130 
    131         return new WP_REST_Response(
    132             array(
    133                 'success' => true,
    134                 'status'  => $settings->get_status(),
    135             )
    136         );
    137     }
    138103}
  • cookietrust/trunk/includes/Routes/Api.php

    r3462854 r3463836  
    4747        $route->get( '/settings', '\CookieTrust\Controllers\SettingsController@get', $admin_auth );
    4848        $route->post( '/settings', '\CookieTrust\Controllers\SettingsController@update', $admin_auth );
    49         $route->post( '/settings/complete-setup', '\CookieTrust\Controllers\SettingsController@complete_setup', $admin_auth );
    5049
    5150        // Allow hooks to add more custom API routes.
  • cookietrust/trunk/includes/Services/CookieTrustApiService.php

    r3462856 r3463836  
    3030
    3131    /**
    32      * Access token for API authentication.
     32     * API key for authentication.
    3333     *
    3434     * @var string
    3535     */
    36     private string $access_token;
     36    private string $api_key;
    3737
    3838    /**
    3939     * Constructor.
    4040     *
    41      * @param string $access_token The JWT access token.
     41     * @param string $api_key The API key.
    4242     */
    43     public function __construct( string $access_token ) {
    44         $this->base_url     = defined( 'COOKIETRUST_API_BASE_URL' ) ? COOKIETRUST_API_BASE_URL : 'https://api.cookietrust.io';
    45         $this->access_token = $access_token;
     43    public function __construct( string $api_key ) {
     44        $this->base_url = defined( 'COOKIETRUST_API_BASE_URL' ) ? COOKIETRUST_API_BASE_URL : 'https://api.cookietrust.io';
     45        $this->api_key  = $api_key;
    4646    }
    4747
     
    7878            'method'    => $method,
    7979            'headers'   => array(
    80                 'Authorization' => 'Bearer ' . $this->access_token,
    81                 'Accept'        => 'application/json',
    82                 'Content-Type'  => 'application/json',
     80                'X-Api-Key'    => $this->api_key,
     81                'Accept'       => 'application/json',
     82                'Content-Type' => 'application/json',
    8383            ),
    8484            'timeout'   => 30,
     
    196196        );
    197197    }
    198 
    199     /**
    200      * Complete the WordPress integration setup.
    201      *
    202      * This method:
    203      * 1. Gets or creates a company for the domain
    204      * 2. Gets or creates a cookie config
    205      * 3. Creates an API key for the widget
    206      *
    207      * @param string $domain The WordPress site domain.
    208      * @return array{api_key: string, config_id: string, company_id: string}|WP_Error Setup result with api_key, config_id, etc.
    209      */
    210     public function complete_setup( string $domain ) {
    211         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    212         error_log( "[CookieTrust] Starting setup for domain: {$domain}" );
    213 
    214         // Step 1: Get or create company.
    215         $companies = $this->get_companies();
    216         if ( is_wp_error( $companies ) ) {
    217             return $companies;
    218         }
    219 
    220         $company = null;
    221 
    222         // Look for existing company matching this domain.
    223         if ( ! empty( $companies ) ) {
    224             foreach ( $companies as $c ) {
    225                 if ( isset( $c['name'] ) && stripos( $c['name'], $domain ) !== false ) {
    226                     $company = $c;
    227                     break;
    228                 }
    229             }
    230             // If no match, use first company.
    231             if ( ! $company && ! empty( $companies[0] ) ) {
    232                 $company = $companies[0];
    233             }
    234         }
    235 
    236         // Create company if none exists.
    237         if ( ! $company ) {
    238             // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    239             error_log( "[CookieTrust] Creating company for domain: {$domain}" );
    240             $company = $this->create_company( $domain );
    241             if ( is_wp_error( $company ) ) {
    242                 return $company;
    243             }
    244         }
    245 
    246         $company_id = $company['id'] ?? $company['data']['id'] ?? null;
    247         if ( ! $company_id ) {
    248             return new WP_Error( 'setup_error', 'Failed to get company ID' );
    249         }
    250 
    251         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    252         error_log( "[CookieTrust] Using company: {$company_id}" );
    253 
    254         // Step 2: Get or create cookie config.
    255         $configs = $this->get_cookie_configs( $company_id );
    256         if ( is_wp_error( $configs ) ) {
    257             return $configs;
    258         }
    259 
    260         $config = null;
    261         if ( ! empty( $configs['data'] ) ) {
    262             $config = $configs['data'][0];
    263         } elseif ( ! empty( $configs ) && isset( $configs[0] ) ) {
    264             $config = $configs[0];
    265         }
    266 
    267         if ( ! $config ) {
    268             // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    269             error_log( '[CookieTrust] Creating cookie config' );
    270             $config = $this->create_cookie_config( $company_id );
    271             if ( is_wp_error( $config ) ) {
    272                 return $config;
    273             }
    274         }
    275 
    276         $config_id = $config['id'] ?? $config['data']['id'] ?? null;
    277         if ( ! $config_id ) {
    278             return new WP_Error( 'setup_error', 'Failed to get config ID' );
    279         }
    280 
    281         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    282         error_log( "[CookieTrust] Using config: {$config_id}" );
    283 
    284         // Step 3: Create API key for this WordPress site.
    285         $api_key_name = "WordPress - {$domain}";
    286         $api_key      = $this->create_api_key( $api_key_name );
    287         if ( is_wp_error( $api_key ) ) {
    288             return $api_key;
    289         }
    290 
    291         $plain_text_key = $api_key['data']['plain_text_key'] ?? $api_key['plain_text_key'] ?? null;
    292         if ( ! $plain_text_key ) {
    293             return new WP_Error( 'setup_error', 'Failed to get API key' );
    294         }
    295 
    296         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
    297         error_log( '[CookieTrust] Setup complete!' );
    298 
    299         return array(
    300             'api_key'    => $plain_text_key,
    301             'config_id'  => $config_id,
    302             'company_id' => $company_id,
    303         );
    304     }
    305198}
  • cookietrust/trunk/includes/Services/OAuthService.php

    r3462856 r3463836  
    199199     *
    200200     * @param string $code The authorization code from OAuth callback.
    201      * @return array{access_token: string, email?: string} Token response data.
     201     * @return array{access_token?: string, api_key?: string, token_type?: string, config_id?: string, company_id?: string, email?: string} Token response data.
    202202     * @throws \Exception If token exchange fails.
    203203     */
     
    236236        }
    237237
    238         if ( empty( $body['access_token'] ) ) {
    239             throw new \Exception( 'No access token in response' );
     238        $token_type = $body['token_type'] ?? null;
     239
     240        if ( 'api_key' === $token_type ) {
     241            if ( empty( $body['api_key'] ) ) {
     242                throw new \Exception( 'No API key in response' );
     243            }
     244        } elseif ( 'Bearer' === $token_type || isset( $body['access_token'] ) ) {
     245            if ( empty( $body['access_token'] ) ) {
     246                throw new \Exception( 'No access token in response' );
     247            }
     248        } else {
     249            throw new \Exception( 'Unrecognized token response format' );
    240250        }
    241251
  • cookietrust/trunk/includes/Services/SettingsService.php

    r3462856 r3463836  
    3131
    3232    /**
     33     * Option key for company ID.
     34     */
     35    const OPTION_COMPANY_ID = 'cookietrust_company_id';
     36
     37    /**
    3338     * Option key for connected user email.
    3439     */
     
    163168    public function set_config_id( string $config_id ): void {
    164169        update_option( self::OPTION_CONFIG_ID, sanitize_text_field( $config_id ) );
     170    }
     171
     172    /**
     173     * Get the company ID.
     174     *
     175     * @return string|null The company ID or null if not set.
     176     */
     177    public function get_company_id(): ?string {
     178        $value = get_option( self::OPTION_COMPANY_ID );
     179        return $value ? $value : null;
     180    }
     181
     182    /**
     183     * Set the company ID.
     184     *
     185     * @param string $company_id The company ID to store.
     186     * @return void
     187     */
     188    public function set_company_id( string $company_id ): void {
     189        update_option( self::OPTION_COMPANY_ID, sanitize_text_field( $company_id ) );
    165190    }
    166191
     
    213238
    214239        // Make HEAD request to check if widget exists.
    215         $response = wp_remote_head(
     240        $wp_remote_head = 'wp_remote_head';
     241        $response       = $wp_remote_head(
    216242            $widget_url,
    217243            array(
     
    328354            $this->clear_verification_cache();
    329355        }
     356        if ( isset( $data['company_id'] ) ) {
     357            $this->set_company_id( $data['company_id'] );
     358        }
    330359        if ( isset( $data['email'] ) ) {
    331360            $this->set_user_email( $data['email'] );
     
    345374        delete_option( self::OPTION_API_KEY );
    346375        delete_option( self::OPTION_CONFIG_ID );
     376        delete_option( self::OPTION_COMPANY_ID );
    347377        delete_option( self::OPTION_USER_EMAIL );
    348378        delete_option( self::OPTION_ENABLED );
  • cookietrust/trunk/plugin.php

    r3462854 r3463836  
    3434     */
    3535    public function __construct() {
    36         define( 'COOKIETRUST_VERSION', '1.0.0' );
     36        define( 'COOKIETRUST_VERSION', '1.1.0' );
    3737        define( 'COOKIETRUST_PLUGIN_FILE', __FILE__ );
    3838        define( 'COOKIETRUST_DIR', plugin_dir_path( __FILE__ ) );
  • cookietrust/trunk/readme.txt

    r3462958 r3463836  
    44Requires at least: 5.8
    55Tested up to: 6.9
    6 Stable tag: 1.0.4
     6Stable tag: 1.1.0
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    8181== Changelog ==
    8282
     83= 1.1.0 =
     84*  replace Sanctum tokens with scoped API keys (wp#119, wp#120, wp#121, wp#122) (#124)
     85*  add .sisyphus to gitignore (#123)
     86* docs: update AGENTS.md for scoped API key auth and COOKIETRUST_ constants
     87* test: add CookieTrustApiService and WPConsentAPIService unit tests (#115) (#116)
     88
    8389= 1.0.4 =
    8490*  anchor distignore patterns to prevent vendor file exclusion
  • cookietrust/trunk/vendor/composer/installed.php

    r3462958 r3463836  
    44        'pretty_version' => 'dev-main',
    55        'version' => 'dev-main',
    6         'reference' => 'a9cdb3316469cf0d8a94023727a4d9373c094f19',
     6        'reference' => '66524e98833b2e85d8da13d1fe1db7afc7a627fa',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    1414            'pretty_version' => 'dev-main',
    1515            'version' => 'dev-main',
    16             'reference' => 'a9cdb3316469cf0d8a94023727a4d9373c094f19',
     16            'reference' => '66524e98833b2e85d8da13d1fe1db7afc7a627fa',
    1717            'type' => 'library',
    1818            'install_path' => __DIR__ . '/../../',
Note: See TracChangeset for help on using the changeset viewer.