<?php
/**
 * Plugin Name: Priizmo AI Summary & FAQ Schema
 * Description: Optimizes existing and new blog posts for AI search engines (ChatGPT, Perplexity, etc.) by generating AI-powered TL;DR summaries and structured FAQ schema.
 * Version:     1.4.4
 * Author:      Priizmo
 * Author URI:  https://priizmo.com
 * License:     GPLv3 or later
 * License URI: https://www.gnu.org/licenses/gpl-3.0.html
 * Text Domain: priizmo-ai-summary-faq-schema
 * Requires at least: 6.0
 * Requires PHP: 7.4
 */

// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Core plugin constants. Guarded individually with defined() (rather than
 * plain define()) so that if both the free and premium versions of this
 * plugin are active at once (e.g. mid-upgrade, before Freemius's automatic
 * deactivation of the redundant copy takes effect on the next request),
 * the second copy's file doesn't emit "Constant already defined" warnings.
 */
if ( ! defined( 'PRIIZMO_ENGINE_VERSION' ) ) {
	define( 'PRIIZMO_ENGINE_VERSION', '1.4.4' );
}
if ( ! defined( 'PRIIZMO_ENGINE_PLUGIN_FILE' ) ) {
	define( 'PRIIZMO_ENGINE_PLUGIN_FILE', __FILE__ );
}
if ( ! defined( 'PRIIZMO_ENGINE_PLUGIN_DIR' ) ) {
	define( 'PRIIZMO_ENGINE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
}
if ( ! defined( 'PRIIZMO_ENGINE_PLUGIN_URL' ) ) {
	define( 'PRIIZMO_ENGINE_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
}
if ( ! defined( 'PRIIZMO_ENGINE_OPTION_NAME' ) ) {
	define( 'PRIIZMO_ENGINE_OPTION_NAME', 'priizmo_engine_settings' );
}

if ( ! defined( 'PRIIZMO_ENGINE_IS_PREMIUM_CODE' ) ) {
	// Overwritten (set to `false`) by build.sh when producing the free
	// distribution. The premium codebase (this repo, and the premium zip)
	// always leaves it `true`. Despite the "WP_FS__" naming convention
	// used throughout the Freemius SDK itself for its own internal
	// constants, this specific constant isn't one of them -- it's our
	// own internal bookkeeping, invented purely to decide what boolean
	// to hand Freemius's `is_premium` config value at init time, so it
	// gets our own PRIIZMO_ENGINE_ prefix instead (per WP.org Plugin Review
	// guidance to prefix plugin-defined globals distinctly, rather than
	// with a generic/reserved-looking term like "wp").
	define( 'PRIIZMO_ENGINE_IS_PREMIUM_CODE', false );
}

/**
 * Whether the site owner has opted in to the Freemius SDK (licensing
 * validation for Pro, and update checks). This is a plugin-side gate,
 * checked BEFORE the Freemius SDK is ever loaded/instantiated below --
 * per WordPress.org Plugin Directory Guidelines 7 & 9, the free
 * distribution must not phone home (to Freemius or anyone else) without
 * clear, explicit opt-in that defaults to OFF, so relying solely on
 * Freemius's own internal connect/skip screen (which still requires
 * loading and instantiating the SDK first) isn't sufficient on its own.
 *
 * Reads the option directly with plain get_option() rather than via
 * Priizmo_Engine_Settings::get_defaults() -- that class isn't require'd
 * until further down, but this needs to be decided first, before the
 * Freemius SDK include below.
 *
 * Defaults to PRIIZMO_ENGINE_IS_PREMIUM_CODE: OFF for the free
 * distribution (opt-in required, satisfying the guidelines above), ON
 * for the premium distribution (sold directly via Freemius checkout,
 * not distributed through WordPress.org, so not subject to those
 * guidelines -- paying customers reasonably expect license validation
 * to work out of the box). Either default can be freely overridden by
 * the site owner from the Settings page at any time.
 *
 * @return bool
 */
function priizmo_engine_licensing_opt_in_enabled() {
	$settings = get_option( PRIIZMO_ENGINE_OPTION_NAME, array() );

	return isset( $settings['licensing_opt_in'] )
		? (bool) $settings['licensing_opt_in']
		: PRIIZMO_ENGINE_IS_PREMIUM_CODE;
}

/**
 * Freemius SDK integration + this plugin's own bootstrap, structured
 * exactly per Freemius's documented auto-deactivation pattern:
 *
 *   if ( function_exists( 'priizmo_engine_fs' ) ) {
 *       priizmo_engine_fs()->set_basename( true, __FILE__ );
 *   } else {
 *       if ( ! function_exists( 'priizmo_engine_fs' ) ) {
 *           // ... Freemius integration snippet ...
 *       }
 *       // ... this plugin's own bootstrap ...
 *   }
 *
 * If priizmo_engine_fs() is already defined -- meaning the OTHER version's
 * (free vs. premium) copy of this same file already ran earlier in this
 * same request -- we must not redeclare it or run our own bootstrap a
 * second time (PHP binds unconditional top-level function/class
 * declarations while *compiling* a file, before any runtime guard placed
 * before them gets a chance to run, so those would otherwise fatal with
 * "Cannot redeclare ..."). Instead we just tell the existing Freemius
 * instance which basename is now the premium one via
 * set_basename( true, __FILE__ ), which triggers Freemius's automatic
 * deactivation of the redundant copy on the next request. Everything
 * else -- our own class requires, helper functions, and the core plugin
 * bootstrap -- lives inside the `else`, so it only ever runs once.
 */
if ( function_exists( 'priizmo_engine_fs' ) ) {
	priizmo_engine_fs()->set_basename( true, __FILE__ );
} else {
	/**
	 * DO NOT REMOVE THIS IF, IT IS ESSENTIAL FOR THE
	 * `function_exists` CALL ABOVE TO PROPERLY WORK.
	 */
	if ( ! function_exists( 'priizmo_engine_fs' ) ) {
		/**
		 * Creates, memoizes, and returns the Freemius SDK instance for this
		 * plugin. Real Freemius product credentials (Freemius dashboard >
		 * SDK tab). The `secret_key` is intentionally NOT hardcoded here --
		 * it's picked up by the SDK from the WP_FS__aeo-engine_SECRET_KEY
		 * constant, defined in wp-config.php on development/staging installs
		 * only (see local wp-config.php and the Freemius "org compliant"
		 * secret-key handling docs). Note: the constant name is keyed off
		 * the `slug` value below ('aeo-engine'), NOT `premium_slug` or the
		 * `menu` slug -- see Freemius::_get_secret_key() /
		 * WP_FS__{$this->_slug}_SECRET_KEY in the SDK source.
		 *
		 * @return Freemius
		 */
		function priizmo_engine_fs() {
			global $priizmo_engine_fs;

			if ( ! isset( $priizmo_engine_fs ) ) {
				if ( ! priizmo_engine_licensing_opt_in_enabled() ) {
					// Licensing/update-check opt-in is off (the default for
					// the free distribution) -- do not require the SDK file
					// or call fs_dynamic_init() at all, so no remote call of
					// any kind is possible. priizmo_engine_is_pro() and every
					// other Freemius-dependent code path already treat a
					// non-object return value here as "Freemius
					// unavailable" and degrade gracefully (see the
					// catch block below for the same pattern).
					$priizmo_engine_fs = false;

					return $priizmo_engine_fs;
				}

				// Include Freemius SDK.
				require_once dirname( __FILE__ ) . '/vendor/freemius/start.php';

				try {
					$priizmo_engine_fs = fs_dynamic_init(
						array(
							'id'                  => '34333',
							// This is the permanent Freemius product slug (tied to
							// product ID 34333) and does NOT change when the
							// product's display name is renamed on the Freemius
							// dashboard -- do not confuse with `premium_slug` or
							// `menu.slug` below, both of which DID change.
							'slug'                => 'aeo-engine',
							'premium_slug'        => 'priizmo-ai-summary-faq-schema-premium',
							'type'                => 'plugin',
							'public_key'          => 'pk_dbbf62de46f59c0f48288d3b4a184',
							'is_premium'          => PRIIZMO_ENGINE_IS_PREMIUM_CODE, // Set at build time; see build.sh.
							'premium_suffix'      => 'Pro',
							'has_premium_version' => true,
							'has_addons'          => false,
							'has_paid_plans'      => true,
							'is_org_compliant'    => true,
							'menu'                => array(
								'slug'    => 'priizmo-ai-summary-faq-schema',
								'contact' => false,
								'support' => false,
								'parent'  => array(
									'slug' => 'options-general.php',
								),
							),
						)
					);
				} catch ( Exception $e ) {
					// Fail soft instead of a white-screen fatal (e.g. if
					// credentials/constants above are ever misconfigured) so the
					// rest of the plugin keeps working (priizmo_engine_is_pro()
					// simply reports "not Pro") while that's resolved.
					$priizmo_engine_fs = false;

					if ( is_admin() && current_user_can( 'manage_options' ) ) {
						add_action(
							'admin_notices',
							function () use ( $e ) {
								printf(
									'<div class="notice notice-error"><p>%s</p></div>',
									esc_html(
										sprintf(
											/* translators: %s: underlying exception message. */
											__( 'Priizmo AI Summary & FAQ Schema: Freemius SDK failed to initialize (%s). Licensing/upgrade features are disabled until this is resolved.', 'priizmo-ai-summary-faq-schema' ),
											$e->getMessage()
										)
									)
								);
							}
						);
					}
				}
			}

			return $priizmo_engine_fs;
		}

		// Init Freemius.
		priizmo_engine_fs();

		// Signal that SDK was initiated.
		do_action( 'priizmo_engine_fs_loaded' );
	}

	require_once PRIIZMO_ENGINE_PLUGIN_DIR . 'includes/class-priizmo-engine-settings.php';
	require_once PRIIZMO_ENGINE_PLUGIN_DIR . 'includes/class-priizmo-engine-post-meta.php';
	require_once PRIIZMO_ENGINE_PLUGIN_DIR . 'includes/class-priizmo-engine-ai-client.php';
	require_once PRIIZMO_ENGINE_PLUGIN_DIR . 'includes/class-priizmo-engine-rest.php';
	require_once PRIIZMO_ENGINE_PLUGIN_DIR . 'includes/class-priizmo-engine-editor.php';
	require_once PRIIZMO_ENGINE_PLUGIN_DIR . 'includes/class-priizmo-engine-display.php';

	/**
	 * One-time migration for sites upgrading from a pre-1.4.0 version
	 * (when this plugin's internal prefix was renamed from "aeo" to
	 * "priizmo" -- see the 1.4.0 changelog entry). That rename changed
	 * the *stored* option name (not just the PHP constant referring to
	 * it) from 'aeo_engine_settings' to 'priizmo_engine_settings', and the
	 * cached-summary/FAQ post meta keys from '_aeo_cached_summary' /
	 * '_aeo_cached_faq_data' to '_priizmo_cached_summary' /
	 * '_priizmo_cached_faq_data'. Without this, upgrading looks (from the
	 * plugin's perspective) exactly like a brand new install: existing
	 * API keys/prompts/settings and every already-generated summary/FAQ
	 * are still physically in the database, just under names the plugin
	 * no longer reads -- see also register_activation_hook() below, which
	 * does NOT re-run on a normal version upgrade (only on an actual
	 * activate/deactivate cycle), so seeding fresh defaults there isn't a
	 * substitute for this.
	 *
	 * Hooked on `plugins_loaded` (rather than called unconditionally here)
	 * so it runs on the very first request after upgrade regardless of
	 * whether that's an admin or a front-end visitor request -- the
	 * front-end summary/FAQ display depends on the post meta rename too.
	 * Guarded by its own marker option so the (cheap, but non-zero) check
	 * only ever actually does work once.
	 */
	function priizmo_engine_migrate_legacy_aeo_prefix() {
		if ( get_option( 'priizmo_engine_migrated_legacy_aeo_prefix', false ) ) {
			return;
		}

		// Settings: only copy across if the new option doesn't already
		// have real data (e.g. from a fresh install) and the old one
		// exists. wp_parse_args() backfills any settings key introduced
		// since the old data was last saved (e.g. 'licensing_opt_in'),
		// the same way Priizmo_Engine_Settings::maybe_set_defaults() does.
		$new_settings = get_option( 'priizmo_engine_settings', false );
		$old_settings = get_option( 'aeo_engine_settings', false );

		if ( false === $new_settings && is_array( $old_settings ) ) {
			update_option(
				'priizmo_engine_settings',
				wp_parse_args( $old_settings, Priizmo_Engine_Settings::get_defaults() )
			);
		}

		// Active bulk batch state: low-stakes (just display state for an
		// in-progress run), but cheap to carry over too.
		$new_batch = get_option( 'priizmo_engine_active_bulk_batch', false );
		$old_batch = get_option( 'aeo_engine_active_bulk_batch', false );

		if ( false === $new_batch && false !== $old_batch ) {
			update_option( 'priizmo_engine_active_bulk_batch', $old_batch );
		}

		// Post meta: rename in place, directly via $wpdb, rather than
		// looping through get_posts()/update_post_meta() -- sites with a
		// large already-processed archive could have thousands of rows,
		// and a single UPDATE is both faster and avoids the row-by-row
		// cache-invalidation overhead of the WP meta API for a one-time
		// bulk rename.
		global $wpdb;

		$wpdb->query(
			$wpdb->prepare(
				"UPDATE {$wpdb->postmeta} SET meta_key = %s WHERE meta_key = %s",
				'_priizmo_cached_summary',
				'_aeo_cached_summary'
			)
		);
		$wpdb->query(
			$wpdb->prepare(
				"UPDATE {$wpdb->postmeta} SET meta_key = %s WHERE meta_key = %s",
				'_priizmo_cached_faq_data',
				'_aeo_cached_faq_data'
			)
		);

		update_option( 'priizmo_engine_migrated_legacy_aeo_prefix', true );
	}
	add_action( 'plugins_loaded', 'priizmo_engine_migrate_legacy_aeo_prefix' );

	/**
	 * Returns whether Pro-only functionality should be active: the plugin is
	 * running its premium codebase AND the current install has a usable paid
	 * license (active paid license, valid trial, etc.). Bulk processing is
	 * the only feature gated by this; per WordPress.org Plugin Directory
	 * Guideline 5 (no locking/limiting built-in features behind a license
	 * check), JSON-LD FAQPage schema output is unconditionally available to
	 * everyone -- see Priizmo_Engine_Display::output_faq_schema().
	 *
	 * Uses Freemius's own can_use_premium_code__premium_only() rather than
	 * is_plan( 'pro' ): the latter requires an exact match against a specific
	 * plan slug, so it can incorrectly report "not Pro" for a valid paying
	 * customer whenever the Freemius dashboard's plan slug/name doesn't
	 * literally equal "pro" (e.g. multiple paid tiers, renamed plans, etc.).
	 * can_use_premium_code__premium_only() checks license validity generically
	 * instead, so any valid paid license (or trial) correctly unlocks Pro
	 * features regardless of which specific plan it is.
	 *
	 * @return bool
	 */
	function priizmo_engine_is_pro() {
		if ( ! PRIIZMO_ENGINE_IS_PREMIUM_CODE || ! function_exists( 'priizmo_engine_fs' ) ) {
			return false;
		}

		$fs = priizmo_engine_fs();

		if ( ! is_object( $fs ) ) {
			// Freemius failed to initialize (e.g. placeholder credentials still
			// in place); see the try/catch above.
			return false;
		}

		return $fs->can_use_premium_code__premium_only();
	}


	require_once PRIIZMO_ENGINE_PLUGIN_DIR . 'includes/class-priizmo-engine.php';

	/**
	 * Returns the single instance of the core plugin class, instantiating it if necessary.
	 *
	 * @return Priizmo_Engine
	 */
	function priizmo_engine() {
		return Priizmo_Engine::instance();
	}

	priizmo_engine();

	/**
	 * Runs on plugin activation. Seeds default settings so the settings page
	 * always has sane defaults, without ever overwriting existing values.
	 */
	function priizmo_engine_activate() {
		Priizmo_Engine_Settings::maybe_set_defaults();
	}
	register_activation_hook( __FILE__, 'priizmo_engine_activate' );

	/**
	 * Removes the plugin's stored options and cancels any outstanding
	 * Action Scheduler bulk-processing actions, for the current site and
	 * (on multisite) every site in the network.
	 *
	 * WordPress only supports a single uninstall handler per plugin --
	 * either register_uninstall_hook() or an uninstall.php file, never
	 * both -- and Freemius's SDK, when loaded, already registers its own
	 * uninstall handler internally to report the uninstall event to their
	 * servers. Adding our own uninstall.php (or calling
	 * register_uninstall_hook()) on top of that would silently prevent
	 * Freemius's own handler (and therefore the uninstall event/feedback
	 * reporting) from ever running. Hooking our cleanup to Freemius's own
	 * `after_uninstall` action instead lets both happen correctly:
	 * Freemius reports the uninstall first, then this runs.
	 *
	 * If the licensing/update-check opt-in is off, Freemius was never
	 * loaded at all (see priizmo_engine_fs() above) -- there's no Freemius
	 * uninstall handler to conflict with in that case, so this instead
	 * falls back to WordPress's own register_uninstall_hook() further
	 * below, which is the only way to still clean up our own options for
	 * a site that never opted in.
	 */
	function priizmo_engine_fs_uninstall_cleanup() {
		$cleanup_current_site = function () {
			delete_option( 'priizmo_engine_settings' );
			delete_option( 'priizmo_engine_active_bulk_batch' );

			// Cancel any pending/scheduled bulk-processing actions. Guarded
			// because Action Scheduler may not be loaded during uninstall if
			// this was the only plugin providing it and it has already been
			// deactivated.
			if ( function_exists( 'as_unschedule_all_actions' ) ) {
				as_unschedule_all_actions( 'priizmo_engine_process_post' );
				as_unschedule_all_actions( 'priizmo_engine_enqueue_batch' );
			}
		};

		$cleanup_current_site();

		if ( is_multisite() ) {
			$site_ids = get_sites( array( 'fields' => 'ids' ) );

			foreach ( $site_ids as $site_id ) {
				switch_to_blog( $site_id );
				$cleanup_current_site();
				restore_current_blog();
			}
		}
	}
	$priizmo_engine_fs_instance = priizmo_engine_fs();

	if ( is_object( $priizmo_engine_fs_instance ) ) {
		$priizmo_engine_fs_instance->add_action( 'after_uninstall', 'priizmo_engine_fs_uninstall_cleanup' );
	} else {
		// Freemius was never loaded (licensing/update-check opt-in is
		// off), so there is no Freemius uninstall handler to conflict
		// with -- safe, and necessary, to register our own directly.
		register_uninstall_hook( __FILE__, 'priizmo_engine_fs_uninstall_cleanup' );
	}
}
