Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions includes/Classifai/Helpers/Credentials.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php
/**
* Helper class for credential management functionality.
*
* @package Classifai\Helpers
*/

namespace Classifai\Helpers;

/**
* Credentials class.
*
* Centralized credential management for all providers.
* Allows programmatic override of credentials via filters for integration
* with external secret management services.
*
* @since 3.7.0
*/
class Credentials {

/**
* Get credentials for a provider.
*
* @param string $provider_id The provider ID (e.g., 'azure_openai', 'openai_chatgpt').
* @param string $feature_id The feature ID (e.g., 'feature_title_generation').
* @param array $settings The provider settings from the database.
* @return array Filtered credentials array.
*/
public static function get_credentials( string $provider_id, string $feature_id, array $settings ): array {
/**
* Filter provider credentials before making an API request.
*
* This is the primary hook for integrating external secret management
* services like Azure Key Vault, AWS Secrets Manager, or HashiCorp Vault.
*
* @since 3.7.0
* @hook classifai_provider_credentials
*
* @param array $credentials The credentials array from settings.
* @param string $provider_id The provider ID (e.g., 'azure_openai', 'openai_chatgpt').
* @param string $feature_id The feature ID making the request.
*
* @return array Filtered credentials array.
*/
return apply_filters(
'classifai_provider_credentials',
$settings,
$provider_id,
$feature_id
);
}

/**
* Get a specific credential value.
*
* @param string $provider_id The provider ID.
* @param string $feature_id The feature ID.
* @param array $settings The provider settings.
* @param string $credential_key The specific credential key (e.g., 'api_key').
* @return mixed The credential value.
*/
public static function get_credential(
string $provider_id,
string $feature_id,
array $settings,
string $credential_key
) {
$credentials = self::get_credentials( $provider_id, $feature_id, $settings );
return $credentials[ $credential_key ] ?? '';
}
}
Comment on lines +19 to +71
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new Credentials helper class lacks test coverage. Consider adding unit tests to verify:

  1. The get_credentials() method correctly applies the classifai_provider_credentials filter
  2. The get_credential() method returns the correct individual credential value
  3. Fallback behavior when credentials are not set in the filter
  4. Integration with different provider types (Azure, OpenAI, Watson, etc.)

Copilot uses AI. Check for mistakes.
28 changes: 25 additions & 3 deletions includes/Classifai/Providers/AWS/AmazonPolly.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,26 @@ public function sanitize_settings( array $new_settings ): array {
}

if ( $is_credentials_changed ) {
// Get filtered credentials for authentication check.
$temp_settings = [
'access_key_id' => $new_access_key_id,
'secret_access_key' => $new_secret_access_key,
'aws_region' => $new_aws_region,
];
$credentials = \Classifai\Helpers\Credentials::get_credentials(
static::ID,
$this->feature_instance::ID,
$temp_settings
);

$new_settings[ static::ID ]['access_key_id'] = $new_access_key_id;
$new_settings[ static::ID ]['secret_access_key'] = $new_secret_access_key;
$new_settings[ static::ID ]['aws_region'] = $new_aws_region;
$new_settings[ static::ID ]['voices'] = $this->connect_to_service(
array(
'access_key_id' => $new_access_key_id,
'secret_access_key' => $new_secret_access_key,
'aws_region' => $new_aws_region,
'access_key_id' => $credentials['access_key_id'] ?? $new_access_key_id,
'secret_access_key' => $credentials['secret_access_key'] ?? $new_secret_access_key,
'aws_region' => $credentials['aws_region'] ?? $new_aws_region,
)
);

Expand Down Expand Up @@ -535,6 +547,16 @@ public function get_polly_client( array $aws_config = array() ) {

$default = wp_parse_args( $aws_config, $default );

// Get filtered credentials if no config was passed.
if ( empty( $aws_config ) ) {
$credentials = $this->get_provider_credentials( $this->feature_instance::ID );
$default = array_merge( $default, [
'access_key_id' => $credentials['access_key_id'] ?? $default['access_key_id'],
'secret_access_key' => $credentials['secret_access_key'] ?? $default['secret_access_key'],
'aws_region' => $credentials['aws_region'] ?? $default['aws_region'],
] );
}

// Return if credentials don't exist.
if ( empty( $default['access_key_id'] ) || empty( $default['secret_access_key'] ) ) {
return null;
Expand Down
34 changes: 24 additions & 10 deletions includes/Classifai/Providers/Azure/ComputerVision.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,17 @@ public function sanitize_settings( array $new_settings ) {
$is_api_key_same = $new_settings[ static::ID ]['api_key'] === $settings[ static::ID ]['api_key'];

if ( ! ( $is_authenticated && $is_endpoint_same && $is_api_key_same ) ) {
// Get filtered credentials for authentication check.
$temp_settings = [ static::ID => $new_settings[ static::ID ] ];
$credentials = \Classifai\Helpers\Credentials::get_credentials(
static::ID,
$this->feature_instance::ID,
$temp_settings[ static::ID ]
);

$auth_check = $this->authenticate_credentials(
$new_settings[ static::ID ]['endpoint_url'],
$new_settings[ static::ID ]['api_key']
$credentials['endpoint_url'] ?? $new_settings[ static::ID ]['endpoint_url'],
$credentials['api_key'] ?? $new_settings[ static::ID ]['api_key']
);

if ( is_wp_error( $auth_check ) ) {
Expand Down Expand Up @@ -331,8 +339,9 @@ public static function get_read_status( array $status = [], $attachment_id = nul
* @param int $attachment_id Attachment ID.
*/
public function do_read_cron( string $operation_url, int $attachment_id ) {
$feature = new PDFTextExtraction();
$settings = $feature->get_settings( static::ID );
$feature = new PDFTextExtraction();
$raw_settings = $feature->get_settings( static::ID );
$settings = $this->get_provider_credentials( $feature::ID );

( new Read( $settings, intval( $attachment_id ), $feature ) )->check_read_result( $operation_url );
}
Expand All @@ -351,8 +360,9 @@ public function smart_crop_image( array $metadata, int $attachment_id ) {
return new WP_Error( 'invalid', esc_html__( 'This attachment can\'t be processed.', 'classifai' ) );
}

$feature = new ImageCropping();
$settings = $feature->get_settings( static::ID );
$feature = new ImageCropping();
$raw_settings = $feature->get_settings( static::ID );
$settings = $this->get_provider_credentials( $feature::ID );

if ( ! is_array( $metadata ) || ! is_array( $settings ) ) {
return new WP_Error( 'invalid', esc_html__( 'Invalid data found. Please check your settings and try again.', 'classifai' ) );
Expand Down Expand Up @@ -547,7 +557,8 @@ public function generate_alt_tags( string $image_url, int $attachment_id ) {
*/
public function read_pdf( int $attachment_id ) {
$feature = new PDFTextExtraction();
$settings = $feature->get_settings( static::ID );
$raw_settings = $feature->get_settings( static::ID );
$settings = $this->get_provider_credentials( $feature::ID );
$should_read_pdf = $feature->is_feature_enabled();

if ( ! $should_read_pdf ) {
Expand Down Expand Up @@ -659,6 +670,9 @@ protected function scan_image( string $image_url, ?\Classifai\Features\Feature $

$endpoint_url = $this->prep_api_url( $feature );

// Get filtered credentials.
$credentials = $this->get_provider_credentials( $feature::ID );

/*
* Azure AI Vision requires full image URL. So, if the file URL is relative,
* then we transform it into a full URL.
Expand All @@ -671,7 +685,7 @@ protected function scan_image( string $image_url, ?\Classifai\Features\Feature $
$endpoint_url,
[
'headers' => [
'Ocp-Apim-Subscription-Key' => $settings['api_key'],
'Ocp-Apim-Subscription-Key' => $credentials['api_key'] ?? '',
'Content-Type' => 'application/json',
],
/**
Expand Down Expand Up @@ -722,7 +736,7 @@ protected function scan_image( string $image_url, ?\Classifai\Features\Feature $
* @return string
*/
protected function prep_api_url( ?\Classifai\Features\Feature $feature = null ): string {
$settings = $feature->get_settings( static::ID );
$credentials = $this->get_provider_credentials( $feature::ID );
$api_features = [];

if ( $feature instanceof DescriptiveTextGenerator && $feature->is_feature_enabled() && ! empty( $feature->get_alt_text_settings() ) ) {
Expand All @@ -737,7 +751,7 @@ protected function prep_api_url( ?\Classifai\Features\Feature $feature = null ):
$api_features[] = 'read';
}

$endpoint = add_query_arg( 'features', implode( ',', $api_features ), trailingslashit( $settings['endpoint_url'] ) . $this->analyze_url );
$endpoint = add_query_arg( 'features', implode( ',', $api_features ), trailingslashit( $credentials['endpoint_url'] ?? '' ) . $this->analyze_url );

return $endpoint;
}
Expand Down
16 changes: 11 additions & 5 deletions includes/Classifai/Providers/Azure/Embeddings.php
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,9 @@ public function sanitize_settings( array $new_settings ): array {
* @return string
*/
protected function prep_api_url( ?Feature $feature = null ): string {
$settings = $feature->get_settings( static::ID );
$endpoint = $settings['endpoint_url'] ?? '';
$deployment = $settings['deployment'] ?? '';
$credentials = $this->get_provider_credentials( $feature::ID );
$endpoint = $credentials['endpoint_url'] ?? '';
$deployment = $credentials['deployment'] ?? '';

if ( ! $endpoint ) {
return '';
Expand Down Expand Up @@ -992,12 +992,15 @@ public function generate_embedding( string $text = '', $feature = null ) {
$text
);

// Get filtered credentials.
$credentials = $this->get_provider_credentials( $feature::ID );

// Make our API request.
$response = safe_wp_remote_post(
$this->prep_api_url( $feature ),
[
'headers' => [
'api-key' => $settings[ static::ID ]['api_key'],
'api-key' => $credentials['api_key'] ?? '',
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( $body ),
Expand Down Expand Up @@ -1071,12 +1074,15 @@ public function generate_embeddings( array $strings = [], $feature = null ) {
$strings
);

// Get filtered credentials.
$credentials = $this->get_provider_credentials( $feature::ID );

// Make our API request.
$response = safe_wp_remote_post(
$this->prep_api_url( $feature ),
[
'headers' => [
'api-key' => $settings[ static::ID ]['api_key'],
'api-key' => $credentials['api_key'] ?? '',
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( $body ),
Expand Down
45 changes: 34 additions & 11 deletions includes/Classifai/Providers/Azure/OpenAI.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,18 @@ public function sanitize_settings( array $new_settings ): array {
$is_deployment_same = $new_settings[ static::ID ]['deployment'] === $settings[ static::ID ]['deployment'];

if ( ! ( $is_authenticated && $is_endpoint_same && $is_api_key_same && $is_deployment_same ) ) {
// Get filtered credentials for authentication check.
$temp_settings = [ static::ID => $new_settings[ static::ID ] ];
$credentials = \Classifai\Helpers\Credentials::get_credentials(
static::ID,
$this->feature_instance::ID,
$temp_settings[ static::ID ]
);

$auth_check = $this->authenticate_credentials(
$new_settings[ static::ID ]['endpoint_url'],
$new_settings[ static::ID ]['api_key'],
$new_settings[ static::ID ]['deployment']
$credentials['endpoint_url'] ?? $new_settings[ static::ID ]['endpoint_url'],
$credentials['api_key'] ?? $new_settings[ static::ID ]['api_key'],
$credentials['deployment'] ?? $new_settings[ static::ID ]['deployment']
);

if ( is_wp_error( $auth_check ) ) {
Expand Down Expand Up @@ -262,9 +270,9 @@ public function sanitize_settings( array $new_settings ): array {
* @return string
*/
protected function prep_api_url( ?\Classifai\Features\Feature $feature = null ): string {
$settings = $feature->get_settings( static::ID );
$endpoint = $settings['endpoint_url'] ?? '';
$deployment = $settings['deployment'] ?? '';
$credentials = $this->get_provider_credentials( $feature::ID );
$endpoint = $credentials['endpoint_url'] ?? '';
$deployment = $credentials['deployment'] ?? '';

if ( ! $endpoint ) {
return '';
Expand Down Expand Up @@ -453,12 +461,15 @@ public function generate_excerpt( int $post_id = 0, array $args = [] ) {
$post_id
);

// Get filtered credentials.
$credentials = $this->get_provider_credentials( $feature::ID );

// Make our API request.
$response = safe_wp_remote_post(
$this->prep_api_url( $feature ),
[
'headers' => [
'api-key' => $settings[ static::ID ]['api_key'],
'api-key' => $credentials['api_key'] ?? '',
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( $body ),
Expand Down Expand Up @@ -560,12 +571,15 @@ public function generate_titles( int $post_id = 0, array $args = [] ) {
$post_id
);

// Get filtered credentials.
$credentials = $this->get_provider_credentials( $feature::ID );

// Make our API request.
$response = safe_wp_remote_post(
$this->prep_api_url( $feature ),
[
'headers' => [
'api-key' => $settings[ static::ID ]['api_key'],
'api-key' => $credentials['api_key'] ?? '',
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( $body ),
Expand Down Expand Up @@ -667,12 +681,15 @@ public function resize_content( int $post_id, array $args = array() ) {
$post_id
);

// Get filtered credentials.
$credentials = $this->get_provider_credentials( $feature::ID );

// Make our API request.
$response = safe_wp_remote_post(
$this->prep_api_url( $feature ),
[
'headers' => [
'api-key' => $settings[ static::ID ]['api_key'],
'api-key' => $credentials['api_key'] ?? '',
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( $body ),
Expand Down Expand Up @@ -828,12 +845,15 @@ public function generate_key_takeaways( int $post_id = 0, array $args = [] ) {
$post_id
);

// Get filtered credentials.
$credentials = $this->get_provider_credentials( $feature::ID );

// Make our API request.
$response = safe_wp_remote_post(
$this->prep_api_url( $feature ),
[
'headers' => [
'api-key' => $settings[ static::ID ]['api_key'],
'api-key' => $credentials['api_key'] ?? '',
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( $body ),
Expand Down Expand Up @@ -981,12 +1001,15 @@ public function generate_content( int $post_id = 0, array $args = [] ) {
$post_id
);

// Get filtered credentials.
$credentials = $this->get_provider_credentials( $feature::ID );

// Make our API request.
$response = safe_wp_remote_post(
$this->prep_api_url( $feature ),
[
'headers' => [
'api-key' => $settings[ static::ID ]['api_key'],
'api-key' => $credentials['api_key'] ?? '',
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( $body ),
Expand Down
Loading
Loading