OwlCyberSecurity - MANAGER
Edit File: helper.php
<?php /** * Zip AI - Helper. * * This file contains the helper functions of Zip AI. * Helpers are functions that are used throughout the library. * * @package zip-ai */ namespace ZipAI\Classes; // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } // Classes to be used, in alphabetical order. use ZipAI\Classes\Token_Calculator; use ZipAI\Classes\Utils; /** * The Helper Class. */ class Helper { /** * Get an option from the database. * * @param string $key The option key. * @param mixed $default The option default value if option is not available. * @param boolean $network_override Whether to allow the network admin setting to be overridden on subsites. * @since 1.0.0 * @return mixed The option value. */ public static function get_admin_settings_option( $key, $default = false, $network_override = false ) { // Get the site-wide option if we're in the network admin. return $network_override && is_multisite() ? get_site_option( $key, $default ) : get_option( $key, $default ); } /** * Update an option from the database. * * @param string $key The option key. * @param mixed $value The value to update. * @param bool $network_override Whether to allow the network_override admin setting to be overridden on subsites. * @since 1.0.0 * @return bool True if the option was updated, false otherwise. */ public static function update_admin_settings_option( $key, $value, $network_override = false ) { // Update the site-wide option if we're in the network admin, and return the updated status. return $network_override && is_multisite() ? update_site_option( $key, $value ) : update_option( $key, $value ); } /** * Delete an option from the database for. * * @param string $key The option key. * @param boolean $network_override Whether to allow the network admin setting to be overridden on subsites. * @since 1.0.0 * @return void */ public static function delete_admin_settings_option( $key, $network_override = false ) { // Delete the site-wide option if we're in the network admin. if ( $network_override && is_multisite() ) { delete_site_option( $key ); } else { delete_option( $key ); } } /** * Check if Zip AI is authorized. * * @since 1.0.0 * @return boolean True if Zip AI is authorized, false otherwise. */ public static function is_authorized() { // Get the Zip AI settings. $existing_settings = self::get_admin_settings_option( 'zip_ai_settings' ); // If the Zip AI settings are empty, return false. if ( empty( $existing_settings ) || ! is_array( $existing_settings ) ) { return false; } // Return true if the auth token is set and is a string. return ( ! empty( $existing_settings['auth_token'] ) && is_string( $existing_settings['auth_token'] ) && ! empty( trim( $existing_settings['auth_token'] ) ) ); } /** * Get the Zip AI Settings. * * If used with a key, it will return that specific setting. * If used without a key, it will return the entire settings array. * * @param string $key The setting key. * @param mixed $default The default value to return if the setting is not found. * @since 1.0.0 * @return mixed|array The setting value, or the default. */ public static function get_setting( $key = '', $default = array() ) { // Get the Zip AI settings. $existing_settings = self::get_admin_settings_option( 'zip_ai_settings' ); // If the Zip AI settings are empty, return the fallback. if ( empty( $existing_settings ) || ! is_array( $existing_settings ) ) { return $default; } // If the key is empty, return the entire settings array - otherwise return the specific setting or the fallback. if ( empty( $key ) ) { return $existing_settings; } else { return isset( $existing_settings[ $key ] ) ? $existing_settings[ $key ] : $default; } } /** * Get the Token Count for a given message. * * @param string $message The message to get the token count for. * @since 1.0.0 * @return int The token count. */ public static function get_token_count( $message ) { // Get the formatted token array. $token_array = Token_Calculator::gpt_encode( $message ); // If the token array is empty, return 0, else return the count of the token array. return ( empty( $token_array ) || ! is_array( $token_array ) ) ? 0 : count( $token_array ); } /** * Get the Zip AI Response from the Zip Credit Server. * * @param string $endpoint The endpoint to get the response from. * @param array $body The data to be passed as the request body, if any. * @param array $extra_args Extra arguments to be passed to the request, if any. * @since 1.0.0 * @return array The Zip AI Response. */ public static function get_credit_server_response( $endpoint, $body = [], $extra_args = [] ) { // If the endpoint is not a string, then abandon ship. if ( ! is_string( $endpoint ) ) { return array( 'error' => __( 'The Zip AI Endpoint was not declared', 'astra-sites' ), ); } // Get the Auth Token from the Zip AI Settings. $auth_token = self::get_decrypted_auth_token(); // If the Zip Auth Token is not set, then abandon ship. if ( empty( $auth_token ) || ! is_string( $auth_token ) ) { return array( 'error' => __( 'The Zip AI Auth Token is not set.', 'astra-sites' ), ); } // Set the API URL. $api_url = ZIP_AI_CREDIT_SERVER_API . $endpoint; $api_args = array( 'headers' => array( 'Authorization' => 'Bearer ' . $auth_token, ), 'timeout' => 30, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout -- 30 seconds is required sometime for open ai responses ); // If the data array was passed, add it to the args. if ( ! empty( $body ) && is_array( $body ) ) { $api_args['body'] = $body; } // If there are any extra arguments, then we can overwrite the required arguments. if ( ! empty( $extra_args ) && is_array( $extra_args ) ) { $api_args = array_merge( $api_args, $extra_args ); } // Get the response from the endpoint. $response = wp_remote_post( $api_url, $api_args ); // If the response was an error. if ( is_wp_error( $response ) || empty( $response ) ) { $error_message = __( 'Empty response from API.', 'astra-sites' ); $error_code = __( 'empty_response', 'astra-sites' ); if ( is_wp_error( $response ) ) { $error_message = $response->get_error_message(); $error_code = $response->get_error_code(); } return array( 'error' => $error_message, 'code' => $error_code, ); } // Get the response body. $response_body = wp_remote_retrieve_body( $response ); $response_body = json_decode( $response_body, true ); $status_code = wp_remote_retrieve_response_code( $response ); // Check if the status code is 403 or 401 error. if ( 401 === $status_code || 403 === $status_code ) { $error_message = isset( $response_body['error'] ) ? $response_body['error'] : __( 'You do not have permission to perform this action.', 'astra-sites' ); $error_code = isset( $response_body['code'] ) ? $response_body['code'] : 'forbidden'; return array( 'error' => $error_message, 'code' => $error_code, ); } // If the response body is not a JSON, then abandon ship. if ( 200 !== $status_code || empty( $response_body ) ) { $error_message = __( 'Encountered an error while processing your request. Please try again.', 'astra-sites' ); $error_code = 'unknown_error'; return array( 'error' => $error_message, 'code' => $error_code, ); } // Return the response body. return $response_body; } /** * Get a response from the ZipWP API server. * * @param string $endpoint The endpoint to get the response from. * @since 1.1.2 * @return array The ZipWP API Response. */ public static function get_zipwp_api_response( $endpoint ) { // If the endpoint is not a string, then abandon ship. if ( ! is_string( $endpoint ) ) { return array( 'error' => __( 'The ZipWP Endpoint was not declared', 'astra-sites' ), ); } // Get the ZipWP Token from the Zip AI Settings. $zipwp_token = self::get_decrypted_zipwp_token(); // If the ZipWP Token is not set, then abandon ship. if ( empty( $zipwp_token ) || ! is_string( $zipwp_token ) ) { return array( 'error' => __( 'The ZipWP Token is not set.', 'astra-sites' ), ); } // Set the API URL. $api_url = ZIP_AI_ZIPWP_API . $endpoint; // Get the response from the endpoint. $response = wp_remote_get( $api_url, array( 'headers' => array( 'Content-Type' => 'application/json', 'Accept' => 'application/json', 'Authorization' => 'Bearer ' . $zipwp_token, ), 'sslverify' => false, 'timeout' => 30, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout -- 30 seconds is required sometime for the ZipWP API response ) ); // If the response was an error, or not a 200 status code, then abandon ship. if ( is_wp_error( $response ) || empty( $response['response'] ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { return array( 'error' => __( 'The ZipWP API server is not responding.', 'astra-sites' ), ); } // Get the response body. $response_body = wp_remote_retrieve_body( $response ); // If the response body is not a JSON, then abandon ship. if ( empty( $response_body ) || ! json_decode( $response_body ) ) { return array( 'error' => __( 'The ZipWP API server encountered an error.', 'astra-sites' ), ); } // Return the response body. return json_decode( $response_body, true ); } /** * Get the decrypted token from the Zip AI Settings. * * @param string $token_name The name of the token. * @since 1.1.2 * @return string The decrypted token. */ private static function get_decrypted_token( $token_name ) { // Get the Zip AI Settings. $zip_ai_token = self::get_setting( $token_name ); // Return early if the ZipWP token is not set. if ( empty( $zip_ai_token ) || ! is_string( $zip_ai_token ) ) { return ''; } // Return the decrypted ZipWP token. return ! empty( trim( $zip_ai_token ) ) ? Utils::decrypt( $zip_ai_token ) : ''; } /** * Get the decrypted auth token. * * @since 1.0.0 * @return string The decrypted auth token. */ public static function get_decrypted_auth_token() { return self::get_decrypted_token( 'auth_token' ); } /** * Get the decrypted ZipWP token. * * @since 1.1.2 * @return string The decrypted ZipWP token. */ public static function get_decrypted_zipwp_token() { return self::get_decrypted_token( 'zip_token' ); } /** * Get cached credit details. * * @since 2.0.5 * * @return array|false */ public static function get_cached_credit_details() { $cached = get_transient( 'zip_ai_credit_details' ); return $cached ? json_decode( $cached, true ) : false; } /** * Cache credit details. * * @since 2.0.5 * * @param array $details The credit details to cache. * @param int $duration The cache duration in seconds. * @return void */ public static function cache_credit_details( $details, $duration = 300 ) { // 5 minutes default set_transient( 'zip_ai_credit_details', wp_json_encode( $details ), $duration ); } /** * This helper function returns credit details. * * @since 2.0.5 * @return array */ public static function get_credit_details() { $cached = self::get_cached_credit_details(); if ( false !== $cached ) { return $cached; } // Set the default credit details. $credit_details = array( 'used' => 0, 'total' => 0, 'threshold' => array( 'medium' => ZIP_AI_CREDIT_THRESHOLD_MEDIUM, 'high' => ZIP_AI_CREDIT_THRESHOLD_HIGH, ), 'percentage' => 0, 'status' => 'success', ); // Get the response from the endpoint. $response = self::get_credit_server_response( 'usage' ); // If the response is not an error, then update the credit details. if ( empty( $response['error'] ) && ! empty( $response['total_credits'] ) ) { $credit_details['used'] = ! empty( $response['total_used_credits'] ) ? $response['total_used_credits'] : 0; $credit_details['total'] = $response['total_credits']; $credit_details['percentage'] = intval( ( $credit_details['used'] / $credit_details['total'] ) * 100 ); self::cache_credit_details( $credit_details ); } else { $credit_details['status'] = 'error'; } return $credit_details; } /** * Get fresh credit details without cache. * * @since 2.0.5 * @return array */ public static function get_fresh_credit_details() { // Set the default credit details. $credit_details = array( 'used' => 0, 'total' => 0, 'threshold' => array( 'medium' => ZIP_AI_CREDIT_THRESHOLD_MEDIUM, 'high' => ZIP_AI_CREDIT_THRESHOLD_HIGH, ), 'percentage' => 0, 'status' => 'success', ); // Get the response from the endpoint. $response = self::get_credit_server_response( 'usage' ); // If the response is not an error, then update the credit details. if ( empty( $response['error'] ) && ! empty( $response['total_credits'] ) ) { $credit_details['used'] = ! empty( $response['total_used_credits'] ) ? $response['total_used_credits'] : 0; $credit_details['total'] = $response['total_credits']; $credit_details['percentage'] = intval( ( $credit_details['used'] / $credit_details['total'] ) * 100 ); // Update cache with fresh data. self::cache_credit_details( $credit_details ); } else { $credit_details['status'] = 'error'; } return $credit_details; } /** * This helper function returns the current plan details. * * @since 1.1.2 * @return array */ public static function get_current_plan_details() { // Get current auth token. $current_auth_token = self::get_decrypted_auth_token(); // Get cached auth token. $cached_auth_token = get_transient( 'zip_ai_auth_token' ); // If auth token has changed or doesn't exist, clear all caches. if ( $current_auth_token !== $cached_auth_token ) { delete_transient( 'zip_ai_current_plan_details' ); delete_transient( 'zip_ai_credit_details' ); // Cache the new auth token. set_transient( 'zip_ai_auth_token', $current_auth_token, DAY_IN_SECONDS ); } // Check for cached response first. $cached_response = get_transient( 'zip_ai_current_plan_details' ); if ( false !== $cached_response ) { return json_decode( $cached_response, true ); } $current_plan_details = []; // Get the response from the endpoint. $response = self::get_zipwp_api_response( 'plan/current-plan' ); // If the response is not an error, then use it - else create an error response array. if ( empty( $response['error'] ) && is_array( $response ) ) { $current_plan_details = $response; if ( empty( $current_plan_details['status'] ) ) { $current_plan_details['status'] = 'ok'; } } else { $current_plan_details['status'] = 'error'; if ( ! empty( $response['error'] ) ) { $current_plan_details['error'] = $response['error']; } return $current_plan_details; // Return error immediately without caching. } // Filter for developers to modify cache duration, default 24 hours. $cache_duration = apply_filters( 'zip_ai_plan_details_cache_duration', DAY_IN_SECONDS ); // Validate cache duration. if ( ! is_numeric( $cache_duration ) ) { $cache_duration = DAY_IN_SECONDS; } // Ensure cache duration is between 1 minute and 1 week for optimal performance and freshness. $cache_duration = absint( $cache_duration ); if ( $cache_duration < MINUTE_IN_SECONDS || $cache_duration > WEEK_IN_SECONDS ) { $cache_duration = DAY_IN_SECONDS; } // Cache the response with JSON encoding for security. set_transient( 'zip_ai_current_plan_details', wp_json_encode( $current_plan_details ), $cache_duration ); return $current_plan_details; } /** * Get the authorization middleware url. * * @param array $params An array of parameters to add to the middleware URL. * @since 1.0.0 * @return string The authorization middleware url. */ public static function get_auth_middleware_url( $params = [] ) { // Create the Redirect URL. $redirect_url = add_query_arg( array( 'nonce' => wp_create_nonce( 'zip_ai_auth_nonce' ), 'scs-authorize' => 'true', ), admin_url() ); // Create the Authentication URL. $auth_url = add_query_arg( apply_filters( 'zip_ai_auth_middleware_args', array( 'type' => 'token', 'redirect_url' => rawurlencode( $redirect_url ), ) ), ZIP_AI_MIDDLEWARE ); // Add the plugin param if passed. if ( ! empty( $params['plugin'] ) && is_string( $params['plugin'] ) ) { $auth_url = add_query_arg( 'plugin', sanitize_text_field( $params['plugin'] ), $auth_url ); } // Add the source param if passed. if ( ! empty( $params['source'] ) && is_string( $params['source'] ) ) { $auth_url = add_query_arg( 'source', sanitize_text_field( $params['source'] ), $auth_url ); } // Add the affiliate param if passed. $affiliate = get_option( 'zipwp_partner_url_param', '' ); $affiliate = is_string( $affiliate ) ? sanitize_text_field( $affiliate ) : ''; if ( ! empty( $affiliate ) ) { $auth_url = add_query_arg( 'aff', $affiliate, $auth_url ); } return $auth_url; } /** * Clear the credit details cache. * * @since 2.0.5 * @return void */ public static function clear_credit_details_cache() { delete_transient( 'zip_ai_credit_details' ); } /** * Clear the current plan cache. * * @since 2.0.5 * @return void */ public static function clear_current_plan_cache() { delete_transient( 'zip_ai_current_plan_details' ); } /** * Get the revoke url for the auth token and clear all caches. * * @since 1.0.0 * @return string The authorization revoke url. */ public static function get_auth_revoke_url() { $revoke_url = add_query_arg( apply_filters( 'zip_ai_auth_revoke_args', array( array( 'nonce' => wp_create_nonce( 'zip_ai_auth_nonce' ), 'revoke_zip_ai_authorization_token' => 'definitely', ), admin_url(), ) ) ); return $revoke_url; } }