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( '

%3$s

', 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 ); } }