Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"szepeviktor/phpstan-wordpress": "^2.0.2",
"wp-coding-standards/wpcs": "^3.2.0",
"wp-phpunit/wp-phpunit": "^6.5",
"yoast/phpunit-polyfills": "^4.0"
"yoast/phpunit-polyfills": "^4.0",
"php-stubs/wordpress-stubs": "^6.9.0"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Curious why this was needed? We don't include php-stubs/wordpress-stubs directly but it does look like this is already included in our dependency chain

},
"autoload": {
"psr-4": {
Expand Down
6 changes: 3 additions & 3 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

114 changes: 114 additions & 0 deletions docs/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Welcome to the WordPress AI Experiments plugin development guide. This document
- [Getting Started](#getting-started)
- [Architecture Overview](#architecture-overview)
- [Creating a New Experiment](#creating-a-new-experiment)
- [Experiment Settings](#experiment-settings)
- [Plugin API](#plugin-api)
- [Development Workflow](#development-workflow)
- [Additional Resources](#additional-resources)
Expand Down Expand Up @@ -256,6 +257,119 @@ class My_Experiment extends Abstract_Experiment {
}
```

### Experiment Settings

Experiments can expose custom settings that appear in the AI Experiments settings page. Settings are rendered using the `@wordpress/dataviews` DataForm component and saved via the REST API.

#### Enabling Settings

Override `has_settings()` to indicate your experiment has configurable options:

```php
public function has_settings(): bool {
return true;
}
```

#### Defining Settings Fields

Override `get_settings_fields()` to define your settings schema. Each field follows the `@wordpress/dataviews` field format:

```php
public function get_settings_fields(): array {
return array(
array(
'id' => 'tone',
'type' => 'text',
'label' => __( 'Tone', 'ai' ),
'description' => __( 'The tone to use when generating content.', 'ai' ),
'elements' => array(
array(
'value' => 'professional',
'label' => __( 'Professional', 'ai' ),
),
array(
'value' => 'casual',
'label' => __( 'Casual', 'ai' ),
),
array(
'value' => 'creative',
'label' => __( 'Creative', 'ai' ),
),
),
),
array(
'id' => 'max_suggestions',
'type' => 'integer',
'label' => __( 'Max Suggestions', 'ai' ),
'description' => __( 'Maximum number of suggestions to generate.', 'ai' ),
),
);
}
```

**Supported field types:**

- `text` - Text input (with optional `elements` for dropdown)
- `integer` - Number input
- `boolean` - Toggle/checkbox

#### Getting Settings Values

Override `get_settings_values()` to return current settings. Use `get_field_option_name()` to generate consistent option names:

```php
public function get_settings_values(): array {
return array(
'tone' => get_option( $this->get_field_option_name( 'tone' ), 'professional' ),
'max_suggestions' => (int) get_option( $this->get_field_option_name( 'max_suggestions' ), 3 ),
);
}
```

#### Saving Settings

Override `update_settings()` to handle persistence. Always sanitize and validate input:

```php
public function update_settings( array $data ): bool {
// Handle 'tone' setting with allowlist validation.
if ( isset( $data['tone'] ) ) {
$allowed = array( 'professional', 'casual', 'creative' );
$tone = sanitize_text_field( $data['tone'] );

if ( in_array( $tone, $allowed, true ) ) {
update_option( $this->get_field_option_name( 'tone' ), $tone );
}
}

// Handle 'max_suggestions' setting with range validation.
if ( isset( $data['max_suggestions'] ) ) {
$max = absint( $data['max_suggestions'] );
$max = max( 1, min( 10, $max ) ); // Clamp between 1-10

update_option( $this->get_field_option_name( 'max_suggestions' ), $max );
}

return true;
}
```

#### Using Settings in Your Experiment

Access your settings values anywhere in your experiment:

```php
public function get_tone(): string {
return get_option( $this->get_field_option_name( 'tone' ), 'professional' );
}

public function generate_content(): string {
$tone = $this->get_tone();
// Use $tone in your AI prompt...
}
```

---

## Plugin API
Expand Down
81 changes: 72 additions & 9 deletions includes/Abstracts/Abstract_Experiment.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,19 +191,82 @@ public function register_settings(): void {
}

/**
* Renders experiment-specific settings fields.
* Provides contextual entry points for the experiment.
*
* Override this method in child classes to render custom settings UI
* that will appear within the experiment's card on the settings page.
* This is called after the experiment's main toggle control.
* Child classes can override to return an array of links, for example:
* array(
* array(
* 'label' => __( 'Try', 'ai' ),
* 'url' => admin_url( 'post-new.php' ),
* 'type' => 'try',
* ),
* array(
* 'label' => __( 'Dashboard', 'ai' ),
* 'url' => admin_url( 'admin.php?page=ai-mcp' ),
* 'type' => 'dashboard',
* ),
* );
*
* @since 0.1.0
* @since x.x.x
*
* @return void
* @return array<int, array{label: string, url: string, type?: string}>
*/
public function render_settings_fields(): void {
// Default implementation does nothing.
// Child classes can override to render custom settings UI.
public function get_entry_points(): array {
return array();
}

/**
* Checks if the experiment has custom settings.
*
* Override this method in child classes that have settings to return true.
*
* @since x.x.x
*
* @return bool True if the experiment has settings, false otherwise.
*/
public function has_settings(): bool {
return false;
}

/**
* Returns DataForm-compatible field definitions for experiment settings.
*
* Override in child classes to define custom settings fields.
* Each field should follow the @wordpress/dataviews field schema.
*
* @since x.x.x
*
* @return array<int, array{id: string, type: string, label: string, description?: string, elements?: array<int, array{value: string, label: string}>}> DataForm fields.
*/
public function get_settings_fields(): array {
return array();
}

/**
* Returns current values for experiment settings.
*
* Override in child classes to return settings data.
*
* @since x.x.x
*
* @return array<string, mixed> Settings values keyed by field ID.
*/
public function get_settings_values(): array {
return array();
}

/**
* Updates experiment settings from DataForm data.
*
* Override in child classes to handle settings persistence.
*
* @since x.x.x
*
* @param array<string, mixed> $data Settings data from DataForm.
* @return bool True on success, false on failure.
*/
public function update_settings( array $data ): bool {
return true;
}

/**
Expand Down
53 changes: 53 additions & 0 deletions includes/Contracts/Experiment.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,57 @@ public function register(): void;
* @return bool True if enabled, false otherwise.
*/
public function is_enabled(): bool;

/**
* Provides contextual entry points for the experiment.
*
* @since x.x.x
*
* @return array<int, array{label: string, url: string, type?: string}>
*/
public function get_entry_points(): array;

/**
* Checks if the experiment has custom settings.
*
* @since x.x.x
*
* @return bool True if the experiment has settings, false otherwise.
*/
public function has_settings(): bool;

/**
* Returns DataForm-compatible field definitions for experiment settings.
*
* Each field should be an associative array with at minimum:
* - id: Unique field identifier
* - type: Field type (text, boolean, integer, etc.)
* - label: Human-readable label
*
* @since x.x.x
*
* @return array<int, array{id: string, type: string, label: string, description?: string, elements?: array<int, array{value: string, label: string}>}> DataForm fields.
*/
public function get_settings_fields(): array;

/**
* Returns current values for experiment settings.
*
* Keys should match the field IDs from get_settings_fields().
*
* @since x.x.x
*
* @return array<string, mixed> Settings values keyed by field ID.
*/
public function get_settings_values(): array;

/**
* Updates experiment settings from DataForm data.
*
* @since x.x.x
*
* @param array<string, mixed> $data Settings data from DataForm.
* @return bool True on success, false on failure.
*/
public function update_settings( array $data ): bool;
}
Loading
Loading