plugin_dir = plugin_dir_path( __FILE__ );
$this->plugin_url = plugin_dir_url( __FILE__ );
$this->init();
}
/**
* Initialize the plugin.
*/
private function init() {
// Check if Fusion Builder is active.
add_action( 'admin_init', array( $this, 'check_fusion_builder' ) );
// Enqueue scripts.
// Only enqueue for live builder (frontend).
add_action( 'fusion_builder_enqueue_live_scripts', array( $this, 'enqueue_live_builder_scripts' ) );
// Add settings page.
add_action( 'admin_menu', array( $this, 'add_settings_page' ), 50 );
add_action( 'admin_init', array( $this, 'register_settings' ) );
// AJAX handlers.
add_action( 'wp_ajax_hzail_generate_name', array( $this, 'ajax_generate_name' ) );
}
/**
* Check if Fusion Builder is active.
*/
public function check_fusion_builder() {
if ( ! class_exists( 'FusionBuilder' ) ) {
add_action( 'admin_notices', array( $this, 'fusion_builder_missing_notice' ) );
}
}
/**
* Display notice if Fusion Builder is not active.
*/
public function fusion_builder_missing_notice() {
?>
plugin_url . 'assets/js/ai-labels-for-fusion-builder.js',
array( 'jquery' ),
self::VERSION,
true
);
// Enqueue styles.
wp_enqueue_style(
'ai-labels-for-fusion-builder',
$this->plugin_url . 'assets/css/ai-labels-for-fusion-builder.css',
array(),
self::VERSION
);
// Check if API key is configured (validation is done when saving settings).
$api_key = get_option( 'hzail_openai_key', '' );
$has_valid_api_key = ! empty( $api_key );
// Localize script with AJAX URL, nonce, and settings.
wp_localize_script(
'ai-labels-for-fusion-builder',
'hzailFusionContainerAutoRename',
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'hzail_nonce' ),
'hasValidApiKey' => $has_valid_api_key,
)
);
}
/**
* Add settings page.
*/
public function add_settings_page() {
$parent_slug = 'avada';
global $admin_page_hooks;
if ( empty( $admin_page_hooks ) || ! isset( $admin_page_hooks[ $parent_slug ] ) ) {
$parent_slug = 'options-general.php';
}
$page_hook = add_submenu_page(
$parent_slug,
__( 'AI Labels for Fusion Builder Settings', 'ai-labels-for-fusion-builder' ),
__( 'AI Labels', 'ai-labels-for-fusion-builder' ),
'manage_options',
'ai-labels-for-fusion-builder',
array( $this, 'render_settings_page' )
);
// Enqueue admin scripts only on this settings page.
if ( $page_hook ) {
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
}
if ( 'avada' === $parent_slug && $page_hook ) {
$this->move_submenu_below_maintenance( $parent_slug, 'ai-labels-for-fusion-builder', 'Maintenance' );
}
}
/**
* Enqueue admin scripts for settings page.
*
* @param string $hook Current admin page hook.
*/
public function enqueue_admin_scripts( $hook ) {
// Only enqueue on our settings page.
if ( 'avada_page_ai-labels-for-fusion-builder' !== $hook && 'settings_page_ai-labels-for-fusion-builder' !== $hook ) {
return;
}
// Enqueue jQuery (should already be loaded, but ensure it's a dependency).
wp_enqueue_script( 'jquery' );
// Enqueue admin script.
wp_enqueue_script(
'hzail-admin-settings',
$this->plugin_url . 'assets/js/ai-labels-for-fusion-builder-admin.js',
array( 'jquery' ),
self::VERSION,
true
);
// Localize script with data.
wp_localize_script(
'hzail-admin-settings',
'hzailAdminSettings',
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'action' => 'hzail_test_api_key',
'nonce' => wp_create_nonce( 'hzail_nonce' ),
'strings' => array(
'pleaseEnterKey' => esc_html__( 'Please enter an API key first.', 'ai-labels-for-fusion-builder' ),
'testing' => esc_html__( 'Testing...', 'ai-labels-for-fusion-builder' ),
'keyValid' => esc_html__( 'API key is valid!', 'ai-labels-for-fusion-builder' ),
'keyInvalid' => esc_html__( 'API key is invalid.', 'ai-labels-for-fusion-builder' ),
'errorTesting' => esc_html__( 'Error testing API key. Please try again.', 'ai-labels-for-fusion-builder' ),
'testKey' => esc_html__( 'Test API Key', 'ai-labels-for-fusion-builder' ),
),
)
);
}
/**
* Place submenu link directly after the Avada Maintenance item.
*
* @param string $parent_slug Parent menu slug.
* @param string $target_slug Target submenu slug.
* @param string $after_label Menu label to insert after.
* @return void
*/
private function move_submenu_below_maintenance( $parent_slug, $target_slug, $after_label ) {
global $submenu;
if ( empty( $submenu[ $parent_slug ] ) || ! is_array( $submenu[ $parent_slug ] ) ) {
return;
}
$items = $submenu[ $parent_slug ];
$target_item = null;
foreach ( $items as $index => $item ) {
if ( isset( $item[2] ) && $item[2] === $target_slug ) {
$target_item = $item;
unset( $items[ $index ] );
break;
}
}
if ( null === $target_item ) {
return;
}
$items = array_values( $items );
$insert_at = count( $items );
foreach ( $items as $index => $item ) {
$label = isset( $item[0] ) ? wp_strip_all_tags( $item[0] ) : '';
if ( $label === $after_label ) {
$insert_at = $index + 1;
break;
}
}
array_splice( $items, $insert_at, 0, array( $target_item ) );
$submenu[ $parent_slug ] = $items;
}
/**
* Register settings.
*/
public function register_settings() {
register_setting(
'hzail_settings',
'hzail_openai_key',
array(
'type' => 'string',
'sanitize_callback' => array( $this, 'sanitize_and_validate_api_key' ),
'default' => '',
)
);
// Register AJAX handler for testing API key.
add_action( 'wp_ajax_hzail_test_api_key', array( $this, 'ajax_test_api_key' ) );
}
/**
* Sanitize and validate OpenAI API key.
*
* @param string $api_key API key to validate.
* @return string Sanitized API key, or empty string if invalid.
*/
public function sanitize_and_validate_api_key( $api_key ) {
$api_key = sanitize_text_field( $api_key );
// If empty, allow it (user might be clearing the key).
if ( empty( $api_key ) ) {
return '';
}
// Basic format check: OpenAI API keys start with "sk-" or "sk-proj-"
// Keys can vary in length, so we only check the prefix.
if ( ! preg_match( '/^sk-(proj-)?[a-zA-Z0-9_-]+$/', $api_key ) ) {
add_settings_error(
'hzail_openai_key',
'invalid_format',
__( 'Invalid API key format. OpenAI API keys should start with "sk-" or "sk-proj-"', 'ai-labels-for-fusion-builder' )
);
// Return the old value if validation fails.
return get_option( 'hzail_openai_key', '' );
}
// Validate the key by making a test API call.
$is_valid = $this->validate_openai_key( $api_key );
if ( ! $is_valid ) {
add_settings_error(
'hzail_openai_key',
'invalid_key',
__( 'The API key is invalid or not working. Please check your OpenAI API key and try again.', 'ai-labels-for-fusion-builder' )
);
// Return the old value if validation fails.
return get_option( 'hzail_openai_key', '' );
}
return $api_key;
}
/**
* Validate OpenAI API key by making a test API call.
*
* @param string $api_key API key to validate.
* @return bool True if valid, false otherwise.
*/
private function validate_openai_key( $api_key ) {
if ( empty( $api_key ) ) {
return false;
}
// Make a minimal API call to test the key.
$response = wp_remote_post(
'https://api.openai.com/v1/chat/completions',
array(
'headers' => array(
'Authorization' => 'Bearer ' . $api_key,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode(
array(
'model' => 'gpt-5.1',
'messages' => array(
array(
'role' => 'user',
'content' => 'test',
),
),
//'max_tokens' => 5,
)
),
'timeout' => 60, // Shorter timeout for validation.
)
);
// Check for WP_Error.
if ( is_wp_error( $response ) ) {
return false;
}
$status_code = wp_remote_retrieve_response_code( $response );
$body = json_decode( wp_remote_retrieve_body( $response ), true );
// If we get a 401 (Unauthorized), the key is invalid.
if ( 401 === $status_code ) {
return false;
}
// If we get an error in the response body, the key might be invalid.
if ( isset( $body['error'] ) ) {
// Some errors might still mean the key is valid (e.g., rate limit), but authentication errors mean it's invalid.
if ( isset( $body['error']['code'] ) && in_array( $body['error']['code'], array( 'invalid_api_key', 'authentication_error' ), true ) ) {
return false;
}
}
// If we get a 200 or other success status, the key is valid.
if ( 200 === $status_code || ( $status_code >= 200 && $status_code < 300 ) ) {
return true;
}
// For other status codes, assume invalid for safety.
return false;
}
/**
* AJAX handler to test API key.
*/
public function ajax_test_api_key() {
// Verify nonce.
check_ajax_referer( 'hzail_nonce', 'nonce' );
// Check permissions.
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'ai-labels-for-fusion-builder' ) ) );
}
// Get API key from POST data.
$api_key = isset( $_POST['api_key'] ) ? sanitize_text_field( wp_unslash( $_POST['api_key'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
if ( empty( $api_key ) ) {
wp_send_json_error( array( 'message' => __( 'API key is required.', 'ai-labels-for-fusion-builder' ) ) );
}
// Validate the key.
$is_valid = $this->validate_openai_key( $api_key );
if ( $is_valid ) {
wp_send_json_success( array( 'message' => __( 'API key is valid!', 'ai-labels-for-fusion-builder' ) ) );
} else {
wp_send_json_error( array( 'message' => __( 'API key is invalid or not working. Please check your OpenAI API key.', 'ai-labels-for-fusion-builder' ) ) );
}
}
/**
* Render settings page.
*/
public function render_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
?>
__( 'Permission denied.', 'ai-labels-for-fusion-builder' ) ) );
}
// Get API key and model.
$api_key = get_option( 'hzail_openai_key', '' );
if ( empty( $api_key ) ) {
wp_send_json_error( array( 'message' => __( 'OpenAI API key is not configured. Please set it in Avada > AI Labels.', 'ai-labels-for-fusion-builder' ) ) );
}
// Always use GPT-5.1.
$model = 'gpt-5.1';
// Get container HTML or elements data.
$container_html = isset( $_POST['html'] ) ? wp_kses_post( stripslashes( $_POST['html'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
$elements_data = isset( $_POST['elements'] ) ? json_decode( stripslashes( $_POST['elements'] ), true ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
// Build prompt for OpenAI - prefer HTML if available.
if ( ! empty( $container_html ) ) {
$prompt = $this->build_openai_prompt_from_html( $container_html );
} elseif ( ! empty( $elements_data ) && is_array( $elements_data ) ) {
$prompt = $this->build_openai_prompt( $elements_data );
} else {
wp_send_json_error( array( 'message' => __( 'No container content provided.', 'ai-labels-for-fusion-builder' ) ) );
}
// Call OpenAI API.
$response = wp_remote_post(
'https://api.openai.com/v1/chat/completions',
array(
'headers' => array(
'Authorization' => 'Bearer ' . $api_key,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode(
array(
'model' => $model,
'messages' => array(
array(
'role' => 'system',
'content' => 'You are a helpful assistant that generates concise, descriptive names for website page sections that are useful for web designers and developers. Generate names that identify the main purpose or content type (e.g., "Hero", "Logos", "Testimonials", "Contact", "Features", "About"). Return only the name, nothing else. Keep names short (1-2 words maximum). Do not include the word "Section" in the name.',
),
array(
'role' => 'user',
'content' => $prompt,
),
),
//'temperature' => 0.7,
//'max_tokens' => 20,
)
),
'timeout' => 60,
)
);
// Handle response.
if ( is_wp_error( $response ) ) {
wp_send_json_error( array( 'message' => $response->get_error_message() ) );
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( isset( $body['error'] ) ) {
wp_send_json_error(
array(
'message' => isset( $body['error']['message'] ) ? $body['error']['message'] : __( 'OpenAI API error.', 'ai-labels-for-fusion-builder' ),
)
);
}
if ( ! isset( $body['choices'][0]['message']['content'] ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid response from OpenAI.', 'ai-labels-for-fusion-builder' ) ) );
}
$generated_name = trim( $body['choices'][0]['message']['content'] );
// Clean up the name (remove quotes if present).
$generated_name = trim( $generated_name, ' "\'' );
wp_send_json_success( array( 'name' => $generated_name ) );
}
/**
* Build prompt for OpenAI based on container shortcode/HTML.
*
* @param string $html Container shortcode or HTML content.
* @return string Prompt text.
*/
private function build_openai_prompt_from_html( $html ) {
$prompt = 'A website section contains the following shortcode/HTML content:';
$prompt .= "\n\n" . $html;
$prompt .= "\n\nAnalyze the content and generate a descriptive name for this section that identifies its main purpose or content type (e.g., 'Hero', 'Logos', 'Testimonials', 'Contact', 'Features'). The name should be helpful for web designers and developers to quickly understand what this section contains. Return only the name, nothing else. Do not include the word 'Section' in the name.";
return $prompt;
}
/**
* Build prompt for OpenAI based on container elements.
*
* @param array $elements_data Elements data.
* @return string Prompt text.
*/
private function build_openai_prompt( $elements_data ) {
$element_descriptions = array();
foreach ( $elements_data as $element ) {
$type = isset( $element['type'] ) ? $element['type'] : '';
$name = isset( $element['name'] ) ? $element['name'] : '';
$title = isset( $element['title'] ) ? $element['title'] : '';
$heading = isset( $element['heading'] ) ? $element['heading'] : '';
$description = $name;
if ( $title ) {
$description .= ' titled "' . $title . '"';
} elseif ( $heading ) {
$description .= ' with heading "' . $heading . '"';
}
$element_descriptions[] = $description;
}
$elements_text = implode( ', ', $element_descriptions );
$count = count( $element_descriptions );
$prompt = sprintf(
/* translators: %1$d: number of elements, %2$s: elements description */
'A website section contains %1$d element(s): %2$s. Analyze the elements and generate a descriptive name for this section that identifies its main purpose or content type (e.g., \'Hero\', \'Logos\', \'Testimonials\', \'Contact\', \'Features\'). The name should be helpful for web designers and developers to quickly understand what this section contains. Return only the name, nothing else. Do not include the word \'Section\' in the name.',
$count,
$elements_text
);
return $prompt;
}
}
// Initialize the plugin.
new HZAIL_Fusion_Container_Auto_Rename();