<?php

/**
 * Plugin Name: Schemafy
 * Plugin URI: https://schemafy.net/
 * Description: Build Schema.org JSON-LD without code, manage SEO meta tags and social previews, and keep every rich snippet in one library.
 * Version: 1.9.28
 * Author: Schemafy
 * Author URI: https://profiles.wordpress.org/schemafy/
 * Text Domain: schemafy
 * Domain Path: /languages
 * Requires at least: 5.8
 * Requires PHP: 7.4
 * License: GPLv2 or later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 */
if ( !defined( 'ABSPATH' ) ) {
    exit( 'Direct access not allowed!' );
}
if ( function_exists( 'schemafy_fs' ) ) {
    $schemafy_sdk = schemafy_fs();
    if ( is_object( $schemafy_sdk ) ) {
        $schemafy_sdk->set_basename( true, __FILE__ );
    }
} else {
    if ( !function_exists( 'schemafy_fs' ) ) {
        // Create a helper function for easy SDK access.
        function schemafy_fs() {
            global $schemafy_fs;
            if ( !isset( $schemafy_fs ) ) {
                // Include Freemius SDK.
                require_once dirname( __FILE__ ) . '/vendor/freemius/start.php';
                $schemafy_fs = fs_dynamic_init( array(
                    'id'               => '24699',
                    'slug'             => 'schemafy',
                    'premium_slug'     => 'schemafy',
                    'type'             => 'plugin',
                    'public_key'       => 'pk_8fb05260fe8c5c551bf94f2677d6c',
                    'is_premium'       => false,
                    'is_premium_only'  => false,
                    'has_addons'       => false,
                    'has_free_plan'    => true,
                    'has_paid_plans'   => true,
                    'menu'             => array(
                        'slug'       => 'schemafy-custom-schema',
                        'first-path' => 'admin.php?page=schemafy-custom-schema',
                        'support'    => false,
                        'account'    => true,
                        'pricing'    => true,
                    ),
                    'is_live'          => true,
                    'is_org_compliant' => true,
                ) );
            }
            return $schemafy_fs;
        }

        // Init Freemius. fs_dynamic_init() returns null when the SDK cannot
        // start, so bind the instance once instead of chaining onto every call
        // — the plugin's own features must keep working either way.
        $schemafy_sdk = schemafy_fs();
        if ( is_object( $schemafy_sdk ) ) {
            // Tell Freemius the exact plugin file so it matches updates to the
            // correct installed folder regardless of the premium slug.
            $schemafy_sdk->set_basename( false, __FILE__ );
            // After purchase/activation, redirect to the Smart Builder tab so the
            // user immediately sees their newly unlocked schema type cards.
            $schemafy_sdk->add_filter( 'after_connect_url', function ( $url ) {
                return admin_url( 'admin.php?page=schemafy-custom-schema&tab=builder' );
            } );
            // Cleanup on uninstall. We must use Freemius' after_uninstall hook
            // instead of a physical uninstall.php — Freemius needs to own the
            // uninstall event to send its tracking/feedback, and it rejects the
            // deployment when an uninstall.php file is present.
            $schemafy_sdk->add_action( 'after_uninstall', 'schemafy_uninstall_cleanup' );
            // Signal that SDK was initiated.
            do_action( 'schemafy_fs_loaded' );
        }
    }
    if ( !function_exists( 'schemafy_uninstall_cleanup' ) ) {
        /**
         * Remove Schemafy's internal options, transients and rewrite rules on
         * uninstall. User CONTENT (schemas table + meta tags) is preserved so an
         * accidental delete-and-reinstall never wipes hand-built schemas.
         */
        function schemafy_uninstall_cleanup() {
            global $wpdb;
            $options = array(
                'schemafy_sitemap_settings',
                'schemafy_sitemap_save_lock',
                'schemafy_sitemap_rewrite_version',
                'schemafy_sch_cache_ver',
                'schemafy_schema_db_version',
                'schemafy_schema_activation_notice'
            );
            foreach ( $options as $option ) {
                delete_option( $option );
            }
            $wpdb->query( "DELETE FROM {$wpdb->options}\n                 WHERE option_name LIKE '\\_transient\\_schemafy\\_sch\\_%'\n                    OR option_name LIKE '\\_transient\\_timeout\\_schemafy\\_sch\\_%'\n                    OR option_name LIKE '\\_transient\\_schemafy\\_sitemap\\_%'\n                    OR option_name LIKE '\\_transient\\_timeout\\_schemafy\\_sitemap\\_%'" );
            if ( function_exists( 'flush_rewrite_rules' ) ) {
                flush_rewrite_rules( false );
            }
        }

    }
    if ( !function_exists( 'schemafy_log' ) ) {
        /**
         * Debug logger — only writes when WP_DEBUG is on, so production sites
         * stay quiet and the plugin passes WordPress.org's error_log check.
         *
         * @param string $message Message to log.
         */
        function schemafy_log(  $message  ) {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
                error_log( $message );
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }
        }

    }
    if ( !function_exists( 'schemafy_migrate_legacy_prefix' ) ) {
        /**
         * One-time rename of the legacy "baseo_" data prefix to "schemafy_".
         *
         * Every identifier now carries the plugin's own prefix, but sites that ran
         * an earlier build stored their options, post/user meta and schemas table
         * under the old one. They are renamed here, before any code reads them, so
         * an upgraded site does not come back up with blank settings and an empty
         * schema library. Every statement is idempotent: a failed run is simply
         * repeated on the next request until it completes.
         *
         * ponytail: one wp_cache_flush() instead of per-object invalidation. The
         * ceiling is a single cold cache on the one request that upgrades; if that
         * ever matters, swap it for clean_post_cache() over the affected IDs.
         */
        function schemafy_migrate_legacy_prefix() {
            if ( get_option( 'schemafy_prefix_migrated' ) ) {
                return;
            }
            global $wpdb;
            // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- one-time key rename over whole tables: nothing to cache, and table identifiers cannot be placeholders.
            $wpdb->query( "UPDATE {$wpdb->options} SET option_name = REPLACE( option_name, 'baseo_', 'schemafy_' ) WHERE option_name LIKE 'baseo\\_%'" );
            $wpdb->query( "UPDATE {$wpdb->postmeta} SET meta_key = REPLACE( meta_key, 'baseo_', 'schemafy_' ) WHERE meta_key LIKE 'baseo\\_%' OR meta_key LIKE '\\_baseo\\_%'" );
            $wpdb->query( "UPDATE {$wpdb->usermeta} SET meta_key = REPLACE( meta_key, 'baseo_', 'schemafy_' ) WHERE meta_key LIKE 'baseo\\_%'" );
            // Caches written under the old prefix would shadow fresh output until
            // they expired, so drop them and let the new keys rebuild.
            $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\\_transient\\_baseo\\_%' OR option_name LIKE '\\_transient\\_timeout\\_baseo\\_%'" );
            $legacy_table = $wpdb->prefix . 'baseo_custom_schemas';
            $new_table = $wpdb->prefix . 'schemafy_custom_schemas';
            if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $legacy_table ) ) === $legacy_table && $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $new_table ) ) !== $new_table ) {
                $wpdb->query( "RENAME TABLE `{$legacy_table}` TO `{$new_table}`" );
            }
            // phpcs:enable
            update_option( 'schemafy_prefix_migrated', SCHEMAFY_VERSION, true );
            wp_cache_flush();
        }

    }
    // Plan access (schemafy_can_access) is defined by the paid build in
    // includes/premium/ui__premium_only.php. The free WordPress.org package
    // contains no plan evaluation at all: it ships no paid feature to gate, so
    // every caller guards with function_exists() and simply skips.
    // Define plugin constants — version is read from the plugin header (line 5)
    // so updating "Version: X.X.X" above is the only place that needs to change.
    $_schemafy_data = get_file_data( __FILE__, array(
        'Version' => 'Version',
    ) );
    define( 'SCHEMAFY_VERSION', $_schemafy_data['Version'] );
    define( 'SCHEMAFY_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
    define( 'SCHEMAFY_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
    define( 'SCHEMAFY_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
    // The AI proxy endpoint and its service token are defined by the paid build
    // in includes/premium/ai-handlers__premium_only.php — the only code that
    // uses them. The free WordPress.org package ships no credential.
    // Load required files
    $files_to_load = array(
        'includes/class-schemafy-validator.php',
        'includes/class-schemafy-schema-type-registry.php',
        'includes/class-schemafy-database.php',
        'includes/class-schemafy-meta-store.php',
        'includes/class-schemafy-seo-detector.php',
        'includes/class-schemafy-seo-analyzer.php',
        'includes/class-schemafy-schema-injector.php',
        'includes/class-schemafy-rest-api.php',
        'includes/class-schemafy-gutenberg.php',
        'includes/class-schemafy-sitemap.php',
        'includes/class-schemafy-modular-scripts.php',
        'includes/schemafy-premium-ui.php',
        'includes/class-schemafy-admin.php',
        'includes/builders/class-schemafy-field-generator.php',
        'includes/builders/class-schemafy-schema-builder.php',
        'includes/class-schemafy-core.php',
        'includes/ajax-handler-save-update.php',
        'includes/ajax-handler-dashboard.php'
    );
    foreach ( $files_to_load as $file ) {
        $file_path = SCHEMAFY_PLUGIN_PATH . $file;
        if ( !file_exists( $file_path ) ) {
            wp_die( 'Schemafy Error: Missing required file - ' . esc_html( $file ) );
        }
        require_once $file_path;
    }
    // Premium modules live in includes/premium/ as *__premium_only.php files.
    // Freemius strips these from the free WordPress.org build, so glob() returns
    // nothing there and the premium engine simply isn't present. In the premium
    // build (and local dev) the files exist and load. All references to premium
    // classes are guarded with class_exists() so the free build never fatals.
    foreach ( ( glob( SCHEMAFY_PLUGIN_PATH . 'includes/premium/*__premium_only.php' ) ?: array() ) as $premium_file ) {
        require_once $premium_file;
    }
    // Activation/Deactivation hooks
    register_activation_hook( __FILE__, array('SCHEMAFY_Core', 'activate') );
    register_deactivation_hook( __FILE__, array('SCHEMAFY_Core', 'deactivate') );
    /**
     * Initialize Schemafy plugin
     */
    function schemafy_schema_init() {
        global $schemafy_core_instance;
        // Must run before the first read: the constructors below load settings.
        schemafy_migrate_legacy_prefix();
        $schemafy_core_instance = new SCHEMAFY_Core();
        $schemafy_core_instance->run();
    }

    add_action( 'plugins_loaded', 'schemafy_schema_init' );
}