| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- <?php
- /**
- * Plugin Name: QDR - Studiou WC Mail Queue
- * Plugin URI: https://www.quadarax.com/plugins/studiou-wc-mail-queue
- * Description: Intercepts outbound WooCommerce transactional mail and sends it through a persistent, rate-limited queue so bulk order-status changes cannot exceed the host's outbound mail limit.
- * Version: 1.0.0
- * Requires at least: 6.8
- * Tested up to: 6.9.4
- * Requires PHP: 8.2
- * WC requires at least: 9.8
- * WC tested up to: 10.9.4
- * Author: QUADARAX
- * Author URI: https://www.quadarax.com
- * License: GPL v2 or later
- * License URI: https://www.gnu.org/licenses/gpl-2.0.html
- * Text Domain: studiou-wc-mail-queue
- * Domain Path: /languages
- *
- * Copyright (C) 2026 QUADARAX. All rights reserved.
- * Contact: Dalibor Votruba <dvotruba@quadarax.com>
- *
- * This is an mu-plugin. It lives in a subdirectory and is loaded by the stub in
- * `loader/studiou-wc-mail-queue.php` — see that file for install instructions.
- *
- * @package studiou-wc-mail-queue
- * @copyright 2026 QUADARAX
- * @author Dalibor Votruba <dvotruba@quadarax.com>
- * @license GPL-2.0-or-later
- */
- defined( 'ABSPATH' ) || exit;
- define( 'STUDIOU_WCMQ_VERSION', '1.0.0' );
- define( 'STUDIOU_WCMQ_FILE', __FILE__ );
- define( 'STUDIOU_WCMQ_DIR', plugin_dir_path( __FILE__ ) );
- define( 'STUDIOU_WCMQ_URL', plugin_dir_url( __FILE__ ) );
- define( 'STUDIOU_WCMQ_BASENAME', plugin_basename( __FILE__ ) );
- /**
- * Declare HPOS compatibility.
- *
- * Named (not a closure) so a test harness or another plugin can remove_action it.
- */
- function studiou_wcmq_declare_hpos_compat() {
- if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
- \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', STUDIOU_WCMQ_FILE, true );
- }
- }
- add_action( 'before_woocommerce_init', 'studiou_wcmq_declare_hpos_compat' );
- /**
- * Main plugin class.
- *
- * Its only jobs: guard on WooCommerce, load dependencies, instantiate managers.
- * Managers register their own hooks in their constructors / init().
- */
- final class Studiou_WC_Mail_Queue {
- /**
- * Worker instance. Held so its hooks stay attached.
- *
- * @var Studiou_WCMQ_Worker|null
- */
- private $worker = null;
- /**
- * Admin instance.
- *
- * @var Studiou_WCMQ_Admin|null
- */
- private $admin = null;
- /**
- * Boot entry point.
- */
- public static function init_plugin() {
- $class = __CLASS__;
- new $class();
- }
- /**
- * Constructor.
- */
- public function __construct() {
- if ( ! class_exists( 'WooCommerce' ) ) {
- add_action( 'admin_notices', array( $this, 'woocommerce_missing_notice' ) );
- return;
- }
- $this->load_dependencies();
- Studiou_WCMQ_DB::maybe_upgrade_db();
- Studiou_WCMQ_Utils_Log::init();
- Studiou_WCMQ_Context::init();
- Studiou_WCMQ_Interceptor::init();
- Studiou_WCMQ_State::init();
- Studiou_WCMQ_Reaper::init();
- Studiou_WCMQ_Retention::init();
- $this->worker = new Studiou_WCMQ_Worker();
- if ( is_admin() ) {
- $this->admin = new Studiou_WCMQ_Admin();
- }
- // Action Scheduler only exists once WooCommerce has loaded it. An mu-plugin
- // runs before regular plugins, so nothing AS-dependent may happen at file
- // scope or at plugins_loaded. Defer scheduling to `init`.
- add_action( 'init', array( $this, 'schedule_recurring_actions' ), 20 );
- }
- /**
- * Load class files.
- */
- private function load_dependencies() {
- $files = array(
- 'utils-log.php',
- 'class-wcmq-settings.php',
- 'class-wcmq-db.php',
- 'class-wcmq-state.php',
- 'class-wcmq-context.php',
- 'class-wcmq-queue.php',
- 'class-wcmq-interceptor.php',
- 'class-wcmq-notifier.php',
- 'class-wcmq-worker.php',
- 'class-wcmq-reaper.php',
- 'class-wcmq-retention.php',
- );
- if ( is_admin() ) {
- $files[] = 'class-wcmq-admin.php';
- }
- foreach ( $files as $file ) {
- require_once STUDIOU_WCMQ_DIR . 'includes/' . $file;
- }
- }
- /**
- * Register the recurring reaper + retention actions, exactly once each.
- *
- * Gated to admin / cron / WP-CLI. The `as_next_scheduled_action()` pre-check
- * inside each is what keeps this idempotent, but it is still two DB queries,
- * and running them on every front-end page view buys nothing: those contexts
- * are exactly the ones that never need to bootstrap a schedule.
- */
- public function schedule_recurring_actions() {
- if ( ! function_exists( 'as_schedule_recurring_action' ) ) {
- return;
- }
- $should_schedule = is_admin() || wp_doing_cron() || ( defined( 'WP_CLI' ) && WP_CLI );
- if ( ! $should_schedule ) {
- return;
- }
- Studiou_WCMQ_Reaper::schedule();
- Studiou_WCMQ_Retention::schedule();
- }
- /**
- * Admin notice shown when WooCommerce is absent.
- */
- public function woocommerce_missing_notice() {
- ?>
- <div class="notice notice-error">
- <p><?php esc_html_e( 'Studiou WC Mail Queue requires WooCommerce to be installed and active.', 'studiou-wc-mail-queue' ); ?></p>
- </div>
- <?php
- }
- }
- /**
- * Load translations before anything instantiates.
- *
- * `load_plugin_textdomain()` resolves its relative path against WP_PLUGIN_DIR,
- * so for an mu-plugin it looks in `wp-content/plugins/...` — a directory that
- * does not exist here, and the catalogue silently never loads. The mu-plugin
- * equivalent, `load_muplugin_textdomain()`, resolves against WPMU_PLUGIN_DIR.
- *
- * Both check WP_LANG_DIR/plugins/ first, so a translation shipped by the site
- * still wins. The fallback keeps this working if the plugin is ever dropped into
- * wp-content/plugins/ as a regular plugin instead.
- */
- function studiou_wcmq_load_textdomain() {
- $rel_path = basename( dirname( __FILE__ ) ) . '/languages';
- if ( load_muplugin_textdomain( 'studiou-wc-mail-queue', $rel_path ) ) {
- return;
- }
- load_plugin_textdomain( 'studiou-wc-mail-queue', false, $rel_path );
- }
- add_action( 'plugins_loaded', 'studiou_wcmq_load_textdomain', 5 );
- add_action( 'plugins_loaded', array( 'Studiou_WC_Mail_Queue', 'init_plugin' ), 10 );
|