<?php

/**
 * Plugin Name:       AI Auto Poster
 * Plugin URI:        https://besoft.jp/ai-auto-poster/
 * Description:       Schedule and auto-publish AI-generated articles using Claude, OpenAI, Gemini, and more. Supports featured image generation and SEO optimization.
 * Version:           1.0.5
 * Requires at least: 5.8
 * Tested up to:      6.9
 * Requires PHP:      7.4
 * Author:            besoft
 * Author URI:        https://besoft.jp
 * License:           GPLv2 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       ai-auto-poster
 * Domain Path:       /languages
 */
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
define( 'AIAP_VERSION', '1.0.5' );
define( 'AIAP_PLUGIN_FILE', __FILE__ );
define( 'AIAP_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'AIAP_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'AIAP_CRON_HOOK', 'aiap_run_auto_post' );
// ====================================================================
// Freemius SDK 初期化
// ====================================================================
// SDK 配置 + キー設定後に有効化される。
// SDK が未配置(または ID/Key が未設定)の場合は AIAP_License が常に Free
// として動作するため、プラグイン本体は問題なく起動する。
//
// セットアップ手順:
//   1. Freemius ダッシュボードで Plugin を作成し ID/Public Key を取得
//   2. https://github.com/Freemius/wordpress-sdk から SDK を取得
//   3. SDK を /freemius/ ディレクトリに配置
//   4. 下記の YOUR_PLUGIN_ID_HERE / YOUR_PUBLIC_KEY_HERE を実値に置換
//   5. is_premium をビルドターゲットに応じて true/false に切替
// ====================================================================
if ( !function_exists( 'aiap_fs' ) && file_exists( AIAP_PLUGIN_DIR . 'freemius/start.php' ) ) {
    function aiap_fs() {
        global $aiap_fs;
        if ( !isset( $aiap_fs ) ) {
            require_once AIAP_PLUGIN_DIR . 'freemius/start.php';
            $aiap_fs = fs_dynamic_init( array(
                'id'               => '28483',
                'slug'             => 'ai-auto-poster',
                'type'             => 'plugin',
                'public_key'       => 'pk_adf8184140e63ff13c31375cbf03f',
                'is_premium'       => false,
                'has_addons'       => false,
                'has_paid_plans'   => true,
                'trial'            => array(
                    'days'               => 7,
                    'is_require_payment' => false,
                ),
                'menu'             => array(
                    'slug'    => 'ai-auto-poster',
                    'support' => false,
                    'contact' => false,
                ),
                'is_live'          => true,
                'is_org_compliant' => true,
            ) );
        }
        return $aiap_fs;
    }

    aiap_fs();
    do_action( 'aiap_fs_loaded' );
}
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-i18n.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-http.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-crypto.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-lock.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-lock-keys.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-tabs.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-license.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-logger.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-prompts.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-model-registry.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-settings.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-ai-client.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-ai-response-parser.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-ai-errors.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-ai-providers.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-image-client.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-image-styles.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-image-providers.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-model-sync.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-dedup.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-seo.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-poster.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-scheduler.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-rss-state.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-rss-feeds.php';
require_once AIAP_PLUGIN_DIR . 'includes/class-aiap-rss-composer.php';
require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-admin.php';
require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-admin-pages.php';
require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-admin-rss.php';
require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-admin-controller.php';
require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-admin-view.php';
require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-admin-view-tasks.php';
require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-admin-view-provider.php';
require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-admin-view-wizard.php';
require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-admin-view-settings.php';
require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-admin-view-quickpost.php';
require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-admin-view-prompt.php';
require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-admin-view-logs.php';
require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-admin-ajax.php';
require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-admin-runner.php';
if ( is_admin() ) {
    require_once AIAP_PLUGIN_DIR . 'admin/class-aiap-generated-posts-table.php';
}
register_activation_hook( __FILE__, 'aiap_on_activate' );
register_deactivation_hook( __FILE__, 'aiap_on_deactivate' );
register_uninstall_hook( __FILE__, 'aiap_uninstall_cleanup' );

/**
 * 有効化フック: Scheduler + Model Sync の cron を登録する
 */
function aiap_on_activate() {
    AIAP_Scheduler::activate();
    if ( class_exists( 'AIAP_Model_Sync' ) ) {
        AIAP_Model_Sync::activate();
    }
}

/**
 * 無効化フック: 各 cron を停止する (option は残す)
 */
function aiap_on_deactivate() {
    AIAP_Scheduler::deactivate();
    if ( class_exists( 'AIAP_Model_Sync' ) ) {
        AIAP_Model_Sync::deactivate();
    }
}
/**
 * アンインストール時のクリーンアップ処理
 *
 * register_uninstall_hook() で登録される。WordPress 管理画面でプラグインを
 * 「削除」した時に自動実行される。「無効化」では実行されない。
 *
 * 注意: register_uninstall_hook の制約により、コールバックはグローバル関数
 *       かつメインファイル内に実装する必要がある。プラグインの他のクラス
 *       (AIAP_*) は削除時にロードされないため、ここに直接ロジックを書く。
 *
 * マルチサイト対応: ネットワーク全体のサイトを巡回して各サイトのデータを削除。
 */
function aiap_uninstall_cleanup() {
    if ( !defined( 'WP_UNINSTALL_PLUGIN' ) ) {
        return;
    }
    if ( is_multisite() ) {
        $site_ids = get_sites( array(
            'fields' => 'ids',
            'number' => 0,
        ) );
        foreach ( $site_ids as $site_id ) {
            switch_to_blog( $site_id );
            aiap_uninstall_cleanup_site();
            restore_current_blog();
        }
        // network-wide で保存している可能性のある site_option も削除
        $site_options = array(
            'aiap_settings',
            'aiap_image_settings',
            'aiap_logs',
            'aiap_tasks',
            'aiap_last_run',
            'aiap_run_queue',
            'aiap_override_postdate',
            'aiap_initial_missed_scan_done',
            'aiap_verified_providers',
            'aiap_post_counter',
            'aiap_llms_txt_legacy_cleaned',
            'aiap_autoload_migrated_v1',
            'aiap_image_mode_migrated_v1',
            'aiap_rss_feeds',
            'aiap_rss_state',
            'aiap_model_sync_status'
        );
        foreach ( $site_options as $opt ) {
            delete_site_option( $opt );
        }
    } else {
        aiap_uninstall_cleanup_site();
    }
    // Cron スケジュール解除 (deactivate で解除済みのはずだが念のため)
    // マルチサイト時は各サイトの switch_to_blog 配下で aiap_uninstall_cleanup_site() が
    // 同等の処理を実行済み。ここはシングルサイトおよび念押し。
    wp_clear_scheduled_hook( AIAP_CRON_HOOK );
}

/**
 * 1サイト分のデータをクリーンアップする
 *
 * 'system_prompt' は 'aiap_settings' 配列の中の一要素であり、
 * 独立した option ではないためここには含めない。
 */
function aiap_uninstall_cleanup_site() {
    global $wpdb;
    // ========== オプション削除 ==========
    $options = array(
        'aiap_settings',
        // 一般設定 + 各 AI プロバイダの API キー (暗号化済)
        'aiap_image_settings',
        // 画像生成設定 + 画像 API キー (暗号化済)
        'aiap_logs',
        // 実行ログ (最大7日)
        'aiap_tasks',
        // プロンプトタスク一覧
        'aiap_last_run',
        // cron の最終実行記録
        'aiap_run_queue',
        // 実行待ちキュー (投稿し損ねた予定の処理含む)
        'aiap_override_postdate',
        // バックデート用フラグ
        'aiap_initial_missed_scan_done',
        // 初回インストール後の過去未投稿確認スキップ済みフラグ
        'aiap_verified_providers',
        // APIキーテスト完了フラグ
        'aiap_post_counter',
        // 月次投稿カウンタ (Free プラン用)
        'aiap_llms_txt_legacy_cleaned',
        // 旧物理 llms.txt クリーンアップ実行済みフラグ
        'aiap_autoload_migrated_v1',
        'aiap_image_mode_migrated_v1',
        // featured_image_mode 'inherit'→'on' マイグレーション実行済みフラグ
        'aiap_rss_feeds',
        // RSS フィードライブラリ
        'aiap_rss_state',
        // RSS 使用済み履歴 (フィードごと FIFO 500 件)
        'aiap_model_sync_status',
        // Model Sync の最終取得状況 (provider => last_check / discontinued_model)
    );
    foreach ( $options as $option ) {
        delete_option( $option );
    }
    // ========== Transients 削除 ==========
    $transient_prefixes = array('_transient_aiap_', '_transient_timeout_aiap_');
    foreach ( $transient_prefixes as $prefix ) {
        // アンインストール時のクリーンアップなのでキャッシュ不要、$wpdb 直接呼び出しで問題なし
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like( $prefix ) . '%' ) );
    }
    // ========== ロック option 削除 (aiap_lock_*) ==========
    // AIAP_Lock が add_option ベースの atomic ロックを使うため、'aiap_lock_*' という
    // option が実行時に生成される。通常は release で消えるが、クラッシュ等で残った
    // 残骸をアンインストール時に確実に掃除する。
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
    $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like( 'aiap_lock_' ) . '%' ) );
    // ========== 投稿メタ削除 ==========
    // 投稿に紐付くメタは「投稿の出自情報」として残す方針 (v1.0.1+):
    // _aiap_generated を消すと再有効化後にダッシュボードの 7 日チャートが 0 になる、
    // SEO 系メタ (_aiap_focus_keyword 等) は記事に紐付いた成果物なので保持する価値あり。
    // option (タスク・ログ・カウンタ等) は上で削除済み = 「設定とログは消えるが、生成済み
    // 記事の出自は残る」というユーザー期待に近い挙動とする。
    // 投稿そのものを消したいユーザーは WP 管理から各記事を直接削除してもらう。
    // ========== ユーザーメタ削除 ==========
    $user_meta_keys = array(
        'aiap_last_manual_prompt',
        'aiap_last_manual_language',
        'aiap_last_manual_length',
        'aiap_last_manual_rss_enabled',
        'aiap_last_manual_rss_feed_ids',
        'aiap_last_manual_rss_count',
        'aiap_last_manual_rss_mode',
        'aiap_last_manual_rss_fallback',
    );
    foreach ( $user_meta_keys as $meta_key ) {
        // アンインストール時のクリーンアップなのでキャッシュ不要、$wpdb 直接呼び出しで問題なし
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->delete( $wpdb->usermeta, array(
            'meta_key' => $meta_key,
        ) );
    }
    // ========== Cron スケジュール解除 ==========
    // マルチサイト時は各サイトコンテキストでスケジュールが登録されているため
    // switch_to_blog 配下の本関数からも解除する必要がある。
    wp_clear_scheduled_hook( AIAP_CRON_HOOK );
    // モデル同期 cron も解除 (AIAP_Model_Sync::CRON_HOOK)。uninstall 時は
    // クラス未ロードのため文字列リテラルを直接指定する (register_uninstall_hook
    // の制約により AIAP_* クラスは uninstall 経路ではロードされない)。
    wp_clear_scheduled_hook( 'aiap_model_sync' );
    // llms.txt は動的配信(parse_request フック)に変更されたため、
    // 物理ファイルとしての残骸は cleanup_legacy_llms_txt() が起動時に削除済み。
    // ここでは追加のファイル操作は不要。
}

// プラグイン一覧画面に「設定」リンクを追加(WordPress.org 推奨)
// Free プランの場合は「Upgrade to Pro」リンクも追加(右側に色付きで目立つように)
add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), function ( $links ) {
    $settings_url = admin_url( 'admin.php?page=ai-auto-poster' );
    $settings_link = '<a href="' . esc_url( $settings_url ) . '">' . esc_html( __( 'Settings', 'ai-auto-poster' ) ) . '</a>';
    array_unshift( $links, $settings_link );
    // Free プランなら Upgrade リンクを追加
    if ( class_exists( 'AIAP_License' ) && !AIAP_License::is_pro() ) {
        $upgrade_url = AIAP_License::upgrade_url();
        $links['aiap_upgrade'] = '<a href="' . esc_url( $upgrade_url ) . '" class="aiap-plugin-action-upgrade">' . esc_html( __( 'Upgrade to Pro', 'ai-auto-poster' ) ) . '</a>';
    }
    return $links;
} );
// 翻訳ファイルを先にロードして、管理画面・AJAX・Cron の全文言に同梱辞書を適用する。
add_action( 'plugins_loaded', function () {
    AIAP_I18n::load();
    AIAP_Scheduler::init();
    if ( class_exists( 'AIAP_Model_Sync' ) ) {
        AIAP_Model_Sync::init();
    }
    if ( is_admin() ) {
        AIAP_Admin::init();
    }
    // SEO: フロントエンド出力フック(JSON-LD / OGP / canonical)
    AIAP_SEO::init_hooks();
    // 既存サイトでは autoload=yes で保存されている設定を、autoload=no に変更する。
    // フロント全リクエストで wp_options から自動ロードする必要がないオプション群。
    // フラグ付きで一度だけ実行する。
    aiap_migrate_autoload_no();
    // 既存タスクの featured_image_mode が旧仕様の 'inherit' のままなら 'on' に書き換える。
    // フラグ付きで一度だけ実行する。
    aiap_migrate_image_mode_drop_inherit();
} );
/**
 * 設定オプションを autoload=no にマイグレーション
 *
 * 過去のバージョンでは update_option の第3引数を省略していたため、
 * APIキーや system_prompt 等を含む大きめのオプションが autoload=yes で保存されていた。
 * 1度だけ走らせて autoload を no に書き換える。
 */
function aiap_migrate_autoload_no() {
    $migrated = get_option( 'aiap_autoload_migrated_v1', false );
    if ( $migrated ) {
        return;
    }
    global $wpdb;
    $targets = array('aiap_settings', 'aiap_image_settings', 'aiap_logs');
    foreach ( $targets as $option_name ) {
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->update( $wpdb->options, array(
            'autoload' => 'no',
        ), array(
            'option_name' => $option_name,
        ) );
    }
    wp_cache_delete( 'alloptions', 'options' );
    update_option( 'aiap_autoload_migrated_v1', 1, false );
}

/**
 * featured_image_mode の 'inherit' を 'on' に書き換えるマイグレーション
 *
 * 旧仕様では 'inherit'/'on'/'off' の 3 値だったが、'inherit' は事実上 'on' と同じ挙動になっており、
 * UI からも選択肢が削除済みのため完全廃止。
 * 既存タスクの値が 'inherit' のまま残っているものを 'on' に書き換える。
 * フラグ付きで一度だけ実行する。
 */
function aiap_migrate_image_mode_drop_inherit() {
    $migrated = get_option( 'aiap_image_mode_migrated_v1', false );
    if ( $migrated ) {
        return;
    }
    $tasks = get_option( 'aiap_tasks', array() );
    if ( is_array( $tasks ) && ! empty( $tasks ) ) {
        $changed = false;
        foreach ( $tasks as $i => $task ) {
            if ( ! is_array( $task ) ) {
                continue;
            }
            $mode = isset( $task['featured_image_mode'] ) ? $task['featured_image_mode'] : '';
            if ( $mode !== 'on' && $mode !== 'off' ) {
                $tasks[ $i ]['featured_image_mode'] = 'on';
                $changed = true;
            }
        }
        if ( $changed ) {
            update_option( 'aiap_tasks', $tasks, false );
        }
    }
    update_option( 'aiap_image_mode_migrated_v1', 1, false );
}

// ====================================================================
// Freemius サブメニュー欠落の補完
// ====================================================================
// 問題:
//   Freemius SDK は Free プラン or アクティベーション中の状態で
//   contact / pricing 等の内部サブメニューを WordPress に登録しないが、
//   通知や CTA ボタン側では contact_url() / pricing_url() / checkout_url()
//   が常に内部ページ URL (?page=ai-auto-poster-contact 等) を生成する。
//   その結果、Free ユーザーが通知や「Pro にアップグレード」をクリック
//   すると WordPress が「このページにアクセスする権限がありません」を
//   返してしまう。
//
// 修正:
//   Freemius が登録すべきだった隠しサブメニュー(contact / pricing /
//   account / addons / affiliation)を、まだ未登録なら自前で登録する。
//   描画コールバックには Freemius 自身の _xxx_page_render() メソッドを
//   そのまま使うため、Premium ユーザーと全く同じ UI が表示される。
// ====================================================================
add_action( 'admin_menu', 'aiap_register_missing_freemius_pages', 999 );
/**
 * Freemius が条件付きで登録するサブメニューを、未登録なら補う
 */
function aiap_register_missing_freemius_pages() {
    if ( !function_exists( 'aiap_fs' ) ) {
        return;
    }
    $fs = aiap_fs();
    if ( !$fs ) {
        return;
    }
    global $_registered_pages;
    // Freemius のサブメニュースラッグ → 描画メソッド名 のマップ
    $pages = array(
        'ai-auto-poster-contact'     => '_contact_page_render',
        'ai-auto-poster-pricing'     => '_pricing_page_render',
        'ai-auto-poster-account'     => '_account_page_render',
        'ai-auto-poster-addons'      => '_addons_page_render',
        'ai-auto-poster-affiliation' => '_affiliation_page_render',
    );
    foreach ( $pages as $page_slug => $render_method ) {
        // Freemius がすでに登録済みならスキップ(WordPress 内部の登録済み一覧を確認)
        $hookname = get_plugin_page_hookname( $page_slug, 'ai-auto-poster' );
        if ( !empty( $_registered_pages[$hookname] ) ) {
            continue;
        }
        // Freemius インスタンスに対象メソッドが存在しなければスキップ
        if ( !method_exists( $fs, $render_method ) ) {
            continue;
        }
        // 一旦サブメニューとして登録(WordPress に ?page= URL ハンドラを認識させるため)
        add_submenu_page(
            'ai-auto-poster',
            // 親メニュー(プラグイン本体)
            '',
            // ページタイトル(空)
            '',
            // メニュータイトル
            'manage_options',
            $page_slug,
            array($fs, $render_method)
        );
        // サイドバーの可視メニューからは即座に除去(空メニュー項目による隙間を防ぐ)
        // この呼び出しは $submenu グローバル配列からのみ削除し、$_registered_pages
        // と admin_page フックは残るため、?page=...への直接アクセスは引き続き動作する
        remove_submenu_page( 'ai-auto-poster', $page_slug );
    }
}
