file = $file;
$this->version = $version;
$this->define_constants();
$this->includes();
$this->init_hooks();
}
/**
* Define the plugin constants.
*
* @since 1.0.0
* @return void
*/
private function define_constants() {
define( 'AICW_VERSION', $this->version );
define( 'AICW_FILE', $this->file );
define( 'AICW_PATH', plugin_dir_path( $this->file ) );
define( 'AICW_URL', plugin_dir_url( $this->file ) );
define( 'AICW_ASSETS_URL', AICW_URL . 'assets/' );
}
/**
* Include the required files.
*
* @since 1.0.0
* @return void
*/
private function includes() {
require_once __DIR__ . '/functions.php';
require_once __DIR__ . '/deprecated.php';
}
/**
* Initialize the plugin hooks.
*
* @since 1.0.0
* @return void
*/
private function init_hooks() {
register_activation_hook( AICW_FILE, array( Installer::class, 'install' ) );
register_deactivation_hook( AICW_FILE, array( $this, 'deactivate' ) );
add_filter( 'plugin_action_links_' . plugin_basename( AICW_FILE ), array( $this, 'action_links' ) );
add_action( 'admin_notices', array( $this, 'display_flash_notices' ), 12 );
add_action( 'init', array( $this, 'init' ), 0 );
}
/**
* Get the plugin version.
*
* @since 1.0.0
* @return string The plugin version.
*/
public function get_version() {
return $this->version;
}
/**
* Deactivate the plugin.
*
* @since 1.0.0
* @return void
*/
public function deactivate() {
flush_rewrite_rules();
}
/**
* Add action links to the plugin.
*
* @param array $links The plugin action links.
*
* @since 1.0.0
* @return array The modified plugin action links.
*/
public function action_links( $links ) {
$action_links = array(
'settings' => sprintf(
/* translators: %1$s: Settings URL, %2$s: Settings text */
'%2$s',
esc_url( admin_url( 'admin.php?page=aicw-settings' ) ),
esc_html__( 'Settings', 'ai-content-writer' )
),
);
$action_links = array_merge( $action_links, $links );
if ( ! defined( 'AICW_PRO_VERSION' ) ) {
$action_links['go_pro'] = '' . esc_html__( 'Go Pro', 'ai-content-writer' ) . '';
}
return $action_links;
}
/**
* Add a flash notice.
*
* @param string $notice Notice message.
* @param string $type This can be "info", "warning", "error" or "success", "success" as default.
* @param boolean $dismissible Whether the notice is-dismissible or not.
*
* @since 1.0.0
* @return void
*/
public function flash_notice( $notice = '', $type = 'success', $dismissible = true ) {
$notices = get_option( 'aicw_flash_notices', array() );
$dismissible_text = ( $dismissible ) ? 'is-dismissible' : '';
// Add new notice.
$notices[] = array(
'notice' => $notice,
'type' => $type,
'dismissible' => $dismissible_text,
);
// Update the notices array.
update_option( 'aicw_flash_notices', $notices );
}
/**
* Display flash notices after that, remove the option to prevent notices being displayed forever.
*
* @since 1.0.0
* @return void
*/
public function display_flash_notices() {
$notices = get_option( 'aicw_flash_notices', array() );
foreach ( $notices as $notice ) {
printf(
'
',
esc_attr( $notice['type'] ),
esc_attr( $notice['dismissible'] ),
esc_html( $notice['notice'] ),
);
}
// Reset options to prevent notices being displayed forever.
if ( ! empty( $notices ) ) {
delete_option( 'aicw_flash_notices', array() );
}
}
/**
* Initialize the plugin.
*
* @since 1.0.0
* @return void
*/
public function init() {
// Load common classes.
new Installer();
new PostTypes();
new Cron();
new RestAPI();
new Frontend();
// Load admin classes.
if ( is_admin() ) {
new Admin\Admin();
new Admin\Dashboard();
new Admin\Actions();
new Admin\Settings();
new Admin\Notices();
new Admin\BlockEditor();
new Admin\ClassicEditor();
}
}
/**
* Generate content with the help of Gemini API.
*
* Kept for backwards compatibility; use aicw_get_ai_provider( 'gemini' )->generate() instead.
*
* @param array $prompt_data The prompt data to generate content (expects a `text` key).
*
* @since 1.0.0
* @return string|\WP_Error The generated content.
*/
public function generate_content( $prompt_data ) {
$prompt = isset( $prompt_data['text'] ) ? (string) $prompt_data['text'] : '';
if ( empty( $prompt ) ) {
return new \WP_Error( 'aicw_prompt_not_set', esc_html__( 'Please provide a prompt to generate the content.', 'ai-content-writer' ) );
}
$provider = aicw_get_ai_provider( 'gemini' );
return $provider->generate( $prompt );
}
/**
* Generate content with the help of ChatGPT Open AI API.
*
* Kept for backwards compatibility; use aicw_get_ai_provider( 'chatgpt' )->generate() instead.
*
* @param array $prompt_data The prompt data to generate openAI content.
*
* @since 1.0.0
* @return string|\WP_Error The generated content.
*/
public function generate_openai_content( $prompt_data ) {
$api_secret_key = isset( $prompt_data['secret_key'] ) ? $prompt_data['secret_key'] : '';
// Bail if the API Secret key is not set.
if ( empty( $api_secret_key ) ) {
return new \WP_Error( 'aicw_api_secret_key_not_set', esc_html__( 'Please configure the API settings. A valid ChatGPT OpenAI API key is required to generate the content.', 'ai-content-writer' ) );
}
$prompt = isset( $prompt_data['prompt'] ) ? $prompt_data['prompt'] : '';
if ( empty( $prompt ) ) {
return new \WP_Error( 'aicw_prompt_not_set', esc_html__( 'Please provide a prompt to generate the content.', 'ai-content-writer' ) );
}
$provider = new Services\OpenAI( $api_secret_key );
return $provider->generate(
$prompt,
array(
'system' => isset( $prompt_data['system_tone'] ) ? $prompt_data['system_tone'] : '',
'max_tokens' => isset( $prompt_data['max_tokens'] ) ? absint( $prompt_data['max_tokens'] ) : 8000,
'temperature' => isset( $prompt_data['temperature'] ) ? floatval( $prompt_data['temperature'] ) : 0.7,
)
);
}
/**
* Fetch stock images from the configured image source.
*
* All sources return the same (Pexels-shaped) photo structure:
* `src.original`, `alt`, `photographer`, `url` — so downstream consumers
* (thumbnail cron, featured-image picker) work with any source.
*
* @param string $query The query to search images.
* @param int $per_page The number of images to fetch.
*
* @since 1.0.0
* @return array|\WP_Error The images array.
*/
public function generate_images( $query, $per_page = 20 ) {
$source = get_option( 'aicw_image_source', 'pexels' );
return $this->fetch_images( $source, $query, $per_page );
}
/**
* Fetch stock images from a specific source.
*
* @param string $source The image source: pexels, unsplash, or pixabay.
* @param string $query The query to search images.
* @param int $per_page The number of images to fetch.
*
* @since 2.3.0
* @return array|\WP_Error Normalized images array.
*/
public function fetch_images( $source, $query, $per_page = 20 ) {
/**
* Short-circuit image fetching for add-on sources.
*
* Return a non-null value (normalized photos array or WP_Error) to
* serve the request. Sources registered via `aicw_image_sources`
* implement their fetching here.
*
* @param array|\WP_Error|null $images The images, or null to continue.
* @param string $source The image source slug.
* @param string $query The search query.
* @param int $per_page The number of images requested.
*
* @since 2.4.0
*/
$images = apply_filters( 'aicw_pre_fetch_images', null, $source, $query, $per_page );
if ( null !== $images ) {
return $images;
}
switch ( $source ) {
case 'unsplash':
return $this->fetch_unsplash_images( $query, $per_page );
case 'pixabay':
return $this->fetch_pixabay_images( $query, $per_page );
case 'pexels':
default:
return $this->fetch_pexels_images( $query, $per_page );
}
}
/**
* Fetch images from Pexels.
*
* @param string $query The search query.
* @param int $per_page The number of images to fetch.
*
* @since 2.3.0
* @return array|\WP_Error
*/
protected function fetch_pexels_images( $query, $per_page ) {
$api_key = get_option( 'aicw_pexels_api_key' );
if ( empty( $api_key ) ) {
return new \WP_Error( 'aicw_api_key_not_set', esc_html__( 'Please configure the API settings. A valid Pexels API key is required to generate the images.', 'ai-content-writer' ) );
}
$url = 'https://api.pexels.com/v1/search?query=' . rawurlencode( $query ) . '&per_page=' . absint( $per_page );
$data = $this->fetch_image_api( $url, array( 'Authorization' => $api_key ) );
if ( is_wp_error( $data ) ) {
return $data;
}
if ( empty( $data['photos'] ) ) {
return new \WP_Error( 'unexpected_api_response', esc_html__( 'An unexpected error occurred while generating the images.', 'ai-content-writer' ) );
}
// Pexels is already the canonical shape.
return $data['photos'];
}
/**
* Fetch images from Unsplash, normalized to the Pexels shape.
*
* @param string $query The search query.
* @param int $per_page The number of images to fetch.
*
* @since 2.3.0
* @return array|\WP_Error
*/
protected function fetch_unsplash_images( $query, $per_page ) {
$api_key = get_option( 'aicw_unsplash_api_key' );
if ( empty( $api_key ) ) {
return new \WP_Error( 'aicw_api_key_not_set', esc_html__( 'Please configure the API settings. A valid Unsplash access key is required to generate the images.', 'ai-content-writer' ) );
}
$url = 'https://api.unsplash.com/search/photos?query=' . rawurlencode( $query ) . '&per_page=' . absint( $per_page );
$data = $this->fetch_image_api( $url, array( 'Authorization' => 'Client-ID ' . $api_key ) );
if ( is_wp_error( $data ) ) {
return $data;
}
if ( empty( $data['results'] ) ) {
return new \WP_Error( 'unexpected_api_response', esc_html__( 'An unexpected error occurred while generating the images.', 'ai-content-writer' ) );
}
$photos = array();
foreach ( $data['results'] as $photo ) {
if ( empty( $photo['urls']['regular'] ) ) {
continue;
}
$photos[] = array(
'src' => array(
'original' => $photo['urls']['regular'],
'large' => $photo['urls']['regular'],
'medium' => isset( $photo['urls']['small'] ) ? $photo['urls']['small'] : $photo['urls']['regular'],
),
'alt' => isset( $photo['alt_description'] ) ? (string) $photo['alt_description'] : '',
'photographer' => isset( $photo['user']['name'] ) ? (string) $photo['user']['name'] : '',
'url' => isset( $photo['links']['html'] ) ? (string) $photo['links']['html'] : '',
);
}
return $photos;
}
/**
* Fetch images from Pixabay, normalized to the Pexels shape.
*
* @param string $query The search query.
* @param int $per_page The number of images to fetch.
*
* @since 2.3.0
* @return array|\WP_Error
*/
protected function fetch_pixabay_images( $query, $per_page ) {
$api_key = get_option( 'aicw_pixabay_api_key' );
if ( empty( $api_key ) ) {
return new \WP_Error( 'aicw_api_key_not_set', esc_html__( 'Please configure the API settings. A valid Pixabay API key is required to generate the images.', 'ai-content-writer' ) );
}
// Pixabay requires per_page between 3 and 200.
$per_page = max( 3, absint( $per_page ) );
$url = 'https://pixabay.com/api/?key=' . rawurlencode( $api_key ) . '&q=' . rawurlencode( $query ) . '&image_type=photo&per_page=' . $per_page;
$data = $this->fetch_image_api( $url, array() );
if ( is_wp_error( $data ) ) {
return $data;
}
if ( empty( $data['hits'] ) ) {
return new \WP_Error( 'unexpected_api_response', esc_html__( 'An unexpected error occurred while generating the images.', 'ai-content-writer' ) );
}
$photos = array();
foreach ( $data['hits'] as $photo ) {
if ( empty( $photo['largeImageURL'] ) ) {
continue;
}
$photos[] = array(
'src' => array(
'original' => $photo['largeImageURL'],
'large' => $photo['largeImageURL'],
'medium' => isset( $photo['webformatURL'] ) ? $photo['webformatURL'] : $photo['largeImageURL'],
),
'alt' => isset( $photo['tags'] ) ? (string) $photo['tags'] : '',
'photographer' => isset( $photo['user'] ) ? (string) $photo['user'] : '',
'url' => isset( $photo['pageURL'] ) ? (string) $photo['pageURL'] : '',
);
}
return $photos;
}
/**
* Perform an image API GET request and decode the JSON response.
*
* @param string $url The request URL.
* @param array $headers Request headers.
*
* @since 2.3.0
* @return array|\WP_Error Decoded response.
*/
protected function fetch_image_api( $url, $headers ) {
$response = wp_remote_get(
$url,
array(
'timeout' => 30,
'headers' => $headers,
)
);
if ( is_wp_error( $response ) ) {
return new \WP_Error( 'api_error', $response->get_error_message() );
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! is_array( $data ) ) {
return new \WP_Error( 'unexpected_api_response', esc_html__( 'An unexpected error occurred while generating the images.', 'ai-content-writer' ) );
}
return $data;
}
/**
* Create a log entry
*
* @param string $message The log message.
* @param string $type The log type (e.g., 'success', 'info', 'error', 'warning').
* @param int $campaign_id Optional. The campaign ID associated with the log. Default is empty.
*
* @since 2.0.6
* @return int|\WP_Error The ID of the created log post or WP_Error on failure.
*/
public function create_log( $message, $type = 'info', $campaign_id = '' ) {
if ( empty( $message ) ) {
return new \WP_Error( 'aicw_log_message_empty', esc_html__( 'Log message cannot be empty.', 'ai-content-writer' ) );
}
$log_data = array(
'post_title' => sanitize_title( $type ),
'post_content' => wp_kses_post( $message ),
'post_status' => 'publish',
'post_type' => 'aicw_log',
);
// If campaign ID is provided, add it as post meta.
if ( ! empty( $campaign_id ) ) {
$log_data['meta_input']['_aicw_related_campaign'] = absint( $campaign_id );
}
return wp_insert_post( $log_data );
}
}