= $get_limt) {
$expiration_date = get_transient('wpwand_pgc_limit_expiration');
if (!$expiration_date || current_time('timestamp') > $expiration_date) {
$expiration_date = current_time('timestamp') + 30 * DAY_IN_SECONDS;
set_transient('wpwand_pgc_limit_expiration', $expiration_date, 30 * DAY_IN_SECONDS);
update_option('wpwand_pgc_total_bulk_generated', 0);
return true;
}
return false;
}
return true;
}
}
if (!function_exists('wpwand_templates')) {
/**
* Legacy template/prompt catalog: free + pro + custom, merged. Side-effect-free
* (the legacy transient-sync calls were dropped — the 2.0.0 data layer owns seeding).
*/
function wpwand_templates()
{
$all_prompts = get_option('wpwand_data');
$custom_data = get_option('wpwand_custom_data', []);
// One list now. Templates::flatten() still reads the old {free, pro} shape, which is what a
// Pro licence sync writes into this option.
return array_merge((array) $custom_data, \WPWand\Data\Templates::flatten($all_prompts));
}
}
if (!function_exists('wpwand_ai_error')) {
/**
* Legacy provider-error renderer. Verbatim port (self-contained, no removed deps).
*
* @param object $error stdClass with ->type and ->message (message may be JSON).
*/
function wpwand_ai_error($error)
{
$source = str_replace('_error', '', isset($error->type) ? $error->type : '');
$provider = ucfirst($source);
// Anchored and case-insensitive: WP_Http writes "cURL error 28: …", so the old
// strpos($msg, 'curl') never matched, and an unanchored match would also have claimed any
// provider sentence that merely mentions curl. Same gate as ErrorFormatter::transport_message().
if (isset($error->message) && preg_match('/^cURL error \d+/i', (string) $error->message)) {
return "
{$provider} Error
" . esc_html__('Server is not responding. Please try again later.', 'ai-content-generation') . "
";
}
$error_details = isset($error->message) ? json_decode($error->message) : null;
if (json_last_error() === JSON_ERROR_NONE && is_object($error_details)) {
$message = "{$provider} Error
";
if (isset($error_details->type)) {
$message .= "Type: " . ucwords(str_replace('_', ' ', htmlspecialchars($error_details->type))) . "
";
}
if (isset($error_details->message)) {
$message_text = htmlspecialchars($error_details->message);
$message_text = preg_replace(
'/(https?:\/\/[^\s]+)/',
'$1',
$message_text
);
$message .= "" . $message_text . "
";
}
return $message;
}
return "{$provider} Error
" . htmlspecialchars(isset($error->message) ? $error->message : '') . "
";
}
}
if (!function_exists('wpwand_api_fields_validate')) {
/**
* Legacy AJAX field sanitizer. Verbatim port.
*
* phpcs:disable WordPress.Security.NonceVerification.Missing -- nonce IS checked below.
*/
function wpwand_api_fields_validate()
{
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'wpwand_global_nonce')) {
wp_send_json_error('Nonce verification failed.', 403);
}
return array(
'topic' => isset($_POST['topic']) ? sanitize_text_field(wp_unslash($_POST['topic'])) : '',
'keywords' => isset($_POST['keyword']) ? sanitize_text_field(wp_unslash($_POST['keyword'])) : '',
'no_of_results' => isset($_POST['result_number']) ? absint(sanitize_text_field(wp_unslash($_POST['result_number']))) : 1,
'tone' => isset($_POST['tone']) ? sanitize_text_field(wp_unslash($_POST['tone'])) : '',
'word_count' => isset($_POST['word_limit']) ? intval(sanitize_text_field(wp_unslash($_POST['word_limit']))) + 1000 : '',
'product_name' => isset($_POST['product_name']) ? sanitize_text_field(wp_unslash($_POST['product_name'])) : '',
'description' => isset($_POST['description']) ? sanitize_text_field(wp_unslash($_POST['description'])) : '',
'content' => isset($_POST['content']) ? wp_kses_post(sanitize_text_field(wp_unslash($_POST['content']))) : '',
'content_textarea' => isset($_POST['content_textarea']) ? wp_kses_post(sanitize_text_field(wp_unslash($_POST['content_textarea']))) : '',
'custom_textarea' => isset($_POST['custom_textarea']) ? wp_kses_post(sanitize_text_field(wp_unslash($_POST['custom_textarea']))) : '',
'product_1' => isset($_POST['product_1']) ? wp_kses_post(sanitize_text_field(wp_unslash($_POST['product_1']))) : '',
'product_2' => isset($_POST['product_2']) ? wp_kses_post(sanitize_text_field(wp_unslash($_POST['product_2']))) : '',
'description_1' => isset($_POST['description_1']) ? wp_kses_post(sanitize_text_field(wp_unslash($_POST['description_1']))) : '',
'description_2' => isset($_POST['description_2']) ? wp_kses_post(sanitize_text_field(wp_unslash($_POST['description_2']))) : '',
'subject' => isset($_POST['subject']) ? sanitize_text_field(wp_unslash($_POST['subject'])) : '',
'question' => isset($_POST['question']) ? sanitize_text_field(wp_unslash($_POST['question'])) : '',
'comment' => isset($_POST['comment']) ? sanitize_text_field(wp_unslash($_POST['comment'])) : '',
);
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
}
if (!function_exists('wpwand_generate_ai_content')) {
/**
* Legacy generation entrypoint. The free 2.0.0 code never calls this — only legacy Pro
* does — so it doubles as the Pro feature lock: while Pro is outdated it returns an
* "update required" error (in the new Generator's error shape) instead of generating.
* Once Pro is on 2.0.0 it calls the Generator directly and this shim is never hit.
*
* @return object stdClass with ->choices or ->error (legacy-identical shape).
*/
function wpwand_generate_ai_content($prompt, $number = 1, $args = [])
{
if (wpwand_legacy_pro_is_outdated()) {
return (object) [
'error' => (object) [
'message' => wp_json_encode([
'type' => 'update_required',
'message' => __('Please update WP Wand Pro to version 2.0.0 or higher to keep generating content. Your Pro features are paused until the update completes.', 'ai-content-generation'),
]),
'type' => 'wpwand_error',
'code' => 426,
],
];
}
if (class_exists('WPWand\\Generation\\Generator')) {
return \WPWand\Generation\Generator::generate((string) $prompt, (int) $number, (array) $args);
}
return (object) [
'error' => (object) [
'message' => wp_json_encode(['message' => __('The generation engine is unavailable.', 'ai-content-generation')]),
'type' => 'wpwand_error',
'code' => 500,
],
];
}
}
if (!function_exists('wpwand_loago_icon_url')) {
/**
* Legacy brand/logo icon URL. Faithful port (depends only on the shimmed option getter).
*/
function wpwand_loago_icon_url()
{
$logo = wpwand_get_option('wpwand_logo_icon');
$is_agency = wpwand_get_option('wpwand_pro_tala_agency');
if (!empty($logo) && $is_agency && function_exists('wpwand_pro_init')) {
return $logo;
}
return defined('WPWAND_PLUGIN_URL')
? WPWAND_PLUGIN_URL . 'assets/img/icon.svg'
: plugins_url('assets/img/icon.svg', dirname(__DIR__) . '/wp-wand.php');
}
}
if (!function_exists('wpwand_get_data')) {
/**
* Legacy template-data (re)seed. Only the legacy Pro license-deactivate handler calls
* this; it re-seeds the free catalog (the legacy wpwand_dummy_datas() source is gone, so
* this routes through the 2.0.0 data layer instead).
*/
function wpwand_get_data($sync = false)
{
if (!get_option('wpwand_data') || true === $sync) {
if (class_exists('WPWand\\Data\\Templates')) {
\WPWand\Data\Templates::seed(true);
return true;
}
return false;
}
return [];
}
}
if (!function_exists('wpwand_dall_e_request')) {
/**
* Legacy DALL·E image generation. Only legacy Pro calls this; image generation is a Pro
* feature, so while Pro is outdated it is locked: respond with an update notice (matching
* the legacy wp_send_json HTML-string contract) instead of calling the image API.
*/
function wpwand_dall_e_request($prompt, $args = [])
{
$message = ''
. esc_html__('Image generation is paused until WP Wand Pro is updated to version 2.0.0 or higher.', 'ai-content-generation')
. '
';
wp_send_json($message);
}
}
if (!function_exists('wpwand_pro_card')) {
/**
* Legacy Pro upsell card. Every legacy call site is commented out, so this is a
* defensive no-op kept only so an uncommented call can never fatal.
*/
function wpwand_pro_card()
{
return '';
}
}