| 1 |
<?php |
| 2 |
/** |
| 3 |
* Addons admin page controller + AJAX toggle endpoint. |
| 4 |
* |
| 5 |
* @package EasyInvoice |
| 6 |
*/ |
| 7 |
|
| 8 |
namespace EasyInvoice\Controllers; |
| 9 |
|
| 10 |
use EasyInvoice\Addons\AddonManager; |
| 11 |
use EasyInvoice\Addons\AddonRegistry; |
| 12 |
use EasyInvoice\Addons\LicensePlanResolver; |
| 13 |
|
| 14 |
if (!defined('ABSPATH')) { |
| 15 |
exit; |
| 16 |
} |
| 17 |
|
| 18 |
class AddonsController { |
| 19 |
|
| 20 |
public const PAGE_SLUG = 'easy-invoice-addons'; |
| 21 |
public const NONCE = 'easy_invoice_addons_nonce'; |
| 22 |
|
| 23 |
public function init(): void { |
| 24 |
add_action('wp_ajax_easy_invoice_toggle_addon', [$this, 'ajaxToggleAddon']); |
| 25 |
add_action('admin_enqueue_scripts', [$this, 'enqueueAssets']); |
| 26 |
} |
| 27 |
|
| 28 |
/** |
| 29 |
* Page renderer (called by EasyInvoiceAdmin::mainPageContent). |
| 30 |
*/ |
| 31 |
public function display(): void { |
| 32 |
if (!current_user_can('manage_options')) { |
| 33 |
wp_die(__('Sorry, you are not allowed to access this page.', 'easy-invoice')); |
| 34 |
} |
| 35 |
|
| 36 |
$addons = AddonRegistry::all(); |
| 37 |
$current_plan = LicensePlanResolver::getCurrentPlan(); |
| 38 |
$upgrade_urls = [ |
| 39 |
LicensePlanResolver::PLAN_PROFESSIONAL => 'https://matrixaddons.com/plugins/easy-invoice/#pricing', |
| 40 |
LicensePlanResolver::PLAN_AGENCY => 'https://matrixaddons.com/plugins/easy-invoice/#pricing', |
| 41 |
]; |
| 42 |
|
| 43 |
// License banner state — surfaced below the page header instead of via |
| 44 |
// wp-admin's global admin_notices (which would appear ABOVE the header). |
| 45 |
$license_banner = $this->resolveLicenseBanner($current_plan); |
| 46 |
|
| 47 |
include EASY_INVOICE_PLUGIN_DIR . 'templates/admin/addons-page.php'; |
| 48 |
} |
| 49 |
|
| 50 |
/** |
| 51 |
* Decide whether to show a contextual license banner inside the addons page. |
| 52 |
* |
| 53 |
* @return array{type:string,title:string,message:string,cta_label:string,cta_url:string}|null |
| 54 |
*/ |
| 55 |
private function resolveLicenseBanner(string $current_plan): ?array { |
| 56 |
// No Pro plugin installed → encourage upgrade to unlock any addon. |
| 57 |
if (!function_exists('easy_invoice_has_pro') || !easy_invoice_has_pro()) { |
| 58 |
return [ |
| 59 |
'type' => 'info', |
| 60 |
'title' => __('You\'re on the Free version.', 'easy-invoice'), |
| 61 |
'message' => __('These addons require Easy Invoice Pro. Install Pro to unlock Personal-tier addons — recurring invoices, client portal, reports, item library, partial payments, and more — included at no extra cost. Activate a license key to enable Professional and Agency tiers.', 'easy-invoice'), |
| 62 |
'cta_label' => __('Get Easy Invoice Pro', 'easy-invoice'), |
| 63 |
'cta_url' => 'https://matrixaddons.com/plugins/easy-invoice/#pricing', |
| 64 |
]; |
| 65 |
} |
| 66 |
|
| 67 |
// Pro plugin is installed. Personal is granted without a license |
| 68 |
// (entry-level tier). A license unlocks Professional / Agency. |
| 69 |
$has_valid = class_exists('\\EasyInvoicePro\\Updater\\License') |
| 70 |
&& \EasyInvoicePro\Updater\License::has_valid_license(); |
| 71 |
|
| 72 |
if (!$has_valid) { |
| 73 |
// Pro installed but no license — Personal-tier addons are |
| 74 |
// already usable. Surface an "Activate License" CTA so the |
| 75 |
// merchant knows higher tiers exist. |
| 76 |
return [ |
| 77 |
'type' => 'info', |
| 78 |
'title' => __('You\'re on the Personal plan.', 'easy-invoice'), |
| 79 |
'message' => __('All Personal-tier addons are unlocked. Activate your Easy Invoice Pro license to enable Professional and Agency addons.', 'easy-invoice'), |
| 80 |
'cta_label' => __('Activate License', 'easy-invoice'), |
| 81 |
'cta_url' => admin_url('admin.php?page=easy-invoice-license'), |
| 82 |
]; |
| 83 |
} |
| 84 |
|
| 85 |
// License is valid but a lower tier than the addons that exist — soft suggestion. |
| 86 |
if ($current_plan === LicensePlanResolver::PLAN_PROFESSIONAL) { |
| 87 |
return [ |
| 88 |
'type' => 'info', |
| 89 |
'title' => __('You\'re on the Professional plan.', 'easy-invoice'), |
| 90 |
'message' => __('Upgrade to Agency to unlock Agency-tier addons like White-Label.', 'easy-invoice'), |
| 91 |
'cta_label' => __('See Agency plan', 'easy-invoice'), |
| 92 |
'cta_url' => 'https://matrixaddons.com/plugins/easy-invoice/#pricing', |
| 93 |
]; |
| 94 |
} |
| 95 |
|
| 96 |
return null; // All good — no banner. |
| 97 |
} |
| 98 |
|
| 99 |
/** |
| 100 |
* AJAX: toggle a single addon on/off. |
| 101 |
* |
| 102 |
* Expects POST: addon_id (string), enabled (0|1), _nonce. |
| 103 |
*/ |
| 104 |
public function ajaxToggleAddon(): void { |
| 105 |
check_ajax_referer(self::NONCE, '_nonce'); |
| 106 |
|
| 107 |
if (!current_user_can('manage_options')) { |
| 108 |
wp_send_json_error([ |
| 109 |
'code' => 'forbidden', |
| 110 |
'message' => __('Permission denied.', 'easy-invoice'), |
| 111 |
], 403); |
| 112 |
} |
| 113 |
|
| 114 |
$addon_id = isset($_POST['addon_id']) ? sanitize_key($_POST['addon_id']) : ''; |
| 115 |
$enabled = !empty($_POST['enabled']); |
| 116 |
|
| 117 |
if ($addon_id === '') { |
| 118 |
wp_send_json_error([ |
| 119 |
'code' => 'missing_addon_id', |
| 120 |
'message' => __('Addon ID is required.', 'easy-invoice'), |
| 121 |
], 400); |
| 122 |
} |
| 123 |
|
| 124 |
$result = $enabled |
| 125 |
? AddonManager::enable($addon_id) |
| 126 |
: AddonManager::disable($addon_id); |
| 127 |
|
| 128 |
if (!$result['success']) { |
| 129 |
$payload = [ |
| 130 |
'code' => $result['code'], |
| 131 |
'message' => $result['message'], |
| 132 |
]; |
| 133 |
if (!empty($result['required_plan'])) { |
| 134 |
$payload['required_plan'] = $result['required_plan']; |
| 135 |
$payload['required_plan_label'] = LicensePlanResolver::getPlanLabel($result['required_plan']); |
| 136 |
$payload['upgrade_url'] = $this->upgradeUrlFor($result['required_plan']); |
| 137 |
} |
| 138 |
wp_send_json_error($payload, 200); // 200 — payload tells client what to do |
| 139 |
} |
| 140 |
|
| 141 |
wp_send_json_success([ |
| 142 |
'code' => $result['code'], |
| 143 |
'message' => $result['message'], |
| 144 |
'status' => AddonManager::statusFor($addon_id), |
| 145 |
]); |
| 146 |
} |
| 147 |
|
| 148 |
private function upgradeUrlFor(string $plan): string { |
| 149 |
$base = 'https://matrixaddons.com/plugins/easy-invoice/#pricing'; |
| 150 |
return add_query_arg([ |
| 151 |
'plan' => $plan, |
| 152 |
'discount' => 'EIEARLYBIRD', |
| 153 |
'utm_source' => 'easy-invoice-plugin', |
| 154 |
'utm_medium' => 'addons-page', |
| 155 |
'utm_campaign' => 'addon-upgrade', |
| 156 |
], $base); |
| 157 |
} |
| 158 |
|
| 159 |
public function enqueueAssets(string $hook): void { |
| 160 |
$current_page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : ''; |
| 161 |
if ($current_page !== self::PAGE_SLUG) { |
| 162 |
return; |
| 163 |
} |
| 164 |
|
| 165 |
// Page chrome (sidebar, layout) comes from Easy Invoice's existing Tailwind UI; |
| 166 |
// we only need the toggle/upgrade JS for this page. |
| 167 |
wp_enqueue_script( |
| 168 |
'easy-invoice-addons-page', |
| 169 |
EASY_INVOICE_PLUGIN_URL . 'assets/js/addons-page.js', |
| 170 |
['jquery'], |
| 171 |
EASY_INVOICE_VERSION, |
| 172 |
true |
| 173 |
); |
| 174 |
|
| 175 |
wp_localize_script('easy-invoice-addons-page', 'EasyInvoiceAddons', [ |
| 176 |
'ajaxUrl' => admin_url('admin-ajax.php'), |
| 177 |
'nonce' => wp_create_nonce(self::NONCE), |
| 178 |
'strings' => [ |
| 179 |
'enabling' => __('Enabling…', 'easy-invoice'), |
| 180 |
'disabling' => __('Disabling…', 'easy-invoice'), |
| 181 |
'enableError' => __('Could not enable this addon.', 'easy-invoice'), |
| 182 |
'disableError' => __('Could not disable this addon.', 'easy-invoice'), |
| 183 |
'genericError' => __('Something went wrong. Please try again.', 'easy-invoice'), |
| 184 |
], |
| 185 |
]); |
| 186 |
} |
| 187 |
} |
| 188 |
|