*/ private static $defaults = array( 'enabled' => false, 'batch_size' => 10, ); /** * Plugin display name. * * @var string */ public $name; /** * Plugin slug. * * @var string */ public $slug; /** * Menu title. * * @var string */ public $menu_title; /** * Get singleton instance. * * @return Plugin */ public static function get_instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Initialize plugin (called on newsletter_loaded hook). * * @return void */ public static function instance_init() { $instance = self::get_instance(); $instance->init(); } /** * Constructor. */ private function __construct() { parent::__construct( 'robotstxt-smtp-newsletter', ROBOTSTXT_SMTP_NEWSLETTER_VERSION, ROBOTSTXT_SMTP_NEWSLETTER_PATH ); $this->name = 'SMTP (by ROBOTSTXT)'; $this->slug = 'robotstxt-smtp-newsletter'; $this->menu_title = 'SMTP (by ROBOTSTXT)'; } /** * Setup options. * * Overrides parent to use our custom option name. * * @return void */ public function setup_options() { if ( $this->options ) { return; } // Load our options using our custom option name (not newsletter_ prefix). $this->options = get_option( 'robotstxt_smtp_newsletter_options', array() ); $this->options = wp_parse_args( $this->options, self::$defaults ); } /** * Initialize plugin hooks. * * Overrides parent init() method. * * @return void */ public function init() { parent::init(); // Load required classes. require_once ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'includes/class-queue.php'; require_once ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'includes/class-statistics.php'; require_once ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'includes/class-email-message.php'; // Initialize database tables. Queue::get_instance(); Statistics::get_instance(); // Load text domain. add_action( 'init', array( $this, 'load_textdomain' ) ); // Force batch size in Newsletter cron. add_filter( 'newsletter_max_emails', array( $this, 'filter_max_emails' ), 999 ); // Register admin pages. if ( is_admin() ) { require_once ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'admin/class-settings-page.php'; require_once ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'admin/class-emails-list-page.php'; require_once ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'admin/class-statistics-page.php'; add_action( 'admin_init', array( 'Robotstxt_SMTP_Newsletter\Admin\Settings_Page', 'register_settings_static' ) ); add_action( 'admin_menu', array( 'Robotstxt_SMTP_Newsletter\Admin\Emails_List_Page', 'register' ) ); add_action( 'admin_menu', array( 'Robotstxt_SMTP_Newsletter\Admin\Statistics_Page', 'register' ) ); } } /** * Filter Newsletter's max emails per execution. * * Forces Newsletter to use our configured batch_size. * * @param int $max_emails Newsletter's calculated max emails. * * @return int Our configured batch_size. */ public function filter_max_emails( $max_emails ) { // Only apply if our addon is enabled. $settings = $this->get_settings(); if ( empty( $settings['enabled'] ) ) { return $max_emails; } return absint( $settings['batch_size'] ?? 10 ); } /** * Load plugin text domain for translations. * * @return void */ public function load_textdomain() { load_plugin_textdomain( 'robotstxt-smtp-newsletter', false, dirname( plugin_basename( ROBOTSTXT_SMTP_NEWSLETTER_FILE ) ) . '/languages' ); } /** * Get the mailer instance. * * This method is called by the parent class to get the mailer * that should be registered with Newsletter. * * @return \NewsletterMailer */ public function get_mailer() { // Load required classes. require_once ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'includes/class-queue.php'; require_once ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'includes/class-statistics.php'; require_once ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'includes/class-email-message.php'; // Get our settings and map to Newsletter's expected format. $settings = $this->get_settings(); $smtp_settings = $this->get_smtp_settings(); // Calculate optimal speed (emails per hour) based on rate limits. $speed = $this->calculate_optimal_speed( $smtp_settings ); // CRITICAL: For queue-based architecture, speed must be high enough // that Newsletter loads all emails at once. Newsletter calculates: // batch_size = speed / (3600 / NEWSLETTER_CRON_INTERVAL) // To get batch_size = configured value, we need: // speed = batch_size * (3600 / NEWSLETTER_CRON_INTERVAL) $batch_size = $settings['batch_size'] ?? 10; // Ensure NEWSLETTER_CRON_INTERVAL is loaded if ( ! defined( 'NEWSLETTER_CRON_INTERVAL' ) ) { $cron_file = WP_PLUGIN_DIR . '/newsletter/includes/cron.php'; if ( file_exists( $cron_file ) ) { require_once $cron_file; } } // Calculate runs per hour based on Newsletter's cron interval $cron_interval = defined( 'NEWSLETTER_CRON_INTERVAL' ) ? NEWSLETTER_CRON_INTERVAL : 300; $runs_per_hour = (int) ( 3600 / $cron_interval ); $calculated_speed = max( $speed, $batch_size * $runs_per_hour ); // Newsletter expects 'turbo' for batch_size and 'speed' for emails/hour. $mailer_options = array( 'turbo' => $batch_size, 'speed' => $calculated_speed, ); // Detect if Amazon SES is active. if ( $this->is_amazon_ses_active() ) { require_once ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'includes/class-amazonses-mailer.php'; return new AmazonSES_Mailer( $mailer_options ); } else { require_once ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'includes/class-smtp-mailer.php'; return new SMTP_Mailer( $mailer_options ); } } /** * Get maximum emails per execution. * * Newsletter checks this method to determine batch size. * * @return int */ public function get_max_emails() { $settings = $this->get_settings(); return absint( $settings['batch_size'] ?? 10 ); } /** * Get batch size. * * Alternative method that Newsletter may check. * * @return int */ public function get_batch() { return $this->get_max_emails(); } /** * Override Newsletter's calculation of emails per run. * * Newsletter normally calculates: batch = speed / runs_per_hour * For queue-based architecture, we want to enqueue all emails at once. * * @return int Configured batch_size. */ public function get_emails_per_run() { $settings = $this->get_settings(); if ( empty( $settings['enabled'] ) ) { // If addon is disabled, use parent's calculation. return parent::get_emails_per_run(); } return absint( $settings['batch_size'] ?? 10 ); } /** * Calculate optimal speed (emails per hour) based on rate limits. * * @param array $smtp_settings SMTP settings with rate limits. * * @return int Emails per hour. */ private function calculate_optimal_speed( $smtp_settings ) { // Get rate limits from ROBOTSTXT SMTP settings. $rate_per_hour = absint( $smtp_settings['rate_limit_per_hour'] ?? 0 ); // If hourly limit is set, use it (with 90% safety margin). if ( $rate_per_hour > 0 ) { return (int) floor( $rate_per_hour * 0.9 ); } // If no hourly limit, calculate from per-second limit. $rate_per_second = absint( $smtp_settings['rate_limit_per_second'] ?? 0 ); if ( $rate_per_second > 0 ) { return (int) floor( $rate_per_second * 3600 * 0.9 ); } // If no limits set, use a conservative default based on delivery method. $is_ses = $this->is_amazon_ses_active(); return $is_ses ? 1000 : 500; } /** * Check if Amazon SES integration is active. * * @return bool */ private function is_amazon_ses_active() { if ( ! class_exists( '\Robotstxt_SMTP\Plugin' ) ) { return false; } return \Robotstxt_SMTP\Plugin::is_amazon_ses_integration_active(); } /** * Get plugin settings with defaults. * * @return array */ public function get_settings() { $options = get_option( 'robotstxt_smtp_newsletter_options', array() ); return wp_parse_args( $options, self::$defaults ); } /** * Get SMTP settings from robotstxt-smtp plugin. * * @return array */ public function get_smtp_settings() { if ( ! class_exists( '\Robotstxt_SMTP\Plugin' ) || ! class_exists( '\Robotstxt_SMTP\Admin\Settings_Page' ) ) { return array(); } // Check if network mode is enabled. $scope = get_site_option( 'robotstxt_smtp_configuration_scope', 'site' ); if ( 'network' === $scope ) { $settings = get_site_option( 'robotstxt_smtp_network_options', array() ); } else { $settings = get_option( 'robotstxt_smtp_options', array() ); } return $settings; } /** * Plugin activation hook. * * Creates database tables and migrates settings. * * @return void */ public static function activation_hook() { // Load required classes. require_once ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'includes/class-queue.php'; require_once ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'includes/class-statistics.php'; require_once ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'includes/class-email-message.php'; // Tables are created automatically via __construct(). Queue::get_instance(); Statistics::get_instance(); // Migrate settings: remove deprecated max_per_connection. $options = get_option( 'robotstxt_smtp_newsletter_options', array() ); if ( isset( $options['max_per_connection'] ) ) { unset( $options['max_per_connection'] ); update_option( 'robotstxt_smtp_newsletter_options', $options ); } } }