<?php
/**
 * Plugin Name:       EmpRoster
 * Plugin URI:        https://employeeroster.io
 * Description:       Complete staff rostering, shift scheduling, time & attendance, and leave management. No subscriptions. No per-user fees. Your data, your server.
 * Version:           2.8.22
 * Requires at least: 5.8
 * Requires PHP:      7.4
 * Author:            thealiflab
 * Author URI:        https://thealiflab.github.io/
 * License:           GPL v2 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       employee-roster
 * Domain Path:       /languages
 *

 *
 * @package EmpRoster
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

if ( ! function_exists( 'emr_fs' ) ) {
    /**
     * Freemius SDK not included in this distribution; returns null so that
     * any legacy caller degrades gracefully rather than fataling.
     */
    function emr_fs() {
        return null;
    }
}

define( 'EMPR_VERSION',     '2.8.22' );
define( 'EMPR_PLUGIN_DIR',  plugin_dir_path( __FILE__ ) );
define( 'EMPR_PLUGIN_URL',  plugin_dir_url( __FILE__ ) );
define( 'EMPR_PLUGIN_FILE', __FILE__ );
define( 'EMPR_PLUGIN_BASE', plugin_basename( __FILE__ ) );



$emr_includes = array(
    'includes/class-emr-db.php',
    'includes/class-emr-settings.php',
    'includes/class-emr-roles.php',
    'includes/class-emr-activator.php',
    'includes/class-emr-deactivator.php',
    'includes/class-emr-shifts.php',
    'includes/class-emr-assignments.php',
    'includes/class-emr-leave.php',
    'includes/class-emr-attendance.php',
    'includes/class-emr-availability.php',
    'includes/class-emr-swap-requests.php',
    'includes/class-emr-analytics.php',
    'includes/class-emr-leave-balance.php',
    'includes/class-emr-login-logs.php',
    'includes/class-emr-notifications.php',
    'includes/class-emr-ajax.php',
    'includes/class-emr-rest-api.php',
    'includes/class-emr-roster-shortcode.php',
    'admin/class-emr-admin.php',
    'public/class-emr-public.php',
);

foreach ( $emr_includes as $file ) {
    $path = EMPR_PLUGIN_DIR . $file;
    if ( file_exists( $path ) ) {
        require_once $path;
    } else {
        if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
            error_log( 'EmpRoster: Missing include file: ' . $path );
        }
    }
}

register_activation_hook(   EMPR_PLUGIN_FILE, array( 'EMPR_Activator',   'run' ) );
register_deactivation_hook( EMPR_PLUGIN_FILE, array( 'EMPR_Deactivator', 'run' ) );
register_uninstall_hook(    EMPR_PLUGIN_FILE, array( 'EMPR_Deactivator', 'uninstall' ) );


/**
 * Step 1 — Early user determination for emp-roster REST requests.
 *
 * On security-hardened WordPress sites, the cookie-based current user may not
 * be set when rest_authentication_errors fires. We hook determine_current_user
 * at priority 5 to ensure the session cookie is processed for our namespace
 * before any restriction plugin runs.
 */
function empr_determine_user_for_rest( $user_id ) {
    if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) {
        return $user_id;
    }
    $rest_prefix = trailingslashit( rest_get_url_prefix() );
    $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
    if ( false === strpos( $request_uri, $rest_prefix . 'emp-roster/' ) ) {
        return $user_id;
    }
    if ( $user_id ) {
        return $user_id;
    }
    $nonce = null;
    if ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
        $nonce = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_WP_NONCE'] ) );
    } elseif ( isset( $_REQUEST['_wpnonce'] ) ) {
        $nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
    }
    if ( $nonce ) {
        $verified = wp_verify_nonce( $nonce, 'wp_rest' );
        if ( $verified ) {
            $cookie_user = wp_validate_auth_cookie( '', 'logged_in' );
            if ( $cookie_user ) {
                return $cookie_user;
            }
        }
    }
    return $user_id;
}
add_filter( 'determine_current_user', 'empr_determine_user_for_rest', 5 );

/**
 * Step 2 — Clear any site-wide REST API restrictions for our namespace
 * once the user has been determined above.
 */
function empr_handle_rest_auth( $result ) {
    $rest_prefix = trailingslashit( rest_get_url_prefix() );
    $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';

    if ( false === strpos( $request_uri, $rest_prefix . 'emp-roster/' ) ) {
        return $result;
    }

    if ( true === $result ) {
        return $result;
    }

    if ( is_wp_error( $result ) && is_user_logged_in() ) {
        return null;
    }

    $nonce = null;
    if ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
        $nonce = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_WP_NONCE'] ) );
    } elseif ( isset( $_REQUEST['_wpnonce'] ) ) {
        $nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
    }

    if ( $nonce && wp_verify_nonce( $nonce, 'wp_rest' ) ) {
        return true;
    }

    return null;
}
add_filter( 'rest_authentication_errors', 'empr_handle_rest_auth', 1 );

/**
 * Staff logins are never suspended by this plugin. An older install may have
 * left behind an "access blocked" flag; clear it once here so no one is locked
 * out by stale state. Self-limiting — the option is absent afterwards.
 */
if ( false !== get_option( 'empr_plan_access_blocked' ) ) {
    delete_option( 'empr_plan_access_blocked' );
}

/**
 * Logins are never blocked here. This is a pass-through that keeps the hook
 * wiring intact without ever denying access. (The separate per-user
 * empr_deactivated check still applies where relevant.)
 *
 * @param WP_User|WP_Error $user
 * @param string           $password
 * @return WP_User|WP_Error
 */
function empr_profile_mode_gate( $user, $password ) {
    return $user;
}

function empr_profile_mode_login_styles() {
    // No login restriction applies, so no restriction styling is needed.
}

add_filter( 'wp_authenticate_user', 'empr_profile_mode_gate', 10, 2 );
add_action( 'login_enqueue_scripts', 'empr_profile_mode_login_styles' );
add_filter( 'shake_error_codes', function ( $codes ) {
    $codes[] = 'emr_profile_mode';
    return $codes;
} );

/**
 * Register taxonomies on the init hook so $wp_rewrite is available.
 */
function empr_register_taxonomies() {
    register_taxonomy( 'empr_attachment_type', 'attachment', array(
        'labels'            => array( 'name' => __( 'Attachment Type', 'employee-roster' ) ),
        'public'            => false,
        'show_ui'           => false,
        'show_admin_column' => false,
        'hierarchical'      => false,
    ) );
}
add_action( 'init', 'empr_register_taxonomies' );

/**
 * Bootstrap plugin after all plugins loaded.
 */
function empr_init() {
    // No load_plugin_textdomain() call: WordPress loads translations just-in-time
    // from the Text Domain + Domain Path headers above. Calling it here is redundant
    // and triggers a _load_textdomain_just_in_time notice on WP 6.7+.

    if ( class_exists( 'EMPR_Roles' ) )           { EMPR_Roles::init(); }
    if ( class_exists( 'EMPR_REST_API' ) )         { EMPR_REST_API::init(); }
    if ( class_exists( 'EMPR_Ajax' ) )             { EMPR_Ajax::init(); }
    if ( class_exists( 'EMPR_Admin' ) )            { EMPR_Admin::init(); }
    if ( class_exists( 'EMPR_Public' ) )           { EMPR_Public::init(); }
    if ( class_exists( 'EMPR_Roster_Shortcode' ) ) { EMPR_Roster_Shortcode::init(); }

    // Schedule the daily job sheet cleanup cron if not already registered.
    if ( ! wp_next_scheduled( 'empr_job_sheet_cleanup' ) ) {
        wp_schedule_event( time(), 'daily', 'empr_job_sheet_cleanup' );
    }


    // EMPR_LoginLogs does not ship here, so this cron is never scheduled.
    // Actively clear any pre-existing schedule (e.g. left over from an earlier
    // install on the same site) so it cannot fire and fatal.
    wp_clear_scheduled_hook( 'empr_login_logs_cleanup' );
}
add_action( 'plugins_loaded', 'empr_init' );
add_action( 'empr_job_sheet_cleanup',    array( 'EMPR_Ajax', 'cron_job_sheet_cleanup' ) );

add_action( 'admin_init', array( 'EMPR_Activator', 'maybe_upgrade' ) );

/**
 * Redirect roster-role users directly to the Employee Roster dashboard after login.
 *
 * WP administrators always land on the default WP admin page (index.php) so they
 * retain full access to the WordPress back-end. Only the three custom roster roles
 * (emr_employee, emr_supervisor, emr_manager) are redirected.
 *
 * @param string           $redirect_to           The redirect destination URL.
 * @param string           $requested_redirect_to The requested redirect URL from the login form.
 * @param WP_User|WP_Error $user                  The logged-in user object (or error).
 * @return string
 */
function empr_login_redirect( $redirect_to, $requested_redirect_to, $user ) {
    if ( is_wp_error( $user ) || ! ( $user instanceof WP_User ) ) {
        return $redirect_to;
    }
    if ( user_can( $user, 'manage_options' ) ) {
        return $redirect_to;
    }
    $roster_roles = array( 'emr_employee', 'emr_supervisor', 'emr_manager' );
    if ( ! empty( array_intersect( (array) $user->roles, $roster_roles ) ) ) {
        return admin_url( 'admin.php?page=employee-roster' );
    }
    return $redirect_to;
}
add_filter( 'login_redirect', 'empr_login_redirect', 10, 3 );

/**
 * Add Settings link on Plugins list page.
 */
function empr_plugin_action_links( $links ) {
    $s = '<a href="' . esc_url( admin_url( 'admin.php?page=employee-roster-settings' ) ) . '">'
        . esc_html__( 'Settings', 'employee-roster' ) . '</a>';
    array_unshift( $links, $s );
    return $links;
}
add_filter( 'plugin_action_links_' . EMPR_PLUGIN_BASE, 'empr_plugin_action_links' );
