init_hooks(); } /** * Initialize hooks. */ private function init_hooks() { add_action( 'plugins_loaded', array( $this, 'init' ), 10 ); add_action( 'before_woocommerce_init', array( $this, 'declare_wc_compatibility' ) ); add_action( 'admin_init', array( $this, 'ensure_pages' ) ); add_filter( 'cron_schedules', array( $this, 'add_cron_interval' ) ); register_activation_hook( __FILE__, array( $this, 'activate' ) ); register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) ); } /** * Declare WooCommerce feature compatibility. * * HPOS (custom order tables) is fully supported. The gateway targets the * classic checkout; it simply does not register with block checkout, and * payment_fields() shows a clear message there, so no incompatibility is * declared for cart/checkout blocks (that only produces an admin notice). */ public function declare_wc_compatibility() { if ( class_exists( '\Automattic\WooCommerce\Utilities\FeaturesUtil' ) ) { \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true ); } } /** * Initialize plugin. */ public function init() { $this->load_dependencies(); $this->init_classes(); } /** * Load plugin dependencies. */ private function load_dependencies() { // Core classes (always loaded). require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-afridirect-payment.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-payment-methods.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-admin-menu.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-admin-ui.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-dashboard-data.php'; // Standalone payments (always loaded). require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-payments.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-payments-cpt.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-admin-payments.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-forms-cpt.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-admin-forms-cpt.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-forms-helper.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-shortcodes.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-admin-payment-edit.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-receipts.php'; // WooCommerce-specific classes (only if WC is active). if ( class_exists( 'WooCommerce' ) ) { require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-payment-gateway.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-order-statuses.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-admin-dashboard.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-receipt-generator.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-expiration-handler.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-admin-woo-payments.php'; } // Shared classes (always loaded). require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-proof-handler.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-email-handler.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-ajax-handler.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-admin-methods.php'; // Admin UI classes (always loaded). require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-admin-overview.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-admin-actions.php'; require_once AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'includes/class-admin-settings.php'; } /** * Initialize plugin classes. */ private function init_classes() { // Core classes (always loaded). AfriDirect_Payment_Main::instance(); AfriDirect_Payment_Admin_Menu::instance(); // Must be initialized first. AfriDirect_Payment_Admin_UI::instance(); // Shared header, navigation and styles. // Admin UI classes (must be initialized early for menu ordering). AfriDirect_Payment_Admin_Overview::instance(); // Dashboard (first submenu). AfriDirect_Payment_Admin_Methods::instance(); // Payment Methods (second submenu). AfriDirect_Payment_Forms_CPT::instance(); AfriDirect_Payment_Admin_Forms_CPT::instance(); // Payment Forms (third submenu). AfriDirect_Payment_Admin_Payments::instance(); // Payments (Standalone) (fourth submenu). // WooCommerce-specific admin classes (only if WC is active). if ( class_exists( 'WooCommerce' ) ) { AfriDirect_Payment_Admin_Woo_Payments::instance(); // Payments (WooCommerce) (fifth submenu). } AfriDirect_Payment_Admin_Settings::instance(); // Settings (sixth submenu, WC only). // Other admin classes. AfriDirect_Payment_Admin_Actions::instance(); AfriDirect_Payment_Proof_Handler::instance(); AfriDirect_Payment_Email_Handler::instance(); AfriDirect_Payment_Ajax_Handler::instance(); // Standalone payments (always loaded). AfriDirect_Payments_CPT::instance(); AfriDirect_Payment_Admin_Payment_Edit::instance(); AfriDirect_Payment_Shortcodes::instance(); // WooCommerce-specific classes (only if WC is active). if ( class_exists( 'WooCommerce' ) ) { AfriDirect_Payment_Order_Statuses::instance(); AfriDirect_Payment_Admin_Dashboard::instance(); AfriDirect_Payment_Receipt_Generator::instance(); AfriDirect_Payment_Expiration_Handler::instance(); // Register payment gateway. add_filter( 'woocommerce_payment_gateways', array( $this, 'add_payment_gateway' ) ); } } /** * Add payment gateway to WooCommerce. * * @param array $gateways Existing gateways. * @return array */ public function add_payment_gateway( $gateways ) { $gateways[] = 'AfriDirect_Payment_Gateway'; return $gateways; } /** * Plugin activation. */ public function activate() { // Create upload directory. $upload_dir = wp_upload_dir(); if ( ! $upload_dir['error'] ) { $proof_dir = $upload_dir['basedir'] . '/afridirect-payment-proofs'; if ( ! file_exists( $proof_dir ) ) { wp_mkdir_p( $proof_dir ); // Add .htaccess for security. $htaccess_content = 'deny from all'; @file_put_contents( $proof_dir . '/.htaccess', $htaccess_content ); } } // Create required pages. $this->ensure_pages(); // Ensure cron schedule is registered. add_filter( 'cron_schedules', array( $this, 'add_cron_interval' ) ); // Schedule WP-Cron event for expiration checking (every 5 minutes). if ( ! wp_next_scheduled( 'afridirect_payment_check_expired_orders' ) ) { $schedules = wp_get_schedules(); $interval = isset( $schedules['afridirect_payment_5min'] ) ? 'afridirect_payment_5min' : 'hourly'; wp_schedule_event( time(), $interval, 'afridirect_payment_check_expired_orders' ); } // Flush rewrite rules. flush_rewrite_rules(); } /** * Make sure the front-end pages the plugin redirects to actually exist. * * Idempotent and cheap when the pages are already in place, so it is safe to * call on both activation and admin_init (the latter covers sites that were * already active before this version was installed). */ public function ensure_pages() { $this->ensure_page( 'afridirect_pending_page_id', '[afridirect_payment_pending]', 'afridirect-payment-pending', __( 'Payment Pending', 'afridirect-payment' ) ); $this->ensure_page( 'afridirect_receipt_page_id', '[afridirect_payment_receipt]', 'afridirect-payment-receipt', __( 'Payment Receipt', 'afridirect-payment' ) ); } /** * Ensure a single published page containing the given shortcode exists and its * ID is stored in the given option. * * @param string $option_key Option that stores the resolved page ID. * @param string $shortcode Shortcode the page must contain, e.g. '[afridirect_payment_pending]'. * @param string $slug Preferred post_name when a new page is created. * @param string $title Title used when a new page is created. */ private function ensure_page( $option_key, $shortcode, $slug, $title ) { // Already resolved to a valid page with the shortcode? Nothing to do. $page_id = (int) get_option( $option_key, 0 ); if ( $page_id ) { $page = get_post( $page_id ); if ( $page && 'page' === $page->post_type && 'publish' === $page->post_status && false !== strpos( $page->post_content, $shortcode ) ) { return; } } // A page with the preferred slug already exists. $existing = get_page_by_path( $slug, OBJECT, 'page' ); if ( $existing ) { if ( false === strpos( $existing->post_content, $shortcode ) ) { wp_update_post( array( 'ID' => $existing->ID, 'post_content' => trim( $existing->post_content . "\n\n" . $shortcode ), ) ); } update_option( $option_key, $existing->ID ); return; } // Some other page already contains the shortcode. $found = get_posts( array( 'post_type' => 'page', 'post_status' => 'any', 'posts_per_page' => 1, 's' => $shortcode, 'fields' => 'ids', ) ); if ( ! empty( $found ) ) { update_option( $option_key, (int) $found[0] ); return; } // Create the page. $new_id = wp_insert_post( array( 'post_title' => $title, 'post_name' => $slug, 'post_content' => $shortcode, 'post_status' => 'publish', 'post_type' => 'page', ) ); if ( $new_id && ! is_wp_error( $new_id ) ) { update_option( $option_key, (int) $new_id ); } } /** * Add custom cron interval (5 minutes). * * @param array $schedules Existing schedules. * @return array */ public function add_cron_interval( $schedules ) { if ( ! isset( $schedules['afridirect_payment_5min'] ) ) { $schedules['afridirect_payment_5min'] = array( 'interval' => 5 * MINUTE_IN_SECONDS, 'display' => __( 'Every 5 Minutes', 'afridirect-payment' ), ); } return $schedules; } /** * Plugin deactivation. */ public function deactivate() { // Clear scheduled cron event. $timestamp = wp_next_scheduled( 'afridirect_payment_check_expired_orders' ); if ( $timestamp ) { wp_unschedule_event( $timestamp, 'afridirect_payment_check_expired_orders' ); } flush_rewrite_rules(); } } /** * Initialize plugin. */ function afridirect_payment() { return AfriDirect_Payment::instance(); } // Start the plugin. afridirect_payment();