<?php

/**
 * Plugin Name: Freelance Studio
 * Plugin URI: https://freelancestud.io
 * Description: Complete freelance business management for designers and developers. Manage proposals, contracts, projects, time tracking, invoices, expenses, and client relationships.
 * Version: 8.6.4
 * Author: Jenny Dougherty
 * Author URI: https://freelancestud.io
 * License: GPL v2 or later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: freelance-studio
 * Domain Path: /languages
 *
 * @fs_premium_only
 *   /includes/services/class-fs-google-calendar-service.php,
 *   /includes/controllers/class-fs-booking-controller.php,
 *   /includes/views/bookings/,
 *   /includes/views/public/booking/,
 *   /includes/models/class-fs-meeting-type.php,
 *   /includes/models/class-fs-availability.php,
 *   /includes/models/class-fs-booking.php,
 *   /includes/controllers/class-fs-project-templates-controller.php,
 *   /includes/views/project-templates/,
 *   /includes/models/class-fs-project-template.php,
 *   /includes/models/class-fs-tax-calculator.php,
 *   /includes/models/class-fs-tax-rates.php,
 *   /includes/models/class-fs-quarterly-tax.php,
 *   /includes/models/class-fs-annual-deduction.php,
 *   /includes/views/reporting/quarterly-tax-section.php,
 *   /includes/models/class-fs-email-template.php,
 *   /includes/models/class-fs-recurring-invoice.php,
 *   /includes/controllers/class-fs-recurring-invoices-controller.php,
 *   /includes/views/recurring-invoices/,
 *   /includes/models/class-fs-milestone.php,
 *   /includes/models/class-fs-project-file.php,
 *   /includes/services/class-fs-file-handler.php,
 *   /includes/premium/,
 *   /includes/controllers/class-fs-forms-controller.php,
 *   /includes/views/forms/,
 *   /includes/models/class-fs-form.php,
 *   /includes/models/class-fs-form-response.php,
 *   /includes/models/class-fs-form-template.php
 *
 */
// If this file is called directly, abort.
if ( !defined( 'WPINC' ) ) {
    die;
}
// Define plugin constants
define( 'JEEB_FS_VERSION', '8.6.4' );
define( 'JEEB_FS_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'JEEB_FS_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'JEEB_FS_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
/**
 * Initialize Freemius SDK.
 *
 * Uses function_exists / set_basename pattern so that when both the free
 * and premium versions are installed, one automatically deactivates.
 */
if ( function_exists( 'freelance_studio_fs' ) ) {
    // Free/premium sibling already active — just claim this file.
    freelance_studio_fs()->set_basename( false, __FILE__ );
} else {
    function freelance_studio_fs() {
        global $freelance_studio_fs;
        if ( !isset( $freelance_studio_fs ) ) {
            // Include Freemius SDK.
            require_once dirname( __FILE__ ) . '/freemius/start.php';
            $freelance_studio_fs = fs_dynamic_init( array(
                'id'               => '24684',
                'slug'             => 'freelance-studio',
                'premium_slug'     => 'freelance-studio-pro',
                'type'             => 'plugin',
                'public_key'       => 'pk_4f4afc1a21c6a1c70cdcfbeb08602',
                'is_premium'       => false,
                'premium_suffix'   => 'Pro',
                'has_addons'       => false,
                'has_paid_plans'   => true,
                'is_org_compliant' => true,
                'trial'            => array(
                    'days'               => 14,
                    'is_require_payment' => true,
                ),
                'has_affiliation'  => 'selected',
                'menu'             => array(
                    'slug'    => 'freelance-studio',
                    'contact' => false,
                    'support' => false,
                ),
                'is_live'          => true,
            ) );
        }
        return $freelance_studio_fs;
    }

    // Init Freemius.
    freelance_studio_fs();
    // Register uninstall cleanup with Freemius.
    // This replaces uninstall.php so Freemius can track uninstall feedback.
    function freelance_studio_fs_uninstall_cleanup() {
        // Check if user opted in to data deletion
        $delete_data = get_option( 'fs_delete_data_on_uninstall', '0' );
        if ( $delete_data !== '1' ) {
            return;
        }
        global $wpdb;
        // Remove all plugin tables
        $tables = array(
            'fs_projects',
            'fs_clients',
            'fs_client_contacts',
            'fs_invoices',
            'fs_invoice_items',
            'fs_invoice_projects',
            'fs_proposals',
            'fs_contracts',
            'fs_contract_sections',
            'fs_tasks',
            'fs_milestones',
            'fs_time_entries',
            'fs_expenses',
            'fs_bookings',
            'fs_meeting_types',
            'fs_availability',
            'fs_recurring_invoices',
            'fs_project_types',
            'fs_project_files',
            'fs_preset_services',
            'fs_form_templates',
            'fs_email_log',
            'fs_payment_reminders',
            'fs_annual_deductions',
            'fs_quarterly_tax_payments',
            'fs_manual_income',
            'fs_upload_tokens',
            'fs_user_prefs',
            'fs_task_templates'
        );
        foreach ( $tables as $table ) {
            $table_name = $wpdb->prefix . $table;
            $wpdb->query( "DROP TABLE IF EXISTS {$table_name}" );
        }
        // Remove all plugin options
        $options = $wpdb->get_col( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE 'fs\\_%'" );
        foreach ( $options as $option ) {
            delete_option( $option );
        }
        // Remove transients
        $transients = $wpdb->get_col( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_fs\\_%' OR option_name LIKE '_transient_timeout_fs\\_%'" );
        foreach ( $transients as $transient ) {
            delete_option( $transient );
        }
        // Clear scheduled cron events
        wp_clear_scheduled_hook( 'fs_daily_tasks' );
        wp_clear_scheduled_hook( 'fs_send_invoice_reminders' );
        wp_clear_scheduled_hook( 'fs_generate_recurring_invoices' );
        wp_clear_scheduled_hook( 'fs_send_payment_reminders' );
    }

    freelance_studio_fs()->add_action( 'after_uninstall', 'freelance_studio_fs_uninstall_cleanup' );
    // Signal that SDK was initiated.
    do_action( 'freelance_studio_fs_loaded' );
}
/**
 * Freemius Premium Gating Helper Functions
 *
 * Centralized functions for checking plan status and enforcing limits.
 * These helpers are used throughout the plugin to gate premium features.
 */
/**
 * Check if the current user is on the free plan (not paying and not in trial).
 *
 * @return bool True if on free plan.
 */
function fs_studio_is_free_plan() {
    // Demo mode: always premium
    if ( defined( 'FS_DEMO_MODE' ) && FS_DEMO_MODE ) {
        return false;
    }
    if ( !function_exists( 'freelance_studio_fs' ) ) {
        return false;
    }
    return freelance_studio_fs()->is_not_paying();
}

/**
 * Check if the current user can use premium features.
 * Returns true if the user has an active license or is in a trial.
 *
 * @return bool True if premium features are available.
 */
function fs_studio_can_use_premium() {
    // Demo mode: always premium
    if ( defined( 'FS_DEMO_MODE' ) && FS_DEMO_MODE ) {
        return true;
    }
    if ( !function_exists( 'freelance_studio_fs' ) ) {
        return true;
        // Fail open if SDK not loaded
    }
    return false;
}

/**
 * Free plan limits configuration.
 *
 * @return array Associative array of limit names => max values.
 */
function fs_studio_free_limits() {
    return array(
        'clients'             => 3,
        'projects_per_client' => 1,
        'invoice_sends'       => 3,
        'active_proposals'    => 1,
        'active_contracts'    => 1,
    );
}

/**
 * Check if a specific limit has been reached on the free plan.
 *
 * @param string $limit_type The type of limit to check.
 * @param array  $context    Optional context (e.g., client_id for per-client limits).
 * @return bool True if limit is reached (action should be blocked).
 */
function fs_studio_limit_reached(  $limit_type, $context = array()  ) {
    // Premium users have no limits.
    if ( !fs_studio_is_free_plan() ) {
        return false;
    }
    $limits = fs_studio_free_limits();
    switch ( $limit_type ) {
        case 'clients':
            $count = FS_Client::count( null, true );
            // Exclude internal "Self" client
            return $count >= $limits['clients'];
        case 'projects_per_client':
            $client_id = ( isset( $context['client_id'] ) ? (int) $context['client_id'] : 0 );
            if ( !$client_id ) {
                return false;
            }
            global $wpdb;
            $table = $wpdb->prefix . 'fs_projects';
            $active_count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE client_id = %d AND status IN ('draft', 'active')", $client_id ) );
            return $active_count >= $limits['projects_per_client'];
        case 'invoice_sends':
            $month_key = 'fs_invoice_sends_' . date( 'Y_m' );
            $sends = (int) get_option( $month_key, 0 );
            return $sends >= $limits['invoice_sends'];
        case 'active_proposals':
            global $wpdb;
            $table = $wpdb->prefix . 'fs_proposals';
            $active_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table} WHERE status IN ('draft', 'sent', 'viewed')" );
            return $active_count >= $limits['active_proposals'];
        case 'active_contracts':
            global $wpdb;
            $table = $wpdb->prefix . 'fs_contracts';
            $active_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table} WHERE status IN ('draft', 'sent', 'viewed')" );
            return $active_count >= $limits['active_contracts'];
        default:
            return false;
    }
}

/**
 * Get the current usage count for a specific limit.
 *
 * @param string $limit_type The type of limit.
 * @param array  $context    Optional context.
 * @return int Current count.
 */
function fs_studio_get_usage_count(  $limit_type, $context = array()  ) {
    switch ( $limit_type ) {
        case 'clients':
            return FS_Client::count( null, true );
        case 'projects_per_client':
            $client_id = ( isset( $context['client_id'] ) ? (int) $context['client_id'] : 0 );
            if ( !$client_id ) {
                return 0;
            }
            global $wpdb;
            $table = $wpdb->prefix . 'fs_projects';
            return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE client_id = %d AND status IN ('draft', 'active')", $client_id ) );
        case 'invoice_sends':
            $month_key = 'fs_invoice_sends_' . date( 'Y_m' );
            return (int) get_option( $month_key, 0 );
        case 'active_proposals':
            global $wpdb;
            $table = $wpdb->prefix . 'fs_proposals';
            return (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table} WHERE status IN ('draft', 'sent', 'viewed')" );
        case 'active_contracts':
            global $wpdb;
            $table = $wpdb->prefix . 'fs_contracts';
            return (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table} WHERE status IN ('draft', 'sent', 'viewed')" );
        default:
            return 0;
    }
}

/**
 * Increment the monthly invoice sends counter.
 */
function fs_studio_increment_invoice_sends() {
    $month_key = 'fs_invoice_sends_' . date( 'Y_m' );
    $sends = (int) get_option( $month_key, 0 );
    update_option( $month_key, $sends + 1 );
}

/**
 * Display a premium upgrade notice/prompt.
 *
 * @param string $feature  The feature key for the notice message.
 * @param string $context  Display context: 'inline', 'banner', 'modal', 'tooltip'.
 * @param bool   $echo     Whether to echo or return the HTML.
 * @return string|void HTML markup if $echo is false.
 */
function fs_studio_upgrade_notice(  $feature, $context = 'inline', $echo = true  ) {
    $messages = array(
        'clients'            => array(
            'title' => __( 'Client Limit Reached', 'freelance-studio' ),
            'text'  => __( 'You\'ve reached the 3-client limit on the free plan. Upgrade to Pro for unlimited clients.', 'freelance-studio' ),
        ),
        'projects'           => array(
            'title' => __( 'Project Limit Reached', 'freelance-studio' ),
            'text'  => __( 'Free plan allows 1 active project per client. Upgrade to Pro for unlimited projects.', 'freelance-studio' ),
        ),
        'invoice_sends'      => array(
            'title' => __( 'Invoice Send Limit Reached', 'freelance-studio' ),
            'text'  => __( 'You\'ve used all 3 invoice sends this month. Upgrade to Pro for unlimited sends.', 'freelance-studio' ),
        ),
        'pdf_download'       => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'PDF downloads are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'esignature'         => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Electronic signatures are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'timer'              => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'The live timer is available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'time_reports'       => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Time tracking reports are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'invoice_time'       => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Invoicing time entries is available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'booking'            => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Client scheduling and bookings are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'gcal'               => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Google Calendar sync is available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'templates'          => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Project and contract templates are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'tax_reports'        => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Tax estimation and quarterly reports are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'client_portal'      => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Client portals are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'recurring'          => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Recurring invoices are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'proposals'          => array(
            'title' => __( 'Proposal Limit Reached', 'freelance-studio' ),
            'text'  => __( 'Free plan allows 1 active proposal. Upgrade to Pro for unlimited proposals.', 'freelance-studio' ),
        ),
        'contracts'          => array(
            'title' => __( 'Contract Limit Reached', 'freelance-studio' ),
            'text'  => __( 'Free plan allows 1 active contract. Upgrade to Pro for unlimited contracts.', 'freelance-studio' ),
        ),
        'milestones'         => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Project milestones are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'file_uploads'       => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'File uploads for projects and clients are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'advanced_reports'   => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Client revenue breakdown, project profitability, and report exports are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'custom_emails'      => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Custom email templates are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'recurring_expenses' => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Recurring expenses are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'bill_expenses'      => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Billing expenses to client invoices is available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'forms'              => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Custom intake forms and client questionnaires are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
        'export_reports'     => array(
            'title' => __( 'Pro Feature', 'freelance-studio' ),
            'text'  => __( 'Report exports are available with Freelance Studio Pro.', 'freelance-studio' ),
        ),
    );
    $msg = ( isset( $messages[$feature] ) ? $messages[$feature] : array(
        'title' => __( 'Pro Feature', 'freelance-studio' ),
        'text'  => __( 'This feature requires Freelance Studio Pro.', 'freelance-studio' ),
    ) );
    $upgrade_url = ( function_exists( 'freelance_studio_fs' ) ? freelance_studio_fs()->get_upgrade_url() : '#' );
    $html = '';
    switch ( $context ) {
        case 'banner':
            $html = '<div class="fs-upgrade-banner">' . '<div class="fs-upgrade-banner-content">' . '<span class="fs-upgrade-badge">PRO</span> ' . '<strong>' . esc_html( $msg['title'] ) . '</strong> ' . esc_html( $msg['text'] ) . '</div>' . '<a href="' . esc_url( $upgrade_url ) . '" class="fs-btn fs-btn-primary fs-btn-small">' . __( 'Upgrade to Pro', 'freelance-studio' ) . '</a>' . '</div>';
            break;
        case 'tooltip':
            $html = '<span class="fs-pro-tooltip" title="' . esc_attr( $msg['text'] ) . '">' . '<span class="fs-upgrade-badge-sm">PRO</span>' . '</span>';
            break;
        case 'modal':
            $html = '<div class="fs-upgrade-modal-content">' . '<div class="fs-upgrade-icon">&#x1f31f;</div>' . '<h3>' . esc_html( $msg['title'] ) . '</h3>' . '<p>' . esc_html( $msg['text'] ) . '</p>' . '<a href="' . esc_url( $upgrade_url ) . '" class="fs-btn fs-btn-primary">' . __( 'Upgrade to Pro', 'freelance-studio' ) . '</a>' . '</div>';
            break;
        case 'inline':
        default:
            $html = '<div class="fs-upgrade-inline">' . '<span class="fs-upgrade-badge">PRO</span> ' . esc_html( $msg['text'] ) . ' ' . '<a href="' . esc_url( $upgrade_url ) . '">' . __( 'Upgrade Now', 'freelance-studio' ) . ' &rarr;</a>' . '</div>';
            break;
    }
    if ( $echo ) {
        echo $html;
    }
    return $html;
}

/**
 * Check if the "Powered by Freelance Studio" badge should be shown.
 * Shows on free plan only, on client-facing documents.
 *
 * @return bool True if badge should be displayed.
 */
function fs_studio_show_powered_badge() {
    return fs_studio_is_free_plan();
}

/**
 * The code that runs during plugin activation.
 */
function activate_freelance_studio() {
    require_once JEEB_FS_PLUGIN_DIR . 'includes/class-fs-activator.php';
    FS_Activator::activate();
    // Set flag to force database schema verification on next load
    update_option( 'fs_force_schema_check', '1' );
}

/**
 * The code that runs during plugin deactivation.
 */
function deactivate_freelance_studio() {
    require_once JEEB_FS_PLUGIN_DIR . 'includes/class-fs-deactivator.php';
    FS_Deactivator::deactivate();
}

register_activation_hook( __FILE__, 'activate_freelance_studio' );
register_deactivation_hook( __FILE__, 'deactivate_freelance_studio' );
/**
 * Load the core plugin class
 */
require JEEB_FS_PLUGIN_DIR . 'includes/class-freelance-studio.php';
/**
 * Load helper classes
 */
require JEEB_FS_PLUGIN_DIR . 'includes/helpers/class-fs-view-context.php';
require JEEB_FS_PLUGIN_DIR . 'includes/helpers/class-fs-db-helper.php';
/**
 * Load service classes (conditional for premium-only services)
 */
if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/services/class-fs-file-handler.php' ) ) {
    require JEEB_FS_PLUGIN_DIR . 'includes/services/class-fs-file-handler.php';
}
/**
 * Load plugin text domain for translations.
 * Must be on 'init' or later to avoid WP 6.7+ early loading notice.
 */
/**
 * Check and run migrations if needed
 */
function fs_check_migrations() {
    require_once JEEB_FS_PLUGIN_DIR . 'includes/class-fs-migrator.php';
    FS_Migrator::run();
}

add_action( 'plugins_loaded', 'fs_check_migrations' );
add_action( 'admin_init', 'fs_check_migrations', 5 );
/**
 * Flush rewrite rules when plugin version changes
 * This ensures URL routing works after plugin updates
 */
function fs_maybe_flush_rewrite_rules() {
    $stored_version = get_option( 'fs_rewrite_rules_version', '' );
    // Force flush if version changed
    if ( $stored_version !== JEEB_FS_VERSION ) {
        fs_flush_rewrite_rules_now();
        update_option( 'fs_rewrite_rules_version', JEEB_FS_VERSION );
    }
}

add_action( 'init', 'fs_maybe_flush_rewrite_rules', 99 );
/**
 * Force flush rewrite rules (called during activation and version updates)
 */
function fs_flush_rewrite_rules_now() {
    // Register the rewrite rules first
    if ( class_exists( 'FS_Frontend_Router' ) ) {
        FS_Frontend_Router::add_rewrite_rules();
    }
    // Flush the rules
    flush_rewrite_rules( true );
}

/**
 * AJAX handler to repair booking URLs
 */
function fs_ajax_repair_booking_urls() {
    check_ajax_referer( 'fs_repair_booking_urls', 'nonce' );
    if ( !current_user_can( 'manage_options' ) ) {
        wp_send_json_error( 'Permission denied' );
    }
    // Clear the version flag to force a re-flush
    delete_option( 'fs_rewrite_rules_version' );
    // Register and flush rules
    fs_flush_rewrite_rules_now();
    // Update the version
    update_option( 'fs_rewrite_rules_version', JEEB_FS_VERSION );
    wp_send_json_success( array(
        'message' => 'URLs repaired',
    ) );
}

add_action( 'wp_ajax_fs_repair_booking_urls', 'fs_ajax_repair_booking_urls' );
/**
 * Register activation hook to flush rules
 */
function fs_activate_rewrite_rules() {
    // Need to call this on activation AFTER the plugin is loaded
    add_action( 'init', function () {
        if ( class_exists( 'FS_Frontend_Router' ) ) {
            FS_Frontend_Router::add_rewrite_rules();
        }
        flush_rewrite_rules( true );
        update_option( 'fs_rewrite_rules_version', JEEB_FS_VERSION );
    }, 100 );
}

register_activation_hook( __FILE__, 'fs_activate_rewrite_rules' );
/**
 * Begins execution of the plugin.
 */
function run_freelance_studio() {
    $plugin = new Freelance_Studio();
    $plugin->run();
}

run_freelance_studio();