2026-08-23 15:38:14 +02:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Admin menu, tabs and routing.
|
|
|
|
|
*
|
|
|
|
|
* @package WC_Email_Discount_Manager
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Registers the admin page under WooCommerce and routes tabs/actions.
|
|
|
|
|
*/
|
|
|
|
|
class WC_EDM_Admin {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Admin page slug.
|
|
|
|
|
*/
|
2026-09-20 07:57:02 +02:00
|
|
|
/** Cache key for the dashboard card's counts. */
|
|
|
|
|
const CARD_TRANSIENT = 'wc_edm_card_counts';
|
|
|
|
|
|
2026-08-23 15:38:14 +02:00
|
|
|
const PAGE_SLUG = 'wc-email-discounts';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Groups controller.
|
|
|
|
|
*
|
|
|
|
|
* @var WC_EDM_Admin_Groups
|
|
|
|
|
*/
|
|
|
|
|
private $groups;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Emails controller.
|
|
|
|
|
*
|
|
|
|
|
* @var WC_EDM_Admin_Emails
|
|
|
|
|
*/
|
|
|
|
|
private $emails;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Shipping controller.
|
|
|
|
|
*
|
|
|
|
|
* @var WC_EDM_Admin_Shipping
|
|
|
|
|
*/
|
|
|
|
|
private $shipping;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Audit log controller.
|
|
|
|
|
*
|
|
|
|
|
* @var WC_EDM_Admin_Audit
|
|
|
|
|
*/
|
|
|
|
|
private $audit;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Hook everything up.
|
|
|
|
|
*/
|
|
|
|
|
public function __construct() {
|
|
|
|
|
// Each sub-controller lives in its own file. Guarding here means an
|
|
|
|
|
// incomplete plugin folder costs a tab, not the whole admin screen.
|
|
|
|
|
$this->groups = class_exists( 'WC_EDM_Admin_Groups' ) ? new WC_EDM_Admin_Groups() : null;
|
|
|
|
|
$this->emails = class_exists( 'WC_EDM_Admin_Emails' ) ? new WC_EDM_Admin_Emails() : null;
|
|
|
|
|
$this->shipping = class_exists( 'WC_EDM_Admin_Shipping' ) ? new WC_EDM_Admin_Shipping() : null;
|
|
|
|
|
$this->audit = class_exists( 'WC_EDM_Admin_Audit' ) ? new WC_EDM_Admin_Audit() : null;
|
|
|
|
|
|
|
|
|
|
add_action( 'admin_menu', array( $this, 'register_menu' ) );
|
2026-09-20 07:57:02 +02:00
|
|
|
add_filter( 'wpmgr_dashboard_cards', array( $this, 'dashboard_card' ) );
|
2026-08-23 15:38:14 +02:00
|
|
|
add_action( 'admin_init', array( $this, 'handle_actions' ) );
|
|
|
|
|
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-20 07:57:02 +02:00
|
|
|
/**
|
|
|
|
|
* This plugin's card on the shared Nolimit dashboard.
|
|
|
|
|
*
|
|
|
|
|
* The contract says implementations must not measure anything here,
|
|
|
|
|
* because this filter runs on an admin page load and a slow card makes
|
|
|
|
|
* somebody else's dashboard slow in a way they cannot attribute. Two
|
|
|
|
|
* COUNT(*) on small indexed tables is the most this may cost, and even
|
|
|
|
|
* that is cached: the dashboard is a page people leave open.
|
|
|
|
|
*
|
|
|
|
|
* @param array $cards The cards so far.
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
public function dashboard_card( $cards ) {
|
|
|
|
|
$counts = get_transient( self::CARD_TRANSIENT );
|
|
|
|
|
|
|
|
|
|
if ( ! is_array( $counts ) ) {
|
|
|
|
|
$counts = array(
|
|
|
|
|
'groups' => WC_EDM_DB::count_groups(),
|
|
|
|
|
'emails' => WC_EDM_DB::count_emails( true ),
|
|
|
|
|
);
|
|
|
|
|
set_transient( self::CARD_TRANSIENT, $counts, 5 * MINUTE_IN_SECONDS );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// "warn", not "fail": a shop that has installed this and not set it up
|
|
|
|
|
// yet is not broken, it is unfinished. Reserving red for actual
|
|
|
|
|
// breakage is what keeps red worth reading.
|
|
|
|
|
$status = $counts['groups'] > 0 && $counts['emails'] > 0 ? 'ok' : 'warn';
|
|
|
|
|
|
|
|
|
|
$cards[] = array(
|
|
|
|
|
'title' => __( 'Discounts', 'wc-email-discount' ),
|
|
|
|
|
'status' => $status,
|
|
|
|
|
'headline' => sprintf(
|
|
|
|
|
/* translators: %s: number of email addresses that currently receive a discount. */
|
|
|
|
|
_n( '%s address gets a discount', '%s addresses get a discount', $counts['emails'], 'wc-email-discount' ),
|
|
|
|
|
number_format_i18n( $counts['emails'] )
|
|
|
|
|
),
|
|
|
|
|
'lines' => array(
|
|
|
|
|
__( 'Groups', 'wc-email-discount' ) => number_format_i18n( $counts['groups'] ),
|
|
|
|
|
__( 'Active addresses', 'wc-email-discount' ) => number_format_i18n( $counts['emails'] ),
|
|
|
|
|
),
|
|
|
|
|
'url' => admin_url( 'admin.php?page=' . self::PAGE_SLUG ),
|
|
|
|
|
// The plugin's own colour. Deliberately not WooCommerce's brand
|
|
|
|
|
// purple; see the note in assets/icon.svg.
|
|
|
|
|
'colour' => '#1a7a52',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return $cards;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-23 15:38:14 +02:00
|
|
|
/**
|
2026-09-17 22:05:19 +02:00
|
|
|
* Register the page under the shared Nolimit menu.
|
|
|
|
|
*
|
|
|
|
|
* wpmgr_ensure_menu() must be called first and is a no-op when another of
|
|
|
|
|
* the five plugins already registered the parent — whichever booted first
|
|
|
|
|
* owns it, and none of them may assume it is theirs.
|
|
|
|
|
*
|
|
|
|
|
* The page slug is unchanged, so admin.php?page=… still resolves: moving a
|
|
|
|
|
* page between parents does not move its URL, and an existing bookmark or
|
|
|
|
|
* a link in an email keeps working.
|
2026-08-23 15:38:14 +02:00
|
|
|
*/
|
|
|
|
|
public function register_menu() {
|
2026-09-17 22:05:19 +02:00
|
|
|
if ( function_exists( 'wpmgr_ensure_menu' ) ) {
|
|
|
|
|
wpmgr_ensure_menu();
|
|
|
|
|
$parent = WPMGR_MENU_SLUG;
|
|
|
|
|
} else {
|
|
|
|
|
// The shared file is missing — a half-copied upgrade. Fall back to
|
|
|
|
|
// the old home rather than losing the page entirely.
|
|
|
|
|
$parent = 'woocommerce';
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-23 15:38:14 +02:00
|
|
|
add_submenu_page(
|
2026-09-17 22:05:19 +02:00
|
|
|
$parent,
|
|
|
|
|
__( 'Discounts', 'wc-email-discount' ),
|
|
|
|
|
__( 'Discounts', 'wc-email-discount' ),
|
2026-08-23 15:38:14 +02:00
|
|
|
'manage_woocommerce',
|
|
|
|
|
self::PAGE_SLUG,
|
|
|
|
|
array( $this, 'render_page' )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Enqueue admin CSS/JS on the plugin page only.
|
|
|
|
|
*
|
|
|
|
|
* @param string $hook Current admin page hook.
|
|
|
|
|
*/
|
|
|
|
|
public function enqueue_assets( $hook ) {
|
|
|
|
|
if ( false === strpos( $hook, self::PAGE_SLUG ) ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wp_enqueue_style(
|
|
|
|
|
'wc-edm-admin',
|
|
|
|
|
WC_EDM_PLUGIN_URL . 'assets/css/admin.css',
|
|
|
|
|
array(),
|
|
|
|
|
WC_EDM_VERSION
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
wp_enqueue_script(
|
|
|
|
|
'wc-edm-admin',
|
|
|
|
|
WC_EDM_PLUGIN_URL . 'assets/js/admin.js',
|
|
|
|
|
array( 'jquery', 'jquery-ui-sortable' ),
|
|
|
|
|
WC_EDM_VERSION,
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
wp_localize_script(
|
|
|
|
|
'wc-edm-admin',
|
|
|
|
|
'wcEdmAdmin',
|
|
|
|
|
array(
|
|
|
|
|
'confirmDelete' => __( 'Are you sure you want to delete this item? This cannot be undone.', 'wc-email-discount' ),
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the currently active tab.
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function current_tab() {
|
|
|
|
|
$tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'dashboard'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
|
|
|
$tabs = array_keys( $this->get_tabs() );
|
|
|
|
|
|
|
|
|
|
return in_array( $tab, $tabs, true ) ? $tab : 'dashboard';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Tab definitions.
|
|
|
|
|
*
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
private function get_tabs() {
|
|
|
|
|
$tabs = array( 'dashboard' => __( 'Dashboard', 'wc-email-discount' ) );
|
|
|
|
|
|
|
|
|
|
// Only offer tabs whose controller actually loaded.
|
|
|
|
|
if ( $this->groups ) {
|
|
|
|
|
$tabs['groups'] = __( 'Discount Groups', 'wc-email-discount' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $this->emails ) {
|
|
|
|
|
$tabs['emails'] = __( 'Email Assignments', 'wc-email-discount' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $this->shipping ) {
|
|
|
|
|
$tabs['shipping'] = __( 'Shipping Rules', 'wc-email-discount' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $this->audit ) {
|
|
|
|
|
$tabs['audit'] = __( 'Audit Log', 'wc-email-discount' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $tabs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle POST/GET actions before any output (allows redirects).
|
|
|
|
|
*/
|
|
|
|
|
public function handle_actions() {
|
|
|
|
|
if ( ! isset( $_GET['page'] ) || self::PAGE_SLUG !== $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch ( $this->current_tab() ) {
|
|
|
|
|
case 'groups':
|
|
|
|
|
$this->groups->handle_actions();
|
|
|
|
|
break;
|
|
|
|
|
case 'emails':
|
|
|
|
|
$this->emails->handle_actions();
|
|
|
|
|
break;
|
|
|
|
|
case 'shipping':
|
|
|
|
|
$this->shipping->handle_actions();
|
|
|
|
|
break;
|
|
|
|
|
case 'audit':
|
|
|
|
|
$this->audit->handle_actions();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render the admin page with tab navigation.
|
|
|
|
|
*/
|
|
|
|
|
public function render_page() {
|
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
|
|
|
wp_die( esc_html__( 'You do not have permission to access this page.', 'wc-email-discount' ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$current = $this->current_tab();
|
|
|
|
|
?>
|
|
|
|
|
<div class="wrap wc-edm-wrap">
|
|
|
|
|
<h1><?php esc_html_e( 'Email Discounts', 'wc-email-discount' ); ?></h1>
|
|
|
|
|
|
|
|
|
|
<?php $this->render_notices(); ?>
|
|
|
|
|
|
|
|
|
|
<nav class="nav-tab-wrapper wc-edm-nav">
|
|
|
|
|
<?php foreach ( $this->get_tabs() as $slug => $label ) : ?>
|
|
|
|
|
<a href="<?php echo esc_url( WC_EDM_Helpers::admin_url( array( 'tab' => $slug ) ) ); ?>"
|
|
|
|
|
class="nav-tab <?php echo $current === $slug ? 'nav-tab-active' : ''; ?>">
|
|
|
|
|
<?php echo esc_html( $label ); ?>
|
|
|
|
|
</a>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
<div class="wc-edm-content">
|
|
|
|
|
<?php
|
|
|
|
|
switch ( $current ) {
|
|
|
|
|
case 'groups':
|
|
|
|
|
$this->groups->render();
|
|
|
|
|
break;
|
|
|
|
|
case 'emails':
|
|
|
|
|
$this->emails->render();
|
|
|
|
|
break;
|
|
|
|
|
case 'shipping':
|
|
|
|
|
$this->shipping->render();
|
|
|
|
|
break;
|
|
|
|
|
case 'audit':
|
|
|
|
|
$this->audit->render();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
$this->render_dashboard();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
?>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render success/error notices based on the ?message= query arg.
|
|
|
|
|
*/
|
|
|
|
|
private function render_notices() {
|
|
|
|
|
if ( ! isset( $_GET['message'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$message = sanitize_key( wp_unslash( $_GET['message'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
|
|
|
|
|
|
|
|
$messages = array(
|
|
|
|
|
'saved' => array( 'success', __( 'Saved successfully.', 'wc-email-discount' ) ),
|
|
|
|
|
'deleted' => array( 'success', __( 'Deleted successfully.', 'wc-email-discount' ) ),
|
|
|
|
|
'error' => array( 'error', __( 'Something went wrong. Please try again.', 'wc-email-discount' ) ),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( ! isset( $messages[ $message ] ) ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list( $type, $text ) = $messages[ $message ];
|
|
|
|
|
|
|
|
|
|
printf(
|
|
|
|
|
'<div class="notice notice-%1$s is-dismissible"><p>%2$s</p></div>',
|
|
|
|
|
esc_attr( $type ),
|
|
|
|
|
esc_html( $text )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render the dashboard tab: stats cards, quick actions, recent groups.
|
|
|
|
|
*/
|
|
|
|
|
private function render_dashboard() {
|
|
|
|
|
$group_count = WC_EDM_DB::count_groups();
|
|
|
|
|
$email_count = WC_EDM_DB::count_emails();
|
|
|
|
|
$active_count = WC_EDM_DB::count_emails( true );
|
|
|
|
|
$recent = WC_EDM_DB::get_recent_groups( 5 );
|
|
|
|
|
?>
|
|
|
|
|
<div class="wc-edm-stats">
|
|
|
|
|
<div class="wc-edm-stat-card">
|
|
|
|
|
<span class="wc-edm-stat-number"><?php echo esc_html( $group_count ); ?></span>
|
|
|
|
|
<span class="wc-edm-stat-label"><?php esc_html_e( 'Discount Groups', 'wc-email-discount' ); ?></span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="wc-edm-stat-card">
|
|
|
|
|
<span class="wc-edm-stat-number"><?php echo esc_html( $email_count ); ?></span>
|
|
|
|
|
<span class="wc-edm-stat-label"><?php esc_html_e( 'Email Assignments', 'wc-email-discount' ); ?></span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="wc-edm-stat-card">
|
|
|
|
|
<span class="wc-edm-stat-number"><?php echo esc_html( $active_count ); ?></span>
|
|
|
|
|
<span class="wc-edm-stat-label"><?php esc_html_e( 'Active Assignments', 'wc-email-discount' ); ?></span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<h2><?php esc_html_e( 'Quick Actions', 'wc-email-discount' ); ?></h2>
|
|
|
|
|
<p class="wc-edm-quick-actions">
|
|
|
|
|
<a class="button button-primary" href="<?php echo esc_url( WC_EDM_Helpers::admin_url( array( 'tab' => 'groups', 'action' => 'new' ) ) ); ?>">
|
|
|
|
|
<?php esc_html_e( 'Add Discount Group', 'wc-email-discount' ); ?>
|
|
|
|
|
</a>
|
|
|
|
|
<a class="button button-primary" href="<?php echo esc_url( WC_EDM_Helpers::admin_url( array( 'tab' => 'emails', 'action' => 'new' ) ) ); ?>">
|
|
|
|
|
<?php esc_html_e( 'Add Email Assignment', 'wc-email-discount' ); ?>
|
|
|
|
|
</a>
|
|
|
|
|
<a class="button" href="<?php echo esc_url( WC_EDM_Helpers::admin_url( array( 'tab' => 'shipping' ) ) ); ?>">
|
|
|
|
|
<?php esc_html_e( 'Manage Shipping Rules', 'wc-email-discount' ); ?>
|
|
|
|
|
</a>
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<h2><?php esc_html_e( 'Recent Discount Groups', 'wc-email-discount' ); ?></h2>
|
|
|
|
|
<?php if ( empty( $recent ) ) : ?>
|
|
|
|
|
<p><?php esc_html_e( 'No discount groups yet. Create your first one to get started.', 'wc-email-discount' ); ?></p>
|
|
|
|
|
<?php else : ?>
|
|
|
|
|
<table class="widefat striped wc-edm-table">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th><?php esc_html_e( 'Name', 'wc-email-discount' ); ?></th>
|
|
|
|
|
<th><?php esc_html_e( 'Discount', 'wc-email-discount' ); ?></th>
|
|
|
|
|
<th><?php esc_html_e( 'Categories', 'wc-email-discount' ); ?></th>
|
|
|
|
|
<th><?php esc_html_e( 'Created', 'wc-email-discount' ); ?></th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<?php foreach ( $recent as $group ) : ?>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>
|
|
|
|
|
<a href="<?php echo esc_url( WC_EDM_Helpers::admin_url( array( 'tab' => 'groups', 'action' => 'edit', 'id' => (int) $group->id ) ) ); ?>">
|
|
|
|
|
<strong><?php echo esc_html( $group->name ); ?></strong>
|
|
|
|
|
</a>
|
|
|
|
|
</td>
|
|
|
|
|
<td><?php echo esc_html( wc_format_localized_decimal( $group->discount_percent ) ); ?>%</td>
|
|
|
|
|
<td><?php echo esc_html( count( WC_EDM_DB::get_group_category_ids( (int) $group->id ) ) ); ?></td>
|
|
|
|
|
<td><?php echo esc_html( date_i18n( get_option( 'date_format' ), strtotime( $group->created_at ) ) ); ?></td>
|
|
|
|
|
</tr>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
}
|