<?php
/**
 * Plugin Name:       2fox4 WP Blog Agent
 * Description:       Automatischer KI-Blogautor: recherchiert Themen per Topic Scout, schreibt SEO-optimierte Artikel mit Claude, generiert Beitragsbilder mit Imagen, schreibt Meta-Daten direkt in RankMath/Yoast/SEOPress und erstellt zeitaktuelle Beitr&auml;ge aus RSS-Feeds im Autopilot-Modus. Bring your own API-Key. Entwickler-Webseite: <a href="https://www.2fox4.de/automatischer-ki-wordpress-blog-agent/">2fox4.de</a>
 * Version:           1.8.6
 * Author:            Oliver Deppe
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       wp-blog-agent
 * Domain Path:       /languages
 * Requires at least: 6.4
 * Requires PHP:      8.0
 */

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

/**
 * Define plugin constants.
 */
if ( ! defined( 'WBAG_VERSION' ) ) {
	define( 'WBAG_VERSION', '1.8.6' );
}

if ( ! defined( 'WBAG_PLUGIN_DIR' ) ) {
	define( 'WBAG_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
}

if ( ! defined( 'WBAG_PLUGIN_URL' ) ) {
	define( 'WBAG_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
}

if ( ! defined( 'WBAG_PLUGIN_BASENAME' ) ) {
	define( 'WBAG_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
}

/**
 * Freemius SDK initialization.
 * Provides licensing, payments, and analytics.
 */
if ( ! function_exists( 'wbag_fs' ) ) {
	function wbag_fs() {
		global $wbag_fs;

		if ( ! isset( $wbag_fs ) ) {
			// Include Freemius SDK.
			require_once WBAG_PLUGIN_DIR . 'freemius/start.php';

			$wbag_fs = fs_dynamic_init( array(
				'id'                  => '27485',
				'slug'                => 'wp-blog-agent',
				'type'                => 'plugin',
				'public_key'          => 'pk_4fc96fcd86481a359da46908eff68',
				'is_premium'          => true,
				'premium_suffix'      => 'Pro',
				'has_premium_version' => true,
				'has_addons'          => false,
				'has_paid_plans'      => true,
				'is_org_compliant'    => true,
				'menu'                => array(
					'support' => false,
				),
			) );
		}

		return $wbag_fs;
	}

	// Init Freemius.
	wbag_fs();
	// Signal that SDK was initiated.
	do_action( 'wbag_fs_loaded' );

	/**
	 * Freemius uninstall hook – replaces the standalone uninstall.php.
	 * Only performs full cleanup when the user opted in via
	 * "Daten bei Deinstallation löschen".
	 */
	wbag_fs()->add_action( 'after_uninstall', 'wbag_fs_uninstall_cleanup' );

	function wbag_fs_uninstall_cleanup() {
		$settings        = get_option( 'wbag_settings', array() );
		$clean_uninstall = ! empty( $settings['delete_data_on_uninstall'] );

		if ( ! $clean_uninstall ) {
			// Preserve all settings so they survive reinstall / update.
			wp_clear_scheduled_hook( 'wbag_scheduled_run' );
			delete_option( 'wbag_cron_log' );
			delete_option( 'wbag_excerpt_migration_done' );
			return;
		}

		// ── Full cleanup (user opted in) ──────────────────────────────────

		// Remove plugin options.
		delete_option( 'wbag_settings' );
		delete_option( 'wbag_topic_queue' );
		delete_option( 'wbag_cron_log' );
		delete_option( 'wbag_review_dismissed' );
		delete_option( 'wbag_installed_at' );
		delete_option( 'wbag_excerpt_migration_done' );

		// Remove scheduled cron jobs.
		wp_clear_scheduled_hook( 'wbag_scheduled_run' );

		// Remove post meta from generated posts.
		$wbag_posts = get_posts( array(
			'post_type'      => 'post',
			'post_status'    => 'any',
			'posts_per_page' => -1,
			'fields'         => 'ids',
			'meta_key'       => '_wbag_generated',
			'meta_value'     => '1',
		) );

		foreach ( $wbag_posts as $wbag_post_id ) {
			delete_post_meta( $wbag_post_id, '_wbag_generated' );
			delete_post_meta( $wbag_post_id, '_wbag_topic_data' );
			delete_post_meta( $wbag_post_id, '_wbag_image_prompts' );
			delete_post_meta( $wbag_post_id, '_wbag_generated_at' );
			delete_post_meta( $wbag_post_id, '_wbag_autopilot' );
		}
	}
}

/**
 * Helper: Is the current site running the Pro plan?
 * Returns true also in dev mode (WBAG_DEV_MODE) for local testing.
 */
function wbag_is_pro() {
	if ( defined( 'WBAG_DEV_MODE' ) && WBAG_DEV_MODE ) {
		return true;
	}
	if ( ! function_exists( 'wbag_fs' ) ) {
		return false;
	}
	$fs = wbag_fs();
	// Strict check: user must have an ACTIVE PAID license (not just premium code).
	// is_paying() → true only if there is an active paid plan/license on this install.
	return $fs->is_registered() && $fs->is_paying();
}

/**
 * Include plugin classes.
 */
require_once WBAG_PLUGIN_DIR . 'includes/class-wbag-admin.php';
require_once WBAG_PLUGIN_DIR . 'includes/class-wbag-api.php';
require_once WBAG_PLUGIN_DIR . 'includes/class-wbag-topic-scout.php';
require_once WBAG_PLUGIN_DIR . 'includes/class-wbag-blog-writer.php';
require_once WBAG_PLUGIN_DIR . 'includes/class-wbag-rankmath.php';
require_once WBAG_PLUGIN_DIR . 'includes/class-wbag-scheduler.php';

/**
 * Plugin activation hook.
 */
register_activation_hook(
	__FILE__,
	function() {
		// Set default options if they don't exist.
		if ( ! get_option( 'wbag_settings' ) ) {
			$defaults = array(
				// Identity
				'author_name'       => '',
				'language'          => 'de',
				'formal_address'    => 'sie',

				// Niche
				'niche'             => '',
				'subtopics'         => '',
				'target_audience'   => '',
				'competitor_urls'   => '',

				// Structure
				'word_count_min'    => 2500,
				'word_count_max'    => 4000,
				'faq_count'         => 10,
				'cta_text'          => '',
				'cta_url'           => '',
				'cta_color'         => '#FF6B35',
				'related_posts'     => 3,
				'include_toc'       => true,

				// SEO
				'seo_plugin'        => 'rankmath',
				'focus_kw_strategy' => 'single',
				'internal_links'    => 3,
				'external_links'    => 2,

				// API
				'api_key'           => '',
				'api_model'         => 'claude-sonnet-4-5-20250514',

				// Schedule
				'schedule_enabled'  => false,
				'schedule_days'     => array( 'monday', 'friday' ),
				'schedule_time'     => '09:00',
				'batch_size'        => 5,

				// Images
				'image_style'       => '',
				'image_size'        => '1024x572',
				'image_count'       => 2,
				'image_prompt_lang' => 'en',
			);

			add_option( 'wbag_settings', $defaults );
		}

		// Schedule cron events if scheduling is enabled (Pro only).
		$settings = get_option( 'wbag_settings', array() );
		if ( ! empty( $settings['schedule_enabled'] ) && wbag_is_pro() ) {
			WBAG_Scheduler::schedule( $settings );
		}
	}
);

/**
 * Plugin deactivation hook.
 */
register_deactivation_hook(
	__FILE__,
	function() {
		// Clear cron events.
		WBAG_Scheduler::unschedule();
	}
);

/**
 * Initialize text domain on init hook.
 */
add_action(
	'init',
	function() {
		load_plugin_textdomain(
			'wp-blog-agent',
			false,
			dirname( WBAG_PLUGIN_BASENAME ) . '/languages'
		);
	}
);

/**
 * Initialize the Scheduler so its WP-Cron hook is always registered.
 * Must run on every request (not only admin), because cron runs in a
 * non-admin context.
 */
WBAG_Scheduler::init();

/**
 * Frontend: Inject max-width CSS for WBAG-generated single posts.
 *
 * Applies only when a content_max_width is configured in settings AND
 * the current post was generated by this plugin. Works across themes by
 * targeting the WordPress post-content container.
 *
 * @since 1.3.28
 */
add_action(
	'wp_head',
	function() {
		if ( ! is_singular( 'post' ) ) {
			return;
		}

		$settings  = get_option( 'wbag_settings', array() );
		$max_width = ! empty( $settings['content_max_width'] ) ? intval( $settings['content_max_width'] ) : 0;

		if ( $max_width < 400 ) {
			return;
		}

		// Only apply to posts generated by this plugin.
		$post_id = get_the_ID();
		if ( '1' !== get_post_meta( $post_id, '_wbag_generated', true ) ) {
			return;
		}

		$px = esc_attr( $max_width );
		echo '<style id="wbag-content-width">' . "\n"
			. '/* WP Blog Agent – Content Max Width */' . "\n"
			. '.entry-content,'
			. '.elementor-widget-theme-post-content .elementor-widget-container,'
			. '.single-post .post-content,'
			. '.single-post .site-content .entry-content {'
			. '  max-width: ' . $px . 'px !important;'
			. '  margin-left: auto !important;'
			. '  margin-right: auto !important;'
			. '}' . "\n"
			. '</style>' . "\n";
	}
);

/**
 * Initialize admin classes early so their admin_menu hooks register in time.
 */
add_action(
	'plugins_loaded',
	function() {
		if ( is_admin() ) {
			new WBAG_Admin();
		}
	}
);

/**
 * Customize plugin row meta in the plugins list.
 *
 * Removes the "Details anzeigen" (View details) link and the
 * "Von 2FOX4" author URI link from the plugin row.
 *
 * @since 1.3.65
 */
add_filter(
	'plugin_row_meta',
	function( $plugin_meta, $plugin_file ) {
		if ( WBAG_PLUGIN_BASENAME !== $plugin_file ) {
			return $plugin_meta;
		}

		// Remove "Details anzeigen" / "View details" link.
		foreach ( $plugin_meta as $key => $value ) {
			if ( strpos( $value, 'plugin-install.php?tab=plugin-information' ) !== false ) {
				unset( $plugin_meta[ $key ] );
			}
		}

		return $plugin_meta;
	},
	10,
	2
);

/**
 * Remove the plugin URI link from the plugin name in the plugins list.
 *
 * WordPress wraps the plugin name in a link to Plugin URI. This filter
 * removes that link so the plugin name appears as plain text.
 *
 * @since 1.3.65
 */
add_filter(
	'plugin_action_links_' . plugin_basename( __FILE__ ),
	function( $actions ) {
		return $actions;
	}
);

/**
 * Dynamic excerpt filter for WBAG-generated posts.
 *
 * If a WBAG-generated post has no excerpt (or a very short/broken one),
 * this filter builds a clean excerpt from the RankMath meta_description
 * or by stripping the post content down to the first ~300 characters.
 *
 * This fixes blog listing pages for posts created before v1.3.27
 * where no post_excerpt was set.
 *
 * @since 1.3.27
 */
add_filter(
	'get_the_excerpt',
	function( $excerpt, $post ) {
		// Only act on posts generated by this plugin.
		if ( '1' !== get_post_meta( $post->ID, '_wbag_generated', true ) ) {
			return $excerpt;
		}

		// If there is already a reasonable excerpt (>30 chars, no obvious junk), keep it.
		$clean_excerpt = trim( wp_strip_all_tags( $excerpt ) );
		if ( mb_strlen( $clean_excerpt ) > 30 ) {
			return $excerpt;
		}

		// Build a clean 30-word excerpt from the post content.
		$plain = wp_strip_all_tags( $post->post_content );
		$plain = preg_replace( '/\s+/', ' ', trim( $plain ) );
		$words = preg_split( '/\s+/', $plain, 32, PREG_SPLIT_NO_EMPTY );
		if ( count( $words ) > 30 ) {
			$words = array_slice( $words, 0, 30 );
			return implode( ' ', $words ) . ' ...';
		}

		return implode( ' ', $words );
	},
	10,
	2
);

/**
 * One-time migration: retroactively set post_excerpt for existing
 * WBAG-generated posts that have no excerpt stored.
 *
 * Runs once when the plugin is updated to v1.3.27+.
 *
 * @since 1.3.27
 */
add_action(
	'admin_init',
	function() {
		$migrated = get_option( 'wbag_excerpt_migration_v2_done', false );
		if ( $migrated ) {
			return;
		}

		// Query all WBAG-generated posts with empty excerpts.
		$posts = get_posts( array(
			'post_type'      => 'post',
			'post_status'    => 'any',
			'posts_per_page' => -1,
			'meta_key'       => '_wbag_generated',
			'meta_value'     => '1',
		) );

		foreach ( $posts as $post ) {
			// Build a clean 30-word excerpt from the post content.
			$plain = wp_strip_all_tags( $post->post_content );
			$plain = preg_replace( '/\s+/', ' ', trim( $plain ) );
			$words = preg_split( '/\s+/', $plain, 32, PREG_SPLIT_NO_EMPTY );
			if ( count( $words ) > 30 ) {
				$words = array_slice( $words, 0, 30 );
				$new_excerpt = implode( ' ', $words ) . ' ...';
			} else {
				$new_excerpt = implode( ' ', $words );
			}

			if ( ! empty( $new_excerpt ) ) {
				wp_update_post( array(
					'ID'           => $post->ID,
					'post_excerpt' => $new_excerpt,
				) );
			}
		}

		update_option( 'wbag_excerpt_migration_v2_done', true );
	}
);

/**
 * Add settings link on plugins page.
 */
add_filter(
	'plugin_action_links_' . WBAG_PLUGIN_BASENAME,
	function( $links ) {
		$settings_link = sprintf(
			'<a href="%s">%s</a>',
			esc_url( admin_url( 'admin.php?page=wbag-settings' ) ),
			esc_html__( 'Settings', 'wp-blog-agent' )
		);

		array_unshift( $links, $settings_link );

		return $links;
	}
);
