diff --git a/composer.json b/composer.json
index b2da782c..b087386d 100644
--- a/composer.json
+++ b/composer.json
@@ -4,12 +4,12 @@
"license": "GPL-2.0-or-later",
"repositories": [
{ "type": "vcs", "url": "git@github.com:byteever/plugin.git" },
- { "type": "vcs", "url": "git@github.com:pluginever/framework-model.git" }
+ { "type": "vcs", "url": "git@github.com:byteever/models.git" }
],
"require": {
"php": ">=7.4",
- "byteever/plugin": "^1.0",
- "pluginever/framework-model": "dev-trunk"
+ "byteever/models": "dev-trunk",
+ "byteever/plugin": "^1.0"
},
"require-dev": {
"byteever/byteever-sniffs": "^1.1.3",
@@ -19,12 +19,12 @@
},
"autoload": {
"psr-4": {
- "WooCommerceSerialNumbers\\": "includes/"
+ "PluginEver\\SerialNumbers\\": "includes/"
}
},
"autoload-dev": {
"psr-4": {
- "WooCommerceSerialNumbers\\Tests\\": "tests/phpunit/"
+ "PluginEver\\SerialNumbers\\Tests\\": "tests/phpunit/"
}
},
"config": {
@@ -54,11 +54,11 @@
"extra": {
"strauss": {
"target_directory": "vendor",
- "namespace_prefix": "WooCommerceSerialNumbers\\",
- "classmap_prefix": "WooCommerceSerialNumbers__",
+ "namespace_prefix": "PluginEver\\SerialNumbers\\",
+ "classmap_prefix": "PluginEver_SerialNumbers__",
"packages": [
- "byteever/plugin",
- "pluginever/framework-model"
+ "byteever/models",
+ "byteever/plugin"
]
}
}
diff --git a/includes/API.php b/includes/API.php
index 099d8039..c8d180f8 100644
--- a/includes/API.php
+++ b/includes/API.php
@@ -1,10 +1,10 @@
get_product_id();
-
- if ( $product_id ) {
- update_post_meta( $product_id, '_is_serial_number', 'yes' );
- }
- }
- }
-
- /**
- * Delete activations.
- *
- * @param Key $key The key object.
- *
- * @since 1.4.6
- */
- public function delete_activations( $key ) {
- $activations = $key->get_activations();
- if ( $activations ) {
- foreach ( $activations as $activation ) {
- $activation->delete();
- }
- }
- }
-
- /**
- * Revoke order item keys.
- *
- * @param bool $revoke The revoke flag.
- *
- * @since 1.4.6
- */
- public function revoke_order_item_keys( $revoke ) {
- if ( 'yes' !== get_option( 'wc_serial_numbers_revoke_keys', 'yes' ) ) {
- $revoke = false;
- }
-
- return $revoke;
- }
-
-
- /**
- * Update activation count.
- *
- * @param Activation $activation The activation object.
- *
- * @since 1.0.0
- */
- public function update_activation_count( $activation ) {
- $key = Key::get( $activation->get_serial_id() );
- if ( $key ) {
- $key->recount_remaining_activation();
- }
- }
-}
diff --git a/includes/Activations/Activations.php b/includes/Activations/Activations.php
new file mode 100644
index 00000000..788f3408
--- /dev/null
+++ b/includes/Activations/Activations.php
@@ -0,0 +1,53 @@
+
+ */
+ public array $components = array(
+ Admin::class,
+ );
+
+ /**
+ * Register hooks.
+ *
+ * @since 1.0.0
+ * @return void
+ */
+ public function register(): void {
+ add_action( 'wc_serial_numbers_activation_inserted', array( $this, 'update_activation_count' ) );
+ add_action( 'wc_serial_numbers_activation_deleted', array( $this, 'update_activation_count' ) );
+ }
+
+ /**
+ * Update activation count.
+ *
+ * @param Activation $activation The activation object.
+ *
+ * @since 1.0.0
+ */
+ public function update_activation_count( $activation ) {
+ $key = Key::find( $activation->get_serial_id() );
+ if ( $key ) {
+ $key->recount_remaining_activation();
+ }
+ }
+}
diff --git a/includes/Activations/Admin.php b/includes/Activations/Admin.php
new file mode 100644
index 00000000..a3ff9d30
--- /dev/null
+++ b/includes/Activations/Admin.php
@@ -0,0 +1,36 @@
+app->template->view( 'admin.list-activations' );
+ }
+}
diff --git a/includes/Admin/ListTables/ActivationsTable.php b/includes/Activations/ListTable.php
similarity index 88%
rename from includes/Admin/ListTables/ActivationsTable.php
rename to includes/Activations/ListTable.php
index eeb078f4..368eca68 100644
--- a/includes/Admin/ListTables/ActivationsTable.php
+++ b/includes/Activations/ListTable.php
@@ -1,8 +1,10 @@
get_sortable_columns() ) && 'order_date' !== $orderby ) {
- $args['orderby'] = $orderby;
- }
-
$args = array(
- 'per_page' => $per_page,
- 'paged' => $current_page,
- 'orderby' => $orderby,
- 'order' => $order,
- 'product_id' => $product_id,
- 'order_id' => $order_id,
- 'customer_id' => $customer_id,
- 'include' => $id,
- 'search' => $search,
- 'serial_id' => $serial_id,
+ 'limit' => $per_page,
+ 'page' => $current_page,
+ 'orderby' => $orderby,
+ 'order' => $order,
+ 'product_id' => $product_id,
+ 'order_id' => $order_id,
+ 'include' => $id,
+ 'search' => $search,
+ 'serial_id' => $serial_id,
);
- $this->items = Activation::query( $args );
- $this->total_count = Activation::count( $args );
+ $this->items = wcsn_get_activations( $args );
+ $this->total_count = wcsn_get_activations( $args, true );
$this->set_pagination_args(
array(
@@ -134,7 +130,7 @@ public function process_bulk_actions( $doaction ) {
}
foreach ( $ids as $id ) { // Check the permissions on each.
- $key = Activation::get( $id );
+ $key = Activation::find( $id );
if ( ! $key ) {
continue;
}
diff --git a/includes/Admin/Admin.php b/includes/Admin/Admin.php
index b564e1cb..0d217870 100644
--- a/includes/Admin/Admin.php
+++ b/includes/Admin/Admin.php
@@ -1,8 +1,8 @@
id ) ? $screen->id : '';
- }
-
- // Ensure the table handler is only loaded once. Prevents multiple loads if a plugin calls check_ajax_referer many times.
- remove_action( 'current_screen', array( $this, 'setup_screen' ) );
- remove_action( 'check_ajax_referer', array( $this, 'setup_screen' ) );
- }
-
- /**
- * Validate screen options on update.
- *
- * @param bool|int $status Screen option value. Default false to skip.
- * @param string $option The option name.
- * @param int $value The number of rows to use.
- */
- public function save_screen_options( $status, $option, $value ) {
- if ( in_array( $option, array( 'wsn_keys_per_page', 'wsn_generators_per_page', 'wsn_activations_per_page' ), true ) ) {
- return $value;
- }
-
- return $status;
+ // The pro plugin removes these tabs by the pre-rename static callable names, so the
+ // callback identity must stay 'WooCommerceSerialNumbers\Admin\Menus::{method}'.
+ add_action( 'wc_serial_numbers_tools_tab_import', 'WooCommerceSerialNumbers\\Admin\\Menus::import_tab' );
+ add_action( 'wc_serial_numbers_tools_tab_export', 'WooCommerceSerialNumbers\\Admin\\Menus::export_tab' );
+ add_action( 'wc_serial_numbers_tools_tab_generators', 'WooCommerceSerialNumbers\\Admin\\Menus::generators_tab' );
}
/**
@@ -102,7 +61,7 @@ public function main_menu() {
__( 'Serial Keys', 'wc-serial-numbers' ),
'manage_woocommerce', // phpcs:ignore WordPress.WP.Capabilities.Unknown
'wc-serial-numbers',
- array( $this, 'output_main_page' )
+ $this->app->callback( array( Keys\Admin::class, 'output_page' ) )
);
}
@@ -122,7 +81,7 @@ public function activations_menu() {
__( 'Activations', 'wc-serial-numbers' ),
'manage_woocommerce', // phpcs:ignore WordPress.WP.Capabilities.Unknown
'wc-serial-numbers-activations',
- array( $this, 'output_activations_page' )
+ $this->app->callback( array( Activations\Admin::class, 'output_page' ) )
);
}
@@ -139,7 +98,7 @@ public function tools_menu() {
__( 'Tools', 'wc-serial-numbers' ),
'manage_woocommerce', // phpcs:ignore WordPress.WP.Capabilities.Unknown
'wc-serial-numbers-tools',
- array( $this, 'output_tools_page' )
+ $this->app->callback( array( Tools::class, 'output_page' ) )
);
}
@@ -156,7 +115,7 @@ public function reports_menu() {
__( 'Reports', 'wc-serial-numbers' ),
'manage_woocommerce', // phpcs:ignore WordPress.WP.Capabilities.Unknown
'wc-serial-numbers-reports',
- array( $this, 'output_reports_page' )
+ $this->app->callback( array( Reports::class, 'output_page' ) )
);
}
@@ -196,107 +155,6 @@ public function promo_menu() {
}
}
- /**
- * Output keys page.
- *
- * @since 1.0.0
- * @return void
- */
- public function output_main_page() {
- wp_verify_nonce( '_nonce' );
- $add = isset( $_GET['add'] ) ? true : false;
- $edit = isset( $_GET['edit'] ) ? absint( $_GET['edit'] ) : 0;
- if ( $edit ) {
- $key = new Key( $edit );
- if ( ! $key->exists() ) {
- wp_safe_redirect( remove_query_arg( 'edit' ) );
- exit();
- }
- }
-
- if ( $add ) {
- $key = new Key();
- include __DIR__ . '/views/html-edit-key.php';
- } elseif ( $edit ) {
- include __DIR__ . '/views/html-edit-key.php';
- } else {
- include __DIR__ . '/views/html-list-keys.php';
- }
- }
-
- /**
- * Output activations page.
- *
- * @since 1.0.0
- * @return void
- */
- public function output_activations_page() {
- Admin::view( 'html-list-activations.php' );
- }
-
-
- /**
- * Output tools page.
- *
- * @since 1.0.0
- * @return void
- */
- public function output_tools_page() {
- wp_verify_nonce( '_nonce' );
- $tabs = array(
- 'generators' => __( 'Generators', 'wc-serial-numbers' ),
- 'api' => __( 'API Toolkit', 'wc-serial-numbers' ),
- 'import' => __( 'Import', 'wc-serial-numbers' ),
- 'export' => __( 'Export', 'wc-serial-numbers' ),
- );
-
- // If software support is disabled, remove the activations tab.
- if ( ! wcsn_is_software_support_enabled() ) {
- unset( $tabs['api'] );
- }
-
- $tabs = apply_filters( 'wc_serial_numbers_tools_tabs', $tabs );
- $tab_ids = array_keys( $tabs );
- $current_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : reset( $tab_ids );
- $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
-
- Admin::view(
- 'html-tools.php',
- array(
- 'tabs' => $tabs,
- 'current_tab' => $current_tab,
- 'page' => $page,
- )
- );
- }
-
- /**
- * Output reports page.
- *
- * @since 1.0.0
- * @return void
- */
- public function output_reports_page() {
- wp_verify_nonce( '_nonce' );
- $tabs = array(
- 'stock' => __( 'Stock', 'wc-serial-numbers' ),
- );
-
- $tabs = apply_filters( 'wc_serial_numbers_reports_tabs', $tabs );
- $tab_ids = array_keys( $tabs );
- $current_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : reset( $tab_ids );
- $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
-
- Admin::view(
- 'html-reports.php',
- array(
- 'tabs' => $tabs,
- 'current_tab' => $current_tab,
- 'page' => $page,
- )
- );
- }
-
/**
* Redirect to pro page.
*
@@ -311,19 +169,6 @@ public function go_pro_redirect() {
}
}
- /**
- * Add status tab.
- *
- * @param array $tabs Tabs.
- *
- * @return array
- */
- public static function add_tools_status_tab( $tabs ) {
- $tabs['status'] = __( 'Status', 'wc-serial-numbers' );
-
- return $tabs;
- }
-
/**
* Import tab content.
*
@@ -331,22 +176,7 @@ public static function add_tools_status_tab( $tabs ) {
* @return void
*/
public static function import_tab() {
- ?>
-
-
- make( Tools::class )->import_tab();
}
/**
@@ -356,163 +186,16 @@ public static function import_tab() {
* @return void
*/
public static function export_tab() {
- ?>
-
- make( Tools::class )->export_tab();
}
/**
- * Getnerators tab content.
+ * Generators tab content.
*
* @since 1.4.6
* @return void
*/
public static function generators_tab() {
- ?>
-
- WCSN()->version,
- );
- if ( WCSN()->is_pro_active() && function_exists( 'wc_serial_numbers_pro' ) ) {
- $statuses['Serial Numbers Pro version'] = WCSN_PRO()->get_version();
- }
-
- // Check if required tables exist.
- $required_tables = array(
- 'serial_numbers',
- 'serial_numbers_activations',
- );
- foreach ( $required_tables as $table ) {
- $exists = $GLOBALS['wpdb']->get_var( $GLOBALS['wpdb']->prepare( 'SHOW TABLES LIKE %s', $GLOBALS['wpdb']->prefix . $table ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
- if ( $exists ) {
- $statuses[ $table ] = __( 'Table exists', 'wc-serial-numbers' );
- } else {
- $statuses[ $table ] = __( 'Table does not exist', 'wc-serial-numbers' );
- }
- }
-
- // Cron jobs.
- $cron_jobs = array(
- 'wc_serial_numbers_hourly_event' => __( 'Hourly cron', 'wc-serial-numbers' ),
- 'wc_serial_numbers_daily_event' => __( 'Daily cron', 'wc-serial-numbers' ),
- );
- foreach ( $cron_jobs as $cron_job => $cron_job_name ) {
- $next_scheduled = wp_next_scheduled( $cron_job );
- if ( $next_scheduled ) {
- // translators: %s: Next scheduled time.
- $statuses[ $cron_job_name ] = sprintf( __( 'Next run: %s', 'wc-serial-numbers' ), esc_html( date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $next_scheduled ) ) );
- } else {
- $statuses[ $cron_job_name ] = __( 'Not scheduled', 'wc-serial-numbers' );
- }
- }
- $statuses = apply_filters( 'wc_serial_numbers_plugin_statuses', $statuses );
- ?>
-
-
-
- |
-
-
-
- $value ) : ?>
-
- |
- ‐ |
- |
-
-
-
-
-
-
- - 1,
- 'fields' => 'ids',
- )
- );
- $the_query = new \WP_Query( $args );
- $product_ids = $the_query->get_posts();
- $products = array();
- foreach ( $product_ids as $product_id ) {
- $product = wc_get_product( $product_id );
- if ( ! $product ) {
- continue;
- }
- $products[ $product->get_id() ] = sprintf( '%s (#%d)', $product->get_name(), $product->get_id() );
- }
-
- Admin::view( 'html-api-validation', array( 'products' => $products ) );
- }
-
- /**
- * Activation deactivation section.
- *
- * @since 1.4.6
- * @return void
- */
- public static function api_activation_deactivation_section() {
- $args = array_merge(
- wcsn_get_products_query_args(),
- array(
- 'posts_per_page' => - 1,
- 'fields' => 'ids',
- )
- );
- $the_query = new \WP_Query( $args );
- $product_ids = $the_query->get_posts();
- $products = array();
- foreach ( $product_ids as $product_id ) {
- $product = wc_get_product( $product_id );
- if ( ! $product ) {
- continue;
- }
- $products[ $product->get_id() ] = sprintf( '%s (#%d)', $product->get_name(), $product->get_id() );
- }
-
- Admin::view( 'html-api-actions', array( 'products' => $products ) );
- }
-
- /**
- * Stock section.
- *
- * @since 1.4.6
- * @return void
- */
- public static function reports_stock_tab() {
- Admin::view( 'html-list-stock' );
+ WCSN()->make( Tools::class )->generators_tab();
}
}
diff --git a/includes/Admin/Reports.php b/includes/Admin/Reports.php
new file mode 100644
index 00000000..070bf417
--- /dev/null
+++ b/includes/Admin/Reports.php
@@ -0,0 +1,63 @@
+ __( 'Stock', 'wc-serial-numbers' ),
+ );
+
+ $tabs = apply_filters( 'wc_serial_numbers_reports_tabs', $tabs );
+ $tab_ids = array_keys( $tabs );
+ $current_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : reset( $tab_ids );
+ $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
+
+ $this->app->template->view(
+ 'admin.reports',
+ array(
+ 'tabs' => $tabs,
+ 'current_tab' => $current_tab,
+ 'page' => $page,
+ )
+ );
+ }
+
+ /**
+ * Stock section.
+ *
+ * @since 1.4.6
+ * @return void
+ */
+ public function stock_tab() {
+ $this->app->template->view( 'admin.list-stock' );
+ }
+}
diff --git a/includes/Admin/Settings.php b/includes/Admin/Settings.php
index c6da45d9..50759b34 100644
--- a/includes/Admin/Settings.php
+++ b/includes/Admin/Settings.php
@@ -1,8 +1,8 @@
__( 'Generators', 'wc-serial-numbers' ),
+ 'api' => __( 'API Toolkit', 'wc-serial-numbers' ),
+ 'import' => __( 'Import', 'wc-serial-numbers' ),
+ 'export' => __( 'Export', 'wc-serial-numbers' ),
+ );
+
+ // If software support is disabled, remove the activations tab.
+ if ( ! wcsn_is_software_support_enabled() ) {
+ unset( $tabs['api'] );
+ }
+
+ $tabs = apply_filters( 'wc_serial_numbers_tools_tabs', $tabs );
+ $tab_ids = array_keys( $tabs );
+ $current_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : reset( $tab_ids );
+ $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
+
+ $this->app->template->view(
+ 'admin.tools',
+ array(
+ 'tabs' => $tabs,
+ 'current_tab' => $current_tab,
+ 'page' => $page,
+ )
+ );
+ }
+
+ /**
+ * Add status tab.
+ *
+ * @param array $tabs Tabs.
+ *
+ * @return array
+ */
+ public function add_status_tab( $tabs ) {
+ $tabs['status'] = __( 'Status', 'wc-serial-numbers' );
+
+ return $tabs;
+ }
+
+ /**
+ * Import tab content.
+ *
+ * @since 1.0.0
+ * @return void
+ */
+ public function import_tab() {
+ ?>
+
+
+
+
+
+
+ $this->app->version,
+ );
+ if ( $this->app->is_pro_active() && function_exists( 'wc_serial_numbers_pro' ) ) {
+ $statuses['Serial Numbers Pro version'] = WCSN_PRO()->get_version();
+ }
+
+ // Check if required tables exist.
+ $required_tables = array(
+ 'serial_numbers',
+ 'serial_numbers_activations',
+ );
+ foreach ( $required_tables as $table ) {
+ $exists = $GLOBALS['wpdb']->get_var( $GLOBALS['wpdb']->prepare( 'SHOW TABLES LIKE %s', $GLOBALS['wpdb']->prefix . $table ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+ if ( $exists ) {
+ $statuses[ $table ] = __( 'Table exists', 'wc-serial-numbers' );
+ } else {
+ $statuses[ $table ] = __( 'Table does not exist', 'wc-serial-numbers' );
+ }
+ }
+
+ // Cron jobs.
+ $cron_jobs = array(
+ 'wc_serial_numbers_hourly_event' => __( 'Hourly cron', 'wc-serial-numbers' ),
+ 'wc_serial_numbers_daily_event' => __( 'Daily cron', 'wc-serial-numbers' ),
+ );
+ foreach ( $cron_jobs as $cron_job => $cron_job_name ) {
+ $next_scheduled = wp_next_scheduled( $cron_job );
+ if ( $next_scheduled ) {
+ // translators: %s: Next scheduled time.
+ $statuses[ $cron_job_name ] = sprintf( __( 'Next run: %s', 'wc-serial-numbers' ), esc_html( date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $next_scheduled ) ) );
+ } else {
+ $statuses[ $cron_job_name ] = __( 'Not scheduled', 'wc-serial-numbers' );
+ }
+ }
+ $statuses = apply_filters( 'wc_serial_numbers_plugin_statuses', $statuses );
+ ?>
+
+
+
+ |
+
+
+
+ $value ) : ?>
+
+ |
+ ‐ |
+ |
+
+
+
+
+
+
+ app->template->view( 'admin.api-validation', array( 'products' => $this->get_key_products() ) );
+ }
+
+ /**
+ * Activation deactivation section.
+ *
+ * @since 1.4.6
+ * @return void
+ */
+ public function api_activation_deactivation_section() {
+ $this->app->template->view( 'admin.api-actions', array( 'products' => $this->get_key_products() ) );
+ }
+
+ /**
+ * Get the key enabled products as id => label pairs.
+ *
+ * @since 1.4.6
+ * @return array
+ */
+ protected function get_key_products() {
+ $args = array_merge(
+ wcsn_get_products_query_args(),
+ array(
+ 'posts_per_page' => - 1,
+ 'fields' => 'ids',
+ )
+ );
+ $the_query = new \WP_Query( $args );
+ $product_ids = $the_query->get_posts();
+ $products = array();
+ foreach ( $product_ids as $product_id ) {
+ $product = wc_get_product( $product_id );
+ if ( ! $product ) {
+ continue;
+ }
+ $products[ $product->get_id() ] = sprintf( '%s (#%d)', $product->get_name(), $product->get_id() );
+ }
+
+ return $products;
+ }
+}
diff --git a/includes/Cache.php b/includes/Cache.php
deleted file mode 100644
index e5e92ef3..00000000
--- a/includes/Cache.php
+++ /dev/null
@@ -1,39 +0,0 @@
-query( "update {$wpdb->prefix}serial_numbers set status='expired' where validity !='0' AND (order_date + INTERVAL validity DAY ) < NOW()" );
- }
-
- /**
- * Send low stock email notification.
- *
- * @since 1.2.0
- * @return bool
- */
- public function send_stock_alert_email() {
- if ( 'yes' !== get_option( 'wc_serial_numbers_enable_stock_notification' ) ) {
- return false;
- }
-
- $stock_threshold = get_option( 'wc_serial_numbers_stock_threshold', 5 );
- $to = get_option( 'wc_serial_numbers_notification_recipient', get_option( 'admin_email' ) );
- if ( empty( $to ) ) {
- return false;
- }
-
- $low_stock_products = wcsn_get_stocks_count( $stock_threshold );
- if ( empty( $low_stock_products ) ) {
- return false;
- }
-
- $subject = __( 'Serial Numbers stock running low', 'wc-serial-numbers' );
- /** $woocommerce WooCommerce */
- global $woocommerce;
- $mailer = $woocommerce->mailer();
-
- ob_start();
- wcsn_get_template( 'email-stock-notification.php', array( 'low_stock_products' => $low_stock_products ) );
- $message = ob_get_contents();
- ob_get_clean();
-
- $message = $mailer->wrap_message( $subject, $message );
- $headers = apply_filters( 'woocommerce_email_headers', '', 'wc_serial_numbers_low_stock_notification', $mailer );
- $mailer->send( $to, $subject, $message, $headers, array() );
-
- exit();
- }
-}
diff --git a/includes/Encryption.php b/includes/Encryption.php
index e4970174..5e9a5bb6 100644
--- a/includes/Encryption.php
+++ b/includes/Encryption.php
@@ -2,16 +2,16 @@
// phpcs:ignoreFile WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase,WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
// phpcs:ignoreFile Squiz.Commenting.VariableComment.Missing
-namespace WooCommerceSerialNumbers;
+namespace PluginEver\SerialNumbers;
-use WooCommerceSerialNumbers\B8\Component;
+use PluginEver\SerialNumbers\B8\Component;
defined( 'ABSPATH' ) || exit;
/**
* Class Encryption.
*
* @since 1.0.0
- * @package WooCommerceSerialNumbers
+ * @package PluginEver\SerialNumbers
*/
class Encryption extends Component {
private static $key;
diff --git a/includes/Frontend/Frontend.php b/includes/Frontend/Frontend.php
deleted file mode 100644
index 50dc4d66..00000000
--- a/includes/Frontend/Frontend.php
+++ /dev/null
@@ -1,60 +0,0 @@
-
- */
- public array $components = array(
- Shortcodes::class,
- );
-
- /**
- * Register hooks.
- *
- * @since 1.5.6
- * @return void
- */
- public function register(): void {
- add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
- add_action( 'wc_serial_numbers_before_display_order_keys', 'wcsn_display_order_keys_title', 10, 2 );
- add_action( 'wc_serial_numbers_display_order_keys', 'wcsn_display_order_keys_table', 10, 2 );
- }
-
- /**
- * Enqueue frontend scripts.
- *
- * @since 1.5.6
- * @return void
- */
- public function enqueue_scripts() {
- $this->app->scripts->enqueue_style( 'wc-serial-numbers-frontend', 'frontend.css' );
- $this->app->scripts->enqueue_script( 'wc-serial-numbers-frontend', 'frontend.js', array( 'jquery' ) );
- wp_localize_script(
- 'wc-serial-numbers-frontend',
- 'wc_serial_numbers_frontend_vars',
- array(
- 'ajax_url' => admin_url( 'admin-ajax.php' ),
- 'i18n' => array(
- 'copied' => __( 'Copied', 'wc-serial-numbers' ),
- 'loading' => __( 'Loading', 'wc-serial-numbers' ),
- ),
- )
- );
- }
-}
diff --git a/includes/Installer.php b/includes/Installer.php
index 35ca070b..eb501415 100644
--- a/includes/Installer.php
+++ b/includes/Installer.php
@@ -1,6 +1,6 @@
app->template->view( 'admin.edit-key', array( 'key' => new Key() ) );
+ } elseif ( $edit ) {
+ $this->app->template->view( 'admin.edit-key', array( 'key' => $key ) );
+ } else {
+ $this->app->template->view( 'admin.list-keys' );
+ }
+ }
+
/**
* Handle add/edit key.
*
diff --git a/includes/Keys/Keys.php b/includes/Keys/Keys.php
new file mode 100644
index 00000000..9b3ee52f
--- /dev/null
+++ b/includes/Keys/Keys.php
@@ -0,0 +1,97 @@
+
+ */
+ public array $components = array(
+ Admin::class,
+ );
+
+ /**
+ * Register hooks.
+ *
+ * @since 1.0.0
+ * @return void
+ */
+ public function register(): void {
+ add_action( 'wc_serial_numbers_key_inserted', array( $this, 'enable_product' ) );
+ add_action( 'wc_serial_numbers_key_deleted', array( $this, 'delete_activations' ) );
+ add_action( 'wc_serial_numbers_key_saved', array( $this, 'clear_order_keys_cache' ) );
+ add_action( 'wc_serial_numbers_key_deleted', array( $this, 'clear_order_keys_cache' ) );
+ add_action( 'wc_serial_numbers_order_remove_keys', array( $this, 'clear_order_keys_cache' ) );
+ add_action( 'wc_serial_numbers_order_add_keys', array( $this, 'clear_order_keys_cache' ) );
+ add_action( 'wc_serial_numbers_hourly_event', array( $this, 'expire_outdated_serials' ) );
+ }
+
+ /**
+ * Clear order keys cache.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function clear_order_keys_cache() {
+ delete_transient( 'wcsn_products_stock_count' );
+ }
+
+ /**
+ * Disable all expired serial numbers
+ *
+ * since 1.0.0
+ */
+ public function expire_outdated_serials() {
+ global $wpdb;
+ $wpdb->query( "update {$wpdb->prefix}serial_numbers set status='expired' where validity !='0' AND (order_date + INTERVAL validity DAY ) < NOW()" );
+ }
+
+ /**
+ * Enable product.
+ *
+ * @param Key $key The key object.
+ *
+ * @since 1.4.6
+ */
+ public function enable_product( $key ) {
+ if ( $key instanceof Key ) {
+ $product_id = $key->get_product_id();
+
+ if ( $product_id ) {
+ update_post_meta( $product_id, '_is_serial_number', 'yes' );
+ }
+ }
+ }
+
+ /**
+ * Delete activations.
+ *
+ * @param Key $key The key object.
+ *
+ * @since 1.4.6
+ */
+ public function delete_activations( $key ) {
+ $activations = $key->get_activations();
+ if ( $activations ) {
+ foreach ( $activations as $activation ) {
+ $activation->delete();
+ }
+ }
+ }
+}
diff --git a/includes/Admin/ListTables/KeysTable.php b/includes/Keys/ListTable.php
similarity index 94%
rename from includes/Admin/ListTables/KeysTable.php
rename to includes/Keys/ListTable.php
index ccadb2e2..b90904e2 100644
--- a/includes/Admin/ListTables/KeysTable.php
+++ b/includes/Keys/ListTable.php
@@ -1,8 +1,10 @@
$per_page,
- 'paged' => $current_page,
- 'orderby' => $orderby,
- 'order' => $order,
- 'status' => $status,
- 'product_id' => $product_id,
- 'order_id' => $order_id,
- 'customer_id' => $customer_id,
- 'include' => $id,
- 'search' => $search,
+ 'limit' => $per_page,
+ 'page' => $current_page,
+ 'orderby' => $orderby,
+ 'order' => $order,
+ 'status' => $status,
+ 'product_id' => $product_id,
+ 'order_id' => $order_id,
+ 'include' => $id,
+ 'search' => $search,
);
+ // Constrain to the orders of the given customer.
+ if ( ! empty( $customer_id ) ) {
+ $order_ids = wc_get_orders(
+ array(
+ 'customer_id' => absint( $customer_id ),
+ 'limit' => - 1,
+ 'return' => 'ids',
+ )
+ );
+ $args['order_id__in'] = ! empty( $order_ids ) ? $order_ids : array( 0 );
+ }
+
$this->items = Key::query( $args );
$this->available_count = Key::count( array_merge( $args, array( 'status' => 'available' ) ) );
$this->pending_count = Key::count( array_merge( $args, array( 'status' => 'pending' ) ) );
@@ -303,7 +316,7 @@ public function process_bulk_actions( $doaction ) {
}
foreach ( $ids as $id ) { // Check the permissions on each.
- $key = Key::get( $id );
+ $key = Key::find( $id );
if ( ! $key ) {
continue;
}
diff --git a/includes/Models/Activation.php b/includes/Models/Activation.php
index b4328b24..94c9b6dd 100644
--- a/includes/Models/Activation.php
+++ b/includes/Models/Activation.php
@@ -1,6 +1,6 @@
0,
- 'serial_id' => '',
+ 'serial_id' => 0,
'instance' => '',
'platform' => '',
'activation_time' => '',
// todo add ip address support.
);
+ /**
+ * The attributes that should be cast.
+ *
+ * @since 1.0.0
+ * @var array
+ */
+ protected $casts = array(
+ 'id' => 'int',
+ 'serial_id' => 'int',
+ );
+
+ /**
+ * The searchable attributes.
+ *
+ * @since 1.0.0
+ * @var array
+ */
+ protected $searchable = array(
+ 'id',
+ 'serial_id',
+ 'instance',
+ 'platform',
+ 'activation_time',
+ );
+
/*
|--------------------------------------------------------------------------
| Getters and Setters
@@ -57,7 +104,7 @@ class Activation extends Model {
*
* @since 1.4.6
*
- * @return string
+ * @return int
*/
public function get_id() {
return $this->get_prop( 'id' );
@@ -94,14 +141,14 @@ public function get_serial_id( $context = 'view' ) {
*
* @since 1.4.6
*
- * @return Key
+ * @return Key|null
*/
public function get_key() {
if ( empty( $this->get_serial_id() ) ) {
return null;
}
- return Key::get( $this->get_serial_id() );
+ return Key::find( $this->get_serial_id() );
}
/**
@@ -109,7 +156,7 @@ public function get_key() {
*
* @since 1.4.6
*
- * @return int
+ * @return int|null
*/
public function get_product_id() {
if ( empty( $this->get_key() ) ) {
@@ -124,7 +171,7 @@ public function get_product_id() {
*
* @since 1.4.6
*
- * @return string
+ * @return string|null
*/
public function get_product_title() {
if ( empty( $this->get_key() ) ) {
@@ -237,7 +284,7 @@ public function set_activation_time( $activation_time ) {
* Saves an object in the database.
*
* @since 1.0.0
- * @return true|\WP_Error True on success, WP_Error on failure.
+ * @return static|\WP_Error Object instance on success, WP_Error on failure.
*/
public function save() {
// Serial id is required.
@@ -257,42 +304,4 @@ public function save() {
return parent::save();
}
-
- /*
- |--------------------------------------------------------------------------
- | Query Methods
- |--------------------------------------------------------------------------
- |
- | Methods for reading and manipulating the object properties.
- |
- */
-
- /**
- * Prepare where query.
- *
- * @param array $clauses Query clauses.
- * @param array $args Array of args to pass to the query method.
- *
- * @since 1.0.0
- * @return array
- */
- protected function prepare_where_query( $clauses, $args = array() ) {
- global $wpdb;
- $clauses = parent::prepare_where_query( $clauses, $args );
- // If order_id or product_id is set, we need to join with the key table and filter by those.
- if ( ! empty( $args['order_id'] ) || ! empty( $args['product_id'] ) ) {
- $key_table = ( new Key() )->get_table_name();
- $clauses['join'] .= " INNER JOIN {$wpdb->prefix}" . $key_table . " AS serial_numbers ON {$this->table_name}.serial_id = serial_numbers.id";
- }
-
- if ( ! empty( $args['order_id'] ) ) {
- $clauses['where'] .= $wpdb->prepare( ' AND serial_numbers.order_id = %d', $args['order_id'] );
- }
-
- if ( ! empty( $args['product_id'] ) ) {
- $clauses['where'] .= $wpdb->prepare( ' AND serial_numbers.product_id = %d', $args['product_id'] );
- }
-
- return $clauses;
- }
}
diff --git a/includes/Models/Key.php b/includes/Models/Key.php
index fb86d722..c09a387a 100644
--- a/includes/Models/Key.php
+++ b/includes/Models/Key.php
@@ -1,6 +1,6 @@
0,
'serial_key' => '',
'product_id' => 0,
@@ -49,6 +79,45 @@ class Key extends Model {
'created_date' => '',
);
+ /**
+ * The attributes that should be cast.
+ *
+ * @since 1.0.0
+ * @var array
+ */
+ protected $casts = array(
+ 'id' => 'int',
+ 'product_id' => 'int',
+ 'activation_limit' => 'int',
+ 'activation_count' => 'int',
+ 'order_id' => 'int',
+ 'order_item_id' => 'int',
+ 'vendor_id' => 'int',
+ 'validity' => 'int',
+ );
+
+ /**
+ * The searchable attributes.
+ *
+ * @since 1.0.0
+ * @var array
+ */
+ protected $searchable = array(
+ 'id',
+ 'serial_key',
+ 'product_id',
+ 'activation_limit',
+ 'activation_count',
+ 'order_id',
+ 'order_item_id',
+ 'vendor_id',
+ 'status',
+ 'validity',
+ 'order_date',
+ 'source',
+ 'created_date',
+ );
+
/*
|--------------------------------------------------------------------------
| Getters and Setters
@@ -62,7 +131,7 @@ class Key extends Model {
*
* @since 1.4.6
*
- * @return string
+ * @return int
*/
public function get_id() {
return $this->get_prop( 'id' );
@@ -501,7 +570,7 @@ public function set_created_date( $created_date ) {
* Saves an object in the database.
*
* @since 1.0.0
- * @return true|\WP_Error True on success, WP_Error on failure.
+ * @return static|\WP_Error Object instance on success, WP_Error on failure.
*/
public function save() {
// Product id is required.
@@ -521,7 +590,7 @@ public function save() {
// Duplicate serial key is not allowed.
if ( ! wcsn_is_duplicate_key_allowed() ) {
- $existing = self::get(
+ $existing = static::find(
array(
'serial_key' => $this->get_serial_key(),
)
@@ -560,6 +629,43 @@ public function save() {
return parent::save();
}
+ /**
+ * Create an item in the database.
+ *
+ * The serial key is encrypted at rest before it is written.
+ *
+ * @param array $data Data to be inserted.
+ *
+ * @since 1.0.0
+ * @return \WP_Error|int The ID of the inserted item, or WP_Error on failure.
+ */
+ protected function perform_create( $data ) {
+ if ( ! empty( $data['serial_key'] ) ) {
+ $data['serial_key'] = wcsn_encrypt_key( $data['serial_key'] );
+ }
+
+ return parent::perform_create( $data );
+ }
+
+ /**
+ * Update an item in the database.
+ *
+ * The serial key is encrypted at rest before it is written.
+ *
+ * @param int $id ID of the item to be updated.
+ * @param array $data Data to be updated.
+ *
+ * @since 1.0.0
+ * @return \WP_Error|bool True on success, or WP_Error on failure.
+ */
+ protected function perform_update( $id, $data ) {
+ if ( ! empty( $data['serial_key'] ) ) {
+ $data['serial_key'] = wcsn_encrypt_key( $data['serial_key'] );
+ }
+
+ return parent::perform_update( $id, $data );
+ }
+
/*
|--------------------------------------------------------------------------
| Query Methods
@@ -570,124 +676,77 @@ public function save() {
*/
/**
- * Retrieve the object instance.
+ * Bootstrap the model.
*
- * @param int|array|static $data Object ID or array of arguments.
+ * Wires the encryption boundary into the native read and query hooks:
+ * the serial key is decrypted when read from the database and search
+ * terms are encrypted to match the value at rest.
*
* @since 1.0.0
- *
- * @return static|false Object instance on success, false on failure.
+ * @return void
*/
- public static function get( $data ) {
- // If by is set to serial key, encrypt it.
- if ( is_array( $data ) && array_key_exists( 'serial_key', $data ) ) {
- $data['serial_key'] = wcsn_encrypt_key( $data['serial_key'] );
- }
-
- return parent::get( $data );
+ protected function bootstrap() {
+ parent::bootstrap();
+ $this->on_filter( 'serial_key', 'wcsn_decrypt_key' );
+ $this->on_filter( 'query_args', array( static::class, 'prepare_search' ), 10, 2 );
}
/**
- * Prepare where query.
+ * Find a key by its primary key or column conditions.
*
- * @param array $clauses Query clauses.
- * @param array $args Array of args to pass to the query method.
+ * The serial key is stored encrypted, so a serial_key condition is
+ * encrypted before it is matched against the database.
+ *
+ * @param mixed $id The ID or array of column conditions.
*
* @since 1.0.0
- * @return array
+ * @return static|null The model instance, or null if not found.
*/
- protected function prepare_where_query( $clauses, $args = array() ) {
- global $wpdb;
- $clauses = parent::prepare_where_query( $clauses, $args );
-
- // If customer id is set, find the orders having that customer id and limit the results to those orders.
- if ( ! empty( $args['customer_id'] ) ) {
- $customer_id = absint( $args['customer_id'] );
- $order_ids = wc_get_orders(
- array(
- 'customer_id' => $customer_id,
- 'limit' => - 1,
- 'return' => 'ids',
- )
- );
-
- if ( ! empty( $order_ids ) ) {
- $clauses['where'] .= " AND {$this->table_name}.order_id IN (" . implode( ',', $order_ids ) . ')';
- } else {
- $clauses['where'] .= ' AND 0';
- }
+ public static function find( $id ) {
+ if ( is_array( $id ) && ! empty( $id['serial_key'] ) ) {
+ $id['serial_key'] = wcsn_encrypt_key( $id['serial_key'] );
}
- return $clauses;
+ return parent::find( $id );
}
/**
- * Prepare search query.
+ * Prepare the search query var.
*
- * @param array $clauses Query clauses.
- * @param array $args Array of args to pass to the query method.
+ * The serial key column holds encrypted values, so the search term is
+ * encrypted when compared against that column.
+ *
+ * @param array $args Query arguments.
+ * @param \PluginEver\SerialNumbers\B8\Models\Query $query Query object.
*
* @since 1.0.0
- * @return array
- */
- protected function prepare_search_query( $clauses, $args = array() ) {
- global $wpdb;
- /**
- * Filter the search query before setting up the query.
- *
- * @param array $clauses Query clauses.
- * @param array $args Query arguments.
- * @param static $this Current instance of the class.
- *
- * @return array
- * @since 1.0.0
- */
- $clauses = apply_filters( $this->get_hook_prefix() . '_pre_setup_search_query', $clauses, $args, $this );
-
- if ( ! empty( $args['search'] ) ) {
- $search = $args['search'];
- if ( ! empty( $args['search_columns'] ) ) {
- $search_columns = wp_parse_list( $args['search_columns'] );
- } else {
- /**
- * Filter the columns to search in when performing a search query.
- *
- * @param array $search_columns Array of columns to search in.
- * @param array $args Query arguments.
- * @param static $object Current instance of the class.
- *
- * @return array
- * @since 1.0.0
- */
- $search_columns = apply_filters( $this->get_hook_prefix() . '_search_columns', $this->get_searchable_keys(), $args, $this );
- }
- $search_columns = array_filter( array_unique( $search_columns ) );
- $like = '%' . $wpdb->esc_like( $search ) . '%';
-
- $search_clauses = array();
- foreach ( $search_columns as $column ) {
- if ( 'serial_key' === $column ) {
- $like = '%' . $wpdb->esc_like( wcsn_encrypt_key( $search ) ) . '%';
- }
- $search_clauses[] = $wpdb->prepare( $this->table_name . '.' . $column . ' LIKE %s', $like ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
- }
+ * @return array Query arguments.
+ */
+ public static function prepare_search( $args, $query ) {
+ if ( empty( $args['search'] ) ) {
+ return $args;
+ }
- if ( ! empty( $search_clauses ) ) {
- $clauses['where'] .= 'AND (' . implode( ' OR ', $search_clauses ) . ')';
- }
+ $search = $args['search'];
+ $columns = ! empty( $args['search_columns'] ) ? wp_parse_list( $args['search_columns'] ) : ( new static() )->get_searchable();
+ $columns = array_filter( array_unique( (array) $columns ) );
+
+ if ( ! empty( $columns ) ) {
+ $query->where(
+ function ( $q ) use ( $columns, $search ) {
+ foreach ( $columns as $column ) {
+ $term = 'serial_key' === $column ? wcsn_encrypt_key( $search ) : $search;
+ $q->where( $column, 'LIKE', $term );
+ }
+ },
+ 'OR'
+ );
}
- /**
- * Filter the search query after setting up the query.
- *
- * @param array $clauses Query clauses.
- * @param array $args Query arguments.
- * @param static $this Current instance of the class.
- *
- * @return array
- * @since 1.0.0
- */
- return apply_filters( $this->get_hook_prefix() . '_setup_search_query', $clauses, $args, $this );
+ $args['search'] = '';
+ $args['search_columns'] = array();
+
+ return $args;
}
/*
@@ -875,6 +934,6 @@ public function print_key( $masked = false ) {
$key = sprintf( '%s', esc_attr( $key ), esc_attr( $key ), $key );
}
- return apply_filters( $this->get_hook_prefix() . '_display_key', $key, $this );
+ return $this->apply_filters( 'display_key', $key, $this );
}
}
diff --git a/includes/Models/Model.php b/includes/Models/Model.php
index 5d1bcf9a..9371f4e2 100644
--- a/includes/Models/Model.php
+++ b/includes/Models/Model.php
@@ -1,21 +1,218 @@
'id',
+ 'order' => 'ASC',
+ 'limit' => 20,
+ );
+
+ /*
+ |--------------------------------------------------------------------------
+ | Getters and Setters
+ |--------------------------------------------------------------------------
+ */
+
+ /**
+ * Get an attribute, routed through the model's getter method when one exists.
+ *
+ * @param string $key The name of the attribute.
+ * @param mixed $fallback Optional fallback value if the key is not found.
+ *
+ * @since 1.0.0
+ * @return mixed The value of the attribute.
+ */
+ public function get( string $key, $fallback = null ) {
+ $getter = "get_{$key}";
+ if ( ! method_exists( self::class, $getter ) && method_exists( $this, $getter ) ) {
+ return $this->$getter();
+ }
+
+ return parent::get( $key, $fallback );
+ }
+
+ /**
+ * Set an attribute, routed through the model's setter method when one exists.
+ *
+ * @param string $key The name of the attribute.
+ * @param mixed $value The value to set.
+ *
+ * @since 1.0.0
+ * @return static
+ */
+ public function set( string $key, $value ) {
+ $setter = "set_{$key}";
+ if ( ! method_exists( self::class, $setter ) && method_exists( $this, $setter ) ) {
+ $this->$setter( $value );
+
+ return $this;
+ }
+
+ return parent::set( $key, $value );
+ }
+
+ /**
+ * Gets a prop for a getter method.
+ *
+ * @param string $prop Name of prop to get.
+ * @param string $context What the value is for. Valid values are 'view' and 'edit'.
+ *
+ * @since 1.0.0
+ * @return mixed
+ */
+ protected function get_prop( $prop, $context = 'edit' ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- Context kept for getter signatures.
+ if ( array_key_exists( $prop, $this->attributes ) ) {
+ return $this->attributes[ $prop ];
+ }
+
+ if ( array_key_exists( $prop, $this->metadata ) ) {
+ return $this->metadata[ $prop ];
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets a prop for a setter method.
+ *
+ * Only known columns and registered metadata are written, unknown props
+ * are ignored.
+ *
+ * @param string $prop Name of prop to set.
+ * @param mixed $value Value to set.
+ *
+ * @since 1.0.0
+ * @return void
+ */
+ protected function set_prop( $prop, $value ) {
+ if ( array_key_exists( $prop, $this->attributes ) ) {
+ $this->attributes[ $prop ] = $value;
+ } elseif ( array_key_exists( $prop, $this->metadata ) ) {
+ $this->metadata[ $prop ] = $value;
+ }
+ }
+
+ /**
+ * Get date prop.
+ *
+ * @param string $prop Name of prop to get.
+ * @param string $context What the value is for. Valid values are 'view' and 'edit'.
+ * @param string $format Date format.
+ *
+ * @since 1.0.0
+ * @return string|null
+ */
+ public function get_date_prop( $prop, $context = 'edit', $format = 'Y-m-d H:i:s' ) {
+ $datetime = $this->sanitize_date( $this->get_prop( $prop, $context ) );
+
+ return $datetime ? date( $format, strtotime( $datetime ) ) : null; // @codingStandardsIgnoreLine - date() is ok here.
+ }
+
+ /**
+ * Sets a date prop whilst handling formatting and datetime objects.
+ *
+ * @param string $prop Name of prop to set.
+ * @param string|integer $value Value of the prop.
+ * @param string $format Date format.
+ *
+ * @since 1.0.0
+ * @return void
+ */
+ public function set_date_prop( $prop, $value, $format = 'Y-m-d H:i:s' ) {
+ $date = $this->sanitize_date( $value );
+ if ( ! empty( $date ) ) {
+ $date = date( $format, strtotime( $date ) ); // @codingStandardsIgnoreLine - date() is ok here.
+ }
+ $this->set_prop( $prop, $date );
+ }
+
+ /*
+ |--------------------------------------------------------------------------
+ | Helpers
+ |--------------------------------------------------------------------------
+ */
+
+ /**
+ * Checks if a date is valid or not.
+ *
+ * @param string $date Date to check.
+ *
+ * @since 1.0.0
+ * @return bool
+ */
+ public function is_date_valid( $date ) {
+ if ( empty( preg_replace( '/[^0-9]/', '', (string) $date ) ) ) {
+ return false;
+ }
+
+ return (bool) strtotime( (string) $date );
+ }
+
+ /**
+ * Sanitize date property.
+ * If the date is a valid date, it will be returned to the given format.
+ *
+ * @param string $date Date.
+ *
+ * @since 1.0.0
+ * @return string|null
+ */
+ public function sanitize_date( $date ) {
+ if ( empty( $date ) || '0000-00-00 00:00:00' === $date || '0000-00-00' === $date ) {
+ return null;
+ }
+
+ if ( ! $this->is_date_valid( $date ) ) {
+ return null;
+ }
+
+ // get the date format from the given date.
+ $length = strlen( (string) $date );
+ switch ( $length ) {
+ case 8:
+ $format = 'H:i:s';
+ break;
+ case 10:
+ $format = 'Y-m-d';
+ break;
+ case 19:
+ default:
+ $format = 'Y-m-d H:i:s';
+ break;
+ }
+
+ $d = \DateTime::createFromFormat( $format, (string) $date );
+
+ return $d && $d->format( $format ) === $date ? $d->format( $format ) : null;
+ }
}
diff --git a/includes/Admin/Orders.php b/includes/Orders/Admin.php
similarity index 97%
rename from includes/Admin/Orders.php
rename to includes/Orders/Admin.php
index 4451e610..a6c56c14 100644
--- a/includes/Admin/Orders.php
+++ b/includes/Orders/Admin.php
@@ -1,17 +1,27 @@
+ */
+ public array $components = array(
+ Admin::class,
+ );
+
/**
* Register hooks.
*
diff --git a/includes/Plugin.php b/includes/Plugin.php
index 8c13e9b6..e118f0f5 100644
--- a/includes/Plugin.php
+++ b/includes/Plugin.php
@@ -1,6 +1,6 @@
rest_authorization_required_code() ) );
+ }
+
+ $query = Activation::query();
+
+ foreach ( $request->get_params() as $key => $value ) {
+ $query->set_var( $key, $value );
+ }
+
+ $query = $this->apply_query_filters( $query, $request, 'activations' );
+
+ /**
+ * Filters the activation query before execution.
+ *
+ * @since 2.4.0
+ *
+ * @param mixed $query Query object.
+ * @param WP_REST_Request $request Request object.
+ */
+ $query = WCSN()->apply_filters( 'rest_activation_query', $query, $request );
+
+ $items = $query->get();
+ $total = $query->count();
+ $page = (int) $query->get_var( 'page', 1 );
+ $per_page = (int) $query->get_var( 'per_page', 20 );
+
+ $results = array();
+ foreach ( $items as $item ) {
+ $data = $this->prepare_item_for_response( $item, $request );
+ $results[] = $this->prepare_response_for_collection( $data );
+ }
+
+ $response = rest_ensure_response( $results );
+ $max_pages = $total > 0 ? (int) ceil( $total / $per_page ) : 0;
+
+ $response->header( 'X-WP-Total', (int) $total );
+ $response->header( 'X-WP-TotalPages', (int) $max_pages );
+
+ $base = add_query_arg( urlencode_deep( $request->get_query_params() ), rest_url( $request->get_route() ) );
+
+ if ( $page > 1 ) {
+ $prev_page = min( $page - 1, $max_pages );
+ $response->link_header( 'prev', add_query_arg( 'page', $prev_page, $base ) );
+ }
+
+ if ( $max_pages > $page ) {
+ $response->link_header( 'next', add_query_arg( 'page', $page + 1, $base ) );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Retrieves one activation.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function get_item( $request ) {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown -- WooCommerce capability.
+ return new WP_Error( 'rest_forbidden', __( 'Not allowed.', 'wc-serial-numbers' ), array( 'status' => rest_authorization_required_code() ) );
+ }
+
+ $item = Activation::find( $request->get_param( 'id' ) );
+
+ if ( ! $item ) {
+ return new WP_Error( 'rest_activation_not_found', __( 'Activation not found.', 'wc-serial-numbers' ), array( 'status' => 404 ) );
+ }
+
+ return $this->prepare_item_for_response( $item, $request );
+ }
+
+ /**
+ * Creates one activation.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function create_item( $request ) {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown -- WooCommerce capability.
+ return new WP_Error( 'rest_forbidden', __( 'Not allowed.', 'wc-serial-numbers' ), array( 'status' => rest_authorization_required_code() ) );
+ }
+
+ if ( ! empty( $request->get_param( 'id' ) ) ) {
+ return new WP_Error( 'rest_activation_exists', __( 'Cannot create existing activation.', 'wc-serial-numbers' ), array( 'status' => 400 ) );
+ }
+
+ $item = $this->prepare_item_for_database( $request );
+
+ if ( is_wp_error( $item ) ) {
+ return $item;
+ }
+
+ /**
+ * Filters the activation object before saving.
+ *
+ * @since 2.4.0
+ *
+ * @param Activation $item Activation object.
+ * @param WP_REST_Request $request Request object.
+ */
+ $item = WCSN()->apply_filters( 'rest_pre_insert_activation', $item, $request );
+ $item = $item->save();
+
+ if ( is_wp_error( $item ) ) {
+ return $item;
+ }
+
+ $response = $this->prepare_item_for_response( $item, $request );
+ $response->set_status( 201 );
+ $response->header( 'Location', rest_url( $request->get_route() . '/' . $item->id ) );
+
+ return $response;
+ }
+
+ /**
+ * Updates one activation.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function update_item( $request ) {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown -- WooCommerce capability.
+ return new WP_Error( 'rest_forbidden', __( 'Not allowed.', 'wc-serial-numbers' ), array( 'status' => rest_authorization_required_code() ) );
+ }
+
+ $item = $this->prepare_item_for_database( $request );
+
+ if ( is_wp_error( $item ) ) {
+ return $item;
+ }
+
+ if ( ! $item->exists() ) {
+ return new WP_Error( 'rest_activation_not_found', __( 'Activation not found.', 'wc-serial-numbers' ), array( 'status' => 404 ) );
+ }
+
+ /**
+ * Filters the activation object before saving.
+ *
+ * @since 2.4.0
+ *
+ * @param Activation $item Activation object.
+ * @param WP_REST_Request $request Request object.
+ */
+ $item = WCSN()->apply_filters( 'rest_pre_insert_activation', $item, $request );
+ $item = $item->save();
+
+ if ( is_wp_error( $item ) ) {
+ return $item;
+ }
+
+ return $this->prepare_item_for_response( $item, $request );
+ }
+
+ /**
+ * Deletes one activation.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function delete_item( $request ) {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown -- WooCommerce capability.
+ return new WP_Error( 'rest_forbidden', __( 'Not allowed.', 'wc-serial-numbers' ), array( 'status' => rest_authorization_required_code() ) );
+ }
+
+ $item = Activation::find( $request->get_param( 'id' ) );
+
+ if ( ! $item ) {
+ return new WP_Error( 'rest_activation_not_found', __( 'Activation not found.', 'wc-serial-numbers' ), array( 'status' => 404 ) );
+ }
+
+ $response = $this->prepare_item_for_response( $item, $request );
+ $previous = $response->get_data();
+ $deleted = $item->delete();
+
+ if ( is_wp_error( $deleted ) ) {
+ return $deleted;
+ }
+
+ return rest_ensure_response(
+ array(
+ 'deleted' => true,
+ 'previous' => $previous,
+ )
+ );
+ }
+
+ /**
+ * Retrieves form options for activations.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function get_options( $request ) {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown -- WooCommerce capability.
+ return new WP_Error( 'rest_forbidden', __( 'Not allowed.', 'wc-serial-numbers' ), array( 'status' => rest_authorization_required_code() ) );
+ }
+
+ $options = array(
+ 'bulk_actions' => array(
+ array(
+ 'value' => 'delete',
+ 'label' => __( 'Delete', 'wc-serial-numbers' ),
+ ),
+ ),
+ 'import_fields' => array(),
+ 'export_columns' => array(),
+ );
+
+ /**
+ * Filters the activation form options.
+ *
+ * @since 2.4.0
+ *
+ * @param array $options Options data.
+ * @param WP_REST_Request $request Request object.
+ */
+ return rest_ensure_response( WCSN()->apply_filters( 'rest_activation_options', $options, $request ) );
+ }
+
+ /**
+ * Batch operations.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function batch_items( $request ) {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown -- WooCommerce capability.
+ return new WP_Error( 'rest_forbidden', __( 'Not allowed.', 'wc-serial-numbers' ), array( 'status' => rest_authorization_required_code() ) );
+ }
+
+ return $this->process_batch( $request );
+ }
+
+ /**
+ * Import activations from CSV.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function import_items( $request ) {
+ return new WP_Error( 'rest_not_implemented', __( 'Import not yet implemented.', 'wc-serial-numbers' ), array( 'status' => 501 ) );
+ }
+
+ /**
+ * Export activations to CSV.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function export_items( $request ) {
+ return new WP_Error( 'rest_not_implemented', __( 'Export not yet implemented.', 'wc-serial-numbers' ), array( 'status' => 501 ) );
+ }
+
+ /**
+ * Prepare activation for response.
+ *
+ * @since 2.4.0
+ *
+ * @param Activation $item Activation object.
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response Response.
+ */
+ public function prepare_item_for_response( $item, $request ): WP_REST_Response {
+ $raw = $item->to_array();
+ $data = array();
+
+ foreach ( $raw as $key => $value ) {
+ switch ( $key ) {
+ case 'activation_time':
+ $data[ $key ] = $this->prepare_date_response( $value );
+ break;
+
+ default:
+ $data[ $key ] = $value;
+ break;
+ }
+ }
+
+ /**
+ * Filters the activation data before creating the response.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Response $response Response object.
+ * @param Activation $item Activation object.
+ * @param WP_REST_Request $request Request object.
+ */
+ return WCSN()->apply_filters( 'rest_prepare_activation', rest_ensure_response( $data ), $item, $request );
+ }
+
+ /**
+ * Prepare activation for database.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return Activation|WP_Error Activation object or error.
+ */
+ protected function prepare_item_for_database( $request ) {
+ $id = $request->get_param( 'id' );
+ $item = $id ? Activation::find( $id ) : new Activation();
+
+ if ( $id && ! $item ) {
+ return new WP_Error( 'rest_activation_not_found', __( 'Activation not found.', 'wc-serial-numbers' ), array( 'status' => 404 ) );
+ }
+
+ foreach ( $request->get_params() as $key => $value ) {
+ switch ( $key ) {
+ case 'activation_time':
+ $item->$key = $this->prepare_date_for_database( $value );
+ break;
+
+ default:
+ $item->$key = $value;
+ break;
+ }
+ }
+
+ return $item;
+ }
+}
diff --git a/includes/RestAPI/Controllers/Controller.php b/includes/RestAPI/Controllers/Controller.php
new file mode 100644
index 00000000..43deb800
--- /dev/null
+++ b/includes/RestAPI/Controllers/Controller.php
@@ -0,0 +1,290 @@
+ 'http://json-schema.org/draft-04/schema#',
+ 'type' => 'object',
+ 'properties' => array(
+ 'id' => array(
+ 'type' => 'integer',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ ),
+ );
+ }
+
+ /**
+ * Retrieves the query params for the collection.
+ *
+ * @since 2.4.0
+ *
+ * @return array Collection parameters.
+ */
+ public function get_collection_params() {
+ $params = parent::get_collection_params();
+
+ $params['per_page']['default'] = 20;
+
+ return $params;
+ }
+
+ /**
+ * Prepare date for response (MySQL to RFC3339).
+ *
+ * @since 2.4.0
+ *
+ * @param string|null $date MySQL datetime.
+ *
+ * @return string|null RFC3339 date.
+ */
+ protected function prepare_date_response( ?string $date ): ?string {
+ if ( empty( $date ) || '0000-00-00 00:00:00' === $date ) {
+ return null;
+ }
+
+ return mysql_to_rfc3339( $date );
+ }
+
+ /**
+ * Prepare date for database (RFC3339 to MySQL).
+ *
+ * @since 2.4.0
+ *
+ * @param string|null $date RFC3339 datetime.
+ *
+ * @return string|null MySQL datetime.
+ */
+ protected function prepare_date_for_database( ?string $date ): ?string {
+ if ( empty( $date ) ) {
+ return null;
+ }
+
+ $timestamp = rest_parse_date( $date );
+
+ if ( ! $timestamp ) {
+ return null;
+ }
+
+ return gmdate( 'Y-m-d H:i:s', $timestamp );
+ }
+
+ /**
+ * Convert items into value/label options array.
+ *
+ * Accepts two formats:
+ * 1. Associative array (key => label): prepare_options( ['available' => 'Available', 'sold' => 'Sold'] )
+ * 2. Array of arrays/objects with pluck keys: prepare_options( $items, 'id', 'name' )
+ *
+ * @since 2.4.0
+ *
+ * @param array $items Items to convert.
+ * @param string $value_key Key to pluck for value. Empty = treat as associative key => label.
+ * @param string $label_key Key to pluck for label.
+ *
+ * @return array Options array of [ ['value' => ..., 'label' => ...], ... ].
+ */
+ protected function prepare_options( array $items, string $value_key = '', string $label_key = '' ): array {
+ if ( empty( $items ) ) {
+ return array();
+ }
+
+ // Associative array: key => label.
+ if ( empty( $value_key ) ) {
+ $options = array();
+ foreach ( $items as $value => $label ) {
+ $options[] = array(
+ 'value' => $value,
+ 'label' => $label,
+ );
+ }
+ return $options;
+ }
+
+ // Array of arrays/objects: pluck by keys.
+ return array_map(
+ function ( $item ) use ( $value_key, $label_key ) {
+ $item = (array) $item;
+ return array(
+ 'value' => $item[ $value_key ] ?? '',
+ 'label' => $item[ $label_key ] ?? '',
+ );
+ },
+ array_values( $items )
+ );
+ }
+
+ /**
+ * Prepare a single related model for response (belongs_to, has_one).
+ *
+ * @since 2.4.0
+ *
+ * @param mixed $item Related model instance.
+ * @param string $controller Controller class name.
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return array|null Prepared relation data or null.
+ */
+ protected function prepare_relation( $item, string $controller, WP_REST_Request $request ): ?array {
+ if ( empty( $item ) ) {
+ return null;
+ }
+
+ $ctrl = new $controller();
+
+ return $ctrl->prepare_response_for_collection( $ctrl->prepare_item_for_response( $item, $request ) );
+ }
+
+ /**
+ * Prepare a collection of related models for response (has_many, belongs_to_many).
+ *
+ * @since 2.4.0
+ *
+ * @param array $items Array of related model instances.
+ * @param string $controller Controller class name.
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return array Prepared relations data.
+ */
+ protected function prepare_relations( array $items, string $controller, WP_REST_Request $request ): array {
+ if ( empty( $items ) ) {
+ return array();
+ }
+
+ $ctrl = new $controller();
+
+ return array_map(
+ fn( $item ) => $ctrl->prepare_response_for_collection( $ctrl->prepare_item_for_response( $item, $request ) ),
+ $items
+ );
+ }
+
+ /**
+ * Apply query filters from request.
+ *
+ * @since 2.4.0
+ *
+ * @param mixed $query The query object.
+ * @param WP_REST_Request $request The REST API request.
+ * @param string $object_type Object type for filter hook (e.g., 'keys', 'activations').
+ *
+ * @return mixed The modified query object.
+ */
+ protected function apply_query_filters( $query, WP_REST_Request $request, string $object_type = '' ) {
+ $filters = $request->get_param( 'filters' );
+
+ if ( ! empty( $filters['filters'] ) && is_array( $filters['filters'] ) ) {
+ $boolean = 'any' === ( $filters['match'] ?? 'all' ) ? 'OR' : 'AND';
+ $query->where(
+ function ( $q ) use ( $filters ) {
+ foreach ( $filters['filters'] as $filter ) {
+ if ( ! empty( $filter['field'] ) && ! empty( $filter['operator'] ) ) {
+ $q->where( $filter['field'], $filter['operator'], $filter['value'] ?? null );
+ }
+ }
+ },
+ $boolean
+ );
+ }
+
+ if ( $object_type ) {
+ /**
+ * Filters the query after applying filters.
+ *
+ * @since 2.4.0
+ *
+ * @param mixed $query The query object.
+ * @param array $filters The filters array.
+ * @param WP_REST_Request $request The REST API request.
+ */
+ $query = WCSN()->apply_filters( "rest_{$object_type}_apply_query_filters", $query, $filters, $request );
+ }
+
+ return $query;
+ }
+
+ /**
+ * Process batch operations.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ protected function process_batch( $request ) {
+ $items = $request->get_params();
+ $response = array();
+
+ /**
+ * Filters the maximum number of items allowed in a batch request.
+ *
+ * @since 2.4.0
+ *
+ * @param int $limit Maximum batch size. Default 100.
+ */
+ $limit = WCSN()->apply_filters( 'rest_batch_limit', 100 );
+ $total = count( $items['create'] ?? array() ) + count( $items['update'] ?? array() ) + count( $items['delete'] ?? array() );
+
+ if ( $total > $limit ) {
+ // translators: %d is the maximum batch size.
+ return new WP_Error( 'rest_batch_too_large', sprintf( __( 'Batch limit is %d items.', 'wc-serial-numbers' ), $limit ), array( 'status' => 413 ) );
+ }
+
+ foreach ( $items['create'] ?? array() as $data ) {
+ $req = new WP_REST_Request( 'POST' );
+ $req->set_body_params( $data );
+ $res = $this->create_item( $req );
+ $response['create'][] = is_wp_error( $res ) ? array( 'error' => $res->get_error_message() ) : $res->get_data();
+ }
+
+ foreach ( $items['update'] ?? array() as $data ) {
+ $req = new WP_REST_Request( 'PUT' );
+ $req->set_body_params( $data );
+ $res = $this->update_item( $req );
+ $response['update'][] = is_wp_error( $res ) ? array(
+ 'id' => $data['id'] ?? 0,
+ 'error' => $res->get_error_message(),
+ ) : $res->get_data();
+ }
+
+ foreach ( $items['delete'] ?? array() as $id ) {
+ $id = is_array( $id ) ? ( $id['id'] ?? 0 ) : (int) $id;
+ $req = new WP_REST_Request( 'DELETE' );
+ $req->set_param( 'id', $id );
+ $res = $this->delete_item( $req );
+ $response['delete'][] = is_wp_error( $res ) ? array(
+ 'id' => $id,
+ 'error' => $res->get_error_message(),
+ ) : $res->get_data();
+ }
+
+ return rest_ensure_response( $response );
+ }
+}
diff --git a/includes/RestAPI/Controllers/Keys.php b/includes/RestAPI/Controllers/Keys.php
new file mode 100644
index 00000000..e178f509
--- /dev/null
+++ b/includes/RestAPI/Controllers/Keys.php
@@ -0,0 +1,382 @@
+ rest_authorization_required_code() ) );
+ }
+
+ $query = Key::query();
+
+ foreach ( $request->get_params() as $key => $value ) {
+ $query->set_var( $key, $value );
+ }
+
+ $query = $this->apply_query_filters( $query, $request, 'keys' );
+
+ /**
+ * Filters the key query before execution.
+ *
+ * @since 2.4.0
+ *
+ * @param mixed $query Query object.
+ * @param WP_REST_Request $request Request object.
+ */
+ $query = WCSN()->apply_filters( 'rest_key_query', $query, $request );
+
+ $items = $query->get();
+ $total = $query->count();
+ $page = (int) $query->get_var( 'page', 1 );
+ $per_page = (int) $query->get_var( 'per_page', 20 );
+
+ $results = array();
+ foreach ( $items as $item ) {
+ $data = $this->prepare_item_for_response( $item, $request );
+ $results[] = $this->prepare_response_for_collection( $data );
+ }
+
+ $response = rest_ensure_response( $results );
+ $max_pages = $total > 0 ? (int) ceil( $total / $per_page ) : 0;
+
+ $response->header( 'X-WP-Total', (int) $total );
+ $response->header( 'X-WP-TotalPages', (int) $max_pages );
+
+ $base = add_query_arg( urlencode_deep( $request->get_query_params() ), rest_url( $request->get_route() ) );
+
+ if ( $page > 1 ) {
+ $prev_page = min( $page - 1, $max_pages );
+ $response->link_header( 'prev', add_query_arg( 'page', $prev_page, $base ) );
+ }
+
+ if ( $max_pages > $page ) {
+ $response->link_header( 'next', add_query_arg( 'page', $page + 1, $base ) );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Retrieves one key.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function get_item( $request ) {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown -- WooCommerce capability.
+ return new WP_Error( 'rest_forbidden', __( 'Not allowed.', 'wc-serial-numbers' ), array( 'status' => rest_authorization_required_code() ) );
+ }
+
+ $item = Key::find( $request->get_param( 'id' ) );
+
+ if ( ! $item ) {
+ return new WP_Error( 'rest_key_not_found', __( 'Key not found.', 'wc-serial-numbers' ), array( 'status' => 404 ) );
+ }
+
+ return $this->prepare_item_for_response( $item, $request );
+ }
+
+ /**
+ * Creates one key.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function create_item( $request ) {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown -- WooCommerce capability.
+ return new WP_Error( 'rest_forbidden', __( 'Not allowed.', 'wc-serial-numbers' ), array( 'status' => rest_authorization_required_code() ) );
+ }
+
+ if ( ! empty( $request->get_param( 'id' ) ) ) {
+ return new WP_Error( 'rest_key_exists', __( 'Cannot create existing key.', 'wc-serial-numbers' ), array( 'status' => 400 ) );
+ }
+
+ $item = $this->prepare_item_for_database( $request );
+
+ if ( is_wp_error( $item ) ) {
+ return $item;
+ }
+
+ /**
+ * Filters the key object before saving.
+ *
+ * @since 2.4.0
+ *
+ * @param Key $item Key object.
+ * @param WP_REST_Request $request Request object.
+ */
+ $item = WCSN()->apply_filters( 'rest_pre_insert_key', $item, $request );
+ $item = $item->save();
+
+ if ( is_wp_error( $item ) ) {
+ return $item;
+ }
+
+ $response = $this->prepare_item_for_response( $item, $request );
+ $response->set_status( 201 );
+ $response->header( 'Location', rest_url( $request->get_route() . '/' . $item->id ) );
+
+ return $response;
+ }
+
+ /**
+ * Updates one key.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function update_item( $request ) {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown -- WooCommerce capability.
+ return new WP_Error( 'rest_forbidden', __( 'Not allowed.', 'wc-serial-numbers' ), array( 'status' => rest_authorization_required_code() ) );
+ }
+
+ $item = $this->prepare_item_for_database( $request );
+
+ if ( is_wp_error( $item ) ) {
+ return $item;
+ }
+
+ if ( ! $item->exists() ) {
+ return new WP_Error( 'rest_key_not_found', __( 'Key not found.', 'wc-serial-numbers' ), array( 'status' => 404 ) );
+ }
+
+ /**
+ * Filters the key object before saving.
+ *
+ * @since 2.4.0
+ *
+ * @param Key $item Key object.
+ * @param WP_REST_Request $request Request object.
+ */
+ $item = WCSN()->apply_filters( 'rest_pre_insert_key', $item, $request );
+ $item = $item->save();
+
+ if ( is_wp_error( $item ) ) {
+ return $item;
+ }
+
+ return $this->prepare_item_for_response( $item, $request );
+ }
+
+ /**
+ * Deletes one key.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function delete_item( $request ) {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown -- WooCommerce capability.
+ return new WP_Error( 'rest_forbidden', __( 'Not allowed.', 'wc-serial-numbers' ), array( 'status' => rest_authorization_required_code() ) );
+ }
+
+ $item = Key::find( $request->get_param( 'id' ) );
+
+ if ( ! $item ) {
+ return new WP_Error( 'rest_key_not_found', __( 'Key not found.', 'wc-serial-numbers' ), array( 'status' => 404 ) );
+ }
+
+ $response = $this->prepare_item_for_response( $item, $request );
+ $previous = $response->get_data();
+ $deleted = $item->delete();
+
+ if ( is_wp_error( $deleted ) ) {
+ return $deleted;
+ }
+
+ return rest_ensure_response(
+ array(
+ 'deleted' => true,
+ 'previous' => $previous,
+ )
+ );
+ }
+
+ /**
+ * Retrieves form options for keys.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function get_options( $request ) {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown -- WooCommerce capability.
+ return new WP_Error( 'rest_forbidden', __( 'Not allowed.', 'wc-serial-numbers' ), array( 'status' => rest_authorization_required_code() ) );
+ }
+
+ $options = array(
+ 'statuses' => $this->prepare_options( wcsn_get_key_statuses() ),
+ 'sources' => $this->prepare_options( wcsn_get_key_sources() ),
+ 'bulk_actions' => array(
+ array(
+ 'value' => 'delete',
+ 'label' => __( 'Delete', 'wc-serial-numbers' ),
+ ),
+ ),
+ 'import_fields' => array(),
+ 'export_columns' => array(),
+ );
+
+ /**
+ * Filters the key form options.
+ *
+ * @since 2.4.0
+ *
+ * @param array $options Options data.
+ * @param WP_REST_Request $request Request object.
+ */
+ return rest_ensure_response( WCSN()->apply_filters( 'rest_key_options', $options, $request ) );
+ }
+
+ /**
+ * Batch operations.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function batch_items( $request ) {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown -- WooCommerce capability.
+ return new WP_Error( 'rest_forbidden', __( 'Not allowed.', 'wc-serial-numbers' ), array( 'status' => rest_authorization_required_code() ) );
+ }
+
+ return $this->process_batch( $request );
+ }
+
+ /**
+ * Import keys from CSV.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function import_items( $request ) {
+ return new WP_Error( 'rest_not_implemented', __( 'Import not yet implemented.', 'wc-serial-numbers' ), array( 'status' => 501 ) );
+ }
+
+ /**
+ * Export keys to CSV.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response|WP_Error Response or error.
+ */
+ public function export_items( $request ) {
+ return new WP_Error( 'rest_not_implemented', __( 'Export not yet implemented.', 'wc-serial-numbers' ), array( 'status' => 501 ) );
+ }
+
+ /**
+ * Prepare key for response.
+ *
+ * @since 2.4.0
+ *
+ * @param Key $item Key object.
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return WP_REST_Response Response.
+ */
+ public function prepare_item_for_response( $item, $request ): WP_REST_Response {
+ $raw = $item->to_array();
+ $data = array();
+
+ foreach ( $raw as $key => $value ) {
+ switch ( $key ) {
+ case 'order_date':
+ case 'created_date':
+ $data[ $key ] = $this->prepare_date_response( $value );
+ break;
+
+ default:
+ $data[ $key ] = $value;
+ break;
+ }
+ }
+
+ /**
+ * Filters the key data before creating the response.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Response $response Response object.
+ * @param Key $item Key object.
+ * @param WP_REST_Request $request Request object.
+ */
+ return WCSN()->apply_filters( 'rest_prepare_key', rest_ensure_response( $data ), $item, $request );
+ }
+
+ /**
+ * Prepare key for database.
+ *
+ * @since 2.4.0
+ *
+ * @param WP_REST_Request $request Request object.
+ *
+ * @return Key|WP_Error Key object or error.
+ */
+ protected function prepare_item_for_database( $request ) {
+ $id = $request->get_param( 'id' );
+ $item = $id ? Key::find( $id ) : new Key();
+
+ if ( $id && ! $item ) {
+ return new WP_Error( 'rest_key_not_found', __( 'Key not found.', 'wc-serial-numbers' ), array( 'status' => 404 ) );
+ }
+
+ foreach ( $request->get_params() as $key => $value ) {
+ switch ( $key ) {
+ case 'order_date':
+ case 'created_date':
+ $item->$key = $this->prepare_date_for_database( $value );
+ break;
+
+ default:
+ $item->$key = $value;
+ break;
+ }
+ }
+
+ return $item;
+ }
+}
diff --git a/includes/RestAPI.php b/includes/RestAPI/Controllers/Software.php
similarity index 91%
rename from includes/RestAPI.php
rename to includes/RestAPI/Controllers/Software.php
index e00356ce..6480000e 100644
--- a/includes/RestAPI.php
+++ b/includes/RestAPI/Controllers/Software.php
@@ -1,36 +1,27 @@
$key,
'product_id' => $product_id,
@@ -208,7 +199,7 @@ public function validate_key( $request ) {
$product_id = absint( $request->get_param( 'product_id' ) );
$key = sanitize_text_field( $request->get_param( 'serial_key' ) );
- $serial_key = Key::get(
+ $serial_key = Key::find(
array(
'serial_key' => $key,
'product_id' => $product_id,
@@ -225,11 +216,11 @@ public function validate_key( $request ) {
'status' => 'sold' === $serial_key->get_status() ? 'active' : $serial_key->get_status(),
'product_id' => $serial_key->get_product_id(),
'product' => $serial_key->get_product_title(),
- 'activations' => $serial_key->get_activations(
- array(
- 'limit' => - 1,
- 'output' => ARRAY_A,
- )
+ 'activations' => array_map(
+ function ( $activation ) {
+ return $activation->to_array();
+ },
+ $serial_key->get_activations( array( 'limit' => - 1 ) )
),
// Deprecated.
@@ -259,7 +250,7 @@ public function activate_key( $request ) {
$instance = md5( $email . $platform . time() );
}
- $serial_key = Key::get(
+ $serial_key = Key::find(
array(
'serial_key' => $key,
'product_id' => $product_id,
@@ -267,7 +258,7 @@ public function activate_key( $request ) {
);
// Check if instance is already activated.
- $activation = Activation::get(
+ $activation = Activation::find(
array(
'serial_id' => $serial_key->get_id(),
'instance' => $instance,
@@ -306,11 +297,11 @@ public function activate_key( $request ) {
'expires_at' => $serial_key->get_expire_date(),
'product_id' => $serial_key->get_product_id(),
'product' => $serial_key->get_product_title(),
- 'activations' => $serial_key->get_activations(
- array(
- 'limit' => - 1,
- 'output' => ARRAY_A,
- )
+ 'activations' => array_map(
+ function ( $activation ) {
+ return $activation->to_array();
+ },
+ $serial_key->get_activations( array( 'limit' => - 1 ) )
),
// Deprecated.
@@ -337,14 +328,14 @@ public function deactivate_key( $request ) {
return new \WP_Error( 'missing_instance', __( 'Instance is missing, You must provide an instance to deactivate license.', 'wc-serial-numbers' ), array( 'status' => 400 ) );
}
- $serial_key = Key::get(
+ $serial_key = Key::find(
array(
'serial_key' => $key,
'product_id' => $product_id,
)
);
- $activation = Activation::get(
+ $activation = Activation::find(
array(
'serial_id' => $serial_key->get_id(),
'instance' => $instance,
@@ -369,11 +360,11 @@ public function deactivate_key( $request ) {
'expires_at' => $serial_key->get_expire_date(),
'product_id' => $serial_key->get_product_id(),
'product' => $serial_key->get_product_title(),
- 'activations' => $serial_key->get_activations(
- array(
- 'limit' => - 1,
- 'output' => ARRAY_A,
- )
+ 'activations' => array_map(
+ function ( $activation ) {
+ return $activation->to_array();
+ },
+ $serial_key->get_activations( array( 'limit' => - 1 ) )
),
// Deprecated.
@@ -395,7 +386,7 @@ public function version_check( $request ) {
$product_id = absint( $request->get_param( 'product_id' ) );
$key = sanitize_text_field( $request->get_param( 'serial_key' ) );
- $serial_key = Key::get(
+ $serial_key = Key::find(
array(
'serial_key' => $key,
'product_id' => $product_id,
diff --git a/includes/RestAPI/Routes.php b/includes/RestAPI/Routes.php
new file mode 100644
index 00000000..e1a45dc8
--- /dev/null
+++ b/includes/RestAPI/Routes.php
@@ -0,0 +1,47 @@
+app->router;
+
+ // Resources.
+ $router->resource( 'keys', Keys::class );
+ $router->resource( 'activations', Activations::class );
+
+ // Software licensing endpoints, registered under the legacy unversioned namespace.
+ $this->app->make( Software::class )->register_routes();
+ }
+}
diff --git a/includes/Frontend/Shortcodes.php b/includes/Shop.php
similarity index 91%
rename from includes/Frontend/Shortcodes.php
rename to includes/Shop.php
index 250d1187..854f0d72 100644
--- a/includes/Frontend/Shortcodes.php
+++ b/includes/Shop.php
@@ -1,17 +1,17 @@
app->scripts->enqueue_style( 'wc-serial-numbers-frontend', 'frontend.css' );
+ $this->app->scripts->enqueue_script( 'wc-serial-numbers-frontend', 'frontend.js', array( 'jquery' ) );
+ wp_localize_script(
+ 'wc-serial-numbers-frontend',
+ 'wc_serial_numbers_frontend_vars',
+ array(
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
+ 'i18n' => array(
+ 'copied' => __( 'Copied', 'wc-serial-numbers' ),
+ 'loading' => __( 'Loading', 'wc-serial-numbers' ),
+ ),
+ )
+ );
+ }
}
diff --git a/includes/Admin/ListTables/StockTable.php b/includes/Stocks/ListTable.php
similarity index 96%
rename from includes/Admin/ListTables/StockTable.php
rename to includes/Stocks/ListTable.php
index 6e08b512..196b8cd5 100644
--- a/includes/Admin/ListTables/StockTable.php
+++ b/includes/Stocks/ListTable.php
@@ -1,6 +1,8 @@
set_stock_quantity( $quantity );
$product->save();
}
+
+ /**
+ * Send low stock email notification.
+ *
+ * @since 1.2.0
+ * @return bool
+ */
+ public function send_stock_alert_email() {
+ if ( 'yes' !== get_option( 'wc_serial_numbers_enable_stock_notification' ) ) {
+ return false;
+ }
+
+ $stock_threshold = get_option( 'wc_serial_numbers_stock_threshold', 5 );
+ $to = get_option( 'wc_serial_numbers_notification_recipient', get_option( 'admin_email' ) );
+ if ( empty( $to ) ) {
+ return false;
+ }
+
+ $low_stock_products = wcsn_get_stocks_count( $stock_threshold );
+ if ( empty( $low_stock_products ) ) {
+ return false;
+ }
+
+ $subject = __( 'Serial Numbers stock running low', 'wc-serial-numbers' );
+ /** $woocommerce WooCommerce */
+ global $woocommerce;
+ $mailer = $woocommerce->mailer();
+
+ ob_start();
+ wcsn_get_template( 'email-stock-notification.php', array( 'low_stock_products' => $low_stock_products ) );
+ $message = ob_get_contents();
+ ob_get_clean();
+
+ $message = $mailer->wrap_message( $subject, $message );
+ $headers = apply_filters( 'woocommerce_email_headers', '', 'wc_serial_numbers_low_stock_notification', $mailer );
+ $mailer->send( $to, $subject, $message, $headers, array() );
+
+ exit();
+ }
}
diff --git a/includes/deprecated.php b/includes/deprecated.php
index 9d0829ab..bb105e0f 100644
--- a/includes/deprecated.php
+++ b/includes/deprecated.php
@@ -5,11 +5,46 @@
* Deprecated functions.
*
* @since 1.4.6
- * @package WooCommerceSerialNumbers/Functions
+ * @package PluginEver\SerialNumbers/Functions
*/
defined( 'ABSPATH' ) || exit;
+/**
+ * Alias the old WooCommerceSerialNumbers classes to their new names.
+ *
+ * Registered prepended so the alias resolves before the Composer autoloader
+ * reports the old class as missing.
+ */
+spl_autoload_register(
+ function ( $class_name ) {
+ if ( 0 !== strpos( $class_name, 'WooCommerceSerialNumbers\\' ) ) {
+ return;
+ }
+
+ $renamed = array(
+ 'WooCommerceSerialNumbers\\Admin\\ListTables\\KeysTable' => PluginEver\SerialNumbers\Keys\ListTable::class,
+ 'WooCommerceSerialNumbers\\Admin\\ListTables\\ActivationsTable' => PluginEver\SerialNumbers\Activations\ListTable::class,
+ 'WooCommerceSerialNumbers\\Admin\\ListTables\\StockTable' => PluginEver\SerialNumbers\Stocks\ListTable::class,
+ 'WooCommerceSerialNumbers\\Admin\\Requests' => PluginEver\SerialNumbers\Keys\Admin::class,
+ 'WooCommerceSerialNumbers\\Admin\\Orders' => PluginEver\SerialNumbers\Orders\Admin::class,
+ 'WooCommerceSerialNumbers\\Admin\\Products' => PluginEver\SerialNumbers\Products::class,
+ 'WooCommerceSerialNumbers\\Frontend\\Frontend' => PluginEver\SerialNumbers\Shop::class,
+ 'WooCommerceSerialNumbers\\Frontend\\Shortcodes' => PluginEver\SerialNumbers\Shop::class,
+ );
+
+ $target = isset( $renamed[ $class_name ] )
+ ? $renamed[ $class_name ]
+ : str_replace( 'WooCommerceSerialNumbers\\', 'PluginEver\\SerialNumbers\\', $class_name );
+
+ if ( class_exists( $target ) ) {
+ class_alias( $target, $class_name );
+ }
+ },
+ true,
+ true
+);
+
/**
* Get manager role.
*
@@ -424,7 +459,7 @@ function wc_serial_numbers_validate_boolean( $string ) {
*
* @since 1.5.6
* @deprecated 1.5.6
- * @return WooCommerceSerialNumbers\Plugin
+ * @return PluginEver\SerialNumbers\Plugin
*/
function wc_serial_numbers() {
return WCSN();
diff --git a/includes/functions.php b/includes/functions.php
index e13c80ec..cd4463e4 100644
--- a/includes/functions.php
+++ b/includes/functions.php
@@ -3,16 +3,16 @@
* Essential functions for the plugin.
*
* @since 1.0.0
- * @package WooCommerceSerialNumbers
+ * @package PluginEver\SerialNumbers
*/
-use WooCommerceSerialNumbers\Encryption;
-use WooCommerceSerialNumbers\Models\Activation;
-use WooCommerceSerialNumbers\Models\Key;
+use PluginEver\SerialNumbers\Encryption;
+use PluginEver\SerialNumbers\Models\Activation;
+use PluginEver\SerialNumbers\Models\Key;
defined( 'ABSPATH' ) || exit;
-require_once __DIR__ . '/Functions/Template.php';
+require_once __DIR__ . '/template.php';
/**
* Get manager role.
@@ -162,7 +162,7 @@ function wcsn_get_order_object( $order ) {
* @return Key|WP_Error object on success, WP_Error object on failure.
*/
function wcsn_insert_key( $args, $wp_error = true ) {
- return Key::insert( $args, $wp_error );
+ return Key::insert( $args, null, $wp_error );
}
/**
@@ -180,9 +180,32 @@ function wcsn_get_keys( $args = array(), $count = false ) {
'offset' => 0,
'orderby' => 'id',
'order' => 'DESC',
- 'fields' => 'all',
);
$args = wp_parse_args( $args, $defaults );
+
+ // Map the function's public per_page/paged args to the native limit/page args.
+ if ( isset( $args['per_page'] ) ) {
+ $args['limit'] = (int) $args['per_page'];
+ unset( $args['per_page'] );
+ }
+ if ( isset( $args['paged'] ) ) {
+ $args['page'] = max( 1, (int) $args['paged'] );
+ unset( $args['paged'] );
+ }
+
+ // Constrain to the orders of the given customer.
+ if ( ! empty( $args['customer_id'] ) ) {
+ $order_ids = wc_get_orders(
+ array(
+ 'customer_id' => absint( $args['customer_id'] ),
+ 'limit' => - 1,
+ 'return' => 'ids',
+ )
+ );
+ $args['order_id__in'] = ! empty( $order_ids ) ? $order_ids : array( 0 );
+ }
+ unset( $args['customer_id'] );
+
if ( $count ) {
return Key::count( $args );
}
@@ -199,7 +222,9 @@ function wcsn_get_keys( $args = array(), $count = false ) {
* @return Key|false
*/
function wcsn_get_key( $key ) {
- return Key::get( $key );
+ $key = Key::find( $key );
+
+ return $key ? $key : false;
}
/**
@@ -246,14 +271,45 @@ function wcsn_get_activations( $args = array(), $count = false ) {
'offset' => 0,
'orderby' => 'id',
'order' => 'DESC',
- 'fields' => 'all',
);
$args = wp_parse_args( $args, $defaults );
+
+ // Map the function's public per_page/paged args to the native limit/page args.
+ if ( isset( $args['per_page'] ) ) {
+ $args['limit'] = (int) $args['per_page'];
+ unset( $args['per_page'] );
+ }
+ if ( isset( $args['paged'] ) ) {
+ $args['page'] = max( 1, (int) $args['paged'] );
+ unset( $args['paged'] );
+ }
+
+ // order_id and product_id are key columns, constrain through the keys table.
+ $order_id = ! empty( $args['order_id'] ) ? absint( $args['order_id'] ) : 0;
+ $product_id = ! empty( $args['product_id'] ) ? absint( $args['product_id'] ) : 0;
+ unset( $args['order_id'], $args['product_id'] );
+
+ $query = Activation::query()->set_var( $args );
+
+ if ( $order_id || $product_id ) {
+ $key_table = ( new Key() )->get_table();
+ $activation_table = ( new Activation() )->get_table();
+ $query->join( $key_table, "{$activation_table}.serial_id", "{$key_table}.id" );
+
+ if ( $order_id ) {
+ $query->where( "{$key_table}.order_id", $order_id );
+ }
+
+ if ( $product_id ) {
+ $query->where( "{$key_table}.product_id", $product_id );
+ }
+ }
+
if ( $count ) {
- return Activation::count( $args );
+ return $query->count();
}
- return Activation::query( $args );
+ return $query->get();
}
/**
@@ -265,7 +321,9 @@ function wcsn_get_activations( $args = array(), $count = false ) {
* @return Activation|false
*/
function wcsn_get_activation( $activation ) {
- return Activation::get( $activation );
+ $activation = Activation::find( $activation );
+
+ return $activation ? $activation : false;
}
/**
@@ -447,7 +505,6 @@ function wcsn_order_get_unfulfilled_items( $order_id ) {
*/
function wcsn_order_update_keys( $order_id ) {
$order = wcsn_get_order_object( $order_id );
- $customer_id = $order->get_customer_id();
$line_items = wcsn_get_order_line_items_data( $order );
$revoke_statues = wcsn_get_revoke_statuses();
$order_status = $order->get_status( 'edit' );
@@ -549,12 +606,11 @@ function wcsn_order_update_keys( $order_id ) {
// Assign keys to order.
foreach ( $keys as $key ) {
- $key->set_data(
+ $key->fill(
array(
'order_id' => $order_id,
'order_item_id' => $item['order_item_id'],
'order_date' => $order->get_date_created() ? $order->get_date_created()->format( 'Y-m-d H:i:s' ) : current_time( 'mysql' ),
- 'customer_id' => $customer_id,
'status' => 'sold',
)
);
@@ -679,7 +735,7 @@ function wcsn_order_remove_keys( $order_id, $product_id = null ) {
$props['order_item_id'] = 0;
$props['order_date'] = null;
}
- $key->set_data( $props );
+ $key->fill( $props );
$key->save();
}
@@ -713,7 +769,6 @@ function wcsn_order_replace_key( $order_id, $product_id = null, $key_id = null )
$args = array(
'order_id' => $order_id,
'limit' => - 1,
- 'no_count' => true,
);
if ( ! empty( $product_id ) ) {
@@ -752,7 +807,7 @@ function wcsn_order_replace_key( $order_id, $product_id = null, $key_id = null )
$props['order_item_id'] = 0;
$props['order_date'] = null;
}
- $key->set_data( $props );
+ $key->fill( $props );
if ( $key->save() ) {
++$replaced;
}
diff --git a/includes/Functions/Template.php b/includes/template.php
similarity index 98%
rename from includes/Functions/Template.php
rename to includes/template.php
index ab173581..31f2051c 100644
--- a/includes/Functions/Template.php
+++ b/includes/template.php
@@ -3,10 +3,10 @@
* Template functions.
*
* @since 1.4.6
- * @package WooCommerceSerialNumbers/Functions
+ * @package PluginEver\SerialNumbers/Functions
*/
-use WooCommerceSerialNumbers\Models\Key;
+use PluginEver\SerialNumbers\Models\Key;
defined( 'ABSPATH' ) || exit;
diff --git a/languages/wc-serial-numbers.pot b/languages/wc-serial-numbers.pot
index 89a53c7b..9fbc95ae 100644
--- a/languages/wc-serial-numbers.pot
+++ b/languages/wc-serial-numbers.pot
@@ -9,20 +9,19 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"POT-Creation-Date: 2026-06-10T18:24:21+00:00\n"
+"POT-Creation-Date: 2026-06-10T19:33:07+00:00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"X-Generator: WP-CLI 2.12.0\n"
"X-Domain: wc-serial-numbers\n"
#. Plugin Name of the plugin
#: wc-serial-numbers.php
-#: includes/Admin/Admin.php:286
-#: includes/Admin/Menus.php:56
-#: includes/Admin/Menus.php:90
-#: includes/Admin/Menus.php:91
-#: includes/Admin/Menus.php:435
-#: includes/Admin/Products.php:94
-#: includes/Functions/Template.php:226
+#: includes/Admin/Admin.php:285
+#: includes/Admin/Menus.php:49
+#: includes/Admin/Menus.php:50
+#: includes/Admin/Tools.php:186
+#: includes/Products.php:104
+#: includes/template.php:226
msgid "Serial Numbers"
msgstr ""
@@ -46,1376 +45,1433 @@ msgstr ""
msgid "https://pluginever.com/"
msgstr ""
+#: includes/Activations/ListTable.php:41
+#: includes/Keys/ListTable.php:358
+msgid "Activation"
+msgstr ""
+
+#: includes/Activations/ListTable.php:42
+#: includes/Admin/Menus.php:80
+#: includes/Admin/Menus.php:81
+#: templates/admin/list-activations.php:18
+msgid "Activations"
+msgstr ""
+
+#: includes/Activations/ListTable.php:102
+msgid "No activations found. Once a serial key is activated, it will appear here."
+msgstr ""
+
+#: includes/Activations/ListTable.php:115
+#: includes/Keys/ListTable.php:283
+#: includes/Stocks/ListTable.php:84
+msgid "Filter"
+msgstr ""
+
+#: includes/Activations/ListTable.php:166
+#: includes/Activations/ListTable.php:241
+#: includes/Keys/ListTable.php:338
+#: includes/Keys/ListTable.php:430
+#: includes/RestAPI/Controllers/Activations.php:248
+#: includes/RestAPI/Controllers/Keys.php:250
+#: templates/admin/edit-key.php:130
+msgid "Delete"
+msgstr ""
+
+#: includes/Activations/ListTable.php:178
+#: includes/Shop.php:151
+#: templates/admin/api-actions.php:141
+msgid "Instance"
+msgstr ""
+
+#: includes/Activations/ListTable.php:179
+#: includes/deprecated.php:394
+#: includes/Keys/ListTable.php:352
+#: includes/Shop.php:49
+#: includes/Shop.php:146
+#: includes/Stocks/ListTable.php:97
+#: templates/admin/api-actions.php:118
+#: templates/admin/api-validation.php:121
+#: templates/admin/edit-key.php:37
+msgid "Product"
+msgstr ""
+
+#: includes/Activations/ListTable.php:180
+#: includes/Compat.php:91
+#: includes/functions.php:1015
+#: includes/Keys/ListTable.php:351
+#: includes/Orders/Admin.php:279
+#: includes/template.php:42
+#: templates/admin/api-actions.php:131
+#: templates/admin/api-validation.php:134
+msgid "Key"
+msgstr ""
+
+#: includes/Activations/ListTable.php:181
+#: includes/Shop.php:153
+#: templates/admin/api-actions.php:152
+msgid "Platform"
+msgstr ""
+
+#: includes/Activations/ListTable.php:182
+msgid "Activation Time"
+msgstr ""
+
#. translators: %s: link to the plugin page
-#: includes/Admin/Admin.php:78
+#: includes/Admin/Admin.php:77
#, php-format
msgid "%s is not functional because you are using outdated version of the plugin, please update to the version 1.3.8 or higher."
msgstr ""
#. translators: 1: discount amount 2: promo code 3: link start 4: link end 5: plugin name 6: PRO label
-#: includes/Admin/Admin.php:94
+#: includes/Admin/Admin.php:93
#, php-format
msgid "Upgrade to %6$s to unlock the full potential of %5$s and avail a %1$s discount by using the promo code %2$s. %3$s Upgrade Now%4$s."
msgstr ""
-#: includes/Admin/Admin.php:119
-#: includes/Admin/Menus.php:172
-#: includes/Admin/Menus.php:173
+#: includes/Admin/Admin.php:118
+#: includes/Admin/Menus.php:131
+#: includes/Admin/Menus.php:132
msgid "Settings"
msgstr ""
-#: includes/Admin/Admin.php:137
+#: includes/Admin/Admin.php:136
msgid "Go Pro"
msgstr ""
-#: includes/Admin/Admin.php:160
+#: includes/Admin/Admin.php:159
#: includes/Admin/Settings.php:69
msgid "Documentation"
msgstr ""
-#: includes/Admin/Admin.php:166
+#: includes/Admin/Admin.php:165
msgid "Support"
msgstr ""
-#: includes/Admin/Admin.php:172
+#: includes/Admin/Admin.php:171
msgid "Review"
msgstr ""
-#: includes/Admin/Admin.php:178
+#: includes/Admin/Admin.php:177
msgid "More Plugins"
msgstr ""
-#: includes/Admin/Admin.php:211
+#: includes/Admin/Admin.php:210
msgid "Search by product"
msgstr ""
-#: includes/Admin/Admin.php:212
+#: includes/Admin/Admin.php:211
msgid "Search by order"
msgstr ""
-#: includes/Admin/Admin.php:213
+#: includes/Admin/Admin.php:212
msgid "Search by customer"
msgstr ""
-#: includes/Admin/Admin.php:214
+#: includes/Admin/Admin.php:213
msgid "Show"
msgstr ""
-#: includes/Admin/Admin.php:215
+#: includes/Admin/Admin.php:214
msgid "Hide"
msgstr ""
-#: includes/Admin/Admin.php:216
-#: includes/Frontend/Frontend.php:54
+#: includes/Admin/Admin.php:215
+#: includes/Shop.php:348
msgid "Copied"
msgstr ""
#. translators: 1: Plugin name 2: WordPress
-#: includes/Admin/Admin.php:253
+#: includes/Admin/Admin.php:252
#, php-format
msgid "Thank you for using %1$s! Share your appreciation with a five-star review %2$s."
msgstr ""
-#: includes/Admin/Admin.php:255
+#: includes/Admin/Admin.php:254
msgid "Thanks :)"
msgstr ""
#. translators: 1: Plugin version
-#: includes/Admin/Admin.php:273
+#: includes/Admin/Admin.php:272
#, php-format
msgid "Version %s"
msgstr ""
-#: includes/Admin/ListTables/ActivationsTable.php:39
-#: includes/Admin/ListTables/KeysTable.php:356
-msgid "Activation"
+#: includes/Admin/ListTables/ListTable.php:140
+#: includes/Admin/ListTables/ListTable.php:142
+msgid "Filter by order"
+msgstr ""
+
+#: includes/Admin/ListTables/ListTable.php:163
+msgid "Filter by product"
+msgstr ""
+
+#: includes/Admin/ListTables/ListTable.php:186
+msgid "Filter by customer"
+msgstr ""
+
+#: includes/Admin/Menus.php:60
+#: includes/Admin/Menus.php:61
+#: templates/admin/list-keys.php:18
+msgid "Serial Keys"
+msgstr ""
+
+#: includes/Admin/Menus.php:97
+#: includes/Admin/Menus.php:98
+msgid "Tools"
+msgstr ""
+
+#: includes/Admin/Menus.php:114
+#: includes/Admin/Menus.php:115
+msgid "Reports"
+msgstr ""
+
+#: includes/Admin/Menus.php:150
+#: includes/Products.php:205
+#: includes/Products.php:226
+msgid "Upgrade to Pro"
+msgstr ""
+
+#: includes/Admin/Reports.php:36
+#: includes/Stocks/ListTable.php:100
+msgid "Stock"
+msgstr ""
+
+#: includes/Admin/Settings.php:86
+msgid "General"
+msgstr ""
+
+#: includes/Admin/Settings.php:120
+msgid "General Settings"
+msgstr ""
+
+#: includes/Admin/Settings.php:122
+msgid "These options determine the behavior and operation of the plugin."
+msgstr ""
+
+#: includes/Admin/Settings.php:126
+msgid "Auto-complete orders"
+msgstr ""
+
+#: includes/Admin/Settings.php:128
+msgid "Automatically completes orders after successful payments."
+msgstr ""
+
+#: includes/Admin/Settings.php:133
+msgid "Reuse keys"
+msgstr ""
+
+#: includes/Admin/Settings.php:135
+msgid "Recover failed, refunded keys for selling again."
+msgstr ""
+
+#: includes/Admin/Settings.php:136
+msgid "If you enable this option, the keys will be available for selling again if the order is refunded or failed."
+msgstr ""
+
+#: includes/Admin/Settings.php:142
+msgid "Revoke keys"
+msgstr ""
+
+#: includes/Admin/Settings.php:144
+msgid "Revoke keys when the order status changes to cancelled or refunded."
+msgstr ""
+
+#: includes/Admin/Settings.php:145
+msgid "If you enable this option, the keys will be revoked when the order status changes to cancelled or refunded."
+msgstr ""
+
+#: includes/Admin/Settings.php:150
+msgid "Hide keys"
+msgstr ""
+
+#: includes/Admin/Settings.php:152
+msgid "Keys will be masked in the list table."
+msgstr ""
+
+#: includes/Admin/Settings.php:157
+msgid "Disable software support"
+msgstr ""
+
+#: includes/Admin/Settings.php:159
+msgid "Disable Software Licensing support & API functionalities."
+msgstr ""
+
+#: includes/Admin/Settings.php:160
+msgid "If you enable this option, the activation menu and it’s functionality will be turned off."
+msgstr ""
+
+#: includes/Admin/Settings.php:166
+msgid "Manage Stocks"
+msgstr ""
+
+#: includes/Admin/Settings.php:168
+msgid "Manage stocks for the key enabled products."
+msgstr ""
+
+#: includes/Admin/Settings.php:169
+msgid "Enable stock management for key-enabled products. This works only if you select \"Manually Added\" as the key source and enable stock management for the product. Variable product is not supported."
+msgstr ""
+
+#: includes/Admin/Settings.php:175
+msgid "WooCommerce PDF Invoices"
+msgstr ""
+
+#: includes/Admin/Settings.php:177
+msgid "Enable WooCommerce PDF Invoices."
+msgstr ""
+
+#. translators: %s: documentation link
+#: includes/Admin/Settings.php:180
+#, php-format
+msgid "If you enable this option, the plugin will be compatible with WooCommerce PDF Invoices & Packing Slips plugins and will show the serial keys in the invoice. Check out the documentation for more details."
+msgstr ""
+
+#: includes/Admin/Settings.php:191
+msgid "Stock Notification"
+msgstr ""
+
+#: includes/Admin/Settings.php:193
+msgid "These options determine the operation of the key's stock notification."
+msgstr ""
+
+#: includes/Admin/Settings.php:197
+msgid "Stock notification email"
+msgstr ""
+
+#: includes/Admin/Settings.php:199
+msgid "Sends notification emails when key stock is low."
+msgstr ""
+
+#: includes/Admin/Settings.php:205
+msgid "Stock threshold"
+msgstr ""
+
+#: includes/Admin/Settings.php:207
+msgid "An email notification will be sent when the key stock falls below the specified number."
+msgstr ""
+
+#: includes/Admin/Settings.php:212
+msgid "Notification recipient email"
+msgstr ""
+
+#: includes/Admin/Settings.php:214
+msgid "The email address which will be used to send email notifications."
+msgstr ""
+
+#: includes/Admin/Settings.php:287
+msgid "Create and assign keys for WooCommerce variable products."
+msgstr ""
+
+#: includes/Admin/Settings.php:288
+msgid "Generate bulk keys with your custom key generator rule."
+msgstr ""
+
+#: includes/Admin/Settings.php:289
+msgid "Random & sequential key order for the generator rules."
+msgstr ""
+
+#: includes/Admin/Settings.php:290
+msgid "Automatic key generator to auto-create & assign keys with orders."
+msgstr ""
+
+#: includes/Admin/Settings.php:291
+msgid "License key management option from the order page with required actions."
+msgstr ""
+
+#: includes/Admin/Settings.php:292
+msgid "Support for bulk import/export of keys from/to CSV."
+msgstr ""
+
+#: includes/Admin/Settings.php:293
+msgid "Send keys via SMS with Twilio."
+msgstr ""
+
+#: includes/Admin/Settings.php:294
+msgid "Option to sell keys even if there are no available keys in the stock."
+msgstr ""
+
+#: includes/Admin/Settings.php:295
+msgid "Custom deliverable quantity to deliver multiple keys with a single product."
+msgstr ""
+
+#: includes/Admin/Settings.php:296
+msgid "Manual delivery option to manually deliver license keys instead of automatic."
+msgstr ""
+
+#: includes/Admin/Settings.php:297
+msgid "Email template to easily and quickly customize the order confirmation & low stock alert email."
+msgstr ""
+
+#: includes/Admin/Settings.php:298
+msgid "Many more ..."
+msgstr ""
+
+#: includes/Admin/Settings.php:302
+msgid "Want More?"
+msgstr ""
+
+#: includes/Admin/Settings.php:303
+msgid "This plugin offers a premium version which comes with the following features:"
+msgstr ""
+
+#: includes/Admin/Settings.php:309
+msgid "Upgrade to PRO"
+msgstr ""
+
+#: includes/Admin/Tools.php:39
+#: includes/Admin/Tools.php:134
+msgid "Generators"
+msgstr ""
+
+#: includes/Admin/Tools.php:40
+msgid "API Toolkit"
+msgstr ""
+
+#: includes/Admin/Tools.php:41
+#: templates/admin/list-keys.php:25
+msgid "Import"
+msgstr ""
+
+#: includes/Admin/Tools.php:42
+#: templates/admin/list-keys.php:29
+msgid "Export"
+msgstr ""
+
+#: includes/Admin/Tools.php:73
+#: includes/Compat.php:111
+#: includes/functions.php:1040
+#: includes/Keys/ListTable.php:362
+#: includes/Orders/Admin.php:291
+#: includes/template.php:78
+#: templates/admin/edit-key.php:92
+msgid "Status"
+msgstr ""
+
+#: includes/Admin/Tools.php:88
+#: includes/Admin/Tools.php:95
+#: includes/Admin/Tools.php:113
+#: includes/Admin/Tools.php:131
+msgid "Available in Pro Version"
+msgstr ""
+
+#: includes/Admin/Tools.php:89
+#: includes/Admin/Tools.php:96
+#: includes/Admin/Tools.php:114
+#: includes/Admin/Tools.php:132
+msgid "Upgrade to Pro Now"
+msgstr ""
+
+#: includes/Admin/Tools.php:91
+#: includes/Admin/Tools.php:98
+msgid "Import Serial Numbers"
+msgstr ""
+
+#: includes/Admin/Tools.php:116
+msgid "Export Serial Numbers"
+msgstr ""
+
+#: includes/Admin/Tools.php:161
+msgid "Table exists"
+msgstr ""
+
+#: includes/Admin/Tools.php:163
+msgid "Table does not exist"
+msgstr ""
+
+#: includes/Admin/Tools.php:169
+msgid "Hourly cron"
+msgstr ""
+
+#: includes/Admin/Tools.php:170
+msgid "Daily cron"
+msgstr ""
+
+#. translators: %s: Next scheduled time.
+#: includes/Admin/Tools.php:176
+#, php-format
+msgid "Next run: %s"
+msgstr ""
+
+#: includes/Admin/Tools.php:178
+msgid "Not scheduled"
+msgstr ""
+
+#: includes/API.php:80
+msgid "Missing data."
+msgstr ""
+
+#. translators: %1$s: product title, %2$s: stock quantity.
+#: includes/Cart.php:58
+#, php-format
+msgid "Sorry, there aren’t enough Serial Keys for %1$s. Please remove this item or lower the quantity. For now, we have %2$s Serial Keys for this product."
+msgstr ""
+
+#: includes/Compat.php:86
+#: includes/Orders/Admin.php:274
+msgid "Serial keys sold with this product:"
+msgstr ""
+
+#: includes/Compat.php:95
+#: includes/Orders/Admin.php:283
+msgid "Expire date"
+msgstr ""
+
+#: includes/Compat.php:96
+#: includes/functions.php:1036
+#: includes/Keys/ListTable.php:534
+#: includes/Orders/Admin.php:284
+#: includes/template.php:63
+msgid "Lifetime"
+msgstr ""
+
+#: includes/Compat.php:99
+#: includes/Orders/Admin.php:287
+#: templates/admin/edit-key.php:67
+msgid "Activation limit"
+msgstr ""
+
+#: includes/Compat.php:100
+#: includes/Orders/Admin.php:288
+msgid "Unlimited"
+msgstr ""
+
+#: includes/Compat.php:103
+msgid "Activation count"
+msgstr ""
+
+#: includes/Compat.php:107
+msgid "Activation email"
+msgstr ""
+
+#: includes/deprecated.php:189
+msgid "Serial number not found."
+msgstr ""
+
+#: includes/deprecated.php:395
+msgid "Serial Number"
+msgstr ""
+
+#: includes/deprecated.php:396
+#: includes/Shop.php:52
+#: includes/Shop.php:149
+#: templates/admin/api-actions.php:162
+#: templates/admin/api-validation.php:144
+#: templates/admin/edit-key.php:154
+msgid "Email"
+msgstr ""
+
+#: includes/deprecated.php:397
+#: includes/functions.php:1025
+#: includes/template.php:52
+msgid "Activation Limit"
+msgstr ""
+
+#: includes/deprecated.php:398
+msgid "Expires"
+msgstr ""
+
+#: includes/functions.php:46
+#: includes/Keys/ListTable.php:180
+#: includes/Keys/ListTable.php:237
+msgid "Available"
+msgstr ""
+
+#: includes/functions.php:47
+#: includes/Keys/ListTable.php:185
+#: includes/Keys/ListTable.php:244
+msgid "Pending"
+msgstr ""
+
+#: includes/functions.php:48
+#: includes/Keys/ListTable.php:190
+#: includes/Keys/ListTable.php:251
+#: includes/Stocks/ListTable.php:99
+msgid "Sold"
+msgstr ""
+
+#: includes/functions.php:49
+#: includes/Keys/ListTable.php:195
+#: includes/Keys/ListTable.php:258
+#: includes/template.php:72
+msgid "Expired"
+msgstr ""
+
+#: includes/functions.php:50
+#: includes/Keys/ListTable.php:200
+#: includes/Keys/ListTable.php:265
+msgid "Cancelled"
+msgstr ""
+
+#: includes/functions.php:91
+msgid "Manually added"
+msgstr ""
+
+#. translators: 1: product title 2: source and 3: Quantity
+#: includes/functions.php:539
+#, php-format
+msgid "There is not enough serial numbers for the product %1$s from selected source %2$s, needed total %3$d."
+msgstr ""
+
+#: includes/functions.php:1020
+#: includes/template.php:47
+msgid "Activation Email"
msgstr ""
-#: includes/Admin/ListTables/ActivationsTable.php:40
-#: includes/Admin/Menus.php:121
-#: includes/Admin/Menus.php:122
-#: includes/Admin/views/html-list-activations.php:18
-msgid "Activations"
+#: includes/functions.php:1026
+#: includes/functions.php:1031
+#: includes/template.php:53
+#: includes/template.php:58
+msgid "None"
msgstr ""
-#: includes/Admin/ListTables/ActivationsTable.php:100
-msgid "No activations found. Once a serial key is activated, it will appear here."
+#: includes/functions.php:1030
+#: includes/template.php:57
+msgid "Activation Count"
msgstr ""
-#: includes/Admin/ListTables/ActivationsTable.php:113
-#: includes/Admin/ListTables/KeysTable.php:281
-#: includes/Admin/ListTables/StockTable.php:82
-msgid "Filter"
+#: includes/functions.php:1035
+#: includes/template.php:62
+msgid "Expire Date"
msgstr ""
-#: includes/Admin/ListTables/ActivationsTable.php:164
-#: includes/Admin/ListTables/ActivationsTable.php:239
-#: includes/Admin/ListTables/KeysTable.php:336
-#: includes/Admin/ListTables/KeysTable.php:428
-#: includes/Admin/views/html-edit-key.php:130
-msgid "Delete"
+#: includes/Installer.php:60
+msgid "Once a Minute"
msgstr ""
-#: includes/Admin/ListTables/ActivationsTable.php:176
-#: includes/Admin/views/html-api-actions.php:141
-#: includes/Frontend/Shortcodes.php:148
-msgid "Instance"
+#: includes/Keys/Admin.php:81
+#: includes/Orders/Admin.php:81
+#: includes/Orders/Admin.php:220
+msgid "You do not have permission to perform this action."
msgstr ""
-#: includes/Admin/ListTables/ActivationsTable.php:177
-#: includes/Admin/ListTables/KeysTable.php:350
-#: includes/Admin/ListTables/StockTable.php:95
-#: includes/Admin/views/html-api-actions.php:118
-#: includes/Admin/views/html-api-validation.php:121
-#: includes/Admin/views/html-edit-key.php:37
-#: includes/deprecated.php:359
-#: includes/Frontend/Shortcodes.php:46
-#: includes/Frontend/Shortcodes.php:143
-msgid "Product"
+#: includes/Keys/Admin.php:124
+msgid "Key added successfully."
msgstr ""
-#: includes/Admin/ListTables/ActivationsTable.php:178
-#: includes/Admin/ListTables/KeysTable.php:349
-#: includes/Admin/Orders.php:269
-#: includes/Admin/views/html-api-actions.php:131
-#: includes/Admin/views/html-api-validation.php:134
-#: includes/Compat.php:91
-#: includes/functions.php:1015
-#: includes/Functions/Template.php:42
-msgid "Key"
+#: includes/Keys/Admin.php:126
+msgid "Key updated successfully."
msgstr ""
-#: includes/Admin/ListTables/ActivationsTable.php:179
-#: includes/Admin/views/html-api-actions.php:152
-#: includes/Frontend/Shortcodes.php:150
-msgid "Platform"
+#: includes/Keys/Admin.php:145
+#: includes/Keys/Admin.php:208
+#: includes/Keys/Admin.php:283
+msgid "You do not have permission to access this endpoint."
msgstr ""
-#: includes/Admin/ListTables/ActivationsTable.php:180
-msgid "Activation Time"
+#. translators: $1: customer name, $2 customer id, $3: customer email
+#: includes/Keys/Admin.php:319
+#, php-format
+msgid "%1$s (#%2$s - %3$s)"
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:78
+#: includes/Keys/ListTable.php:80
msgid "key"
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:79
+#: includes/Keys/ListTable.php:81
msgid "keys"
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:170
+#: includes/Keys/ListTable.php:172
msgid "No keys found."
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:170
+#: includes/Keys/ListTable.php:172
msgid "Add new key"
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:174
+#: includes/Keys/ListTable.php:176
msgid "Keys can have one of the following statuses:"
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:178
-#: includes/Admin/ListTables/KeysTable.php:235
-#: includes/functions.php:46
-msgid "Available"
-msgstr ""
-
-#: includes/Admin/ListTables/KeysTable.php:180
+#: includes/Keys/ListTable.php:182
msgid "This means the key is available for purchase."
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:183
-#: includes/Admin/ListTables/KeysTable.php:242
-#: includes/functions.php:47
-msgid "Pending"
-msgstr ""
-
-#: includes/Admin/ListTables/KeysTable.php:185
+#: includes/Keys/ListTable.php:187
msgid "This means the key has been sold, but the order has not been completed yet."
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:188
-#: includes/Admin/ListTables/KeysTable.php:249
-#: includes/Admin/ListTables/StockTable.php:97
-#: includes/functions.php:48
-msgid "Sold"
-msgstr ""
-
-#: includes/Admin/ListTables/KeysTable.php:190
+#: includes/Keys/ListTable.php:192
msgid "This means the key has been sold, and the order has been completed."
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:193
-#: includes/Admin/ListTables/KeysTable.php:256
-#: includes/functions.php:49
-#: includes/Functions/Template.php:72
-msgid "Expired"
-msgstr ""
-
-#: includes/Admin/ListTables/KeysTable.php:195
+#: includes/Keys/ListTable.php:197
msgid "This means the key has expired and is no longer valid."
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:198
-#: includes/Admin/ListTables/KeysTable.php:263
-#: includes/functions.php:50
-msgid "Cancelled"
-msgstr ""
-
-#: includes/Admin/ListTables/KeysTable.php:200
+#: includes/Keys/ListTable.php:202
msgid "This means the key has been cancelled and is no longer available for purchase or use."
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:226
+#: includes/Keys/ListTable.php:228
msgid "All keys."
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:228
+#: includes/Keys/ListTable.php:230
msgid "All"
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:233
+#: includes/Keys/ListTable.php:235
msgid "Available for sell."
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:240
+#: includes/Keys/ListTable.php:242
msgid "Pending payment."
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:247
+#: includes/Keys/ListTable.php:249
msgid "Sold keys."
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:254
+#: includes/Keys/ListTable.php:256
msgid "Expired keys."
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:261
+#: includes/Keys/ListTable.php:263
msgid "Cancelled keys."
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:337
+#: includes/Keys/ListTable.php:339
msgid "Reset Activations"
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:351
+#: includes/Keys/ListTable.php:353
msgid "Order"
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:352
+#: includes/Keys/ListTable.php:354
msgid "Validity"
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:359
+#: includes/Keys/ListTable.php:361
msgid "Order Date"
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:360
-#: includes/Admin/Menus.php:322
-#: includes/Admin/Orders.php:281
-#: includes/Admin/views/html-edit-key.php:92
-#: includes/Compat.php:111
-#: includes/functions.php:1040
-#: includes/Functions/Template.php:78
-msgid "Status"
-msgstr ""
-
#. translators: %d: key id.
-#: includes/Admin/ListTables/KeysTable.php:426
+#: includes/Keys/ListTable.php:428
#, php-format
msgid "ID: %d"
msgstr ""
-#: includes/Admin/ListTables/KeysTable.php:427
-#: includes/Admin/ListTables/StockTable.php:143
+#: includes/Keys/ListTable.php:429
+#: includes/Stocks/ListTable.php:145
msgid "Edit"
msgstr ""
#. translators: %1$s: validity, %2$s: validity.
-#: includes/Admin/ListTables/KeysTable.php:522
+#: includes/Keys/ListTable.php:524
#, php-format
msgid "%s Day
After purchase"
msgid_plural "%s Days
After purchase"
msgstr[0] ""
msgstr[1] ""
-#: includes/Admin/ListTables/KeysTable.php:532
-#: includes/Admin/Orders.php:274
-#: includes/Compat.php:96
-#: includes/functions.php:1036
-#: includes/Functions/Template.php:63
-msgid "Lifetime"
-msgstr ""
-
-#: includes/Admin/ListTables/ListTable.php:140
-#: includes/Admin/ListTables/ListTable.php:142
-msgid "Filter by order"
-msgstr ""
-
-#: includes/Admin/ListTables/ListTable.php:163
-msgid "Filter by product"
-msgstr ""
-
-#: includes/Admin/ListTables/ListTable.php:186
-msgid "Filter by customer"
-msgstr ""
-
-#: includes/Admin/ListTables/StockTable.php:20
-msgid "stock"
-msgstr ""
-
-#: includes/Admin/ListTables/StockTable.php:21
-msgid "stocks"
-msgstr ""
-
-#: includes/Admin/ListTables/StockTable.php:70
-msgid "No products selling serial keys from \"stock\" found."
-msgstr ""
-
-#: includes/Admin/ListTables/StockTable.php:96
-msgid "Source"
-msgstr ""
-
-#: includes/Admin/ListTables/StockTable.php:98
-#: includes/Admin/Menus.php:282
-msgid "Stock"
-msgstr ""
-
-#: includes/Admin/ListTables/StockTable.php:172
-msgid "Manual"
-msgstr ""
-
-#: includes/Admin/ListTables/StockTable.php:174
-msgid "Generator Rule"
-msgstr ""
-
-#: includes/Admin/ListTables/StockTable.php:176
-msgid "Auto Generated"
-msgstr ""
-
-#: includes/Admin/ListTables/StockTable.php:178
-msgid "Unknown"
-msgstr ""
-
-#: includes/Admin/Menus.php:101
-#: includes/Admin/Menus.php:102
-#: includes/Admin/views/html-list-keys.php:18
-msgid "Serial Keys"
-msgstr ""
-
-#: includes/Admin/Menus.php:138
-#: includes/Admin/Menus.php:139
-msgid "Tools"
-msgstr ""
-
-#: includes/Admin/Menus.php:155
-#: includes/Admin/Menus.php:156
-msgid "Reports"
-msgstr ""
-
-#: includes/Admin/Menus.php:191
-#: includes/Admin/Products.php:195
-#: includes/Admin/Products.php:216
-msgid "Upgrade to Pro"
-msgstr ""
-
-#: includes/Admin/Menus.php:247
-#: includes/Admin/Menus.php:383
-msgid "Generators"
-msgstr ""
-
-#: includes/Admin/Menus.php:248
-msgid "API Toolkit"
-msgstr ""
-
-#: includes/Admin/Menus.php:249
-#: includes/Admin/views/html-list-keys.php:25
-msgid "Import"
-msgstr ""
-
-#: includes/Admin/Menus.php:250
-#: includes/Admin/views/html-list-keys.php:29
-msgid "Export"
-msgstr ""
-
-#: includes/Admin/Menus.php:337
-#: includes/Admin/Menus.php:344
-#: includes/Admin/Menus.php:362
-#: includes/Admin/Menus.php:380
-msgid "Available in Pro Version"
-msgstr ""
-
-#: includes/Admin/Menus.php:338
-#: includes/Admin/Menus.php:345
-#: includes/Admin/Menus.php:363
-#: includes/Admin/Menus.php:381
-msgid "Upgrade to Pro Now"
-msgstr ""
-
-#: includes/Admin/Menus.php:340
-#: includes/Admin/Menus.php:347
-msgid "Import Serial Numbers"
-msgstr ""
-
-#: includes/Admin/Menus.php:365
-msgid "Export Serial Numbers"
+#: includes/Models/Activation.php:292
+msgid "Serial id is required."
msgstr ""
-#: includes/Admin/Menus.php:410
-msgid "Table exists"
+#: includes/Models/Activation.php:297
+msgid "Instance is required."
msgstr ""
-#: includes/Admin/Menus.php:412
-msgid "Table does not exist"
+#: includes/Models/Key.php:578
+msgid "Product id is required."
msgstr ""
-#: includes/Admin/Menus.php:418
-msgid "Hourly cron"
+#: includes/Models/Key.php:583
+msgid "Product id is invalid."
msgstr ""
-#: includes/Admin/Menus.php:419
-msgid "Daily cron"
+#: includes/Models/Key.php:588
+#: includes/RestAPI/Controllers/Software.php:143
+msgid "Serial key is required."
msgstr ""
-#. translators: %s: Next scheduled time.
-#: includes/Admin/Menus.php:425
-#, php-format
-msgid "Next run: %s"
+#: includes/Models/Key.php:600
+msgid "Serial key already exists. Duplicate serial keys are not allowed."
msgstr ""
-#: includes/Admin/Menus.php:427
-msgid "Not scheduled"
+#: includes/Models/Key.php:606
+msgid "Order id is invalid."
msgstr ""
-#: includes/Admin/Orders.php:55
-#: includes/Admin/Orders.php:191
+#: includes/Orders/Admin.php:65
+#: includes/Orders/Admin.php:201
msgid "Add serial keys"
msgstr ""
-#: includes/Admin/Orders.php:56
-#: includes/Admin/Orders.php:192
+#: includes/Orders/Admin.php:66
+#: includes/Orders/Admin.php:202
msgid "Remove serial keys"
msgstr ""
-#: includes/Admin/Orders.php:71
-#: includes/Admin/Orders.php:210
-#: includes/Admin/Requests.php:44
-msgid "You do not have permission to perform this action."
-msgstr ""
-
-#: includes/Admin/Orders.php:81
+#: includes/Orders/Admin.php:91
msgid "Serial keys added successfully to the order."
msgstr ""
-#: includes/Admin/Orders.php:85
+#: includes/Orders/Admin.php:95
msgid "Serial keys removed successfully from the order."
msgstr ""
-#: includes/Admin/Orders.php:122
-#: includes/Admin/Orders.php:162
+#: includes/Orders/Admin.php:132
+#: includes/Orders/Admin.php:172
msgid "Order is fullfilled and completed."
msgstr ""
-#: includes/Admin/Orders.php:125
-#: includes/Admin/Orders.php:165
+#: includes/Orders/Admin.php:135
+#: includes/Orders/Admin.php:175
msgid "Order is fullfilled and processing."
msgstr ""
-#: includes/Admin/Orders.php:128
-#: includes/Admin/Orders.php:168
+#: includes/Orders/Admin.php:138
+#: includes/Orders/Admin.php:178
msgid "Order is fullfilled and on-hold."
msgstr ""
-#: includes/Admin/Orders.php:131
-#: includes/Admin/Orders.php:171
+#: includes/Orders/Admin.php:141
+#: includes/Orders/Admin.php:181
msgid "Order is fullfilled and draft."
msgstr ""
-#: includes/Admin/Orders.php:134
-#: includes/Admin/Orders.php:174
+#: includes/Orders/Admin.php:144
+#: includes/Orders/Admin.php:184
msgid "Order is not fullfilled."
msgstr ""
#. Translators: %d: number of orders.
-#: includes/Admin/Orders.php:226
+#: includes/Orders/Admin.php:236
#, php-format
msgid "%d orders updated successfully."
msgstr ""
-#: includes/Admin/Orders.php:264
-#: includes/Compat.php:86
-msgid "Serial keys sold with this product:"
-msgstr ""
-
-#: includes/Admin/Orders.php:273
-#: includes/Compat.php:95
-msgid "Expire date"
-msgstr ""
-
-#: includes/Admin/Orders.php:277
-#: includes/Admin/views/html-edit-key.php:67
-#: includes/Compat.php:99
-msgid "Activation limit"
-msgstr ""
-
-#: includes/Admin/Orders.php:278
-#: includes/Compat.php:100
-msgid "Unlimited"
+#: includes/Orders/Admin.php:318
+msgid "View Details"
msgstr ""
-#: includes/Admin/Orders.php:308
-msgid "View Details"
+#: includes/Orders/Orders.php:92
+msgid "Order automatically completed by the Serial Numbers for WooCommerce."
msgstr ""
-#: includes/Admin/Products.php:116
+#: includes/Products.php:126
msgid "Sell keys"
msgstr ""
-#: includes/Admin/Products.php:117
+#: includes/Products.php:127
msgid "Enable this if you are selling keys with this product."
msgstr ""
-#: includes/Admin/Products.php:130
+#: includes/Products.php:140
msgid "Delivery quantity"
msgstr ""
-#: includes/Admin/Products.php:131
+#: includes/Products.php:141
msgid "Number of key(s) will be delivered per item. Available in PRO."
msgstr ""
-#: includes/Admin/Products.php:151
-#: includes/Admin/Products.php:183
+#: includes/Products.php:161
+#: includes/Products.php:193
msgid "Key source"
msgstr ""
-#: includes/Admin/Products.php:169
+#: includes/Products.php:179
msgid "Software version"
msgstr ""
-#: includes/Admin/Products.php:170
+#: includes/Products.php:180
msgid "Version number for the software. Ignore if it's not a software."
msgstr ""
-#: includes/Admin/Products.php:171
+#: includes/Products.php:181
msgid "e.g. 1.0"
msgstr ""
-#: includes/Admin/Products.php:185
+#: includes/Products.php:195
msgid "key available."
msgid_plural "keys available."
msgstr[0] ""
msgstr[1] ""
-#: includes/Admin/Products.php:193
+#: includes/Products.php:203
msgid "Want to sell keys for variable products?"
msgstr ""
-#: includes/Admin/Products.php:214
+#: includes/Products.php:224
msgid "The free version of Serial Numbers for WooCommerce does not support product variation."
msgstr ""
-#: includes/Admin/Products.php:233
+#: includes/Products.php:243
msgid "You do not have permission to save this data."
msgstr ""
-#: includes/Admin/Requests.php:87
-msgid "Key added successfully."
+#: includes/RestAPI/Controllers/Activations.php:31
+#: includes/RestAPI/Controllers/Activations.php:94
+#: includes/RestAPI/Controllers/Activations.php:117
+#: includes/RestAPI/Controllers/Activations.php:163
+#: includes/RestAPI/Controllers/Activations.php:205
+#: includes/RestAPI/Controllers/Activations.php:241
+#: includes/RestAPI/Controllers/Activations.php:277
+#: includes/RestAPI/Controllers/Keys.php:31
+#: includes/RestAPI/Controllers/Keys.php:94
+#: includes/RestAPI/Controllers/Keys.php:117
+#: includes/RestAPI/Controllers/Keys.php:163
+#: includes/RestAPI/Controllers/Keys.php:205
+#: includes/RestAPI/Controllers/Keys.php:241
+#: includes/RestAPI/Controllers/Keys.php:279
+msgid "Not allowed."
msgstr ""
-#: includes/Admin/Requests.php:89
-msgid "Key updated successfully."
+#: includes/RestAPI/Controllers/Activations.php:100
+#: includes/RestAPI/Controllers/Activations.php:173
+#: includes/RestAPI/Controllers/Activations.php:211
+#: includes/RestAPI/Controllers/Activations.php:361
+msgid "Activation not found."
msgstr ""
-#: includes/Admin/Requests.php:108
-#: includes/Admin/Requests.php:171
-#: includes/Admin/Requests.php:246
-msgid "You do not have permission to access this endpoint."
+#: includes/RestAPI/Controllers/Activations.php:121
+msgid "Cannot create existing activation."
msgstr ""
-#. translators: $1: customer name, $2 customer id, $3: customer email
-#: includes/Admin/Requests.php:282
+#: includes/RestAPI/Controllers/Activations.php:293
+#: includes/RestAPI/Controllers/Keys.php:295
+msgid "Import not yet implemented."
+msgstr ""
+
+#: includes/RestAPI/Controllers/Activations.php:306
+#: includes/RestAPI/Controllers/Keys.php:308
+msgid "Export not yet implemented."
+msgstr ""
+
+#. translators: %d is the maximum batch size.
+#: includes/RestAPI/Controllers/Controller.php:257
#, php-format
-msgid "%1$s (#%2$s - %3$s)"
+msgid "Batch limit is %d items."
msgstr ""
-#: includes/Admin/Settings.php:86
-msgid "General"
+#: includes/RestAPI/Controllers/Keys.php:100
+#: includes/RestAPI/Controllers/Keys.php:173
+#: includes/RestAPI/Controllers/Keys.php:211
+#: includes/RestAPI/Controllers/Keys.php:364
+msgid "Key not found."
msgstr ""
-#: includes/Admin/Settings.php:120
-msgid "General Settings"
+#: includes/RestAPI/Controllers/Keys.php:121
+msgid "Cannot create existing key."
msgstr ""
-#: includes/Admin/Settings.php:122
-msgid "These options determine the behavior and operation of the plugin."
+#: includes/RestAPI/Controllers/Software.php:139
+msgid "Invalid product ID."
msgstr ""
-#: includes/Admin/Settings.php:126
-msgid "Auto-complete orders"
+#: includes/RestAPI/Controllers/Software.php:154
+msgid "Serial key is invalid."
msgstr ""
-#: includes/Admin/Settings.php:128
-msgid "Automatically completes orders after successful payments."
+#: includes/RestAPI/Controllers/Software.php:159
+msgid "Serial key is not authorized to use."
msgstr ""
-#: includes/Admin/Settings.php:133
-msgid "Reuse keys"
+#: includes/RestAPI/Controllers/Software.php:165
+msgid "Please complete your order to activate the serial key."
msgstr ""
-#: includes/Admin/Settings.php:135
-msgid "Recover failed, refunded keys for selling again."
+#: includes/RestAPI/Controllers/Software.php:170
+msgid "Serial key is not valid for this product."
msgstr ""
-#: includes/Admin/Settings.php:136
-msgid "If you enable this option, the keys will be available for selling again if the order is refunded or failed."
+#: includes/RestAPI/Controllers/Software.php:175
+msgid "Invalid email address."
msgstr ""
-#: includes/Admin/Settings.php:142
-msgid "Revoke keys"
+#: includes/RestAPI/Controllers/Software.php:180
+msgid "Serial key is expired."
msgstr ""
-#: includes/Admin/Settings.php:144
-msgid "Revoke keys when the order status changes to cancelled or refunded."
+#: includes/RestAPI/Controllers/Software.php:182
+msgid "Serial key is cancelled."
msgstr ""
-#: includes/Admin/Settings.php:145
-msgid "If you enable this option, the keys will be revoked when the order status changes to cancelled or refunded."
+#: includes/RestAPI/Controllers/Software.php:185
+msgid "Invalid serial key."
msgstr ""
-#: includes/Admin/Settings.php:150
-msgid "Hide keys"
+#: includes/RestAPI/Controllers/Software.php:211
+msgid "Serial key is valid."
msgstr ""
-#: includes/Admin/Settings.php:152
-msgid "Keys will be masked in the list table."
+#: includes/RestAPI/Controllers/Software.php:269
+msgid "Instance is already activated."
msgstr ""
-#: includes/Admin/Settings.php:157
-msgid "Disable software support"
+#: includes/RestAPI/Controllers/Software.php:274
+msgid "Activation limit reached."
msgstr ""
-#: includes/Admin/Settings.php:159
-msgid "Disable Software Licensing support & API functionalities."
+#: includes/RestAPI/Controllers/Software.php:290
+msgid "Serial key is activated."
msgstr ""
-#: includes/Admin/Settings.php:160
-msgid "If you enable this option, the activation menu and it’s functionality will be turned off."
+#: includes/RestAPI/Controllers/Software.php:328
+msgid "Instance is missing, You must provide an instance to deactivate license."
msgstr ""
-#: includes/Admin/Settings.php:166
-msgid "Manage Stocks"
+#: includes/RestAPI/Controllers/Software.php:346
+msgid "Instance not found."
msgstr ""
-#: includes/Admin/Settings.php:168
-msgid "Manage stocks for the key enabled products."
+#: includes/RestAPI/Controllers/Software.php:354
+msgid "Serial key is deactivated."
msgstr ""
-#: includes/Admin/Settings.php:169
-msgid "Enable stock management for key-enabled products. This works only if you select \"Manually Added\" as the key source and enable stock management for the product. Variable product is not supported."
+#: includes/Shop.php:48
+msgid "Serial Key Validation"
msgstr ""
-#: includes/Admin/Settings.php:175
-msgid "WooCommerce PDF Invoices"
+#: includes/Shop.php:50
+#: includes/Shop.php:147
+msgid "Serial Key"
msgstr ""
-#: includes/Admin/Settings.php:177
-msgid "Enable WooCommerce PDF Invoices."
+#: includes/Shop.php:51
+#: includes/Shop.php:148
+msgid "Enter your serial key"
msgstr ""
-#. translators: %s: documentation link
-#: includes/Admin/Settings.php:180
-#, php-format
-msgid "If you enable this option, the plugin will be compatible with WooCommerce PDF Invoices & Packing Slips plugins and will show the serial keys in the invoice. Check out the documentation for more details."
+#: includes/Shop.php:53
+#: includes/Shop.php:114
+#: includes/Shop.php:150
+#: includes/Shop.php:226
+msgid "Enter your email"
msgstr ""
-#: includes/Admin/Settings.php:191
-msgid "Stock Notification"
+#: includes/Shop.php:54
+#: templates/admin/api-validation.php:164
+msgid "Validate"
msgstr ""
-#: includes/Admin/Settings.php:193
-msgid "These options determine the operation of the key's stock notification."
+#: includes/Shop.php:85
+msgid "No products found."
msgstr ""
-#: includes/Admin/Settings.php:197
-msgid "Stock notification email"
+#: includes/Shop.php:99
+#: includes/Shop.php:210
+msgid "Select a product"
msgstr ""
-#: includes/Admin/Settings.php:199
-msgid "Sends notification emails when key stock is low."
+#: includes/Shop.php:145
+msgid "Activate/Deactivate Serial Key"
+msgstr ""
+
+#: includes/Shop.php:152
+msgid "Enter your instance"
msgstr ""
-#: includes/Admin/Settings.php:205
-msgid "Stock threshold"
+#: includes/Shop.php:154
+msgid "Enter platform"
msgstr ""
-#: includes/Admin/Settings.php:207
-msgid "An email notification will be sent when the key stock falls below the specified number."
+#: includes/Shop.php:155
+#: templates/admin/api-actions.php:172
+msgid "Action"
msgstr ""
-#: includes/Admin/Settings.php:212
-msgid "Notification recipient email"
+#: includes/Shop.php:156
+#: templates/admin/api-actions.php:195
+msgid "Submit"
msgstr ""
-#: includes/Admin/Settings.php:214
-msgid "The email address which will be used to send email notifications."
+#: includes/Shop.php:162
+#: templates/admin/api-actions.php:175
+msgid "Activate"
msgstr ""
-#: includes/Admin/Settings.php:287
-msgid "Create and assign keys for WooCommerce variable products."
+#: includes/Shop.php:163
+#: templates/admin/api-actions.php:176
+msgid "Deactivate"
msgstr ""
-#: includes/Admin/Settings.php:288
-msgid "Generate bulk keys with your custom key generator rule."
+#: includes/Shop.php:196
+msgid "Could not find any products with serial numbers enabled."
msgstr ""
-#: includes/Admin/Settings.php:289
-msgid "Random & sequential key order for the generator rules."
+#: includes/Shop.php:278
+#: includes/Shop.php:301
+msgid "Invalid request."
msgstr ""
-#: includes/Admin/Settings.php:290
-msgid "Automatic key generator to auto-create & assign keys with orders."
+#: includes/Shop.php:349
+msgid "Loading"
msgstr ""
-#: includes/Admin/Settings.php:291
-msgid "License key management option from the order page with required actions."
+#: includes/Stocks/ListTable.php:22
+msgid "stock"
msgstr ""
-#: includes/Admin/Settings.php:292
-msgid "Support for bulk import/export of keys from/to CSV."
+#: includes/Stocks/ListTable.php:23
+msgid "stocks"
msgstr ""
-#: includes/Admin/Settings.php:293
-msgid "Send keys via SMS with Twilio."
+#: includes/Stocks/ListTable.php:72
+msgid "No products selling serial keys from \"stock\" found."
msgstr ""
-#: includes/Admin/Settings.php:294
-msgid "Option to sell keys even if there are no available keys in the stock."
+#: includes/Stocks/ListTable.php:98
+msgid "Source"
msgstr ""
-#: includes/Admin/Settings.php:295
-msgid "Custom deliverable quantity to deliver multiple keys with a single product."
+#: includes/Stocks/ListTable.php:174
+msgid "Manual"
msgstr ""
-#: includes/Admin/Settings.php:296
-msgid "Manual delivery option to manually deliver license keys instead of automatic."
+#: includes/Stocks/ListTable.php:176
+msgid "Generator Rule"
msgstr ""
-#: includes/Admin/Settings.php:297
-msgid "Email template to easily and quickly customize the order confirmation & low stock alert email."
+#: includes/Stocks/ListTable.php:178
+msgid "Auto Generated"
msgstr ""
-#: includes/Admin/Settings.php:298
-msgid "Many more ..."
+#: includes/Stocks/ListTable.php:180
+msgid "Unknown"
msgstr ""
-#: includes/Admin/Settings.php:302
-msgid "Want More?"
+#: includes/Stocks/Stocks.php:109
+msgid "Serial Numbers stock running low"
msgstr ""
-#: includes/Admin/Settings.php:303
-msgid "This plugin offers a premium version which comes with the following features:"
+#: includes/template.php:70
+msgid "Active"
msgstr ""
-#: includes/Admin/Settings.php:309
-msgid "Upgrade to PRO"
+#: includes/template.php:307
+msgid "Order is waiting for serial numbers to be assigned."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:21
+#: templates/admin/api-actions.php:21
msgid "API Actions"
msgstr ""
-#: includes/Admin/views/html-api-actions.php:26
+#: templates/admin/api-actions.php:26
msgid "You can use the API to perform actions on your website or on another website."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:29
+#: templates/admin/api-actions.php:29
msgid "To perform an action, you need to send a POST request to the following URL:"
msgstr ""
-#: includes/Admin/views/html-api-actions.php:33
-#: includes/Admin/views/html-api-validation.php:46
+#: templates/admin/api-actions.php:33
+#: templates/admin/api-validation.php:46
msgid "The request must contain the following parameters:"
msgstr ""
-#: includes/Admin/views/html-api-actions.php:37
-#: includes/Admin/views/html-api-actions.php:104
-#: includes/Admin/views/html-api-validation.php:50
-#: includes/Admin/views/html-api-validation.php:105
+#: templates/admin/api-actions.php:37
+#: templates/admin/api-actions.php:104
+#: templates/admin/api-validation.php:50
+#: templates/admin/api-validation.php:105
msgid "The ID of the product for which the serial key is valid."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:40
-#: includes/Admin/views/html-api-validation.php:53
+#: templates/admin/api-actions.php:40
+#: templates/admin/api-validation.php:53
msgid "The serial key to validate."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:43
+#: templates/admin/api-actions.php:43
msgid "The request type. Must be set to \"activate\" or \"deactivate\"."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:46
+#: templates/admin/api-actions.php:46
msgid "Instance is the base of activation and deactivation. It is a unique identifier for the installation. For example, you can use the domain name of the website."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:49
+#: templates/admin/api-actions.php:49
msgid "Optional. The platform on which the serial key is used. For example, \"Windows\" or \"Mac\"."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:52
-#: includes/Admin/views/html-api-validation.php:59
+#: templates/admin/api-actions.php:52
+#: templates/admin/api-validation.php:59
msgid "Using email is completely voluntary. The API will verify that the serial number is associated with the given email address."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:57
-#: includes/Admin/views/html-api-validation.php:63
+#: templates/admin/api-actions.php:57
+#: templates/admin/api-validation.php:63
msgid "Example:"
msgstr ""
-#: includes/Admin/views/html-api-actions.php:75
-#: includes/Admin/views/html-api-validation.php:80
+#: templates/admin/api-actions.php:75
+#: templates/admin/api-validation.php:80
msgid "The API will return a JSON response with the following parameters:"
msgstr ""
-#: includes/Admin/views/html-api-actions.php:80
+#: templates/admin/api-actions.php:80
msgid "The response code. \"key_activated\" or \"key_deactivated\" if the request was successful. \"invalid_key\" or \"invalid_request\" if the request was not successful."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:83
-#: includes/Admin/views/html-api-validation.php:87
+#: templates/admin/api-actions.php:83
+#: templates/admin/api-validation.php:87
msgid "The response message. If the serial key is valid, the message will be \"Serial key is valid\"."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:86
+#: templates/admin/api-actions.php:86
msgid "Activated or deactivated when the request is successful."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:89
-#: includes/Admin/views/html-api-validation.php:90
+#: templates/admin/api-actions.php:89
+#: templates/admin/api-validation.php:90
msgid "The activation limit for the serial key."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:92
-#: includes/Admin/views/html-api-validation.php:93
+#: templates/admin/api-actions.php:92
+#: templates/admin/api-validation.php:93
msgid "The number of activations for the serial key."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:95
-#: includes/Admin/views/html-api-validation.php:96
+#: templates/admin/api-actions.php:95
+#: templates/admin/api-validation.php:96
msgid "The number of activations left for the serial key."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:98
-#: includes/Admin/views/html-api-validation.php:99
+#: templates/admin/api-actions.php:98
+#: templates/admin/api-validation.php:99
msgid "The expiration date for the serial key."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:101
+#: templates/admin/api-actions.php:101
msgid "The expiration date for the serial key in Unix timestamp format."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:107
-#: includes/Admin/views/html-api-validation.php:108
+#: templates/admin/api-actions.php:107
+#: templates/admin/api-validation.php:108
msgid "The name of the product for which the serial key is valid."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:110
-#: includes/Admin/views/html-api-validation.php:111
+#: templates/admin/api-actions.php:110
+#: templates/admin/api-validation.php:111
msgid "The list of activations for the serial key."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:113
+#: templates/admin/api-actions.php:113
msgid "You can use the form below to test the API activation/deactivation."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:126
+#: templates/admin/api-actions.php:126
msgid "Select a product to activate/deactivate serial key for."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:133
+#: templates/admin/api-actions.php:133
msgid "Please enter serial key to activate/deactivate"
msgstr ""
-#: includes/Admin/views/html-api-actions.php:135
+#: templates/admin/api-actions.php:135
msgid "Required field. Enter serial key to activate/deactivate."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:143
+#: templates/admin/api-actions.php:143
msgid "Please enter a unique instance"
msgstr ""
-#: includes/Admin/views/html-api-actions.php:145
+#: templates/admin/api-actions.php:145
msgid "Required field. Instance is the unique identifier of the activation record. It is used to identify the activation when activating/deactivating serial key."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:154
+#: templates/admin/api-actions.php:154
msgid "Please enter a platform. e.g. Windows"
msgstr ""
-#: includes/Admin/views/html-api-actions.php:156
+#: templates/admin/api-actions.php:156
msgid "Optional field. Platform is the extra information of the activation record. You can use it to identify the platform of the activation."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:162
-#: includes/Admin/views/html-api-validation.php:144
-#: includes/Admin/views/html-edit-key.php:154
-#: includes/deprecated.php:361
-#: includes/Frontend/Shortcodes.php:49
-#: includes/Frontend/Shortcodes.php:146
-msgid "Email"
-msgstr ""
-
-#: includes/Admin/views/html-api-actions.php:164
-#: includes/Admin/views/html-api-validation.php:146
+#: templates/admin/api-actions.php:164
+#: templates/admin/api-validation.php:146
msgid "Please enter a valid email address"
msgstr ""
-#: includes/Admin/views/html-api-actions.php:166
+#: templates/admin/api-actions.php:166
msgid "Optional field when duplicate key is off. If email is provided, only serial key that are assigned to the email will be activated/deactivated otherwise ignored."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:172
-#: includes/Frontend/Shortcodes.php:152
-msgid "Action"
-msgstr ""
-
-#: includes/Admin/views/html-api-actions.php:175
-#: includes/Frontend/Shortcodes.php:159
-msgid "Activate"
-msgstr ""
-
-#: includes/Admin/views/html-api-actions.php:176
-#: includes/Frontend/Shortcodes.php:160
-msgid "Deactivate"
-msgstr ""
-
-#: includes/Admin/views/html-api-actions.php:179
+#: templates/admin/api-actions.php:179
msgid "Select an action to perform on the serial key."
msgstr ""
-#: includes/Admin/views/html-api-actions.php:185
-#: includes/Admin/views/html-api-validation.php:153
+#: templates/admin/api-actions.php:185
+#: templates/admin/api-validation.php:153
msgid "API response"
msgstr ""
-#: includes/Admin/views/html-api-actions.php:195
-#: includes/Frontend/Shortcodes.php:153
-msgid "Submit"
-msgstr ""
-
-#: includes/Admin/views/html-api-validation.php:20
+#: templates/admin/api-validation.php:20
msgid "API Validation"
msgstr ""
#. translators: %s: link to the pro version
-#: includes/Admin/views/html-api-validation.php:30
+#: templates/admin/api-validation.php:30
#, php-format
msgid "You are using the free version of Serial Numbers for WooCommerce. Upgrade to Pro to get more features."
msgstr ""
-#: includes/Admin/views/html-api-validation.php:39
+#: templates/admin/api-validation.php:39
msgid "You can use the API to validate serial keys on your website or on another website."
msgstr ""
-#: includes/Admin/views/html-api-validation.php:42
+#: templates/admin/api-validation.php:42
msgid "To validate a serial key, you need to send a GET request to the following URL:"
msgstr ""
-#: includes/Admin/views/html-api-validation.php:56
+#: templates/admin/api-validation.php:56
msgid "The request type. Must be set to \"validate\"."
msgstr ""
-#: includes/Admin/views/html-api-validation.php:84
+#: templates/admin/api-validation.php:84
msgid "The response code. If the serial key is valid, the code will be \"key_valid\"."
msgstr ""
-#: includes/Admin/views/html-api-validation.php:102
+#: templates/admin/api-validation.php:102
msgid "The status of the serial key."
msgstr ""
-#: includes/Admin/views/html-api-validation.php:115
+#: templates/admin/api-validation.php:115
msgid "You can test the API using the form below."
msgstr ""
-#: includes/Admin/views/html-api-validation.php:129
+#: templates/admin/api-validation.php:129
msgid "Select a product to validate serial key for."
msgstr ""
-#: includes/Admin/views/html-api-validation.php:136
+#: templates/admin/api-validation.php:136
msgid "Please enter serial key to validate"
msgstr ""
-#: includes/Admin/views/html-api-validation.php:138
+#: templates/admin/api-validation.php:138
msgid "Required field. Enter serial key to validate."
msgstr ""
-#: includes/Admin/views/html-api-validation.php:148
+#: templates/admin/api-validation.php:148
msgid "Optional field. If email is provided, only serial key that are assigned to the email will be validated otherwise ignored."
msgstr ""
-#: includes/Admin/views/html-api-validation.php:164
-#: includes/Frontend/Shortcodes.php:51
-msgid "Validate"
-msgstr ""
-
-#: includes/Admin/views/html-edit-key.php:16
+#: templates/admin/edit-key.php:16
msgid "Edit Serial Key"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:18
+#: templates/admin/edit-key.php:18
msgid "Add Another"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:22
+#: templates/admin/edit-key.php:22
msgid "Go Back"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:31
+#: templates/admin/edit-key.php:31
msgid "Key Details"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:40
+#: templates/admin/edit-key.php:40
msgid "Select Product"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:50
+#: templates/admin/edit-key.php:50
msgid "Select the product for which this key is applicable."
msgstr ""
-#: includes/Admin/views/html-edit-key.php:56
+#: templates/admin/edit-key.php:56
msgid "Serial key"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:61
+#: templates/admin/edit-key.php:61
msgid "Enter your serial key, also supports multiline. For example: 4CE0460D0G-4CE0460D1G-4CE0460D2G"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:71
+#: templates/admin/edit-key.php:71
msgid "Maximum number of times the key can be used to activate the software. If the product is not software, keep it blank."
msgstr ""
-#: includes/Admin/views/html-edit-key.php:77
+#: templates/admin/edit-key.php:77
msgid "Valid for"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:82
+#: templates/admin/edit-key.php:82
msgid "Days"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:85
+#: templates/admin/edit-key.php:85
msgid "Number of days the key will be valid from the purchase date. Leave it blank for lifetime validity."
msgstr ""
-#: includes/Admin/views/html-edit-key.php:99
+#: templates/admin/edit-key.php:99
msgid "Serial key status auto-updates with order status. Avoid manual changes."
msgstr ""
-#: includes/Admin/views/html-edit-key.php:104
+#: templates/admin/edit-key.php:104
msgid "Order ID"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:106
+#: templates/admin/edit-key.php:106
msgid "Select Order"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:115
+#: templates/admin/edit-key.php:115
msgid "The order to which the serial number will be assigned."
msgstr ""
-#: includes/Admin/views/html-edit-key.php:126
+#: templates/admin/edit-key.php:126
msgid "Actions"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:132
+#: templates/admin/edit-key.php:132
msgid "Save Key"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:139
+#: templates/admin/edit-key.php:139
msgid "Customer details"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:146
+#: templates/admin/edit-key.php:146
msgid "Name"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:162
+#: templates/admin/edit-key.php:162
msgid "Address"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:171
+#: templates/admin/edit-key.php:171
msgid "Phone"
msgstr ""
-#: includes/Admin/views/html-edit-key.php:181
+#: templates/admin/edit-key.php:181
msgid "View Order"
msgstr ""
-#: includes/Admin/views/html-list-activations.php:26
+#: templates/admin/list-activations.php:26
msgid "Search activation"
msgstr ""
-#: includes/Admin/views/html-list-keys.php:21
+#: templates/admin/list-keys.php:21
msgid "Add New"
msgstr ""
-#: includes/Admin/views/html-list-keys.php:33
+#: templates/admin/list-keys.php:33
msgid "Generate"
msgstr ""
-#: includes/Admin/views/html-list-keys.php:44
+#: templates/admin/list-keys.php:44
msgid "Search key"
msgstr ""
-#: includes/Admin/views/html-list-stock.php:16
+#: templates/admin/list-stock.php:16
msgid "Search"
msgstr ""
#. translators: 1: plugin name 2: WooCommerce
-#: includes/Admin/views/html-notice-dependency.php:13
+#: templates/admin/notices/dependency.php:13
#, php-format
msgid "%1$s requires %2$s to be installed and active."
msgstr ""
-#: includes/Admin/views/html-notice-dependency.php:15
+#: templates/admin/notices/dependency.php:15
msgid "WooCommerce"
msgstr ""
-#: includes/API.php:80
-msgid "Missing data."
-msgstr ""
-
-#. translators: %1$s: product title, %2$s: stock quantity.
-#: includes/Cart.php:58
-#, php-format
-msgid "Sorry, there aren’t enough Serial Keys for %1$s. Please remove this item or lower the quantity. For now, we have %2$s Serial Keys for this product."
-msgstr ""
-
-#: includes/Compat.php:103
-msgid "Activation count"
-msgstr ""
-
-#: includes/Compat.php:107
-msgid "Activation email"
-msgstr ""
-
-#: includes/Cron.php:59
-msgid "Serial Numbers stock running low"
-msgstr ""
-
-#: includes/deprecated.php:154
-msgid "Serial number not found."
-msgstr ""
-
-#: includes/deprecated.php:360
-msgid "Serial Number"
-msgstr ""
-
-#: includes/deprecated.php:362
-#: includes/functions.php:1025
-#: includes/Functions/Template.php:52
-msgid "Activation Limit"
-msgstr ""
-
-#: includes/deprecated.php:363
-msgid "Expires"
-msgstr ""
-
-#: includes/Frontend/Frontend.php:55
-msgid "Loading"
-msgstr ""
-
-#: includes/Frontend/Shortcodes.php:45
-msgid "Serial Key Validation"
-msgstr ""
-
-#: includes/Frontend/Shortcodes.php:47
-#: includes/Frontend/Shortcodes.php:144
-msgid "Serial Key"
-msgstr ""
-
-#: includes/Frontend/Shortcodes.php:48
-#: includes/Frontend/Shortcodes.php:145
-msgid "Enter your serial key"
-msgstr ""
-
-#: includes/Frontend/Shortcodes.php:50
-#: includes/Frontend/Shortcodes.php:111
-#: includes/Frontend/Shortcodes.php:147
-#: includes/Frontend/Shortcodes.php:223
-msgid "Enter your email"
-msgstr ""
-
-#: includes/Frontend/Shortcodes.php:82
-msgid "No products found."
-msgstr ""
-
-#: includes/Frontend/Shortcodes.php:96
-#: includes/Frontend/Shortcodes.php:207
-msgid "Select a product"
-msgstr ""
-
-#: includes/Frontend/Shortcodes.php:142
-msgid "Activate/Deactivate Serial Key"
-msgstr ""
-
-#: includes/Frontend/Shortcodes.php:149
-msgid "Enter your instance"
-msgstr ""
-
-#: includes/Frontend/Shortcodes.php:151
-msgid "Enter platform"
-msgstr ""
-
-#: includes/Frontend/Shortcodes.php:193
-msgid "Could not find any products with serial numbers enabled."
-msgstr ""
-
-#: includes/Frontend/Shortcodes.php:275
-#: includes/Frontend/Shortcodes.php:298
-msgid "Invalid request."
-msgstr ""
-
-#: includes/functions.php:91
-msgid "Manually added"
-msgstr ""
-
-#. translators: 1: product title 2: source and 3: Quantity
-#: includes/functions.php:539
-#, php-format
-msgid "There is not enough serial numbers for the product %1$s from selected source %2$s, needed total %3$d."
-msgstr ""
-
-#: includes/functions.php:1020
-#: includes/Functions/Template.php:47
-msgid "Activation Email"
-msgstr ""
-
-#: includes/functions.php:1026
-#: includes/functions.php:1031
-#: includes/Functions/Template.php:53
-#: includes/Functions/Template.php:58
-msgid "None"
-msgstr ""
-
-#: includes/functions.php:1030
-#: includes/Functions/Template.php:57
-msgid "Activation Count"
-msgstr ""
-
-#: includes/functions.php:1035
-#: includes/Functions/Template.php:62
-msgid "Expire Date"
-msgstr ""
-
-#: includes/Functions/Template.php:70
-msgid "Active"
-msgstr ""
-
-#: includes/Functions/Template.php:307
-msgid "Order is waiting for serial numbers to be assigned."
-msgstr ""
-
-#: includes/Installer.php:60
-msgid "Once a Minute"
-msgstr ""
-
-#: includes/Models/Activation.php:245
-msgid "Serial id is required."
-msgstr ""
-
-#: includes/Models/Activation.php:250
-msgid "Instance is required."
-msgstr ""
-
-#: includes/Models/Key.php:509
-msgid "Product id is required."
-msgstr ""
-
-#: includes/Models/Key.php:514
-msgid "Product id is invalid."
-msgstr ""
-
-#: includes/Models/Key.php:519
-#: includes/RestAPI.php:152
-msgid "Serial key is required."
-msgstr ""
-
-#: includes/Models/Key.php:531
-msgid "Serial key already exists. Duplicate serial keys are not allowed."
-msgstr ""
-
-#: includes/Models/Key.php:537
-msgid "Order id is invalid."
-msgstr ""
-
-#: includes/Orders.php:82
-msgid "Order automatically completed by the Serial Numbers for WooCommerce."
-msgstr ""
-
-#: includes/RestAPI.php:148
-msgid "Invalid product ID."
-msgstr ""
-
-#: includes/RestAPI.php:163
-msgid "Serial key is invalid."
-msgstr ""
-
-#: includes/RestAPI.php:168
-msgid "Serial key is not authorized to use."
-msgstr ""
-
-#: includes/RestAPI.php:174
-msgid "Please complete your order to activate the serial key."
-msgstr ""
-
-#: includes/RestAPI.php:179
-msgid "Serial key is not valid for this product."
-msgstr ""
-
-#: includes/RestAPI.php:184
-msgid "Invalid email address."
-msgstr ""
-
-#: includes/RestAPI.php:189
-msgid "Serial key is expired."
-msgstr ""
-
-#: includes/RestAPI.php:191
-msgid "Serial key is cancelled."
-msgstr ""
-
-#: includes/RestAPI.php:194
-msgid "Invalid serial key."
-msgstr ""
-
-#: includes/RestAPI.php:220
-msgid "Serial key is valid."
-msgstr ""
-
-#: includes/RestAPI.php:278
-msgid "Instance is already activated."
-msgstr ""
-
-#: includes/RestAPI.php:283
-msgid "Activation limit reached."
-msgstr ""
-
-#: includes/RestAPI.php:299
-msgid "Serial key is activated."
-msgstr ""
-
-#: includes/RestAPI.php:337
-msgid "Instance is missing, You must provide an instance to deactivate license."
-msgstr ""
-
-#: includes/RestAPI.php:355
-msgid "Instance not found."
-msgstr ""
-
-#: includes/RestAPI.php:363
-msgid "Serial key is deactivated."
-msgstr ""
-
#: templates/email-stock-notification.php:14
msgid "Hi There,"
msgstr ""
diff --git a/includes/Admin/views/html-api-actions.php b/templates/admin/api-actions.php
similarity index 99%
rename from includes/Admin/views/html-api-actions.php
rename to templates/admin/api-actions.php
index c84d7433..bfd7f3a5 100644
--- a/includes/Admin/views/html-api-actions.php
+++ b/templates/admin/api-actions.php
@@ -2,7 +2,7 @@
/**
* API actions
*
- * @package WooCommerceSerialNumbers/Admin/Views
+ * @package PluginEver\SerialNumbers/Admin/Views
* @version 1.4.6
*/
diff --git a/includes/Admin/views/html-api-validation.php b/templates/admin/api-validation.php
similarity index 99%
rename from includes/Admin/views/html-api-validation.php
rename to templates/admin/api-validation.php
index 4013fc26..15753e73 100644
--- a/includes/Admin/views/html-api-validation.php
+++ b/templates/admin/api-validation.php
@@ -2,7 +2,7 @@
/**
* API Validation
*
- * @package WooCommerceSerialNumbers/Admin/Views
+ * @package PluginEver\SerialNumbers/Admin/Views
* @version 1.4.6
*/
diff --git a/includes/Admin/views/html-edit-key.php b/templates/admin/edit-key.php
similarity index 98%
rename from includes/Admin/views/html-edit-key.php
rename to templates/admin/edit-key.php
index 47da7852..fc974dbc 100644
--- a/includes/Admin/views/html-edit-key.php
+++ b/templates/admin/edit-key.php
@@ -2,9 +2,9 @@
/**
* The template for editing a key.
*
- * @package WooCommerceSerialNumbers/Admin/Views
+ * @package PluginEver\SerialNumbers/Admin/Views
* @version 1.4.6
- * @var \WooCommerceSerialNumbers\Models\Key $key
+ * @var \PluginEver\SerialNumbers\Models\Key $key
*/
defined( 'ABSPATH' ) || exit;
diff --git a/includes/Admin/views/html-list-activations.php b/templates/admin/list-activations.php
similarity index 83%
rename from includes/Admin/views/html-list-activations.php
rename to templates/admin/list-activations.php
index c4dcaaa0..7441c918 100644
--- a/includes/Admin/views/html-list-activations.php
+++ b/templates/admin/list-activations.php
@@ -3,12 +3,12 @@
* List Activations.
*
* @since 1.0.0
- * @package WooCommerceSerialNumbers\Admin\Views
+ * @package PluginEver\SerialNumbers\Admin\Views
*/
defined( 'ABSPATH' ) || exit;
-$list_table = new WooCommerceSerialNumbers\Admin\ListTables\ActivationsTable();
+$list_table = new PluginEver\SerialNumbers\Activations\ListTable();
$doaction = $list_table->current_action();
$list_table->process_bulk_actions( $doaction );
?>
diff --git a/includes/Admin/views/html-list-keys.php b/templates/admin/list-keys.php
similarity index 93%
rename from includes/Admin/views/html-list-keys.php
rename to templates/admin/list-keys.php
index eab9cdfd..73c2d4cd 100644
--- a/includes/Admin/views/html-list-keys.php
+++ b/templates/admin/list-keys.php
@@ -3,12 +3,12 @@
* List keys.
*
* @since 1.0.0
- * @package WooCommerceSerialNumbers\Admin\Views
+ * @package PluginEver\SerialNumbers\Admin\Views
*/
defined( 'ABSPATH' ) || exit;
-$list_table = new WooCommerceSerialNumbers\Admin\ListTables\KeysTable();
+$list_table = new PluginEver\SerialNumbers\Keys\ListTable();
$doaction = $list_table->current_action();
$list_table->process_bulk_actions( $doaction );
?>
diff --git a/includes/Admin/views/html-list-stock.php b/templates/admin/list-stock.php
similarity index 76%
rename from includes/Admin/views/html-list-stock.php
rename to templates/admin/list-stock.php
index 9b7cd740..97b3f305 100644
--- a/includes/Admin/views/html-list-stock.php
+++ b/templates/admin/list-stock.php
@@ -2,11 +2,11 @@
/**
* List all stocks
*
- * @package WooCommerceSerialNumbers/Admin/Views
+ * @package PluginEver\SerialNumbers/Admin/Views
*/
defined( 'ABSPATH' ) || exit;
-$list_table = new WooCommerceSerialNumbers\Admin\ListTables\StockTable();
+$list_table = new PluginEver\SerialNumbers\Stocks\ListTable();
?>