add new classes License and LicenseAPI
This commit is contained in:
0
admin/assets/css/admin-licenses.css
Normal file
0
admin/assets/css/admin-licenses.css
Normal file
0
admin/assets/js/admin-licenses.js
Normal file
0
admin/assets/js/admin-licenses.js
Normal file
5
admin/page-licenses.php
Normal file
5
admin/page-licenses.php
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?php if ( ! defined( 'ABSPATH' ) ) exit; ?>
|
||||||
|
<div class="wrap formipay-page">
|
||||||
|
<h1><?php echo esc_html__('Licenses', 'formipay'); ?></h1>
|
||||||
|
<div id="formipay-licenses-table"></div>
|
||||||
|
</div>
|
||||||
@@ -50,6 +50,10 @@ class Init {
|
|||||||
\Formipay\Customer::get_instance();
|
\Formipay\Customer::get_instance();
|
||||||
\Formipay\Render::get_instance();
|
\Formipay\Render::get_instance();
|
||||||
\Formipay\Thankyou::get_instance();
|
\Formipay\Thankyou::get_instance();
|
||||||
|
|
||||||
|
\Formipay\License::get_instance();
|
||||||
|
\Formipay\LicenseAPI::get_instance();
|
||||||
|
|
||||||
new \Formipay\Token;
|
new \Formipay\Token;
|
||||||
|
|
||||||
do_action('formipay_init');
|
do_action('formipay_init');
|
||||||
|
|||||||
477
includes/License.php
Normal file
477
includes/License.php
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
<?php
|
||||||
|
namespace Formipay;
|
||||||
|
use Formipay\Traits\SingletonTrait;
|
||||||
|
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||||
|
|
||||||
|
class License {
|
||||||
|
use SingletonTrait;
|
||||||
|
|
||||||
|
const DB_VERSION_OPTION = 'formipay_licenses_db_version';
|
||||||
|
|
||||||
|
protected function __construct() {
|
||||||
|
// DB & boot
|
||||||
|
add_action('init', [$this, 'create_db']);
|
||||||
|
|
||||||
|
// Admin
|
||||||
|
add_action('admin_menu', [$this, 'add_menu']);
|
||||||
|
add_action('admin_enqueue_scripts', [$this, 'enqueue']);
|
||||||
|
|
||||||
|
// Admin AJAX
|
||||||
|
add_action('wp_ajax_formipay-tabledata-licenses', [$this, 'tabledata']);
|
||||||
|
add_action('wp_ajax_formipay-delete-license', [$this, 'delete']);
|
||||||
|
add_action('wp_ajax_formipay-bulk-delete-license', [$this, 'bulk_delete']);
|
||||||
|
|
||||||
|
// Issue on order lifecycle
|
||||||
|
add_action('formipay/order/completed', [$this, 'issue_for_order']);
|
||||||
|
add_action('formipay/order/new', [$this, 'maybe_issue_for_new_order']);
|
||||||
|
|
||||||
|
// Inject product settings via Product's filter surface
|
||||||
|
add_filter('formipay/product-config', [$this, 'inject_product_fields'], 65);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create/upgrade licenses table */
|
||||||
|
public function create_db() {
|
||||||
|
global $wpdb; $charset_collate = $wpdb->get_charset_collate();
|
||||||
|
$table = $wpdb->prefix . 'formipay_licenses';
|
||||||
|
$sql = "CREATE TABLE `$table` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||||
|
`created_date` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_date` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
`order_id` bigint(20) NOT NULL,
|
||||||
|
`form_id` int(11) NOT NULL,
|
||||||
|
`customer_email` varchar(191) NULL,
|
||||||
|
`license_key` varchar(191) NOT NULL,
|
||||||
|
`status` varchar(20) DEFAULT 'active',
|
||||||
|
`expires_at` datetime NULL,
|
||||||
|
`meta_data` longtext NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `order_id` (`order_id`),
|
||||||
|
KEY `form_id` (`form_id`),
|
||||||
|
KEY `customer_email` (`customer_email`),
|
||||||
|
KEY `status` (`status`),
|
||||||
|
KEY `expires_at` (`expires_at`)
|
||||||
|
) $charset_collate;";
|
||||||
|
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||||
|
dbDelta($sql);
|
||||||
|
// TODO: set/update self::DB_VERSION_OPTION if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add Licenses submenu */
|
||||||
|
public function add_menu() {
|
||||||
|
add_submenu_page(
|
||||||
|
'formipay',
|
||||||
|
__('Licenses', 'formipay'),
|
||||||
|
__('Licenses', 'formipay'),
|
||||||
|
'manage_options',
|
||||||
|
'formipay-licenses',
|
||||||
|
[$this, 'page_licenses']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function page_licenses() {
|
||||||
|
include FORMIPAY_PATH . 'admin/page-licenses.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Enqueue admin assets for Licenses page */
|
||||||
|
public function enqueue() {
|
||||||
|
global $current_screen; if (!$current_screen) return;
|
||||||
|
if ($current_screen->id === 'formipay_page_formipay-licenses') {
|
||||||
|
wp_enqueue_style('page-licenses', FORMIPAY_URL . 'admin/assets/css/admin-licenses.css', [], FORMIPAY_VERSION, 'all');
|
||||||
|
wp_enqueue_script('page-licenses', FORMIPAY_URL . 'admin/assets/js/admin-licenses.js', ['jquery', 'gridjs'], FORMIPAY_VERSION, true);
|
||||||
|
wp_localize_script('page-licenses', 'formipay_licenses_page', [
|
||||||
|
'ajax_url' => admin_url('admin-ajax.php'),
|
||||||
|
'site_url' => site_url(),
|
||||||
|
'columns' => [
|
||||||
|
'id' => __('ID','formipay'),
|
||||||
|
'product' => __('Product','formipay'),
|
||||||
|
'order' => __('Order','formipay'),
|
||||||
|
'email' => __('Email','formipay'),
|
||||||
|
'key' => __('Key','formipay'),
|
||||||
|
'status' => __('Status','formipay'),
|
||||||
|
'expiry' => __('Expiry','formipay'),
|
||||||
|
'date' => __('Date','formipay'),
|
||||||
|
],
|
||||||
|
'filter_form' => [
|
||||||
|
'products' => [
|
||||||
|
'placeholder' => __('Filter by Product','formipay'),
|
||||||
|
'noresult_text' => __('No results found','formipay')
|
||||||
|
],
|
||||||
|
'status' => [
|
||||||
|
'placeholder' => __('Filter by Status','formipay'),
|
||||||
|
'noresult_text' => __('No results found','formipay')
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'modal' => [
|
||||||
|
'delete' => [
|
||||||
|
'question' => __('Do you want to delete the license?','formipay'),
|
||||||
|
'cancelButton' => __('Cancel','formipay'),
|
||||||
|
'confirmButton' => __('Delete Permanently','formipay')
|
||||||
|
],
|
||||||
|
'bulk_delete' => [
|
||||||
|
'question' => __('Do you want to delete the selected license(s)?','formipay'),
|
||||||
|
'cancelButton' => __('Cancel','formipay'),
|
||||||
|
'confirmButton' => __('Confirm','formipay')
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'nonce' => wp_create_nonce('formipay-admin-licenses')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** GridJS data source */
|
||||||
|
public function tabledata() {
|
||||||
|
check_ajax_referer('formipay-admin-licenses', '_wpnonce');
|
||||||
|
|
||||||
|
global $wpdb;
|
||||||
|
$table = $wpdb->prefix . 'formipay_licenses';
|
||||||
|
|
||||||
|
// Filters / params
|
||||||
|
$status = isset($_REQUEST['status']) ? sanitize_text_field(wp_unslash($_REQUEST['status'])) : 'all';
|
||||||
|
$product = isset($_REQUEST['product']) ? (int) $_REQUEST['product'] : 0;
|
||||||
|
$search = isset($_REQUEST['search']) ? sanitize_text_field(wp_unslash($_REQUEST['search'])) : '';
|
||||||
|
$limit = isset($_REQUEST['limit']) ? max(1, (int)$_REQUEST['limit']) : 20;
|
||||||
|
$offset = isset($_REQUEST['offset']) ? max(0, (int)$_REQUEST['offset']) : 0;
|
||||||
|
|
||||||
|
$wheres = ["`id` > %d"];
|
||||||
|
$args = [0];
|
||||||
|
|
||||||
|
if ($status && $status !== 'all') {
|
||||||
|
$wheres[] = "`status` = %s";
|
||||||
|
$args[] = $status;
|
||||||
|
}
|
||||||
|
if ($product > 0) {
|
||||||
|
$wheres[] = "`form_id` = %d";
|
||||||
|
$args[] = $product;
|
||||||
|
}
|
||||||
|
if ($search !== '') {
|
||||||
|
// search in email or tail of license key
|
||||||
|
$wheres[] = "(`customer_email` LIKE %s OR `license_key` LIKE %s)";
|
||||||
|
$like = '%' . $wpdb->esc_like($search) . '%';
|
||||||
|
$args[] = $like;
|
||||||
|
$args[] = $like;
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_sql = implode(' AND ', $wheres);
|
||||||
|
|
||||||
|
// Total count
|
||||||
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
$total = (int) $wpdb->get_var($wpdb->prepare(
|
||||||
|
"SELECT COUNT(*) FROM $table WHERE $where_sql",
|
||||||
|
$args
|
||||||
|
));
|
||||||
|
|
||||||
|
// Results page
|
||||||
|
$args_page = $args;
|
||||||
|
$args_page[] = (int)$offset;
|
||||||
|
$args_page[] = (int)$limit;
|
||||||
|
|
||||||
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
$rows = $wpdb->get_results($wpdb->prepare(
|
||||||
|
"SELECT * FROM $table WHERE $where_sql ORDER BY `id` DESC LIMIT %d, %d",
|
||||||
|
$args_page
|
||||||
|
), ARRAY_A);
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
if (!empty($rows)) {
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$pid = (int)$r['form_id'];
|
||||||
|
$title = $pid ? get_the_title($pid) : '';
|
||||||
|
$results[] = [
|
||||||
|
'ID' => (int)$r['id'],
|
||||||
|
'title' => $title,
|
||||||
|
'order' => (int)$r['order_id'],
|
||||||
|
'email' => $r['customer_email'],
|
||||||
|
'key' => $this->mask_key($r['license_key']),
|
||||||
|
'status' => $this->compute_effective_status($r),
|
||||||
|
'expiry' => $r['expires_at'],
|
||||||
|
'date' => $r['created_date'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json([
|
||||||
|
'results' => $results,
|
||||||
|
'total' => $total,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Delete single license */
|
||||||
|
public function delete() {
|
||||||
|
check_ajax_referer('formipay-admin-licenses', '_wpnonce');
|
||||||
|
|
||||||
|
if (empty($_REQUEST['id'])) {
|
||||||
|
wp_send_json_error([
|
||||||
|
'title' => esc_html__('Failed', 'formipay'),
|
||||||
|
'message' => esc_html__('License id is missing.', 'formipay'),
|
||||||
|
'icon' => 'error'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
global $wpdb;
|
||||||
|
$id = (int) $_REQUEST['id'];
|
||||||
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
$deleted = $wpdb->delete($wpdb->prefix.'formipay_licenses', ['id' => $id], ['%d']);
|
||||||
|
|
||||||
|
if ($deleted) {
|
||||||
|
wp_send_json_success([
|
||||||
|
'title' => esc_html__('Deleted', 'formipay'),
|
||||||
|
'message' => esc_html__('License is deleted permanently.', 'formipay'),
|
||||||
|
'icon' => 'success'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_error([
|
||||||
|
'title' => esc_html__('Failed', 'formipay'),
|
||||||
|
'message' => esc_html__('Failed to delete license. Please try again.', 'formipay'),
|
||||||
|
'icon' => 'error'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Bulk delete */
|
||||||
|
public function bulk_delete() {
|
||||||
|
check_ajax_referer('formipay-admin-licenses', '_wpnonce');
|
||||||
|
|
||||||
|
$ids = isset($_REQUEST['ids']) ? (array) $_REQUEST['ids'] : [];
|
||||||
|
if (empty($ids)) {
|
||||||
|
wp_send_json_error([
|
||||||
|
'title' => esc_html__('Failed', 'formipay'),
|
||||||
|
'message' => esc_html__('There is no license selected. Please try again.', 'formipay'),
|
||||||
|
'icon' => 'error'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
global $wpdb;
|
||||||
|
$success = 0; $failed = 0;
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
$id = (int)$id;
|
||||||
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
$deleted = $wpdb->delete($wpdb->prefix.'formipay_licenses', ['id' => $id], ['%d']);
|
||||||
|
if ($deleted) $success++; else $failed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$report = '';
|
||||||
|
if ($success > 0) $report .= sprintf(__(' Deleted %d license(s).', 'formipay'), $success);
|
||||||
|
if ($failed > 0) $report .= sprintf(__(' Failed %d license(s).', 'formipay'), $failed);
|
||||||
|
|
||||||
|
wp_send_json_success([
|
||||||
|
'title' => esc_html__('Done!', 'formipay'),
|
||||||
|
'message' => trim($report) ?: esc_html__('No changes.', 'formipay'),
|
||||||
|
'icon' => 'info'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Issue on completed order */
|
||||||
|
public function issue_for_order($order) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
if (empty($order)) return;
|
||||||
|
|
||||||
|
// $order might be object (stdClass) or array (from filter/get)
|
||||||
|
$row = is_array($order) ? (object)$order : $order;
|
||||||
|
if (empty($row->id)) return;
|
||||||
|
|
||||||
|
// Unserialize DB columns if needed
|
||||||
|
$items = isset($row->items) ? maybe_unserialize($row->items) : [];
|
||||||
|
$formData = isset($row->form_data) ? maybe_unserialize($row->form_data) : [];
|
||||||
|
$metaData = isset($row->meta_data) ? maybe_unserialize($row->meta_data) : [];
|
||||||
|
$form_id = isset($row->form_id) ? (int)$row->form_id : 0;
|
||||||
|
|
||||||
|
// Quantity: prefer top-level qty in stored order_data; fallback to 1
|
||||||
|
$qty = 1;
|
||||||
|
if (is_array($formData) && isset($formData['qty'])) {
|
||||||
|
$qty = max(1, (int)$formData['qty']);
|
||||||
|
} elseif (is_array($items) && !empty($items[0]['qty'])) {
|
||||||
|
$qty = max(1, (int)$items[0]['qty']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($form_id <= 0) return;
|
||||||
|
|
||||||
|
// Read product license settings
|
||||||
|
$settings = $this->get_product_license_settings($form_id);
|
||||||
|
if (!$settings['enabled']) return;
|
||||||
|
|
||||||
|
// Idempotency: if per-qty is ON, expect qty rows; else expect 1 row
|
||||||
|
$expected = $settings['per_qty'] ? $qty : 1;
|
||||||
|
$already = (int)$wpdb->get_var(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT COUNT(*) FROM {$wpdb->prefix}formipay_licenses WHERE order_id=%d AND form_id=%d",
|
||||||
|
(int)$row->id, (int)$form_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if ($already >= $expected) return; // already issued enough
|
||||||
|
|
||||||
|
$to_issue = max(0, $expected - $already);
|
||||||
|
if ($to_issue < 1) return;
|
||||||
|
|
||||||
|
// Try to find buyer email from the submitted form payload
|
||||||
|
$customer_email = $this->extract_email_from_order($formData);
|
||||||
|
|
||||||
|
$issued = [];
|
||||||
|
for ($i = 0; $i < $to_issue; $i++) {
|
||||||
|
$plain_key = apply_filters('formipay/license/generate_key', $this->generate_key(), $row, $form_id, $i);
|
||||||
|
$expires_at = $settings['expiry_days'] > 0
|
||||||
|
? gmdate('Y-m-d H:i:s', time() + ($settings['expiry_days'] * DAY_IN_SECONDS))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
$row_data = [
|
||||||
|
'order_id' => (int)$row->id,
|
||||||
|
'form_id' => (int)$form_id,
|
||||||
|
'customer_email' => sanitize_text_field($customer_email),
|
||||||
|
'license_key' => sanitize_text_field($plain_key),
|
||||||
|
'status' => 'active',
|
||||||
|
'expires_at' => $expires_at,
|
||||||
|
'meta_data' => maybe_serialize([
|
||||||
|
'activations' => [
|
||||||
|
'max' => (int)$settings['max_activations'],
|
||||||
|
'count' => 0,
|
||||||
|
'devices'=> []
|
||||||
|
]
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
$wpdb->insert($wpdb->prefix.'formipay_licenses', $row_data);
|
||||||
|
$row_data['id'] = (int)$wpdb->insert_id;
|
||||||
|
$issued[] = $row_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($issued)) {
|
||||||
|
/**
|
||||||
|
* Fires after license rows are issued for an order.
|
||||||
|
*
|
||||||
|
* @param array $issued Rows that were created.
|
||||||
|
* @param object $order Original order object/row.
|
||||||
|
*/
|
||||||
|
do_action('formipay/license/issued', $issued, $row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Catch immediate completed orders */
|
||||||
|
public function maybe_issue_for_new_order($order) {
|
||||||
|
$row = is_array($order) ? (object)$order : $order;
|
||||||
|
if (!empty($row->status) && $row->status === 'completed') {
|
||||||
|
$this->issue_for_order($row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add small Licensing fields into Product General tab */
|
||||||
|
public function inject_product_fields($fields) {
|
||||||
|
// Inject a tiny group under General tab (consistent with your structure)
|
||||||
|
$group = [
|
||||||
|
'setting_product_licensing' => [
|
||||||
|
'type' => 'group_title',
|
||||||
|
'label' => __('Licensing', 'formipay'),
|
||||||
|
'description' => __('Enable license issuance for this product.', 'formipay'),
|
||||||
|
'group' => 'started'
|
||||||
|
],
|
||||||
|
'_formipay_license_enabled' => [
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'label' => __('Enable License', 'formipay'),
|
||||||
|
],
|
||||||
|
'_formipay_license_per_quantity' => [
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'label' => __('Issue per Quantity', 'formipay'),
|
||||||
|
'value' => true,
|
||||||
|
'dependency' => array(
|
||||||
|
'key' => '_formipay_license_enabled',
|
||||||
|
'value' => 'not_empty'
|
||||||
|
),
|
||||||
|
],
|
||||||
|
'_formipay_license_expiry_days' => [
|
||||||
|
'type' => 'number',
|
||||||
|
'label' => __('Expiry (days)', 'formipay'),
|
||||||
|
'value' => 0,
|
||||||
|
'dependency' => array(
|
||||||
|
'key' => '_formipay_license_enabled',
|
||||||
|
'value' => 'not_empty'
|
||||||
|
),
|
||||||
|
],
|
||||||
|
'_formipay_license_max_activations' => [
|
||||||
|
'type' => 'number',
|
||||||
|
'label' => __('Max Activations', 'formipay'),
|
||||||
|
'value' => 1,
|
||||||
|
'dependency' => array(
|
||||||
|
'key' => '_formipay_license_enabled',
|
||||||
|
'value' => 'not_empty'
|
||||||
|
)
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$last = array_key_last($group);
|
||||||
|
$group[$last]['group'] = 'ended';
|
||||||
|
|
||||||
|
$fields['formipay_product_settings']['licenses'] = array(
|
||||||
|
'name' => __('Licensing', 'formipay'),
|
||||||
|
'fields' => $group
|
||||||
|
);
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Helpers (signatures only) ===
|
||||||
|
|
||||||
|
/** Return masked key tail for UI */
|
||||||
|
private function mask_key($key) {
|
||||||
|
$len = strlen($key);
|
||||||
|
if ($len <= 8) return $key;
|
||||||
|
return substr($key, 0, 4) . str_repeat('*', $len - 8) . substr($key, -4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Compute effective status (treat past-expiry as expired) */
|
||||||
|
private function compute_effective_status($row) {
|
||||||
|
$status = is_array($row) ? ($row['status'] ?? '') : ($row->status ?? '');
|
||||||
|
$expires = is_array($row) ? ($row['expires_at'] ?? null) : ($row->expires_at ?? null);
|
||||||
|
|
||||||
|
if ($status === 'active' && !empty($expires)) {
|
||||||
|
// Compare in UTC
|
||||||
|
$now = gmdate('Y-m-d H:i:s');
|
||||||
|
if ($expires < $now) return 'expired';
|
||||||
|
}
|
||||||
|
return $status ?: 'active';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read product license settings */
|
||||||
|
private function get_product_license_settings($form_id) {
|
||||||
|
$enabled = (bool) get_post_meta($form_id, '_formipay_license_enabled', true);
|
||||||
|
$per_qty = (bool) get_post_meta($form_id, '_formipay_license_per_quantity', true);
|
||||||
|
$expiry_days = (int) get_post_meta($form_id, '_formipay_license_expiry_days', true);
|
||||||
|
$max_activations = (int) get_post_meta($form_id, '_formipay_license_max_activations', true);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'enabled' => $enabled,
|
||||||
|
'per_qty' => $per_qty,
|
||||||
|
'expiry_days' => max(0, $expiry_days),
|
||||||
|
'max_activations' => $max_activations > 0 ? $max_activations : 1,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generate a random human-friendly license key */
|
||||||
|
private function generate_key() {
|
||||||
|
// 20 hex chars => split in groups of 4: XXXX-XXXX-XXXX-XXXX-XXXX
|
||||||
|
$hex = strtoupper(bin2hex(random_bytes(10)));
|
||||||
|
return implode('-', str_split($hex, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Try to extract buyer email from stored order_data payload */
|
||||||
|
private function extract_email_from_order($formData) {
|
||||||
|
if (!is_array($formData)) return '';
|
||||||
|
// If payload structure is like ['form_data' => [field => ['value' => ...]]]
|
||||||
|
if (isset($formData['form_data']) && is_array($formData['form_data'])) {
|
||||||
|
foreach ($formData['form_data'] as $k => $v) {
|
||||||
|
$val = is_array($v) && isset($v['value']) ? $v['value'] : $v;
|
||||||
|
if (is_string($val) && filter_var($val, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
return $val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Also check first level
|
||||||
|
foreach ($formData as $k => $v) {
|
||||||
|
if (is_string($v) && filter_var($v, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
return $v;
|
||||||
|
}
|
||||||
|
if (is_array($v) && isset($v['value']) && filter_var($v['value'], FILTER_VALIDATE_EMAIL)) {
|
||||||
|
return $v['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
71
includes/LicenseAPI.php
Normal file
71
includes/LicenseAPI.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
namespace Formipay;
|
||||||
|
use Formipay\Traits\SingletonTrait;
|
||||||
|
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||||
|
|
||||||
|
class LicenseAPI {
|
||||||
|
use SingletonTrait;
|
||||||
|
|
||||||
|
protected function __construct() {
|
||||||
|
add_action('rest_api_init', [$this, 'register_routes']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register_routes() {
|
||||||
|
$ns = 'formipay/v1';
|
||||||
|
register_rest_route($ns, '/license/verify', [
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => [$this, 'verify'],
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
]);
|
||||||
|
register_rest_route($ns, '/license/activate', [
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => [$this, 'activate'],
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
]);
|
||||||
|
register_rest_route($ns, '/license/deactivate', [
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => [$this, 'deactivate'],
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
]);
|
||||||
|
register_rest_route($ns, '/license/revoke', [
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => [$this, 'revoke'],
|
||||||
|
'permission_callback' => [$this, 'revoke_permission'], // optional secret protection
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Handlers (stubs) ===
|
||||||
|
|
||||||
|
public function verify(\WP_REST_Request $req) {
|
||||||
|
// TODO: rate-limit, load by (key, form_id), compute effective status, return JSON
|
||||||
|
return new \WP_REST_Response(['ok' => true, 'status' => 'active']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function activate(\WP_REST_Request $req) {
|
||||||
|
// TODO: validate, idempotent per device_id, enforce max, update meta_data.activations
|
||||||
|
return new \WP_REST_Response(['ok' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deactivate(\WP_REST_Request $req) {
|
||||||
|
// TODO: remove device, idempotent
|
||||||
|
return new \WP_REST_Response(['ok' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function revoke(\WP_REST_Request $req) {
|
||||||
|
// TODO: optional header check, set status=revoked
|
||||||
|
return new \WP_REST_Response(['ok' => true, 'status' => 'revoked']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function revoke_permission(\WP_REST_Request $req) {
|
||||||
|
// TODO: if you want to protect revoke via an option-stored secret
|
||||||
|
return true; // or false
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Helpers (signatures only) ===
|
||||||
|
|
||||||
|
private function rate_limit_ok() { /* TODO */ return true; }
|
||||||
|
private function load_license($key, $form_id) { /* TODO */ return null; }
|
||||||
|
private function effective_status($row) { /* TODO */ return 'active'; }
|
||||||
|
private function update_activations($row, $device_id, $op = 'add', $meta = []) { /* TODO */ return $row; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user