<?php
/**
 * Plugin Name: Dennis GEO – AI Visibility
 * Plugin URI:  https://dennisgeo.com/
 * Description: GEO (Generative Engine Optimization) plugin that makes your WordPress site visible to AI search engines like ChatGPT, Perplexity, Google AI Overviews, and Claude. Enhanced schema, llms.txt, AI crawler management, and full SEO. WooCommerce compatible.
 * Version:     1.4.3
 * Author:      aistaffing.se
 * Author URI:  https://aistaffing.se
 * License:     GPL-2.0-or-later
 * Text Domain: dennis-geo
 * Requires at least: 6.4
 * Tested up to:      6.9
 * Requires PHP: 8.0
 */

defined( 'ABSPATH' ) || exit;

define( 'DENNIS_SEO_VERSION', '1.4.3' );
define( 'DENNIS_SEO_DB_VERSION', 1 );
define( 'DENNIS_SEO_FILE', __FILE__ );
define( 'DENNIS_SEO_PATH', plugin_dir_path( __FILE__ ) );
define( 'DENNIS_SEO_URL', plugin_dir_url( __FILE__ ) );

/**
 * Canonical list of all post meta keys with their sanitize callbacks.
 * Single source of truth — all classes reference this.
 */
define( 'DENNIS_SEO_META_KEYS', array(
	'_dennis_seo_title'            => 'sanitize_text_field',
	'_dennis_seo_description'      => 'sanitize_textarea_field',
	'_dennis_seo_keyphrase'        => 'sanitize_text_field',
	'_dennis_seo_canonical'        => 'esc_url_raw',
	'_dennis_seo_robots_noindex'   => 'sanitize_text_field',
	'_dennis_seo_robots_nofollow'  => 'sanitize_text_field',
	'_dennis_seo_og_title'         => 'sanitize_text_field',
	'_dennis_seo_og_description'   => 'sanitize_textarea_field',
	'_dennis_seo_og_image'         => 'esc_url_raw',
	'_dennis_seo_schema_type'      => 'sanitize_text_field',
	'_dennis_seo_llms_include'     => 'sanitize_text_field',
	'_dennis_seo_llms_description' => 'sanitize_textarea_field',
) );

// Load classes.
require_once DENNIS_SEO_PATH . 'includes/class-settings.php';
require_once DENNIS_SEO_PATH . 'includes/class-meta-box.php';
require_once DENNIS_SEO_PATH . 'includes/class-frontend.php';
require_once DENNIS_SEO_PATH . 'includes/class-sitemap.php';
require_once DENNIS_SEO_PATH . 'includes/class-llms-txt.php';
require_once DENNIS_SEO_PATH . 'includes/class-rest-meta.php';
require_once DENNIS_SEO_PATH . 'includes/class-import-export.php';
require_once DENNIS_SEO_PATH . 'includes/class-yoast-compat.php';
require_once DENNIS_SEO_PATH . 'includes/class-audit.php';
require_once DENNIS_SEO_PATH . 'includes/class-populate-api.php';

/**
 * Run database migrations when the plugin is updated.
 * Add new migrations as functions to the $migrations array, keyed by target version.
 */
function dennis_seo_maybe_migrate() {
	$current = (int) get_option( 'dennis_seo_db_version', 0 );

	if ( $current >= DENNIS_SEO_DB_VERSION ) {
		return;
	}

	// Add migrations here as: version => callable.
	// Example:
	// $migrations = array(
	//     2 => 'dennis_seo_migrate_v2',
	// );
	$migrations = array();

	ksort( $migrations );

	foreach ( $migrations as $version => $callback ) {
		if ( $current < $version && is_callable( $callback ) ) {
			call_user_func( $callback );
		}
	}

	update_option( 'dennis_seo_db_version', DENNIS_SEO_DB_VERSION );
}
add_action( 'admin_init', 'dennis_seo_maybe_migrate' );

/**
 * Initialize plugin components.
 */

function dennis_seo_init() {
	new Dennis_SEO_Audit();
	new Dennis_SEO_Settings();
	new Dennis_SEO_Meta_Box();
	new Dennis_SEO_Rest_Meta();
	new Dennis_SEO_Llms_Txt();
	new Dennis_SEO_Import_Export();
	new Dennis_SEO_Yoast_Compat();
	new Dennis_SEO_Populate_API();

	// Sitemap — only register our own if Yoast's sitemap is not active.
	if ( ! Dennis_SEO_Yoast_Compat::is_yoast_active() ) {
		new Dennis_SEO_Sitemap();
	} else {
		// Still register rewrite rules for our sitemap as fallback,
		// but Yoast compat class will disable Yoast's sitemap so ours takes over.
		new Dennis_SEO_Sitemap();
	}

	// Frontend only.
	if ( ! is_admin() ) {
		new Dennis_SEO_Frontend();
	}
}
add_action( 'plugins_loaded', 'dennis_seo_init' );

/**
 * Suggest privacy policy content for the WP privacy page.
 */
function dennis_seo_privacy_policy() {
	if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) {
		return;
	}
	wp_add_privacy_policy_content(
		__( 'Dennis GEO', 'dennis-geo' ),
		wp_kses_post(
			'<h2>' . __( 'Dennis GEO – AI Visibility for WordPress', 'dennis-geo' ) . '</h2>' .
			'<p>' . __( 'This plugin runs entirely on your server and does not collect or transmit any visitor data. It adds structured markup (schema, meta tags, llms.txt) to your pages — all generated locally.', 'dennis-geo' ) . '</p>' .
			'<p>' . __( 'If you use the optional AI Populate service ($19, one-time), the text content of your published pages is sent to api.dennisgeo.com for processing. The generated SEO fields are returned and stored in your WordPress database. Processing logs are retained for 30 days, then deleted. Your content is not used to train AI models.', 'dennis-geo' ) . '</p>' .
			'<p>' . __( 'Payment is processed by Stripe. We receive your email address and a Stripe customer ID — not your card details.', 'dennis-geo' ) . '</p>'
		)
	);
}
add_action( 'admin_init', 'dennis_seo_privacy_policy' );

/**
 * Remove known theme SEO functions to avoid duplicate meta tags, schema, and OG output.
 * These remove_action calls are safe on any theme — removing a non-existent action is a no-op.
 */
function dennis_seo_remove_theme_seo() {
	// UpperEight theme.
	remove_action( 'wp_head', 'uppereight_schema_output' );
	remove_action( 'wp_head', 'uppereight_faq_schema' );
	remove_action( 'wp_head', 'uppereight_service_schema' );
	remove_action( 'wp_head', 'uppereight_meta_description' );
	remove_action( 'wp_head', 'uppereight_og_meta' );
	remove_filter( 'document_title_parts', 'uppereight_document_title_parts' );
	remove_filter( 'document_title_separator', 'uppereight_document_title_separator' );

	// Add remove_action calls for other themes here if needed.
}
add_action( 'after_setup_theme', 'dennis_seo_remove_theme_seo', 99 );

/**
 * Disable WordPress core sitemaps (we provide our own).
 */
add_filter( 'wp_sitemaps_enabled', '__return_false' );

/**
 * Remove WordPress core's noindex robots meta when blog_public is 0.
 * Our plugin handles robots directives per-page — we don't want WP core's
 * blanket noindex conflicting with our output.
 *
 * When Yoast is active, Yoast also manages wp_robots — our Yoast compat layer
 * handles the priority between Dennis and Yoast for robots output.
 */
remove_filter( 'wp_robots', 'wp_robots_noindex' );
remove_filter( 'wp_robots', 'wp_robots_noindex_embeds' );
remove_filter( 'wp_robots', 'wp_robots_noindex_search' );
add_filter( 'wp_robots', function ( $robots ) {
	unset( $robots['noindex'] );
	unset( $robots['nofollow'] );
	return array();
}, 99999 );

/**
 * Auto-detect domain change (e.g. after All-in-One WP Migration) and flush caches + rewrite rules.
 */
function dennis_seo_check_domain_change() {
	$stored_url = get_option( 'dennis_seo_last_home_url', '' );
	$current_url = home_url();
	if ( $stored_url !== $current_url ) {
		delete_transient( 'dennis_seo_llms_txt' );
		delete_transient( 'dennis_seo_llms_full_txt' );
		delete_transient( 'dennis_seo_sitemap_post' );
		delete_transient( 'dennis_seo_sitemap_page' );
		Dennis_SEO_Sitemap::register_rewrite_rules();
		Dennis_SEO_Llms_Txt::register_rewrite_rules();
		flush_rewrite_rules();
		update_option( 'dennis_seo_last_home_url', $current_url );
	}
}
add_action( 'admin_init', 'dennis_seo_check_domain_change' );

/**
 * Flush rewrite rules on activation.
 */
function dennis_seo_activate() {
	Dennis_SEO_Sitemap::register_rewrite_rules();
	Dennis_SEO_Llms_Txt::register_rewrite_rules();
	flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'dennis_seo_activate' );

/**
 * Flush rewrite rules on deactivation.
 */
function dennis_seo_deactivate() {
	flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'dennis_seo_deactivate' );
