Kristian could not find this plugin in the Nolimit menu, and it still called
itself WooCommerce Email Discount Manager. Both are the display side of the
family rename; neither touches the installed folder.
Plugin Name is now "Nolimit Discounts". The folder, the text domain, the option
prefix and the three tables are deliberately unchanged: WordPress identifies a
plugin as <folder>/<file>, so renaming the folder would not move the
installation on the live shop — it would put a second copy beside it and leave
the old one active. That is a migration with a shim, and it is Kristian's call
rather than a side effect of a label change.
includes/shared-menu.php added, byte-identical to the family copy
(f4862d5c…c63185), and the page moved from the WooCommerce submenu to the
shared parent. The page slug is unchanged, so admin.php?page=… still resolves
and an existing bookmark keeps working — moving a page between parents does not
move its URL.
The label is claimed through the wpmgr_menu_title filter from this plugin's own
bootstrap, NOT by editing the shared file. Which copy of that file a site loads
is decided by activation order, and PHP cannot un-declare a function, so the
first copy wins for the whole request: editing it would make a site with three
of five plugins updated show "Manager" or "Nolimit" depending on who booted
first, and change its mind when a plugin is deactivated. The filter is
idempotent and a plugin still on an older build changes nothing.
The filter is registered before the WooCommerce gate, for the same reason the
updater is: the shared menu's label must not depend on whether this particular
plugin got past a check.
Two assertions, both proven red first. The label is asserted through the filter
rather than by reading the menu, because the menu is only built in an admin
request and the smoke tests run under WP-CLI. And the shared file's sha256 is
asserted, so editing it goes red here rather than going strange on a shop —
appending one comment line produced:
FAIL and the shared menu file is the family copy, unedited
— expected 'f4862d5c…', got '45df6b55…'
Also caught in passing by the kit, working exactly as intended: the new
shared-menu.php was untracked, and "every file the plugin loads is a file the
release contains" failed rather than shipping a zip without it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
346 lines
9.9 KiB
PHP
346 lines
9.9 KiB
PHP
<?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.
|
|
*/
|
|
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' ) );
|
|
add_action( 'admin_init', array( $this, 'handle_actions' ) );
|
|
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
public function register_menu() {
|
|
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';
|
|
}
|
|
|
|
add_submenu_page(
|
|
$parent,
|
|
__( 'Discounts', 'wc-email-discount' ),
|
|
__( 'Discounts', 'wc-email-discount' ),
|
|
'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
|
|
}
|
|
}
|