Skip to content

Commit 84c9928

Browse files
jeffpauldkotterfelixarntz
authored
Merge pull request #100 from dkotter/add/require-credentials
Disable Experiments when no valid AI credentials are found Co-authored-by: dkotter <dkotter@git.wordpress.org> Co-authored-by: felixarntz <flixos90@git.wordpress.org>
2 parents e86c5be + a6e50c2 commit 84c9928

File tree

9 files changed

+183
-4
lines changed

9 files changed

+183
-4
lines changed

includes/Abstracts/Abstract_Experiment.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
use WordPress\AI\Contracts\Experiment;
1313
use WordPress\AI\Exception\Invalid_Experiment_Metadata_Exception;
14+
use WordPress\AI\Settings\Settings_Registration;
15+
16+
use function WordPress\AI\has_valid_ai_credentials;
1417

1518
/**
1619
* Base implementation for experiments.
@@ -148,7 +151,7 @@ final public function is_enabled(): bool {
148151
}
149152

150153
// Check global experiments toggle first.
151-
$global_enabled = (bool) get_option( 'ai_experiments_enabled', false );
154+
$global_enabled = (bool) get_option( Settings_Registration::GLOBAL_OPTION, false );
152155
if ( ! $global_enabled ) {
153156
$this->enabled_cache = false;
154157
return false;
@@ -168,6 +171,11 @@ final public function is_enabled(): bool {
168171
*/
169172
$is_enabled = (bool) apply_filters( "ai_experiment_{$this->id}_enabled", $experiment_enabled );
170173

174+
// Check if we have valid AI credentials.
175+
if ( ! has_valid_ai_credentials() ) {
176+
$is_enabled = false;
177+
}
178+
171179
// Cache the result.
172180
$this->enabled_cache = $is_enabled;
173181

includes/Settings/Settings_Page.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
use WordPress\AI\Asset_Loader;
1515
use WordPress\AI\Experiment_Registry;
1616

17+
use function WordPress\AI\has_ai_credentials;
18+
use function WordPress\AI\has_valid_ai_credentials;
19+
1720
/**
1821
* Manages the admin settings page for AI experiments.
1922
*
@@ -125,6 +128,30 @@ public function render_page(): void {
125128
<div class="wrap">
126129
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
127130

131+
<?php
132+
// If we don't have proper credentials, show an error message and return early.
133+
if ( ! has_valid_ai_credentials() ) {
134+
if ( ! has_ai_credentials() ) {
135+
$error_message = sprintf(
136+
/* translators: 1: Link to the AI credentials settings page. */
137+
__( 'Before you can enable experiments, you need to ensure you have one or more AI credentials set <a href="%s">here</a>', 'ai' ),
138+
admin_url( 'options-general.php?page=wp-ai-client' )
139+
);
140+
} else {
141+
$error_message = sprintf(
142+
/* translators: 1: Link to the AI credentials settings page. */
143+
__( 'Before you can enable experiments, you need to ensure you have set valid AI credentials <a href="%s">here</a>', 'ai' ),
144+
admin_url( 'options-general.php?page=wp-ai-client' )
145+
);
146+
}
147+
148+
wp_admin_notice( $error_message, array( 'type' => 'error' ) );
149+
return;
150+
}
151+
?>
152+
153+
<?php settings_errors(); ?>
154+
128155
<form method="post" action="options.php">
129156
<?php
130157
settings_fields( Settings_Registration::OPTION_GROUP );

includes/bootstrap.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ function load(): void {
178178
*/
179179
function initialize_experiments(): void {
180180
try {
181+
// Initialize the WP AI Client.
182+
AI_Client::init();
183+
181184
$registry = new Experiment_Registry();
182185
$loader = new Experiment_Loader( $registry );
183186
$loader->register_default_experiments();
@@ -193,9 +196,6 @@ function initialize_experiments(): void {
193196
$settings_page->init();
194197
}
195198

196-
// Initialize the WP AI Client.
197-
AI_Client::init();
198-
199199
// Register our post-related WordPress Abilities.
200200
$post_abilities = new Posts();
201201
$post_abilities->register();

includes/helpers.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
namespace WordPress\AI;
1111

12+
use Throwable;
13+
use WordPress\AI_Client\AI_Client;
14+
1215
/**
1316
* Normalizes the content by cleaning it and removing unwanted HTML tags.
1417
*
@@ -155,3 +158,66 @@ function get_preferred_models(): array {
155158
*/
156159
return (array) apply_filters( 'ai_preferred_models', $preferred_models );
157160
}
161+
162+
/**
163+
* Checks if we have AI credentials set.
164+
*
165+
* @since 0.1.0
166+
*
167+
* @return bool True if we have AI credentials, false otherwise.
168+
*/
169+
function has_ai_credentials(): bool {
170+
$credentials = get_option( 'wp_ai_client_provider_credentials', array() );
171+
172+
// If there are no credentials, return false.
173+
if ( ! is_array( $credentials ) || empty( $credentials ) ) {
174+
return false;
175+
}
176+
177+
// If all of the AI keys are empty, return false; otherwise, return true.
178+
return ! empty(
179+
array_filter(
180+
$credentials,
181+
static function ( $api_key ): bool {
182+
return is_string( $api_key ) && '' !== $api_key;
183+
}
184+
)
185+
);
186+
}
187+
188+
/**
189+
* Checks if we have valid AI credentials.
190+
*
191+
* @since 0.1.0
192+
*
193+
* @return bool True if we have valid AI credentials, false otherwise.
194+
*/
195+
function has_valid_ai_credentials(): bool {
196+
// If we have no AI credentials, return false.
197+
if ( ! has_ai_credentials() ) {
198+
return false;
199+
}
200+
201+
/**
202+
* Filters whether valid AI credentials are available.
203+
*
204+
* Allows overriding the credentials check, useful for testing.
205+
*
206+
* @since 0.1.0
207+
* @hook ai_pre_has_valid_credentials_check
208+
*
209+
* @param bool|null $has_valid_credentials Whether valid credentials are available. Return null to use default check.
210+
* @return bool|null True if valid credentials are available, false otherwise, or null to use default check.
211+
*/
212+
$valid = apply_filters( 'ai_pre_has_valid_credentials_check', null );
213+
if ( null !== $valid ) {
214+
return (bool) $valid;
215+
}
216+
217+
// See if we have credentials that give us access to generate text.
218+
try {
219+
return AI_Client::prompt( 'Test' )->is_supported_for_text_generation();
220+
} catch ( Throwable $t ) {
221+
return false;
222+
}
223+
}

tests/Integration/Includes/Abstracts/Abstract_AbilityTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,32 @@ public function register(): void {
138138
*/
139139
class Abstract_AbilityTest extends WP_UnitTestCase {
140140

141+
/**
142+
* Set up test case.
143+
*
144+
* @since 0.1.0
145+
*/
146+
public function setUp(): void {
147+
parent::setUp();
148+
149+
// Set up mock AI credentials so has_ai_credentials() returns true.
150+
update_option( 'wp_ai_client_provider_credentials', array( 'openai' => 'test-api-key' ) );
151+
152+
// Mock has_valid_ai_credentials to return true for tests.
153+
add_filter( 'ai_pre_has_valid_credentials_check', '__return_true' );
154+
}
155+
156+
/**
157+
* Tear down test case.
158+
*
159+
* @since 0.1.0
160+
*/
161+
public function tearDown(): void {
162+
delete_option( 'wp_ai_client_provider_credentials' );
163+
remove_filter( 'ai_pre_has_valid_credentials_check', '__return_true' );
164+
parent::tearDown();
165+
}
166+
141167
/**
142168
* Test that constructor properly sets up the ability.
143169
*

tests/Integration/Includes/Experiment_LoaderTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,28 @@ class Experiment_LoaderTest extends WP_UnitTestCase {
7777
*/
7878
public function setUp(): void {
7979
parent::setUp();
80+
81+
// Set up mock AI credentials so has_ai_credentials() returns true.
82+
update_option( 'wp_ai_client_provider_credentials', array( 'openai' => 'test-api-key' ) );
83+
84+
// Mock has_valid_ai_credentials to return true for tests.
85+
add_filter( 'ai_pre_has_valid_credentials_check', '__return_true' );
86+
8087
$this->registry = new Experiment_Registry();
8188
$this->loader = new Experiment_Loader( $this->registry );
8289
}
8390

91+
/**
92+
* Tear down test case.
93+
*
94+
* @since 0.1.0
95+
*/
96+
public function tearDown(): void {
97+
delete_option( 'wp_ai_client_provider_credentials' );
98+
remove_filter( 'ai_pre_has_valid_credentials_check', '__return_true' );
99+
parent::tearDown();
100+
}
101+
84102
/**
85103
* Test register_default_experiments registers default experiments.
86104
*

tests/Integration/Includes/Experiment_RegistryTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,27 @@ class Experiment_Registry_Test extends WP_UnitTestCase {
6363
*/
6464
public function setUp(): void {
6565
parent::setUp();
66+
67+
// Set up mock AI credentials so has_ai_credentials() returns true.
68+
update_option( 'wp_ai_client_provider_credentials', array( 'openai' => 'test-api-key' ) );
69+
70+
// Mock has_valid_ai_credentials to return true for tests.
71+
add_filter( 'ai_pre_has_valid_credentials_check', '__return_true' );
72+
6673
$this->registry = new Experiment_Registry();
6774
}
6875

76+
/**
77+
* Tear down test case.
78+
*
79+
* @since 0.1.0
80+
*/
81+
public function tearDown(): void {
82+
delete_option( 'wp_ai_client_provider_credentials' );
83+
remove_filter( 'ai_pre_has_valid_credentials_check', '__return_true' );
84+
parent::tearDown();
85+
}
86+
6987
/**
7088
* Test registering an experiment.
7189
*

tests/Integration/Includes/Experiments/Example_Experiment/Example_ExperimentTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ class Example_ExperimentTest extends WP_UnitTestCase {
2626
public function setUp(): void {
2727
parent::setUp();
2828

29+
// Set up mock AI credentials so has_ai_credentials() returns true.
30+
update_option( 'wp_ai_client_provider_credentials', array( 'openai' => 'test-api-key' ) );
31+
32+
// Mock has_valid_ai_credentials to return true for tests.
33+
add_filter( 'ai_pre_has_valid_credentials_check', '__return_true' );
34+
2935
// Enable experiments globally and individually.
3036
update_option( 'ai_experiments_enabled', true );
3137
update_option( 'ai_experiment_example-experiment_enabled', true );
@@ -48,6 +54,8 @@ public function tearDown(): void {
4854
wp_set_current_user( 0 );
4955
delete_option( 'ai_experiments_enabled' );
5056
delete_option( 'ai_experiment_example-experiment_enabled' );
57+
delete_option( 'wp_ai_client_provider_credentials' );
58+
remove_filter( 'ai_pre_has_valid_credentials_check', '__return_true' );
5159
parent::tearDown();
5260
}
5361

tests/Integration/Includes/Experiments/Title_Generation/Title_GenerationTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ class Title_GenerationTest extends WP_UnitTestCase {
2626
public function setUp(): void {
2727
parent::setUp();
2828

29+
// Set up mock AI credentials so has_ai_credentials() returns true.
30+
update_option( 'wp_ai_client_provider_credentials', array( 'openai' => 'test-api-key' ) );
31+
32+
// Mock has_valid_ai_credentials to return true for tests.
33+
add_filter( 'ai_pre_has_valid_credentials_check', '__return_true' );
34+
2935
// Enable experiments globally and individually.
3036
update_option( 'ai_experiments_enabled', true );
3137
update_option( 'ai_experiment_title-generation_enabled', true );
@@ -47,6 +53,8 @@ public function tearDown(): void {
4753
wp_set_current_user( 0 );
4854
delete_option( 'ai_experiments_enabled' );
4955
delete_option( 'ai_experiment_title-generation_enabled' );
56+
delete_option( 'wp_ai_client_provider_credentials' );
57+
remove_filter( 'ai_pre_has_valid_credentials_check', '__return_true' );
5058
parent::tearDown();
5159
}
5260

0 commit comments

Comments
 (0)