Skip to content

Commit c368c31

Browse files
committed
Merge branch 'develop' into feature/tts-bkg-schedule
2 parents 5ef3b29 + 945ec01 commit c368c31

File tree

16 files changed

+866
-14
lines changed

16 files changed

+866
-14
lines changed

includes/Classifai/Admin/Settings.php

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Classifai\Services\ServicesManager;
88
use Classifai\Taxonomy\TaxonomyFactory;
99
use Classifai\Helpers\CredentialReuse;
10+
use Classifai\Providers\CredentialObfuscator;
1011

1112
use function Classifai\get_asset_info;
1213
use function Classifai\get_plugin;
@@ -226,14 +227,18 @@ public function get_nlu_taxonomies(): array {
226227
/**
227228
* Get the settings.
228229
*
229-
* @return array The settings.
230+
* Obfuscates sensitive credentials before returning to prevent
231+
* exposure of API keys in the frontend.
232+
*
233+
* @return array The settings with credentials obfuscated.
230234
*/
231235
public function get_settings(): array {
232236
$features = $this->get_features( true );
233237
$settings = [];
234238

235239
foreach ( $features as $feature ) {
236-
$settings[ $feature::ID ] = $feature->get_settings();
240+
$feature_settings = $feature->get_settings();
241+
$settings[ $feature::ID ] = CredentialObfuscator::obfuscate_feature_settings( $feature_settings );
237242
}
238243

239244
return $settings;
@@ -472,6 +477,12 @@ public function update_settings_permissions_check(): bool {
472477
public function get_registration_settings_callback(): \WP_REST_Response {
473478
$service_manager = new ServicesManager();
474479
$settings = $service_manager->get_settings();
480+
481+
// Obfuscate the license key before returning.
482+
if ( isset( $settings['license_key'] ) ) {
483+
$settings['license_key'] = CredentialObfuscator::obfuscate( $settings['license_key'] );
484+
}
485+
475486
return rest_ensure_response( $settings );
476487
}
477488

@@ -487,12 +498,19 @@ public function update_registration_settings_callback( \WP_REST_Request $request
487498
require_once ABSPATH . 'wp-admin/includes/template.php';
488499
}
489500

490-
$service_manager = new ServicesManager();
491-
$settings = $service_manager->get_settings();
492-
$new_settings = $service_manager->sanitize_settings( $request->get_json_params() );
501+
$service_manager = new ServicesManager();
502+
$current_settings = $service_manager->get_settings();
503+
$new_settings = $request->get_json_params();
504+
505+
// If the license key is obfuscated, use the current value.
506+
if ( isset( $new_settings['license_key'] ) && CredentialObfuscator::is_obfuscated( $new_settings['license_key'] ) ) {
507+
$new_settings['license_key'] = $current_settings['license_key'] ?? '';
508+
}
509+
510+
$new_settings = $service_manager->sanitize_settings( $new_settings );
493511

494512
// Update the settings with the new values.
495-
$new_settings = array_merge( $settings, $new_settings );
513+
$new_settings = array_merge( $current_settings, $new_settings );
496514
update_option( 'classifai_settings', $new_settings );
497515

498516
$setting_errors = get_settings_errors();

includes/Classifai/Features/ContentGeneration.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Classifai\Providers\OpenAI\ChatGPT;
77
use Classifai\Providers\Localhost\Ollama;
88
use Classifai\Services\LanguageProcessing;
9+
use Classifai\Features\QuickDraftIntegration;
910
use WP_REST_Server;
1011
use WP_REST_Request;
1112
use WP_Error;
@@ -113,6 +114,9 @@ public function setup() {
113114
*/
114115
public function feature_setup() {
115116
add_action( 'enqueue_block_assets', [ $this, 'enqueue_editor_assets' ] );
117+
118+
$quick_draft = new QuickDraftIntegration();
119+
$quick_draft->init();
116120
}
117121

118122
/**
@@ -260,17 +264,18 @@ public function get_enable_description(): string {
260264
*/
261265
public function get_feature_default_settings(): array {
262266
return [
263-
'prompt' => [
267+
'prompt' => [
264268
[
265269
'title' => esc_html__( 'ClassifAI default', 'classifai' ),
266270
'prompt' => $this->prompt,
267271
'original' => 1,
268272
],
269273
],
270-
'post_types' => [
274+
'post_types' => [
271275
'post' => 'post',
272276
],
273-
'provider' => ChatGPT::ID,
277+
'provider' => ChatGPT::ID,
278+
'enable_quick_draft' => false,
274279
];
275280
}
276281

@@ -315,6 +320,9 @@ public function sanitize_default_feature_settings( array $new_settings ): array
315320
}
316321
}
317322

323+
// Sanitize Quick Draft setting.
324+
$new_settings['enable_quick_draft'] = isset( $new_settings['enable_quick_draft'] ) ? (bool) $new_settings['enable_quick_draft'] : false;
325+
318326
return $new_settings;
319327
}
320328
}

includes/Classifai/Features/Feature.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use WP_REST_Request;
66
use WP_Error;
7+
use Classifai\Providers\CredentialObfuscator;
78

89
use function Classifai\find_provider_class;
910
use function Classifai\should_use_legacy_settings_panel;
@@ -301,6 +302,13 @@ public function sanitize_settings( array $settings ): array {
301302
// Sanitize the feature specific settings.
302303
$new_settings = $this->sanitize_default_feature_settings( $new_settings );
303304

305+
// Preserve obfuscated credentials for all Providers.
306+
// This ensures switching Providers doesn't save obfuscated values for inactive Providers.
307+
$new_settings = CredentialObfuscator::merge_all_provider_credentials(
308+
$new_settings,
309+
$current_settings
310+
);
311+
304312
// Sanitize the provider specific settings.
305313
$provider_instance = $this->get_feature_provider_instance( $new_settings['provider'] );
306314

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
<?php
2+
/**
3+
* Quick Draft Integration Feature.
4+
*
5+
* Integrates ClassifAI Content Generation with WordPress Quick Draft widget.
6+
*/
7+
8+
namespace Classifai\Features;
9+
10+
use Classifai\Features\ContentGeneration;
11+
use WP_REST_Server;
12+
use WP_REST_Request;
13+
use WP_Error;
14+
15+
use function Classifai\get_asset_info;
16+
17+
if ( ! defined( 'ABSPATH' ) ) {
18+
exit;
19+
}
20+
21+
/**
22+
* Quick Draft Integration Feature.
23+
*
24+
* Integrates ClassifAI Content Generation with WordPress Quick Draft widget.
25+
*/
26+
class QuickDraftIntegration {
27+
28+
/**
29+
* Content Generation Feature instance.
30+
*
31+
* @var ContentGeneration
32+
*/
33+
private $content_generation;
34+
35+
/**
36+
* Constructor.
37+
*/
38+
public function __construct() {
39+
$this->content_generation = new ContentGeneration();
40+
}
41+
42+
/**
43+
* Initialize the Quick Draft integration.
44+
*/
45+
public function init() {
46+
// Check if Quick Draft integration is enabled.
47+
if ( ! $this->is_quick_draft_enabled() ) {
48+
return;
49+
}
50+
51+
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
52+
add_action( 'rest_api_init', [ $this, 'register_endpoints' ] );
53+
}
54+
55+
/**
56+
* Check if Quick Draft integration is enabled.
57+
*
58+
* @return bool
59+
*/
60+
public function is_quick_draft_enabled(): bool {
61+
$settings = $this->content_generation->get_settings();
62+
return isset( $settings['enable_quick_draft'] ) ? (bool) $settings['enable_quick_draft'] : true;
63+
}
64+
65+
/**
66+
* Enqueue Quick Draft assets on the dashboard.
67+
*/
68+
public function enqueue_assets() {
69+
$screen = get_current_screen();
70+
71+
// Only load on dashboard.
72+
if ( ! $screen || 'dashboard' !== $screen->id ) {
73+
return;
74+
}
75+
76+
// Only load if user can create posts.
77+
if ( ! current_user_can( 'edit_posts' ) ) {
78+
return;
79+
}
80+
81+
wp_enqueue_script(
82+
'classifai-quick-draft-js',
83+
CLASSIFAI_PLUGIN_URL . 'dist/classifai-quick-draft.js',
84+
array_merge( get_asset_info( 'classifai-quick-draft', 'dependencies' ), array( 'jquery', 'media-editor', 'lodash' ) ),
85+
get_asset_info( 'classifai-quick-draft', 'version' ),
86+
true
87+
);
88+
89+
wp_localize_script(
90+
'classifai-quick-draft-js',
91+
'classifaiQuickDraft',
92+
[
93+
'createContent' => __( 'Create Draft from Prompt', 'classifai' ),
94+
'generating' => __( 'Generating...', 'classifai' ),
95+
'error' => __( 'Error generating content. Please try again.', 'classifai' ),
96+
]
97+
);
98+
99+
wp_enqueue_style(
100+
'classifai-quick-draft-css',
101+
CLASSIFAI_PLUGIN_URL . 'dist/classifai-quick-draft.css',
102+
[],
103+
get_asset_info( 'classifai-quick-draft', 'version' ),
104+
);
105+
}
106+
107+
/**
108+
* Register Quick Draft specific endpoints.
109+
*/
110+
public function register_endpoints() {
111+
register_rest_route(
112+
'classifai/v1',
113+
'quick-draft-generate',
114+
[
115+
'methods' => WP_REST_Server::CREATABLE,
116+
'callback' => [ $this, 'endpoint_callback' ],
117+
'permission_callback' => [ $this, 'permissions_check' ],
118+
'args' => [
119+
'title' => [
120+
'required' => true,
121+
'type' => 'string',
122+
'sanitize_callback' => 'sanitize_text_field',
123+
'validate_callback' => 'rest_validate_request_arg',
124+
'description' => esc_html__( 'The title of the post.', 'classifai' ),
125+
],
126+
'content' => [
127+
'required' => true,
128+
'type' => 'string',
129+
'sanitize_callback' => 'sanitize_textarea_field',
130+
'validate_callback' => 'rest_validate_request_arg',
131+
'description' => esc_html__( 'The prompt to use for content generation.', 'classifai' ),
132+
],
133+
],
134+
]
135+
);
136+
}
137+
138+
/**
139+
* Check permissions for Quick Draft generation.
140+
*
141+
* @return WP_Error|bool
142+
*/
143+
public function permissions_check() {
144+
// Ensure user can create posts.
145+
if ( ! current_user_can( 'edit_posts' ) ) {
146+
return false;
147+
}
148+
149+
$post_type_obj = get_post_type_object( 'post' );
150+
151+
// Ensure the post type is allowed in REST endpoints.
152+
if ( empty( $post_type_obj ) || empty( $post_type_obj->show_in_rest ) ) {
153+
return false;
154+
}
155+
156+
// Ensure the Feature is enabled.
157+
if ( ! $this->content_generation->is_feature_enabled() ) {
158+
return new WP_Error( 'not_enabled', esc_html__( 'Content Generation is not currently enabled.', 'classifai' ) );
159+
}
160+
161+
return true;
162+
}
163+
164+
/**
165+
* Handle Quick Draft content generation.
166+
*
167+
* @param WP_REST_Request $request The full request object.
168+
* @return \WP_REST_Response
169+
*/
170+
public function endpoint_callback( WP_REST_Request $request ) {
171+
$title = $request->get_param( 'title' );
172+
$content = $request->get_param( 'content' );
173+
174+
if ( empty( $title ) || empty( $content ) ) {
175+
return new WP_Error( 'missing_required_parameters', esc_html__( 'Title and content are required.', 'classifai' ) );
176+
}
177+
178+
// Create a new auto-draft post.
179+
$post_data = [
180+
'post_title' => $title,
181+
'post_content' => '',
182+
'post_status' => 'auto-draft',
183+
'post_type' => 'post',
184+
'post_author' => get_current_user_id(),
185+
];
186+
187+
$post_id = wp_insert_post( $post_data, true );
188+
189+
if ( is_wp_error( $post_id ) ) {
190+
return new WP_Error( 'post_creation_failed', esc_html__( 'Failed to create draft post.', 'classifai' ) );
191+
}
192+
193+
// Generate content using the existing content generation logic.
194+
$result = $this->content_generation->run(
195+
$post_id,
196+
'create_content',
197+
[
198+
'title' => $title,
199+
'summary' => $content,
200+
]
201+
);
202+
203+
if ( is_wp_error( $result ) ) {
204+
// Clean up the post if generation failed.
205+
wp_delete_post( $post_id, true );
206+
return $result;
207+
}
208+
209+
// Update the post with generated content.
210+
$updated_post = [
211+
'ID' => $post_id,
212+
'post_content' => $result,
213+
'post_status' => 'draft',
214+
];
215+
216+
$update_result = wp_update_post( $updated_post );
217+
218+
if ( is_wp_error( $update_result ) ) {
219+
return new WP_Error( 'post_update_failed', esc_html__( 'Failed to update post with generated content.', 'classifai' ) );
220+
}
221+
222+
return rest_ensure_response(
223+
[
224+
'post_id' => $post_id,
225+
'edit_url' => admin_url( "post.php?post={$post_id}&action=edit" ),
226+
'content' => $result,
227+
'title' => $title,
228+
'success' => true,
229+
]
230+
);
231+
}
232+
}

includes/Classifai/Providers/Azure/OpenAI.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ protected function authenticate_credentials( array $settings = [] ) {
316316
*/
317317
public function rest_endpoint_callback( $post_id = 0, string $route_to_call = '', array $args = [] ) {
318318
if ( ! $post_id || ! get_post( $post_id ) ) {
319-
return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required to generate titles.', 'classifai' ) );
319+
return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required.', 'classifai' ) );
320320
}
321321

322322
$route_to_call = strtolower( $route_to_call );

includes/Classifai/Providers/Browser/ChromeAI.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public function sanitize_settings( array $new_settings ): array {
8080
*/
8181
public function rest_endpoint_callback( $post_id = 0, string $route_to_call = '', array $args = [] ) {
8282
if ( ! $post_id || ! get_post( $post_id ) ) {
83-
return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required to generate titles.', 'classifai' ) );
83+
return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required.', 'classifai' ) );
8484
}
8585

8686
$route_to_call = strtolower( $route_to_call );

0 commit comments

Comments
 (0)