load_dependencies(); $this->init_components(); $this->init_hooks(); } /** * Load required files */ private function load_dependencies() { // Security classes - SECURITY ENHANCEMENT require_once TICKET_TAILOR_PLUGIN_DIR . 'includes/class-security-logger.php'; // Core classes require_once TICKET_TAILOR_PLUGIN_DIR . 'includes/class-api-client.php'; require_once TICKET_TAILOR_PLUGIN_DIR . 'includes/class-event-manager.php'; require_once TICKET_TAILOR_PLUGIN_DIR . 'includes/class-order-manager.php'; require_once TICKET_TAILOR_PLUGIN_DIR . 'includes/class-webhook-handler.php'; require_once TICKET_TAILOR_PLUGIN_DIR . 'includes/class-admin.php'; require_once TICKET_TAILOR_PLUGIN_DIR . 'includes/class-blocks.php'; require_once TICKET_TAILOR_PLUGIN_DIR . 'includes/class-shortcodes.php'; require_once TICKET_TAILOR_PLUGIN_DIR . 'includes/class-template-loader.php'; // ENTERPRISE: Health check endpoint require_once TICKET_TAILOR_PLUGIN_DIR . 'includes/class-health-check.php'; } /** * Initialize components */ private function init_components() { $this->api = new Ticket_Tailor_API_Client(); $this->events = new Ticket_Tailor_Event_Manager($this->api); $this->orders = new Ticket_Tailor_Order_Manager($this->api); $this->webhooks = new Ticket_Tailor_Webhook_Handler($this->events, $this->orders); $this->admin = new Ticket_Tailor_Admin($this->api, $this->events, $this->orders); // ENTERPRISE: Health check for monitoring new Ticket_Tailor_Health_Check($this->api, $this->events, $this->orders); new Ticket_Tailor_Blocks($this->events); new Ticket_Tailor_Shortcodes($this->events, $this->orders); } /** * Initialize hooks */ private function init_hooks() { register_activation_hook(TICKET_TAILOR_PLUGIN_FILE, array($this, 'activate')); register_deactivation_hook(TICKET_TAILOR_PLUGIN_FILE, array($this, 'deactivate')); add_action('plugins_loaded', array($this, 'load_textdomain')); add_action('init', array($this, 'register_post_types')); add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets')); // Cron jobs for syncing add_action('ticket_tailor_sync_events', array($this->events, 'sync_all_events')); add_action('ticket_tailor_sync_orders', array($this->orders, 'sync_all_orders')); // PERFORMANCE FIX: Cron jobs for cleanup add_action('ticket_tailor_cleanup_security_logs', array($this, 'cleanup_security_logs')); add_action('ticket_tailor_cleanup_rate_limits', array($this, 'cleanup_rate_limits')); } /** * Enqueue frontend assets */ public function enqueue_frontend_assets() { // Enqueue frontend CSS wp_enqueue_style( 'ticket-tailor-frontend', TICKET_TAILOR_PLUGIN_URL . 'assets/css/frontend.css', array(), TICKET_TAILOR_VERSION ); } /** * Plugin activation - SECURITY ENHANCEMENT: Added custom capabilities */ public function activate() { // Create custom tables $this->create_tables(); // SECURITY ENHANCEMENT: Add custom capabilities $this->add_custom_capabilities(); // Schedule cron jobs if (!wp_next_scheduled('ticket_tailor_sync_events')) { wp_schedule_event(time(), 'hourly', 'ticket_tailor_sync_events'); } if (!wp_next_scheduled('ticket_tailor_sync_orders')) { wp_schedule_event(time(), 'hourly', 'ticket_tailor_sync_orders'); } // PERFORMANCE FIX: Schedule cleanup cron jobs if (!wp_next_scheduled('ticket_tailor_cleanup_security_logs')) { wp_schedule_event(time(), 'daily', 'ticket_tailor_cleanup_security_logs'); } if (!wp_next_scheduled('ticket_tailor_cleanup_rate_limits')) { wp_schedule_event(time(), 'hourly', 'ticket_tailor_cleanup_rate_limits'); } // Set activation time if (!get_option('ticket_tailor_activated_time')) { add_option('ticket_tailor_activated_time', time()); } // Set welcome transient set_transient('ticket_tailor_welcome_notice', true, 30); // Flush rewrite rules for custom post types flush_rewrite_rules(); } /** * Add custom capabilities - SECURITY ENHANCEMENT */ private function add_custom_capabilities() { // Administrator gets all capabilities $admin_role = get_role('administrator'); if ($admin_role) { $admin_role->add_cap('manage_ticket_tailor'); $admin_role->add_cap('view_ticket_tailor_dashboard'); $admin_role->add_cap('view_ticket_tailor_events'); $admin_role->add_cap('view_ticket_tailor_orders'); $admin_role->add_cap('sync_ticket_tailor_events'); $admin_role->add_cap('configure_ticket_tailor_settings'); $admin_role->add_cap('configure_ticket_tailor_webhooks'); } // Editor can view but not configure $editor_role = get_role('editor'); if ($editor_role) { $editor_role->add_cap('view_ticket_tailor_dashboard'); $editor_role->add_cap('view_ticket_tailor_events'); $editor_role->add_cap('view_ticket_tailor_orders'); } // Shop Manager (WooCommerce) gets order access $shop_manager_role = get_role('shop_manager'); if ($shop_manager_role) { $shop_manager_role->add_cap('view_ticket_tailor_dashboard'); $shop_manager_role->add_cap('view_ticket_tailor_events'); $shop_manager_role->add_cap('view_ticket_tailor_orders'); $shop_manager_role->add_cap('sync_ticket_tailor_events'); } } /** * Plugin deactivation - FIXED VERSION (No security errors) */ public function deactivate() { // Prevent any admin actions from running during deactivation remove_all_actions('admin_init'); // Clear scheduled events safely $timestamp_events = wp_next_scheduled('ticket_tailor_sync_events'); if ($timestamp_events) { wp_unschedule_event($timestamp_events, 'ticket_tailor_sync_events'); } $timestamp_orders = wp_next_scheduled('ticket_tailor_sync_orders'); if ($timestamp_orders) { wp_unschedule_event($timestamp_orders, 'ticket_tailor_sync_orders'); } // Clear all scheduled hooks wp_clear_scheduled_hook('ticket_tailor_sync_events'); wp_clear_scheduled_hook('ticket_tailor_sync_orders'); wp_clear_scheduled_hook('ticket_tailor_cleanup_security_logs'); wp_clear_scheduled_hook('ticket_tailor_cleanup_rate_limits'); // Clear transients delete_transient('ticket_tailor_events_cache'); delete_transient('ticket_tailor_orders_cache'); delete_transient('ticket_tailor_welcome_notice'); // Clear any user notices - SECURITY FIX: Use prepared statements global $wpdb; $wpdb->query($wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like('_transient_ticket_tailor_notice_') . '%' )); $wpdb->query($wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like('_transient_timeout_ticket_tailor_notice_') . '%' )); // Flush rewrite rules flush_rewrite_rules(); } /** * Create custom database tables - ENTERPRISE: Optimized indexes */ private function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // Events cache table - ENTERPRISE: Added composite index $table_events = $wpdb->prefix . 'ticket_tailor_events'; $sql_events = "CREATE TABLE IF NOT EXISTS $table_events ( id bigint(20) NOT NULL AUTO_INCREMENT, event_id varchar(255) NOT NULL, event_data longtext NOT NULL, last_synced datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY event_id (event_id), KEY last_synced (last_synced), KEY event_id_synced (event_id, last_synced) ) $charset_collate;"; // Orders cache table - ENTERPRISE: Added composite indexes $table_orders = $wpdb->prefix . 'ticket_tailor_orders'; $sql_orders = "CREATE TABLE IF NOT EXISTS $table_orders ( id bigint(20) NOT NULL AUTO_INCREMENT, order_id varchar(255) NOT NULL, event_id varchar(255) NOT NULL, order_data longtext NOT NULL, last_synced datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY order_id (order_id), KEY event_id (event_id), KEY last_synced (last_synced), KEY event_synced (event_id, last_synced), KEY order_synced (order_id, last_synced) ) $charset_collate;"; // Webhook idempotency table - ENTERPRISE: Prevent duplicate webhook processing $table_webhooks = $wpdb->prefix . 'ticket_tailor_webhook_log'; $sql_webhooks = "CREATE TABLE IF NOT EXISTS $table_webhooks ( id bigint(20) NOT NULL AUTO_INCREMENT, webhook_id varchar(255) NOT NULL, event_type varchar(100) NOT NULL, processed_at datetime NOT NULL, ip_address varchar(45) NOT NULL, PRIMARY KEY (id), UNIQUE KEY webhook_id (webhook_id), KEY processed_at (processed_at), KEY event_type (event_type) ) $charset_collate;"; // Security log table $table_security = $wpdb->prefix . 'ticket_tailor_security_log'; $sql_security = "CREATE TABLE IF NOT EXISTS $table_security ( id bigint(20) NOT NULL AUTO_INCREMENT, event_type varchar(50) NOT NULL, user_id bigint(20) DEFAULT 0, ip_address varchar(45) NOT NULL, details longtext, timestamp datetime NOT NULL, PRIMARY KEY (id), KEY event_type (event_type), KEY timestamp (timestamp), KEY ip_address (ip_address) ) $charset_collate;"; // Rate limits table $table_rate_limits = $wpdb->prefix . 'ticket_tailor_rate_limits'; $sql_rate_limits = "CREATE TABLE IF NOT EXISTS $table_rate_limits ( id bigint(20) NOT NULL AUTO_INCREMENT, ip_address varchar(45) NOT NULL, endpoint varchar(255) NOT NULL, request_count int(11) NOT NULL DEFAULT 1, timestamp datetime NOT NULL, PRIMARY KEY (id), KEY ip_endpoint (ip_address, endpoint), KEY timestamp (timestamp) ) $charset_collate;"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta($sql_events); dbDelta($sql_orders); dbDelta($sql_webhooks); dbDelta($sql_security); dbDelta($sql_rate_limits); // Store database version update_option('ticket_tailor_db_version', '2.0'); } /** * Register custom post types * FIXED: Added show_in_menu => false to prevent duplicate menu */ public function register_post_types() { // Register Event post type for better WordPress integration register_post_type('tt_event', array( 'labels' => array( 'name' => __('Events', 'ticket-tailor'), 'singular_name' => __('Event', 'ticket-tailor'), 'add_new' => __('Add New Event', 'ticket-tailor'), 'add_new_item' => __('Add New Event', 'ticket-tailor'), 'edit_item' => __('Edit Event', 'ticket-tailor'), 'new_item' => __('New Event', 'ticket-tailor'), 'view_item' => __('View Event', 'ticket-tailor'), 'search_items' => __('Search Events', 'ticket-tailor'), 'not_found' => __('No events found', 'ticket-tailor'), 'not_found_in_trash' => __('No events found in trash', 'ticket-tailor'), 'all_items' => __('All Events', 'ticket-tailor'), 'menu_name' => __('Events', 'ticket-tailor'), ), 'public' => true, 'has_archive' => true, 'show_in_rest' => true, 'supports' => array('title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'), 'menu_icon' => 'dashicons-tickets-alt', 'rewrite' => array( 'slug' => 'events', 'with_front' => false, ), // FIX: Hide the automatic menu item since we have our own custom menu structure 'show_in_menu' => false, // This removes the duplicate "Events" menu // The events are still accessible through our custom Ticket Tailor menu 'capability_type' => 'post', 'capabilities' => array( 'publish_posts' => 'publish_tt_events', 'edit_posts' => 'edit_tt_events', 'edit_others_posts' => 'edit_others_tt_events', 'delete_posts' => 'delete_tt_events', 'delete_others_posts' => 'delete_others_tt_events', 'read_private_posts' => 'read_private_tt_events', 'edit_post' => 'edit_tt_event', 'delete_post' => 'delete_tt_event', 'read_post' => 'read_tt_event', ), 'map_meta_cap' => true, 'taxonomies' => array('tt_event_category', 'tt_event_tag'), 'show_ui' => true, 'show_in_nav_menus' => true, 'can_export' => true, 'exclude_from_search' => false, 'publicly_queryable' => true, )); // Register Event Category taxonomy register_taxonomy('tt_event_category', 'tt_event', array( 'labels' => array( 'name' => __('Event Categories', 'ticket-tailor'), 'singular_name' => __('Event Category', 'ticket-tailor'), 'search_items' => __('Search Event Categories', 'ticket-tailor'), 'all_items' => __('All Event Categories', 'ticket-tailor'), 'parent_item' => __('Parent Event Category', 'ticket-tailor'), 'parent_item_colon' => __('Parent Event Category:', 'ticket-tailor'), 'edit_item' => __('Edit Event Category', 'ticket-tailor'), 'update_item' => __('Update Event Category', 'ticket-tailor'), 'add_new_item' => __('Add New Event Category', 'ticket-tailor'), 'new_item_name' => __('New Event Category Name', 'ticket-tailor'), 'menu_name' => __('Categories', 'ticket-tailor'), ), 'hierarchical' => true, 'public' => true, 'show_ui' => true, 'show_admin_column' => true, 'show_in_nav_menus' => true, 'show_tagcloud' => true, 'show_in_rest' => true, 'rewrite' => array( 'slug' => 'event-category', 'with_front' => false, 'hierarchical' => true, ), )); // Register Event Tag taxonomy register_taxonomy('tt_event_tag', 'tt_event', array( 'labels' => array( 'name' => __('Event Tags', 'ticket-tailor'), 'singular_name' => __('Event Tag', 'ticket-tailor'), 'search_items' => __('Search Event Tags', 'ticket-tailor'), 'popular_items' => __('Popular Event Tags', 'ticket-tailor'), 'all_items' => __('All Event Tags', 'ticket-tailor'), 'edit_item' => __('Edit Event Tag', 'ticket-tailor'), 'update_item' => __('Update Event Tag', 'ticket-tailor'), 'add_new_item' => __('Add New Event Tag', 'ticket-tailor'), 'new_item_name' => __('New Event Tag Name', 'ticket-tailor'), 'separate_items_with_commas' => __('Separate event tags with commas', 'ticket-tailor'), 'add_or_remove_items' => __('Add or remove event tags', 'ticket-tailor'), 'choose_from_most_used' => __('Choose from the most used event tags', 'ticket-tailor'), 'menu_name' => __('Tags', 'ticket-tailor'), ), 'hierarchical' => false, 'public' => true, 'show_ui' => true, 'show_admin_column' => true, 'show_in_nav_menus' => true, 'show_tagcloud' => true, 'show_in_rest' => true, 'rewrite' => array( 'slug' => 'event-tag', 'with_front' => false, ), )); } /** * Load text domain for translations */ public function load_textdomain() { load_plugin_textdomain( 'ticket-tailor', false, dirname(plugin_basename(TICKET_TAILOR_PLUGIN_FILE)) . '/languages/' ); } /** * Check WooCommerce and HPOS status */ public function get_woocommerce_status() { if (!class_exists('WooCommerce')) { return array( 'active' => false, 'version' => null, 'hpos_enabled' => false, ); } $hpos_enabled = false; if (class_exists('\Automattic\WooCommerce\Utilities\OrderUtil')) { $hpos_enabled = \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled(); } return array( 'active' => true, 'version' => defined('WC_VERSION') ? WC_VERSION : WC()->version, 'hpos_enabled' => $hpos_enabled, ); } /** * Check if the plugin is properly configured */ public function is_configured() { $api_key = get_option('ticket_tailor_api_key', ''); return !empty($api_key); } /** * Get plugin version */ public function get_version() { return TICKET_TAILOR_VERSION; } /** * Get plugin URL */ public function get_plugin_url() { return TICKET_TAILOR_PLUGIN_URL; } /** * Get plugin path */ public function get_plugin_path() { return TICKET_TAILOR_PLUGIN_DIR; } /** * Log debug messages - SECURITY FIX: Store logs outside web root or with protection */ public function log($message, $level = 'info') { if (!get_option('ticket_tailor_debug_mode', false)) { return; } if (is_array($message) || is_object($message)) { $message = print_r($message, true); } // Sanitize message to prevent log injection $message = str_replace(array("\r", "\n"), ' ', $message); $log_entry = sprintf( '[%s] [%s] %s', date('Y-m-d H:i:s'), strtoupper(sanitize_key($level)), $message ); // Use WordPress uploads directory with .htaccess protection $upload_dir = wp_upload_dir(); $log_dir = $upload_dir['basedir'] . '/ticket-tailor-logs'; // Create log directory if it doesn't exist if (!file_exists($log_dir)) { wp_mkdir_p($log_dir); // Create .htaccess to deny web access $htaccess_content = "deny from all\n"; file_put_contents($log_dir . '/.htaccess', $htaccess_content); // Create index.php for additional protection file_put_contents($log_dir . '/index.php', ' 5242880) { rename($log_file, $log_dir . '/debug-' . date('Y-m-d-His') . '.log'); } error_log($log_entry . PHP_EOL, 3, $log_file); } /** * Cleanup security logs - PERFORMANCE FIX * Called by daily cron job */ public function cleanup_security_logs() { $security_logger = new Ticket_Tailor_Security_Logger(); $security_logger->cleanup_old_logs(); $this->log('Security logs cleanup completed', 'info'); } /** * Cleanup rate limit table - PERFORMANCE FIX * Called by hourly cron job */ public function cleanup_rate_limits() { global $wpdb; $table_name = $wpdb->prefix . 'ticket_tailor_rate_limits'; // Delete entries older than 24 hours $threshold = gmdate('Y-m-d H:i:s', time() - DAY_IN_SECONDS); $deleted = $wpdb->query($wpdb->prepare( "DELETE FROM {$table_name} WHERE timestamp < %s", $threshold )); if ($deleted !== false) { $this->log('Rate limit cleanup completed: ' . $deleted . ' entries removed', 'info'); } } /** * Handle plugin upgrade */ public function maybe_upgrade() { $current_version = get_option('ticket_tailor_version', '0.0.0'); if (version_compare($current_version, TICKET_TAILOR_VERSION, '<')) { // Perform upgrade tasks here if needed // Update version update_option('ticket_tailor_version', TICKET_TAILOR_VERSION); } } } /** * Initialize the plugin */ function ticket_tailor() { return Ticket_Tailor_Plugin::get_instance(); } // Declare WooCommerce HPOS compatibility add_action('before_woocommerce_init', function() { if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) { \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', TICKET_TAILOR_PLUGIN_FILE, true ); } }); // Check plugin dependencies add_action('admin_init', function() { // Skip checks during plugin activation/deactivation if (defined('WP_UNINSTALL_PLUGIN') || (isset($_REQUEST['action']) && in_array($_REQUEST['action'], array('activate', 'deactivate', 'delete')))) { return; } if (!is_plugin_active('ticket-tailor/ticket-tailor.php')) { return; } // Check PHP version if (version_compare(PHP_VERSION, '7.2', '<')) { deactivate_plugins(plugin_basename(TICKET_TAILOR_PLUGIN_FILE)); wp_die( esc_html__('Ticket Tailor requires PHP version 7.2 or higher. Please upgrade your PHP version.', 'ticket-tailor'), esc_html__('Plugin Activation Error', 'ticket-tailor'), array('back_link' => true) ); } // Check WordPress version if (version_compare(get_bloginfo('version'), '5.0', '<')) { deactivate_plugins(plugin_basename(TICKET_TAILOR_PLUGIN_FILE)); wp_die( esc_html__('Ticket Tailor requires WordPress version 5.0 or higher. Please upgrade WordPress.', 'ticket-tailor'), esc_html__('Plugin Activation Error', 'ticket-tailor'), array('back_link' => true) ); } }); // Handle uninstall register_uninstall_hook(TICKET_TAILOR_PLUGIN_FILE, 'ticket_tailor_uninstall'); function ticket_tailor_uninstall() { // Only run if explicitly uninstalling if (!defined('WP_UNINSTALL_PLUGIN')) { return; } // Remove options delete_option('ticket_tailor_api_key'); delete_option('ticket_tailor_cache_duration'); delete_option('ticket_tailor_currency'); delete_option('ticket_tailor_debug_mode'); delete_option('ticket_tailor_webhook_secret'); delete_option('ticket_tailor_version'); delete_option('ticket_tailor_db_version'); delete_option('ticket_tailor_activated_time'); // Remove style options delete_option('ticket_tailor_text_color'); delete_option('ticket_tailor_border_color'); delete_option('ticket_tailor_border_radius'); delete_option('ticket_tailor_button_bg'); delete_option('ticket_tailor_button_hover'); delete_option('ticket_tailor_button_text'); // Remove all transients - SECURITY FIX: Use prepared statements global $wpdb; $wpdb->query($wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like('_transient_ticket_tailor_') . '%' )); $wpdb->query($wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like('_transient_timeout_ticket_tailor_') . '%' )); // Remove custom tables - SECURITY FIX: Validate table names $events_table = $wpdb->prefix . 'ticket_tailor_events'; $orders_table = $wpdb->prefix . 'ticket_tailor_orders'; $webhooks_table = $wpdb->prefix . 'ticket_tailor_webhook_log'; $security_table = $wpdb->prefix . 'ticket_tailor_security_log'; $rate_limits_table = $wpdb->prefix . 'ticket_tailor_rate_limits'; // Validate table names match expected pattern if (preg_match('/^[a-zA-Z0-9_]+$/', $events_table)) { $wpdb->query("DROP TABLE IF EXISTS `{$events_table}`"); } if (preg_match('/^[a-zA-Z0-9_]+$/', $orders_table)) { $wpdb->query("DROP TABLE IF EXISTS `{$orders_table}`"); } if (preg_match('/^[a-zA-Z0-9_]+$/', $webhooks_table)) { $wpdb->query("DROP TABLE IF EXISTS `{$webhooks_table}`"); } if (preg_match('/^[a-zA-Z0-9_]+$/', $security_table)) { $wpdb->query("DROP TABLE IF EXISTS `{$security_table}`"); } if (preg_match('/^[a-zA-Z0-9_]+$/', $rate_limits_table)) { $wpdb->query("DROP TABLE IF EXISTS `{$rate_limits_table}`"); } // Clear scheduled hooks wp_clear_scheduled_hook('ticket_tailor_sync_events'); wp_clear_scheduled_hook('ticket_tailor_sync_orders'); wp_clear_scheduled_hook('ticket_tailor_cleanup_security_logs'); wp_clear_scheduled_hook('ticket_tailor_cleanup_rate_limits'); // Remove all posts of custom post type $posts = get_posts(array( 'post_type' => 'tt_event', 'numberposts' => -1, 'post_status' => 'any' )); foreach ($posts as $post) { wp_delete_post($post->ID, true); } // Remove all terms from custom taxonomies $terms = get_terms(array( 'taxonomy' => array('tt_event_category', 'tt_event_tag'), 'hide_empty' => false, )); foreach ($terms as $term) { wp_delete_term($term->term_id, $term->taxonomy); } // Flush rewrite rules flush_rewrite_rules(); } // Start the plugin - FIXED: Delay initialization until WordPress is fully loaded add_action('plugins_loaded', 'ticket_tailor');