diff --git a/assets/css/admin-style.scss b/assets/css/admin-style.scss index b7b2d5a2..c166e63d 100644 --- a/assets/css/admin-style.scss +++ b/assets/css/admin-style.scss @@ -168,8 +168,6 @@ } } - - // Fix select2 width in filter .tablenav { .wcsn_search_product, @@ -184,14 +182,6 @@ } } -// Fix source meta field width. -._serial_key_source_field { - label { - margin: 0; - width: 100%; - } -} - // Status tab .wcsn-tools-tab-status { table { diff --git a/assets/js/admin-product.js b/assets/js/admin-product.js new file mode 100644 index 00000000..455974ae --- /dev/null +++ b/assets/js/admin-product.js @@ -0,0 +1,19 @@ +/** + * WC Serial Numbers + * https://pluginever.com + * + * Copyright (c) 2014 PluginEver + * Licensed under the GPLv2+ license. + */ +jQuery( function ( $ ) { + /********** ADMIN:PRODUCT EDIT **********/ + $(document.body) + .on('change', ':input[name="_serial_key_source"]', function(){ + var source = $(this).val(); + $('*[class*="wcsn_show_if_key_source__"]').hide(); + $('.wcsn_show_if_key_source__' + source).show(); + }); + + // Trigger change to show/hide fields on load. + $(':input[name="_serial_key_source"]').trigger('change'); +}); diff --git a/composer.json b/composer.json index 31f0f532..095fea61 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,10 @@ { "url": "git@github.com:pluginever/framework-model.git", "type": "github" + }, + { + "type": "github", + "url": "git@github.com:byteever/bytekit-models.git" } ], "require": { @@ -28,7 +32,8 @@ "coenjacobs/mozart": "^0.7.1", "pluginever/framework-model": "dev-master", "pluginever/framework-plugin": "dev-master", - "pluginever/framework-settings": "dev-master" + "pluginever/framework-settings": "dev-master", + "byteever/bytekit-models": "dev-master" }, "autoload": { "psr-4": { @@ -65,7 +70,8 @@ "packages": [ "pluginever/framework-plugin", "pluginever/framework-settings", - "pluginever/framework-model" + "pluginever/framework-model", + "byteever/bytekit-models" ] } } diff --git a/composer.lock b/composer.lock index f5970305..313d549e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "db0a4e2eed5394cfab48f679b98ec95b", + "content-hash": "26d152b97ff925db5e9dbb38b5fc65a8", "packages": [], "packages-dev": [ { @@ -53,6 +53,102 @@ }, "time": "2024-06-28T06:59:58+00:00" }, + { + "name": "byteever/bytekit-models", + "version": "dev-master", + "source": { + "type": "git", + "url": "git@github.com:byteever/bytekit-models.git", + "reference": "959c56d6fd9e7b8728349c43ab7ab38491744e36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/byteever/bytekit-models/zipball/959c56d6fd9e7b8728349c43ab7ab38491744e36", + "reference": "959c56d6fd9e7b8728349c43ab7ab38491744e36", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "byteever/byteever-sniffs": "dev-master", + "codeception/module-asserts": "^1.0", + "codeception/module-cli": "^1.0", + "codeception/module-db": "^1.0", + "codeception/module-filesystem": "^1.0", + "codeception/module-phpbrowser": "^1.0", + "codeception/module-rest": "^2.0", + "codeception/module-webdriver": "^1.0", + "codeception/util-universalframework": "^1.0", + "lucatume/wp-browser": "<3.5" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "ByteKit\\Models\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "ByteKit\\Models\\Tests\\Supports\\": "tests/_support", + "ByteKit\\Models\\Tests\\Unit\\": "tests/unit", + "ByteKit\\Models\\Tests\\Functional\\": "tests/functional", + "ByteKit\\Models\\Tests\\Acceptance\\": "tests/acceptance", + "ByteKit\\Models\\Tests\\WPUnit\\": "tests/wpunit" + } + }, + "scripts": { + "phpcs": [ + "@php ./vendor/bin/phpcs --standard=phpcs.xml -s -v" + ], + "phpcbf": [ + "@php ./vendor/bin/phpcbf --standard=phpcs.xml -v" + ], + "test:setup": [ + "bash bin/install-test-env.sh" + ], + "test:build": [ + "vendor/bin/codecept build" + ], + "test:wpunit": [ + "vendor/bin/codecept run wpunit --" + ], + "test:functional": [ + "vendor/bin/codecept run functional --" + ], + "test:acceptance": [ + "vendor/bin/codecept run acceptance --" + ], + "test:gen:wpunit": [ + "vendor/bin/codecept generate:wpunit wpunit" + ], + "test:gen:functional": [ + "vendor/bin/codecept generate:wpunit functional" + ], + "test:gen:acceptance": [ + "vendor/bin/codecept generate:acceptance acceptance" + ], + "test": [ + "vendor/bin/codecept run wpunit" + ] + }, + "license": [ + "GPL-3.0-or-later" + ], + "authors": [ + { + "name": "Sultan Nasir Uddin", + "email": "manikdrmc@gmail.com" + } + ], + "description": "A package to manage models in WordPress.", + "support": { + "source": "https://github.com/byteever/bytekit-models/tree/master", + "issues": "https://github.com/byteever/bytekit-models/issues" + }, + "time": "2024-09-30T12:55:35+00:00" + }, { "name": "coenjacobs/mozart", "version": "0.7.1", @@ -1934,7 +2030,8 @@ "stability-flags": { "pluginever/framework-model": 20, "pluginever/framework-plugin": 20, - "pluginever/framework-settings": 20 + "pluginever/framework-settings": 20, + "byteever/bytekit-models": 20 }, "prefer-stable": true, "prefer-lowest": false, diff --git a/includes/Admin/ListTables/GeneratorsTable.php b/includes/Admin/ListTables/GeneratorsTable.php new file mode 100644 index 00000000..cb1846b2 --- /dev/null +++ b/includes/Admin/ListTables/GeneratorsTable.php @@ -0,0 +1,406 @@ + __( 'generator', 'wc-serial-numbers' ), + 'plural' => __( 'generators', 'wc-serial-numbers' ), + 'ajax' => false, + ) + ); + + $this->base_url = admin_url( 'admin.php?page=wc-serial-numbers-generators' ); + } + + /** + * Prepare table data. + * + * @since 1.4.6 + */ + public function prepare_items() { + wp_verify_nonce( '_wpnonce' ); + $per_page = $this->get_items_per_page( 'wcsn_stocks_per_page' ); + $columns = $this->get_columns(); + $hidden = array(); + $sortable = $this->get_sortable_columns(); + $this->_column_headers = array( $columns, $hidden, $sortable ); + $current_page = $this->get_pagenum(); + $orderby = isset( $_GET['orderby'] ) ? sanitize_key( $_GET['orderby'] ) : 'order_date'; + $order = isset( $_GET['order'] ) ? sanitize_key( $_GET['order'] ) : 'desc'; + $status = ( ! empty( $_GET['status'] ) ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : ''; + + $args = array( + 'limit' => $per_page, + 'page' => $current_page, + 'orderby' => $orderby, + 'order' => $order, + 'status' => $status, + ); + + /** + * Filter the query arguments for the list table. + * + * @param array $args An associative array of arguments. + * + * @since 1.0.0 + */ + $args = apply_filters( 'wc_serial_numbers_pro_generators_table_query_args', $args ); + + $args['no_found_rows'] = false; + $this->items = Generator::results( $args ); + $this->total_count = Generator::count( $args ); + + $this->set_pagination_args( + array( + 'total_items' => $this->total_count, + 'per_page' => $per_page, + ) + ); + } + + /** + * No items found text. + */ + public function no_items() { + esc_html_e( 'No generators found.', 'wc-serial-numbers' ); + } + + /** + * Returns an associative array listing all the views that can be used with this table. + * + * @since 1.0.0 + * @return string[] Array of views. + */ + public function get_views() { + wp_verify_nonce( '_wpnonce' ); + $current = ( ! empty( $_GET['status'] ) ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : ''; + $status_links = array(); + $statuses = array_merge( + array( + 'all' => __( 'All', 'wc-serial-numbers' ), + ), + Generator::get_statuses() + ); + + foreach ( $statuses as $status => $label ) { + $link = 'all' === $status ? $this->base_url : add_query_arg( 'status', $status, $this->base_url ); + $args = 'all' === $status ? array() : array( 'status' => $status ); + $count = Generator::count( $args ); + $label = sprintf( '%s (%s)', esc_html( $label ), number_format_i18n( $count ) ); + + $status_links[ $status ] = array( + 'url' => $link, + 'label' => $label, + 'current' => $current === $status, + ); + } + + return $this->get_views_links( $status_links ); + } + + /** + * Process bulk action. + * + * @param string $doaction Action name. + * + * @since 1.4.6 + */ + public function process_bulk_actions( $doaction ) { + wp_verify_nonce( '_wpnonce' ); + if ( $doaction ) { + if ( isset( $_REQUEST['id'] ) ) { + $ids = wp_parse_id_list( wp_unslash( $_REQUEST['id'] ) ); + $doaction = ( - 1 !== $_REQUEST['action'] ) ? $_REQUEST['action'] : $_REQUEST['action2']; // phpcs:ignore + } elseif ( isset( $_REQUEST['ids'] ) ) { + $ids = array_map( 'absint', $_REQUEST['ids'] ); + } elseif ( wp_get_referer() ) { + wp_safe_redirect( wp_get_referer() ); + exit; + } + + foreach ( $ids as $id ) { // Check the permissions on each. + $generator = Generator::find( $id ); + if ( ! $generator ) { + continue; + } + switch ( $doaction ) { + case 'delete': + $generator->delete(); + break; + + case 'activate': + $generator->status = 'active'; + $generator->save(); + break; + + case 'deactivate': + $generator->status = 'inactive'; + $generator->save(); + break; + } + } + + wp_safe_redirect( wp_get_referer() ); + exit; + } + + parent::process_bulk_actions( $doaction ); + } + + /** + * Get bulk actions + * + * since 1.0.0 + * + * @return array + */ + public function get_bulk_actions() { + return array( + 'delete' => __( 'Delete', 'wc-serial-numbers' ), + 'activate' => __( 'Activate', 'wc-serial-numbers' ), + 'deactivate' => __( 'Deactivate', 'wc-serial-numbers' ), + ); + } + + /** + * since 1.0.0 + * + * @return array + */ + public function get_columns() { + $columns = array( + 'cb' => '', + 'name' => __( 'Name', 'wc-serial-numbers' ), + 'pattern' => __( 'Pattern', 'wc-serial-numbers' ), + 'validity_for' => __( 'Validity For', 'wc-serial-numbers' ), + 'activation_limit' => __( 'Activation Limit', 'wc-serial-numbers' ), + 'status' => __( 'Status', 'wc-serial-numbers' ), + ); + + return apply_filters( 'wc_serial_numbers_pro_generators_table_columns', $columns ); + } + + /** + * Get sortable columns. + * + * @since 1.0.0 + * @return array + */ + protected function get_sortable_columns() { + return apply_filters( + 'wc_serial_numbers_pro_generators_table_sortable_columns', + array( + 'name' => array( 'name', false ), + 'pattern' => array( 'pattern', false ), + 'valid_for' => array( 'valid_for', false ), + 'activation_limit' => array( 'activation_limit', false ), + 'status' => array( 'status', false ), + ) + ); + } + + /** + * Gets the name of the primary column. + * + * @since 1.0.0 + * @access protected + * @return string Name of the primary column. + */ + protected function get_primary_column_name() { + return 'name'; + } + + /** + * Render the bulk edit checkbox + * + * @param Generator $item Item being rendered. + * + * @return string + */ + protected function column_cb( $item ) { + return ""; + } + + /** + * Display name column. + * + * @param Generator $item Item being rendered. + * + * @since 1.0.0 + * @return string + */ + protected function column_name( $item ) { + return sprintf( + '%s', + esc_url( add_query_arg( 'edit', $item->id, $this->base_url ) ), + esc_html( $item->name ) + ); + } + + /** + * Column pattern. + * + * @param Generator $item Item being rendered. + * + * @since 1.0.0 + * @return string + */ + public function column_pattern( $item ) { + return wp_kses_post( '' . esc_html( $item->pattern ) . '' ); + } + + /** + * Column validity_for. + * + * @param Generator $item Item being rendered. + * + * @since 1.0.0 + * @return string + */ + protected function column_validity_for( $item ) { + if ( empty( $item->validity_for ) ) { + return esc_html__( 'Lifetime', 'wc-serial-numbers' ); + } + if ( ! empty( $item->validity_for ) ) { + // translators: %d: number of days. + return sprintf( _nx( '%d day After Purchase', '%d days After Purchase', $item->validity_for, 'valid for days', 'wc-serial-numbers' ), $item->validity_for ); + } + + return '—'; + } + + /** + * Column activation_limit. + * + * @param Generator $item Item being rendered. + * + * @since 1.0.0 + * @return string + */ + protected function column_activation_limit( $item ) { + if ( empty( $item->activation_limit ) ) { + return esc_html__( 'Unlimited', 'wc-serial-numbers' ); + } + + if ( ! empty( $item->activation_limit ) ) { + return number_format( $item->activation_limit ); + } + + return esc_html( $item->activation_limit ); + } + + /** + * Column status. + * + * @param Generator $item Item being rendered. + * + * @since 1.0.0 + * @return string + */ + protected function column_status( $item ) { + return $item->get_status_html(); + } + + /** + * Generates and displays row actions links. + * + * @param Generator $item The object. + * @param string $column_name Current column name. + * @param string $primary Primary column name. + * + * @since 1.0.0 + * @return string Row actions output. + */ + protected function handle_row_actions( $item, $column_name, $primary ) { + if ( $primary !== $column_name ) { + return null; + } + + $actions = array( + 'id' => sprintf( '#%d', esc_attr( $item->id ) ), + 'edit' => sprintf( + '%s', + esc_url( add_query_arg( 'edit', $item->id, $this->base_url ) ), + __( 'Edit', 'wc-serial-numbers' ) + ), + 'delete' => sprintf( + '%s', + esc_url( + wp_nonce_url( + add_query_arg( + array( + 'action' => 'delete', + 'id' => $item->id, + ), + $this->base_url + ), + 'bulk-' . $this->_args['plural'] + ) + ), + __( 'Delete', 'wc-serial-numbers' ) + ), + ); + // based on the status, add activate or deactivate action. + if ( 'active' === $item->status ) { + $actions['deactivate'] = sprintf( + '%s', + esc_url( + wp_nonce_url( + add_query_arg( + array( + 'action' => 'deactivate', + 'id' => $item->id, + ), + $this->base_url + ), + 'bulk-' . $this->_args['plural'] + ) + ), + __( 'Deactivate', 'wc-serial-numbers' ) + ); + } else { + $actions['activate'] = sprintf( + '%s', + esc_url( + wp_nonce_url( + add_query_arg( + array( + 'action' => 'activate', + 'id' => $item->id, + ), + $this->base_url + ), + 'bulk-' . $this->_args['plural'] + ) + ), + __( 'Activate', 'wc-serial-numbers' ) + ); + } + + return $this->row_actions( $actions ); + } +} diff --git a/includes/Admin/Products.php b/includes/Admin/Products.php index d80f988d..33487d78 100644 --- a/includes/Admin/Products.php +++ b/includes/Admin/Products.php @@ -21,6 +21,11 @@ public function __construct() { add_action( 'admin_head', array( __CLASS__, 'print_style' ) ); add_filter( 'woocommerce_product_data_tabs', array( __CLASS__, 'product_data_tab' ) ); add_action( 'woocommerce_product_data_panels', array( __CLASS__, 'product_write_panel' ) ); + add_action( 'wc_serial_numbers_product_options', array( __CLASS__, 'render_enable_sale_keys_options' ) ); + add_action( 'wc_serial_numbers_product_options', array( __CLASS__, 'render_source_options' ) ); + add_action( 'wc_serial_numbers_product_options', array( __CLASS__, 'render_key_options' ) ); + add_action( 'wc_serial_numbers_product_options', array( __CLASS__, 'render_software_options' ) ); + add_action( 'wc_serial_numbers_product_options', array( __CLASS__, 'render_pro_notice_options' ) ); add_filter( 'woocommerce_process_product_meta', array( __CLASS__, 'product_save_data' ) ); add_action( 'woocommerce_product_after_variable_attributes', array( __CLASS__, 'variable_product_content' ), 10, 3 ); } @@ -38,10 +43,10 @@ public static function print_style() { content: "\f112"; } - ._serial_key_source_field label { - margin: 0 !important; - width: 100% !important; - } + /*._serial_key_source_field label {*/ + /* margin: 0 !important;*/ + /* width: 100% !important;*/ + /*}*/ .wc-serial-numbers-upgrade-box { background: #f1f1f1; @@ -103,100 +108,86 @@ public static function product_data_tab( $tabs ) { * since 1.0.0 */ public static function product_write_panel() { - global $post, $woocommerce; - ?> - - get_type(), 'variable' ) !== false ) { + return; + } + + include __DIR__ . '/views/products/product-key-options.php'; + } + + /** + * Render software options. + * + * @param \WC_Product $product product object. + * + * @since 3.0.0 + */ + public static function render_software_options( $product ) { + if ( strpos( $product->get_type(), 'variable' ) !== false || ! wcsn_is_software_support_enabled() ) { + return; + } + + include __DIR__ . '/views/products/product-software-options.php'; + } + + /** + * Render pro notice options. + * + * @param \WC_Product $product product object. + * + * @since 3.0.0 + */ + public static function render_pro_notice_options( $product ) { + // if product is variable, do not show the key options. Also, if pro version is active, do not show the notice. + if ( strpos( $product->get_type(), 'variable' ) !== false || WCSN()->is_premium_active() ) { + return; + } + + include __DIR__ . '/views/products/product-pro-notice-options.php'; } /** @@ -232,10 +223,21 @@ public static function product_save_data() { return; } - $status = isset( $_POST['_is_serial_number'] ) ? 'yes' : 'no'; - $source = isset( $_POST['_serial_key_source'] ) ? sanitize_text_field( wp_unslash( $_POST['_serial_key_source'] ) ) : 'custom_source'; + $status = isset( $_POST['_is_serial_number'] ) ? 'yes' : 'no'; + $source = isset( $_POST['_serial_key_source'] ) ? sanitize_text_field( wp_unslash( $_POST['_serial_key_source'] ) ) : 'automatic'; + $delivery_qty = isset( $_POST['_delivery_quantity'] ) ? absint( wp_unslash( $_POST['_delivery_quantity'] ) ) : 1; update_post_meta( $post->ID, '_is_serial_number', $status ); update_post_meta( $post->ID, '_serial_key_source', $source ); + update_post_meta( $post->ID, '_delivery_quantity', $delivery_qty ); + + // if source is automatic then get the generator id. + if ( 'automatic' === $source ) { + $generator_id = isset( $_POST['_generator_id'] ) ? absint( wp_unslash( $_POST['_generator_id'] ) ) : 0; + $sequential = isset( $_POST['_wcsn_is_sequential'] ) ? 'yes' : 'no'; + update_post_meta( $post->ID, '_generator_id', $generator_id ); + update_post_meta( $post->ID, '_wcsn_is_sequential', $sequential ); + } + // save only if software licensing enabled. if ( wcsn_is_software_support_enabled() ) { $software_version = isset( $_POST['_software_version'] ) ? sanitize_text_field( wp_unslash( $_POST['_software_version'] ) ) : ''; diff --git a/includes/Admin/Requests.php b/includes/Admin/Requests.php index db015bca..e244699b 100644 --- a/includes/Admin/Requests.php +++ b/includes/Admin/Requests.php @@ -2,6 +2,7 @@ namespace WooCommerceSerialNumbers\Admin; +use WooCommerceSerialNumbers\Models\Generator; use WooCommerceSerialNumbers\Models\Key; defined( 'ABSPATH' ) || exit; // Exit if accessed directly. @@ -21,8 +22,9 @@ class Requests { */ public function __construct() { add_action( 'admin_post_wcsn_edit_key', array( __CLASS__, 'handle_edit_key' ) ); - + add_action( 'admin_post_wcsn_edit_generator', array( $this, 'edit_generator' ) ); // Ajax Search. + add_action( 'admin_post_wcsn_generate_bulk_keys', array( __CLASS__, 'generate_bulk_keys' ) ); add_action( 'wp_ajax_wc_serial_numbers_search_product', array( __CLASS__, 'search_product' ) ); add_action( 'wp_ajax_wc_serial_numbers_search_orders', array( __CLASS__, 'search_orders' ) ); add_action( 'wp_ajax_wc_serial_numbers_search_customers', array( __CLASS__, 'search_customers' ) ); @@ -74,7 +76,7 @@ public static function handle_edit_key() { // Adding manually so let's enable to product and set the source. $product_id = $key->get_product_id(); update_post_meta( $product_id, '_is_serial_number', 'yes' ); - update_post_meta( $product_id, '_serial_key_source', 'custom_source' ); + update_post_meta( $product_id, '_serial_key_source', 'preset' ); WCSN()->add_notice( __( 'Key added successfully.', 'wc-serial-numbers' ) ); } else { @@ -86,6 +88,126 @@ public static function handle_edit_key() { exit; } + /** + * Edit generator. + * + * @since 1.0.0 + * + * @return void + */ + public function edit_generator() { + check_admin_referer( 'wcsn_edit_generator' ); + + if ( function_exists( 'wcsn_get_manager_role' ) && ! current_user_can( wcsn_get_manager_role() ) ) { + WCSN()->add_notice( __( 'Error: Sorry, you are not allowed to do this.', 'wc-serial-numbers' ), 'error' ); + wp_safe_redirect( wp_get_referer() ); + exit; + } + + $referer = wp_get_referer(); + $id = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0; + $name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : ''; + $pattern = isset( $_POST['pattern'] ) ? sanitize_text_field( wp_unslash( $_POST['pattern'] ) ) : ''; + $charset = isset( $_POST['charset'] ) ? sanitize_text_field( wp_unslash( $_POST['charset'] ) ) : ''; + $valid_for = isset( $_POST['valid_for'] ) ? absint( $_POST['valid_for'] ) : 0; + $activation_limit = isset( $_POST['activation_limit'] ) ? absint( $_POST['activation_limit'] ) : 0; + $status = isset( $_POST['status'] ) ? sanitize_key( $_POST['status'] ) : 'active'; + + $generator = Generator::make( + array( + 'id' => $id, + 'name' => $name, + 'pattern' => $pattern, + 'charset' => $charset, + 'valid_for' => $valid_for, + 'activation_limit' => $activation_limit, + 'status' => $status, + ) + ); + + $generator = $generator->save(); + if ( is_wp_error( $generator ) ) { + WCSN()->add_notice( $generator->get_error_message(), 'error' ); + wp_safe_redirect( $referer ); + exit; + } + + WCSN()->add_notice( __( 'Generator has been saved successfully.', 'wc-serial-numbers' ) ); + // Remove 'add' query arg from the URL. + $referer = remove_query_arg( 'add', $referer ); + wp_safe_redirect( add_query_arg( array( 'edit' => $generator->id ), $referer ) ); + exit; + } + + /** + * Generate serial numbers. + * + * @since 1.0.0 + * @return void + */ + public static function generate_bulk_keys() { + check_admin_referer( 'wcsn_generate_bulk_keys' ); + + if ( function_exists( 'wcsn_get_manager_role' ) && ! current_user_can( wcsn_get_manager_role() ) ) { + wp_send_json_error( array( 'message' => __( 'You are not allowed, to use this.', 'wc-serial-numbers' ) ) ); + } + + $referer = wp_get_referer(); + $product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0; + $generator_id = isset( $_POST['generator_id'] ) ? absint( $_POST['generator_id'] ) : 0; + $quantity = isset( $_POST['quantity'] ) ? absint( $_POST['quantity'] ) : 1; + + // Check if product id is present. + if ( empty( $product_id ) ) { + WCSN()->add_notice( __( 'Error: Please select a product.', 'wc-serial-numbers' ), 'error' ); + wp_safe_redirect( $referer ); + exit; + } + + // Check if quantity is present. + if ( empty( $quantity ) ) { + WCSN()->add_notice( __( 'Error: Please enter a quantity.', 'wc-serial-numbers' ), 'error' ); + wp_safe_redirect( $referer ); + exit; + } + + // key data. + $data = array( + 'product_id' => $product_id, + 'quantity' => $quantity, + 'source' => 'preset', + ); + + // Check if generator id is present then update $data. + if ( ! empty( $generator_id ) ) { + $generator_args = array( + 'generator_id' => $generator_id, + 'source' => 'automatic', + ); + + // parse arguments. + $data = wp_parse_args( $generator_args, $data ); + } + + // Generate keys. + $keys = wcsn_generate_keys( $data ); + + if ( empty( $keys ) ) { + WCSN()->add_notice( __( 'Could not generate any keys. Please check the generator settings.', 'wc-serial-numbers' ), 'error' ); + wp_safe_redirect( $referer ); + exit; + } + + update_post_meta( $product_id, '_is_serial_number', 'yes' ); + update_post_meta( $product_id, '_serial_key_source', 'preset' ); + + // Translators: %s: number of keys generated. + $notice = sprintf( esc_html__( '%s keys have been generated successfully.', 'wc-serial-numbers' ), number_format( count( $keys ) ) ); + WCSN()->add_notice( $notice ); + wp_safe_redirect( $referer ); + exit(); + } + /** * Search product. * diff --git a/includes/Admin/views/products/product-key-options.php b/includes/Admin/views/products/product-key-options.php new file mode 100644 index 00000000..4f28f2cb --- /dev/null +++ b/includes/Admin/views/products/product-key-options.php @@ -0,0 +1,36 @@ +'; +// Delivery quantity and software version fields. +$delivery_quantity = (int) get_post_meta( $product->get_id(), '_delivery_quantity', true ); +woocommerce_wp_text_input( + apply_filters( + 'wc_serial_numbers_delivery_quantity_field_args', + array( + 'id' => '_delivery_quantity', + 'label' => __( 'Delivery quantity', 'wc-serial-numbers' ), + 'description' => __( 'Number of key(s) will be delivered per item. Available in PRO.', 'wc-serial-numbers' ), + 'value' => empty( $delivery_quantity ) ? 1 : $delivery_quantity, + 'type' => 'number', + 'desc_tip' => true, + ) + ) +); + +/** + * Action hook to add more product key options. + * + * @since 3.0.0 + */ +do_action( 'wc_serial_numbers_product_key_options', $product ); + +echo ''; diff --git a/includes/Admin/views/products/product-options.php b/includes/Admin/views/products/product-options.php new file mode 100644 index 00000000..35199a92 --- /dev/null +++ b/includes/Admin/views/products/product-options.php @@ -0,0 +1,34 @@ + + +is_premium_active() ) { + echo wp_kses_post( + sprintf( + '

%s %s

', + __( 'Want to sell keys for variable products?', 'wc-serial-numbers' ), + 'https://www.pluginever.com/plugins/woocommerce-serial-numbers-pro/?utm_source=product_page_license_area&utm_medium=link&utm_campaign=wc-serial-numbers&utm_content=Upgrade%20to%20Pro', + __( 'Upgrade to Pro', 'wc-serial-numbers' ), + ) + ); +} diff --git a/includes/Admin/views/products/product-selling-keys-options.php b/includes/Admin/views/products/product-selling-keys-options.php new file mode 100644 index 00000000..1a752ff7 --- /dev/null +++ b/includes/Admin/views/products/product-selling-keys-options.php @@ -0,0 +1,32 @@ +'; +// Enable selling keys. +woocommerce_wp_checkbox( + array( + 'id' => '_is_serial_number', + 'label' => __( 'Sell keys', 'wc-serial-numbers' ), + 'description' => __( 'Enable this if you are selling keys with this product.', 'wc-serial-numbers' ), + 'value' => get_post_meta( $product->get_id(), '_is_serial_number', true ), + 'wrapper_class' => '', + 'desc_tip' => false, + ) +); + +/** +* Action hook to add more product key options for selling keys. +* +* @since 3.0.0 +*/ +do_action( 'wc_serial_numbers_product_enable_selling_keys', $product ); + +echo ''; diff --git a/includes/Admin/views/products/product-software-options.php b/includes/Admin/views/products/product-software-options.php new file mode 100644 index 00000000..cd5d5645 --- /dev/null +++ b/includes/Admin/views/products/product-software-options.php @@ -0,0 +1,33 @@ +'; + +woocommerce_wp_text_input( + array( + 'id' => '_software_version', + 'label' => __( 'Software version', 'wc-serial-numbers' ), + 'description' => __( 'Version number for the software. Ignore if it\'s not a software.', 'wc-serial-numbers' ), + 'placeholder' => __( 'e.g. 1.0', 'wc-serial-numbers' ), + 'desc_tip' => true, + ) +); + +/** + * Action hook to add more software options. + * + * @param WC_Product $product Product object. + * + * @since 3.0.0 + */ +do_action( 'wc_serial_numbers_product_software_options', $product ); + +echo ''; diff --git a/includes/Admin/views/products/product-source-options.php b/includes/Admin/views/products/product-source-options.php new file mode 100644 index 00000000..7ffda7c4 --- /dev/null +++ b/includes/Admin/views/products/product-source-options.php @@ -0,0 +1,88 @@ +'; +$source = get_post_meta( $product->get_id(), '_serial_key_source', true ); +$sources = wcsn_get_key_sources(); + +// key source select field. +woocommerce_wp_select( + array( + 'id' => '_serial_key_source', + 'label' => __( 'Key Source', 'wc-serial-numbers' ), + 'value' => $source, + 'options' => $sources, + 'desc_tip' => true, + 'description' => __( 'Automatically generate keys or use preset keys.', 'wc-serial-numbers' ), + 'wrapper_class' => '_serial_key_source', + 'class' => 'wc-enhanced-select', + 'style' => 'width: 50%;', + ) +); + +// Selling & Generate keys using a generator. +$generators = Generator::results( array( 'status' => 'active' ) ); + +// Generate options. +$options = array( + '' => __( 'Default', 'wc-serial-numbers' ), +); +foreach ( $generators as $generator ) { + $options[ $generator->id ] = $generator->name; +} +woocommerce_wp_select( + array( + 'id' => '_generator_id', + 'label' => __( 'Key Generator', 'wc-serial-numbers' ), + 'description' => __( 'Select a specific key generator or leave empty to use default settings.', 'wc-serial-numbers' ), + 'options' => $options, + 'desc_tip' => true, + 'class' => 'wc-enhanced-select', + 'wrapper_class' => 'wcsn_show_if_key_source__automatic', + 'style' => 'width: 50%;', + ) +); + +// Generate sequential keys. +woocommerce_wp_checkbox( + array( + 'id' => '_wcsn_is_sequential', + 'label' => __( 'Sequential Keys', 'wc-serial-numbers' ), + 'description' => __( 'Generate keys in sequential order.', 'wc-serial-numbers' ), + 'value' => get_post_meta( $product->get_id(), '_wcsn_is_sequential', true ), + 'wrapper_class' => 'wcsn_show_if_key_source__automatic', + ) +); + +$stocks = wcsn_get_stocks_count(); +$stock = isset( $stocks[ $product->get_id() ] ) ? $stocks[ $product->get_id() ] : 0; +echo wp_kses_post( + sprintf( + '

%d %s

', + __( 'Preset Stock', 'wc-serial-numbers' ), + $stock, + _n( 'key available.', 'keys available.', $stock, 'wc-serial-numbers' ) + ) +); + +/** + * Action hook to add more product key options for key source. + * + * @param WC_Product $product Product object. + * + * @since 3.0.0 + */ +do_action( 'wc_serial_numbers_product_key_source_options', $product ); + + +echo ''; diff --git a/includes/Models/Generator.php b/includes/Models/Generator.php new file mode 100644 index 00000000..ba36cfbf --- /dev/null +++ b/includes/Models/Generator.php @@ -0,0 +1,209 @@ +get_object_type(); + } + + /** + * The table associated with the model. + * + * @since 2.0.0 + * @var string + */ + protected $table = 'wcsn_generators'; + + /** + * The table columns of the model. + * + * @since 2.0.0 + * @var array + */ + protected $columns = array( + 'id', + 'name', + 'pattern', + 'charset', + 'valid_for', + 'activation_limit', + 'status', + ); + + /** + * The model's attributes. + * + * @since 2.0.0 + * @var array + */ + protected $attributes = array( + 'pattern' => '####-####-####-####', + 'charset' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', + 'status' => 'active', + ); + + /** + * The attributes that should be cast. + * + * @since 2.0.0 + * @var array + */ + protected $casts = array( + 'name' => 'string', + 'pattern' => 'string', + 'charset' => 'string', + 'valid_for' => 'int', + 'activation_limit' => 'int', + 'status' => 'string', + ); + + /** + * Indicates if the model should be timestamped. + * + * @since 2.0.0 + * @var bool + */ + protected $has_timestamps = true; + + /** + * Get searchable attributes. + * + * @since 2.0.0 + * @var array + */ + protected $searchable = array( + 'name', + 'pattern', + 'charset', + 'status', + ); + + /** + * Get status options. + * + * @since 2.0.0 + * @return array + */ + public static function get_statuses() { + return array( + 'active' => __( 'Active', 'wc-serial-numbers' ), + 'inactive' => __( 'Inactive', 'wc-serial-numbers' ), + ); + } + + /* + |-------------------------------------------------------------------------- + | Accessors, Mutators & Relationships + |-------------------------------------------------------------------------- + | This section includes methods for accessing, modifying, and assisting with + | the model's properties. + | - Getters: Retrieve property values. + | - Setters: Update property values. + | - Relationships: Define relationships between models. + |-------------------------------------------------------------------------- + */ + + /** + * Set the status attribute. + * + * @param string $value Status value. + * + * @since 2.0.0 + * @return void + */ + public function set_status( $value ) { + $this->attributes['status'] = in_array( $value, array_keys( self::get_statuses() ), true ) ? $value : 'active'; + } + + /* + |-------------------------------------------------------------------------- + | CRUD Methods + |-------------------------------------------------------------------------- + | This section contains methods for creating, reading, updating, and deleting + | objects in the database. + |-------------------------------------------------------------------------- + */ + + /** + * Saves an object in the database. + * + * @since 2.0.0 + * @return true|\WP_Error True on success, WP_Error on failure. + */ + public function save() { + // pattern is required. + if ( empty( $this->name ) ) { + return new \WP_Error( 'missing-required', __( 'The generator name is required.', 'wc-serial-numbers' ) ); + } + // product_id is required. + if ( empty( $this->pattern ) ) { + return new \WP_Error( 'missing-required', __( 'The generator pattern is required.', 'wc-serial-numbers' ) ); + } + // type is required. + if ( empty( $this->charset ) ) { + return new \WP_Error( 'missing-required', __( 'The generator charset is required.', 'wc-serial-numbers' ) ); + } + + return parent::save(); + } + + /* + |-------------------------------------------------------------------------- + | Helper Methods + |-------------------------------------------------------------------------- + | This section contains utility methods that are not directly related to this + | object but can be used to support its functionality. + |-------------------------------------------------------------------------- + */ + + /** + * Get status label. + * + * @since 2.0.0 + * @return string + */ + public function get_status_label() { + $statuses = self::get_statuses(); + return isset( $statuses[ $this->status ] ) ? $statuses[ $this->status ] : ''; + } + + /** + * Get the key status html. + * + * @since 2.0.0 + * @return string + */ + public function get_status_html() { + return sprintf( '%s', esc_attr( $this->status ), esc_html( $this->get_status_label() ) ); + } +} diff --git a/languages/wc-serial-numbers.pot b/languages/wc-serial-numbers.pot index 60e8800c..45970a43 100644 --- a/languages/wc-serial-numbers.pot +++ b/languages/wc-serial-numbers.pot @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: WC Serial Numbers 2.0.6\n" "Report-Msgid-Bugs-To: https://pluginever.com/support\n" -"POT-Creation-Date: 2024-10-07 09:04:45+00:00\n" +"POT-Creation-Date: 2024-10-14 08:32:57+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -24,6 +24,90 @@ msgstr "" "X-Textdomain-Support: yes\n" "X-Generator: grunt-wp-i18n 1.0.3\n" +#: includes/Admin/ListTables/GeneratorsTable.php:30 +msgid "generator" +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:31 +msgid "generators" +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:89 +msgid "No generators found." +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:104 +#: src/Admin/ListTables/KeysTable.php:228 +msgid "All" +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:183 +#: includes/Admin/ListTables/GeneratorsTable.php:364 +#: src/Admin/ListTables/ActivationsTable.php:164 +#: src/Admin/ListTables/ActivationsTable.php:239 +#: src/Admin/ListTables/KeysTable.php:336 +#: src/Admin/ListTables/KeysTable.php:428 src/Admin/views/html-edit-key.php:130 +msgid "Delete" +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:184 +#: includes/Admin/ListTables/GeneratorsTable.php:400 +#: src/Admin/views/html-api-actions.php:175 src/Frontend/Shortcodes.php:157 +msgid "Activate" +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:185 +#: includes/Admin/ListTables/GeneratorsTable.php:383 +#: src/Admin/views/html-api-actions.php:176 src/Frontend/Shortcodes.php:158 +msgid "Deactivate" +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:197 +#: src/Admin/views/html-edit-generator.php:31 +#: src/Admin/views/html-edit-key.php:146 +msgid "Name" +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:198 +#: src/Admin/views/html-edit-generator.php:45 +msgid "Pattern" +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:199 +msgid "Validity For" +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:200 +#: src/Deprecated/Functions.php:361 src/Functions/Template.php:52 +#: src/functions.php:1026 +msgid "Activation Limit" +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:201 +#: includes/Admin/Orders.php:250 src/Admin/ListTables/KeysTable.php:360 +#: src/Admin/Menus.php:358 src/Admin/views/html-edit-generator.php:102 +#: src/Admin/views/html-edit-key.php:92 src/Functions/Template.php:78 +#: src/functions.php:1041 +msgid "Status" +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:286 +#: includes/Admin/Orders.php:243 src/Admin/ListTables/KeysTable.php:532 +#: src/Functions/Template.php:63 src/functions.php:1037 +msgid "Lifetime" +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:306 +#: includes/Admin/Orders.php:247 +msgid "Unlimited" +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:348 +#: src/Admin/ListTables/KeysTable.php:427 +#: src/Admin/ListTables/StockTable.php:143 +msgid "Edit" +msgstr "" + #: includes/Admin/Orders.php:53 includes/Admin/Orders.php:166 msgid "Add serial keys" msgstr "" @@ -61,7 +145,7 @@ msgstr "" #: src/Admin/ListTables/KeysTable.php:349 #: src/Admin/views/html-api-actions.php:131 #: src/Admin/views/html-api-validation.php:134 src/Functions/Template.php:42 -#: src/functions.php:1015 +#: src/functions.php:1016 msgid "Key" msgstr "" @@ -69,118 +153,183 @@ msgstr "" msgid "Expire date" msgstr "" -#: includes/Admin/Orders.php:243 src/Admin/ListTables/KeysTable.php:532 -#: src/Functions/Template.php:63 src/functions.php:1036 -msgid "Lifetime" -msgstr "" - -#: includes/Admin/Orders.php:246 src/Admin/views/html-edit-key.php:67 +#: includes/Admin/Orders.php:246 src/Admin/views/html-edit-generator.php:87 +#: src/Admin/views/html-edit-key.php:67 msgid "Activation limit" msgstr "" -#: includes/Admin/Orders.php:247 -msgid "Unlimited" -msgstr "" - -#: includes/Admin/Orders.php:250 src/Admin/ListTables/KeysTable.php:360 -#: src/Admin/Menus.php:320 src/Admin/views/html-edit-key.php:92 -#: src/Functions/Template.php:78 src/functions.php:1040 -msgid "Status" -msgstr "" - #: includes/Admin/Orders.php:277 #. translators: %s is the item number. msgid "View Details" msgstr "" -#: includes/Admin/Products.php:92 includes/Admin/Products.php:315 -#: src/Admin/Admin.php:141 src/Admin/Menus.php:54 src/Admin/Menus.php:88 -#: src/Admin/Menus.php:89 src/Admin/Menus.php:433 +#: includes/Admin/Products.php:97 includes/Admin/Products.php:317 +#: src/Admin/Admin.php:151 src/Admin/Menus.php:56 src/Admin/Menus.php:90 +#: src/Admin/Menus.php:91 src/Admin/Menus.php:464 #: src/Functions/Template.php:226 msgid "Serial Numbers" msgstr "" -#: includes/Admin/Products.php:114 -msgid "Sell keys" +#: includes/Admin/Products.php:203 +msgid "" +"The free version of Serial Numbers for WooCommerce does not support product " +"variation." msgstr "" -#: includes/Admin/Products.php:115 -msgid "Enable this if you are selling keys with this product." +#: includes/Admin/Products.php:205 +#: includes/Admin/views/products/product-pro-notice-options.php:18 +#: src/Admin/Menus.php:227 +msgid "Upgrade to Pro" +msgstr "" + +#: includes/Admin/Products.php:222 +msgid "You do not have permission to save this data." +msgstr "" + +#: includes/Admin/Products.php:299 +msgid "Order missing serial numbers for this item." +msgstr "" + +#: includes/Admin/Requests.php:44 +msgid "You do not have permission to perform this action." +msgstr "" + +#: includes/Admin/Requests.php:81 +msgid "Key added successfully." msgstr "" -#: includes/Admin/Products.php:128 +#: includes/Admin/Requests.php:83 +msgid "Key updated successfully." +msgstr "" + +#: includes/Admin/Requests.php:102 +msgid "Error: Sorry, you are not allowed to do this." +msgstr "" + +#: includes/Admin/Requests.php:135 +msgid "Generator has been saved successfully." +msgstr "" + +#: includes/Admin/Requests.php:152 +msgid "You are not allowed, to use this." +msgstr "" + +#: includes/Admin/Requests.php:162 +msgid "Error: Please select a product." +msgstr "" + +#: includes/Admin/Requests.php:169 +msgid "Error: Please enter a quantity." +msgstr "" + +#: includes/Admin/Requests.php:196 +msgid "Could not generate any keys. Please check the generator settings." +msgstr "" + +#: includes/Admin/Requests.php:205 +#. Translators: %s: number of keys generated. +msgid "%s keys have been generated successfully." +msgstr "" + +#: includes/Admin/Requests.php:222 includes/Admin/Requests.php:285 +#: includes/Admin/Requests.php:360 +msgid "You do not have permission to access this endpoint." +msgstr "" + +#: includes/Admin/Requests.php:396 +#. translators: $1: customer name, $2 customer id, $3: customer email +msgid "%1$s (#%2$s - %3$s)" +msgstr "" + +#: includes/Admin/views/products/product-key-options.php:20 msgid "Delivery quantity" msgstr "" -#: includes/Admin/Products.php:129 +#: includes/Admin/views/products/product-key-options.php:21 msgid "Number of key(s) will be delivered per item. Available in PRO." msgstr "" -#: includes/Admin/Products.php:149 includes/Admin/Products.php:181 -msgid "Key source" +#: includes/Admin/views/products/product-pro-notice-options.php:16 +msgid "Want to sell keys for variable products?" msgstr "" -#: includes/Admin/Products.php:167 +#: includes/Admin/views/products/product-selling-keys-options.php:17 +msgid "Sell keys" +msgstr "" + +#: includes/Admin/views/products/product-selling-keys-options.php:18 +msgid "Enable this if you are selling keys with this product." +msgstr "" + +#: includes/Admin/views/products/product-software-options.php:17 msgid "Software version" msgstr "" -#: includes/Admin/Products.php:168 +#: includes/Admin/views/products/product-software-options.php:18 msgid "Version number for the software. Ignore if it's not a software." msgstr "" -#: includes/Admin/Products.php:169 +#: includes/Admin/views/products/product-software-options.php:19 msgid "e.g. 1.0" msgstr "" -#: includes/Admin/Products.php:183 -msgid "key available." -msgid_plural "keys available." -msgstr[0] "" -msgstr[1] "" +#: includes/Admin/views/products/product-source-options.php:22 +msgid "Key Source" +msgstr "" -#: includes/Admin/Products.php:191 -msgid "Want to sell keys for variable products?" +#: includes/Admin/views/products/product-source-options.php:26 +msgid "Automatically generate keys or use preset keys." msgstr "" -#: includes/Admin/Products.php:193 includes/Admin/Products.php:214 -#: src/Admin/Menus.php:189 -msgid "Upgrade to Pro" +#: includes/Admin/views/products/product-source-options.php:38 +msgid "Default" msgstr "" -#: includes/Admin/Products.php:212 -msgid "" -"The free version of Serial Numbers for WooCommerce does not support product " -"variation." +#: includes/Admin/views/products/product-source-options.php:46 +msgid "Key Generator" msgstr "" -#: includes/Admin/Products.php:231 -msgid "You do not have permission to save this data." +#: includes/Admin/views/products/product-source-options.php:47 +#: src/Admin/views/tools/general.php:57 +msgid "Select a specific key generator or leave empty to use default settings." msgstr "" -#: includes/Admin/Products.php:297 -msgid "Order missing serial numbers for this item." +#: includes/Admin/views/products/product-source-options.php:60 +msgid "Sequential Keys" msgstr "" -#: includes/Admin/Requests.php:42 -msgid "You do not have permission to perform this action." +#: includes/Admin/views/products/product-source-options.php:61 +msgid "Generate keys in sequential order." msgstr "" -#: includes/Admin/Requests.php:79 -msgid "Key added successfully." +#: includes/Admin/views/products/product-source-options.php:72 +msgid "Preset Stock" msgstr "" -#: includes/Admin/Requests.php:81 -msgid "Key updated successfully." +#: includes/Admin/views/products/product-source-options.php:74 +msgid "key available." +msgid_plural "keys available." +msgstr[0] "" +msgstr[1] "" + +#: includes/Models/Generator.php:119 src/Functions/Template.php:70 +msgid "Active" msgstr "" -#: includes/Admin/Requests.php:100 includes/Admin/Requests.php:163 -#: includes/Admin/Requests.php:238 -msgid "You do not have permission to access this endpoint." +#: includes/Models/Generator.php:120 +msgid "Inactive" msgstr "" -#: includes/Admin/Requests.php:274 -#. translators: $1: customer name, $2 customer id, $3: customer email -msgid "%1$s (#%2$s - %3$s)" +#: includes/Models/Generator.php:166 +msgid "The generator name is required." +msgstr "" + +#: includes/Models/Generator.php:170 +msgid "The generator pattern is required." +msgstr "" + +#: includes/Models/Generator.php:174 +msgid "The generator charset is required." msgstr "" #: lib/Lib/Model.php:424 @@ -212,7 +361,7 @@ msgstr "" msgid "More Plugins" msgstr "" -#: lib/Lib/Plugin.php:692 src/Admin/Menus.php:170 src/Admin/Menus.php:171 +#: lib/Lib/Plugin.php:692 src/Admin/Menus.php:208 src/Admin/Menus.php:209 msgid "Settings" msgstr "" @@ -248,42 +397,42 @@ msgstr "" msgid "Missing data." msgstr "" -#: src/Admin/Admin.php:65 +#: src/Admin/Admin.php:75 msgid "Search by product" msgstr "" -#: src/Admin/Admin.php:66 +#: src/Admin/Admin.php:76 msgid "Search by order" msgstr "" -#: src/Admin/Admin.php:67 +#: src/Admin/Admin.php:77 msgid "Search by customer" msgstr "" -#: src/Admin/Admin.php:68 +#: src/Admin/Admin.php:78 msgid "Show" msgstr "" -#: src/Admin/Admin.php:69 +#: src/Admin/Admin.php:79 msgid "Hide" msgstr "" -#: src/Admin/Admin.php:70 src/Frontend/Frontend.php:56 +#: src/Admin/Admin.php:80 src/Frontend/Frontend.php:56 msgid "Copied" msgstr "" -#: src/Admin/Admin.php:108 +#: src/Admin/Admin.php:118 #. translators: 1: Plugin name 2: WordPress msgid "" "Thank you for using %1$s! Share your appreciation with a five-star review " "%2$s." msgstr "" -#: src/Admin/Admin.php:110 +#: src/Admin/Admin.php:120 msgid "Thanks :)" msgstr "" -#: src/Admin/Admin.php:128 +#: src/Admin/Admin.php:138 #. translators: 1: Plugin version msgid "Version %s" msgstr "" @@ -293,8 +442,8 @@ msgstr "" msgid "Activation" msgstr "" -#: src/Admin/ListTables/ActivationsTable.php:40 src/Admin/Menus.php:119 -#: src/Admin/Menus.php:120 src/Admin/views/html-list-activations.php:18 +#: src/Admin/ListTables/ActivationsTable.php:40 src/Admin/Menus.php:157 +#: src/Admin/Menus.php:158 src/Admin/views/html-list-activations.php:18 msgid "Activations" msgstr "" @@ -308,13 +457,6 @@ msgstr "" msgid "Filter" msgstr "" -#: src/Admin/ListTables/ActivationsTable.php:164 -#: src/Admin/ListTables/ActivationsTable.php:239 -#: src/Admin/ListTables/KeysTable.php:336 -#: src/Admin/ListTables/KeysTable.php:428 src/Admin/views/html-edit-key.php:130 -msgid "Delete" -msgstr "" - #: src/Admin/ListTables/ActivationsTable.php:176 #: src/Admin/views/html-api-actions.php:141 src/Frontend/Shortcodes.php:146 msgid "Instance" @@ -360,7 +502,7 @@ msgid "Keys can have one of the following statuses:" msgstr "" #: src/Admin/ListTables/KeysTable.php:178 -#: src/Admin/ListTables/KeysTable.php:235 src/functions.php:46 +#: src/Admin/ListTables/KeysTable.php:235 src/functions.php:47 msgid "Available" msgstr "" @@ -369,7 +511,7 @@ msgid "This means the key is available for purchase." msgstr "" #: src/Admin/ListTables/KeysTable.php:183 -#: src/Admin/ListTables/KeysTable.php:242 src/functions.php:47 +#: src/Admin/ListTables/KeysTable.php:242 src/functions.php:48 msgid "Pending" msgstr "" @@ -379,7 +521,7 @@ msgstr "" #: src/Admin/ListTables/KeysTable.php:188 #: src/Admin/ListTables/KeysTable.php:249 -#: src/Admin/ListTables/StockTable.php:97 src/functions.php:48 +#: src/Admin/ListTables/StockTable.php:97 src/functions.php:49 msgid "Sold" msgstr "" @@ -389,7 +531,7 @@ msgstr "" #: src/Admin/ListTables/KeysTable.php:193 #: src/Admin/ListTables/KeysTable.php:256 src/Functions/Template.php:72 -#: src/functions.php:49 +#: src/functions.php:50 msgid "Expired" msgstr "" @@ -398,7 +540,7 @@ msgid "This means the key has expired and is no longer valid." msgstr "" #: src/Admin/ListTables/KeysTable.php:198 -#: src/Admin/ListTables/KeysTable.php:263 src/functions.php:50 +#: src/Admin/ListTables/KeysTable.php:263 src/functions.php:51 msgid "Cancelled" msgstr "" @@ -412,10 +554,6 @@ msgstr "" msgid "All keys." msgstr "" -#: src/Admin/ListTables/KeysTable.php:228 -msgid "All" -msgstr "" - #: src/Admin/ListTables/KeysTable.php:233 msgid "Available for sell." msgstr "" @@ -457,11 +595,6 @@ msgstr "" msgid "ID: %d" msgstr "" -#: src/Admin/ListTables/KeysTable.php:427 -#: src/Admin/ListTables/StockTable.php:143 -msgid "Edit" -msgstr "" - #: src/Admin/ListTables/KeysTable.php:522 #. translators: %1$s: validity, %2$s: validity. msgid "%s Day
After purchase" @@ -498,7 +631,7 @@ msgstr "" msgid "Source" msgstr "" -#: src/Admin/ListTables/StockTable.php:98 src/Admin/Menus.php:280 +#: src/Admin/ListTables/StockTable.php:98 src/Admin/Menus.php:318 msgid "Stock" msgstr "" @@ -506,87 +639,86 @@ msgstr "" msgid "Manual" msgstr "" -#: src/Admin/ListTables/StockTable.php:174 -msgid "Generator Rule" +#: src/Admin/ListTables/StockTable.php:174 src/functions.php:92 +msgid "Automatic" msgstr "" #: src/Admin/ListTables/StockTable.php:176 -msgid "Auto Generated" -msgstr "" - -#: src/Admin/ListTables/StockTable.php:178 msgid "Unknown" msgstr "" -#: src/Admin/Menus.php:99 src/Admin/Menus.php:100 +#: src/Admin/Menus.php:101 src/Admin/Menus.php:102 #: src/Admin/views/html-list-keys.php:18 msgid "Serial Keys" msgstr "" -#: src/Admin/Menus.php:136 src/Admin/Menus.php:137 +#: src/Admin/Menus.php:117 src/Admin/Menus.php:118 +#: src/Admin/views/html-list-generators.php:17 +msgid "Generators" +msgstr "" + +#: src/Admin/Menus.php:174 src/Admin/Menus.php:175 msgid "Tools" msgstr "" -#: src/Admin/Menus.php:153 src/Admin/Menus.php:154 +#: src/Admin/Menus.php:191 src/Admin/Menus.php:192 msgid "Reports" msgstr "" -#: src/Admin/Menus.php:245 src/Admin/Menus.php:381 -msgid "Generators" +#: src/Admin/Menus.php:283 src/Admin/Settings.php:25 +msgid "General" msgstr "" -#: src/Admin/Menus.php:246 +#: src/Admin/Menus.php:284 msgid "API Toolkit" msgstr "" -#: src/Admin/Menus.php:247 src/Admin/views/html-list-keys.php:25 +#: src/Admin/Menus.php:285 src/Admin/views/html-list-keys.php:25 msgid "Import" msgstr "" -#: src/Admin/Menus.php:248 src/Admin/views/html-list-keys.php:29 +#: src/Admin/Menus.php:286 src/Admin/views/html-list-keys.php:29 msgid "Export" msgstr "" -#: src/Admin/Menus.php:335 src/Admin/Menus.php:342 src/Admin/Menus.php:360 -#: src/Admin/Menus.php:378 +#: src/Admin/Menus.php:373 src/Admin/Menus.php:380 src/Admin/Menus.php:398 msgid "Available in Pro Version" msgstr "" -#: src/Admin/Menus.php:336 src/Admin/Menus.php:343 src/Admin/Menus.php:361 -#: src/Admin/Menus.php:379 +#: src/Admin/Menus.php:374 src/Admin/Menus.php:381 src/Admin/Menus.php:399 msgid "Upgrade to Pro Now" msgstr "" -#: src/Admin/Menus.php:338 src/Admin/Menus.php:345 +#: src/Admin/Menus.php:376 src/Admin/Menus.php:383 msgid "Import Serial Numbers" msgstr "" -#: src/Admin/Menus.php:363 +#: src/Admin/Menus.php:401 msgid "Export Serial Numbers" msgstr "" -#: src/Admin/Menus.php:408 +#: src/Admin/Menus.php:439 msgid "Table exists" msgstr "" -#: src/Admin/Menus.php:410 +#: src/Admin/Menus.php:441 msgid "Table does not exist" msgstr "" -#: src/Admin/Menus.php:416 +#: src/Admin/Menus.php:447 msgid "Hourly cron" msgstr "" -#: src/Admin/Menus.php:417 +#: src/Admin/Menus.php:448 msgid "Daily cron" msgstr "" -#: src/Admin/Menus.php:423 +#: src/Admin/Menus.php:454 #. translators: %s: Next scheduled time. msgid "Next run: %s" msgstr "" -#: src/Admin/Menus.php:425 +#: src/Admin/Menus.php:456 msgid "Not scheduled" msgstr "" @@ -604,10 +736,6 @@ msgid "" "discount by using the promo code %2$s. %3$s Upgrade Now%4$s." msgstr "" -#: src/Admin/Settings.php:25 -msgid "General" -msgstr "" - #: src/Admin/Settings.php:46 msgid "General Settings" msgstr "" @@ -951,14 +1079,6 @@ msgstr "" msgid "Action" msgstr "" -#: src/Admin/views/html-api-actions.php:175 src/Frontend/Shortcodes.php:157 -msgid "Activate" -msgstr "" - -#: src/Admin/views/html-api-actions.php:176 src/Frontend/Shortcodes.php:158 -msgid "Deactivate" -msgstr "" - #: src/Admin/views/html-api-actions.php:179 msgid "Select an action to perform on the serial key." msgstr "" @@ -1035,6 +1155,67 @@ msgstr "" msgid "Validate" msgstr "" +#: src/Admin/views/html-edit-generator.php:17 +msgid "Edit generator" +msgstr "" + +#: src/Admin/views/html-edit-generator.php:19 +msgid "Add New Generator" +msgstr "" + +#: src/Admin/views/html-edit-generator.php:22 +msgid "Back" +msgstr "" + +#: src/Admin/views/html-edit-generator.php:38 +msgid "Enter a friendly name for the generator." +msgstr "" + +#: src/Admin/views/html-edit-generator.php:52 +msgid "" +"Enter a pattern for this generator. Use # for random characters and y, m " +"and d for year, month and date respectively." +msgstr "" + +#: src/Admin/views/html-edit-generator.php:59 +msgid "Charset" +msgstr "" + +#: src/Admin/views/html-edit-generator.php:66 +msgid "Enter the charset for the generator. Leave empty for default charset." +msgstr "" + +#: src/Admin/views/html-edit-generator.php:73 +msgid "Valid for (days)" +msgstr "" + +#: src/Admin/views/html-edit-generator.php:79 +#: src/Admin/views/html-edit-key.php:85 +msgid "" +"Number of days the key will be valid from the purchase date. Leave it blank " +"for lifetime validity." +msgstr "" + +#: src/Admin/views/html-edit-generator.php:88 +#: src/Admin/views/html-edit-generator.php:94 +#: src/Admin/views/html-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 "" + +#: src/Admin/views/html-edit-generator.php:119 +msgid "Select the status for this generator." +msgstr "" + +#: src/Admin/views/html-edit-generator.php:130 +msgid "Save Changes" +msgstr "" + +#: src/Admin/views/html-edit-generator.php:130 +msgid "Create" +msgstr "" + #: src/Admin/views/html-edit-key.php:16 msgid "Edit Serial Key" msgstr "" @@ -1069,12 +1250,6 @@ msgid "" "4CE0460D0G-4CE0460D1G-4CE0460D2G" msgstr "" -#: src/Admin/views/html-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 "" - #: src/Admin/views/html-edit-key.php:77 msgid "Valid for" msgstr "" @@ -1083,12 +1258,6 @@ msgstr "" msgid "Days" msgstr "" -#: src/Admin/views/html-edit-key.php:85 -msgid "" -"Number of days the key will be valid from the purchase date. Leave it blank " -"for lifetime validity." -msgstr "" - #: src/Admin/views/html-edit-key.php:99 msgid "Serial key status auto-updates with order status. Avoid manual changes." msgstr "" @@ -1117,10 +1286,6 @@ msgstr "" msgid "Customer details" msgstr "" -#: src/Admin/views/html-edit-key.php:146 -msgid "Name" -msgstr "" - #: src/Admin/views/html-edit-key.php:162 msgid "Address" msgstr "" @@ -1137,6 +1302,7 @@ msgstr "" msgid "Search activation" msgstr "" +#: src/Admin/views/html-list-generators.php:19 #: src/Admin/views/html-list-keys.php:21 msgid "Add New" msgstr "" @@ -1153,6 +1319,59 @@ msgstr "" msgid "Search" msgstr "" +#: src/Admin/views/tools/general.php:14 +msgid "Bulk Keys Generator" +msgstr "" + +#: src/Admin/views/tools/general.php:18 +msgid "" +"Generate keys in bulk for a product. You can generate keys in bulk for a " +"product using this tool." +msgstr "" + +#: src/Admin/views/tools/general.php:21 +#. Translators: %1$s and %2$s are HTML tags. +msgid "" +"%1$sNote%2$s: Product key source will be automatically change to \"Preset\" " +"if it is not already set & Generated keys will be treated as preset keys." +msgstr "" + +#: src/Admin/views/tools/general.php:26 +msgid "Product *" +msgstr "" + +#: src/Admin/views/tools/general.php:28 +msgid "All Products" +msgstr "" + +#: src/Admin/views/tools/general.php:37 +msgid "Select the product associated with the key." +msgstr "" + +#: src/Admin/views/tools/general.php:42 +msgid "Generator" +msgstr "" + +#: src/Admin/views/tools/general.php:43 +msgid "Select a Generator..." +msgstr "" + +#: src/Admin/views/tools/general.php:44 +msgid "Select a key generator..." +msgstr "" + +#: src/Admin/views/tools/general.php:64 +msgid "Quantity *" +msgstr "" + +#: src/Admin/views/tools/general.php:67 +msgid "Enter the number of keys to generate." +msgstr "" + +#: src/Admin/views/tools/general.php:75 +msgid "Generate Serial Keys" +msgstr "" + #: src/Cron.php:57 msgid "Serial Numbers stock running low" msgstr "" @@ -1165,11 +1384,6 @@ msgstr "" msgid "Serial Number" msgstr "" -#: src/Deprecated/Functions.php:361 src/Functions/Template.php:52 -#: src/functions.php:1025 -msgid "Activation Limit" -msgstr "" - #: src/Deprecated/Functions.php:362 msgid "Expires" msgstr "" @@ -1223,27 +1437,23 @@ msgstr "" msgid "Invalid request." msgstr "" -#: src/Functions/Template.php:47 src/functions.php:1020 +#: src/Functions/Template.php:47 src/functions.php:1021 msgid "Activation Email" msgstr "" #: src/Functions/Template.php:53 src/Functions/Template.php:58 -#: src/functions.php:1026 src/functions.php:1031 +#: src/functions.php:1027 src/functions.php:1032 msgid "None" msgstr "" -#: src/Functions/Template.php:57 src/functions.php:1030 +#: src/Functions/Template.php:57 src/functions.php:1031 msgid "Activation Count" msgstr "" -#: src/Functions/Template.php:62 src/functions.php:1035 +#: src/Functions/Template.php:62 src/functions.php:1036 msgid "Expire Date" msgstr "" -#: src/Functions/Template.php:70 -msgid "Active" -msgstr "" - #: src/Functions/Template.php:307 msgid "Order is waiting for serial numbers to be assigned." msgstr "" @@ -1285,23 +1495,23 @@ msgstr "" msgid "Order id is invalid." msgstr "" -#: src/Orders.php:66 +#: src/Orders.php:65 #. translators: %1$s: product title, %2$s: stock quantity. 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 "" -#: src/Orders.php:109 +#: src/Orders.php:108 msgid "Order automatically completed by the Serial Numbers for WooCommerce." msgstr "" -#: src/Plugin.php:76 +#: src/Plugin.php:78 #. translators: 1: plugin name 2: WooCommerce msgid "%1$s requires %2$s to be installed and active." msgstr "" -#: src/Plugin.php:78 +#: src/Plugin.php:80 msgid "WooCommerce" msgstr "" @@ -1369,11 +1579,11 @@ msgstr "" msgid "Serial key is deactivated." msgstr "" -#: src/functions.php:91 -msgid "Manually added" +#: src/functions.php:93 +msgid "Preset" msgstr "" -#: src/functions.php:539 +#: src/functions.php:540 #. translators: 1: product title 2: source and 3: Quantity msgid "" "There is not enough serial numbers for the product %1$s from selected " @@ -1417,4 +1627,12 @@ msgstr "" #. Author URI of the plugin/theme msgid "https://pluginever.com" -msgstr "" \ No newline at end of file +msgstr "" + +#: includes/Admin/ListTables/GeneratorsTable.php:290 +#. translators: %d: number of days. +msgctxt "valid for days" +msgid "%d day After Purchase" +msgid_plural "%d days After Purchase" +msgstr[0] "" +msgstr[1] "" \ No newline at end of file diff --git a/src/Admin/Admin.php b/src/Admin/Admin.php index 2456b68e..4a730177 100644 --- a/src/Admin/Admin.php +++ b/src/Admin/Admin.php @@ -48,6 +48,16 @@ public function init() { * @since 1.0.0 */ public function enqueue_scripts( $hook ) { + + // Check if the current screen is a WooCommerce product add/edit screen. If so, enqueue the script. + if ( 'post.php' === $hook || 'post-new.php' === $hook ) { + $screen = get_current_screen(); + if ( 'product' === $screen->post_type ) { + WCSN()->enqueue_script( 'wcsn-admin-product', 'js/admin-product.js', array( 'jquery' ), WCSN()->get_version(), true ); + } + } + + // Check if the current screen is a WooCommerce Serial Numbers screen. If not, return. if ( ! in_array( $hook, self::get_screen_ids(), true ) ) { return; } @@ -142,6 +152,7 @@ public static function get_screen_ids() { $screen_ids = array( 'toplevel_page_' . $screen_id, 'toplevel_page_wc-serial-numbers', + $screen_id . '_page_wc-serial-numbers-generators', $screen_id . '_page_wc-serial-numbers-activations', $screen_id . '_page_wc-serial-numbers-products', $screen_id . '_page_wc-serial-numbers-tools', diff --git a/src/Admin/ListTables/StockTable.php b/src/Admin/ListTables/StockTable.php index 6e08b512..519a8600 100644 --- a/src/Admin/ListTables/StockTable.php +++ b/src/Admin/ListTables/StockTable.php @@ -168,12 +168,10 @@ public function column_default( $item, $column_name ) { return number_format_i18n( $sold_count ); case 'source': $source = get_post_meta( $item->get_id(), '_serial_key_source', true ); - if ( 'custom_source' === $source ) { + if ( 'preset' === $source ) { $label = esc_html__( 'Manual', 'wc-serial-numbers' ); - } elseif ( 'generator_rule' === $source ) { - $label = esc_html__( 'Generator Rule', 'wc-serial-numbers' ); - } elseif ( 'auto_generated' === $source ) { - $label = esc_html__( 'Auto Generated', 'wc-serial-numbers' ); + } elseif ( 'automatic' === $source ) { + $label = esc_html__( 'Automatic', 'wc-serial-numbers' ); } else { $label = esc_html__( 'Unknown', 'wc-serial-numbers' ); } diff --git a/src/Admin/Menus.php b/src/Admin/Menus.php index f9d3455c..982a9853 100644 --- a/src/Admin/Menus.php +++ b/src/Admin/Menus.php @@ -2,6 +2,7 @@ namespace WooCommerceSerialNumbers\Admin; +use WooCommerceSerialNumbers\Models\Generator; use WooCommerceSerialNumbers\Models\Key; defined( 'ABSPATH' ) || exit; @@ -21,6 +22,7 @@ class Menus { public function __construct() { // Register the menus. add_action( 'admin_menu', array( $this, 'main_menu' ) ); + add_action( 'admin_menu', array( $this, 'generators_menu' ) ); add_action( 'admin_menu', array( $this, 'activations_menu' ), 40 ); add_action( 'admin_menu', array( $this, 'tools_menu' ), 50 ); add_action( 'admin_menu', array( $this, 'reports_menu' ), 60 ); @@ -31,7 +33,7 @@ public function __construct() { add_filter( 'wc_serial_numbers_tools_tabs', array( __CLASS__, 'add_tools_status_tab' ), PHP_INT_MAX ); add_action( 'wc_serial_numbers_tools_tab_import', array( __CLASS__, 'import_tab' ) ); add_action( 'wc_serial_numbers_tools_tab_export', array( __CLASS__, 'export_tab' ) ); - add_action( 'wc_serial_numbers_tools_tab_generators', array( __CLASS__, 'generators_tab' ) ); + add_action( 'wc_serial_numbers_tools_tab_general', array( __CLASS__, 'output_generators_tab' ) ); add_action( 'wc_serial_numbers_tools_tab_status', array( __CLASS__, 'status_tab' ) ); add_action( 'wc_serial_numbers_tools_tab_api', array( __CLASS__, 'api_validation_section' ) ); add_action( 'wc_serial_numbers_tools_tab_api', array( __CLASS__, 'api_activation_deactivation_section' ) ); @@ -104,6 +106,42 @@ public function main_menu() { ); } + /** + * Add the Generators menu. + * + * @since 1.0.0 + */ + public static function generators_menu() { + add_submenu_page( + 'wc-serial-numbers', + __( 'Generators', 'wc-serial-numbers' ), + __( 'Generators', 'wc-serial-numbers' ), + 'manage_options', + 'wc-serial-numbers-generators', + array( __CLASS__, 'render_generators_tab' ) + ); + } + + /** + * Output the generators tab. + * + * @since 1.0.0 + */ + public static function render_generators_tab() { + wp_verify_nonce( '_wpnonce' ); + if ( isset( $_GET['add'] ) || isset( $_GET['edit'] ) ) { + $id = isset( $_GET['edit'] ) ? absint( $_GET['edit'] ) : 0; + $generator = Generator::make( $id ); + if ( ! empty( $id ) && ! $generator->exists() ) { + wp_safe_redirect( remove_query_arg( 'edit' ) ); + exit(); + } + Admin::view( 'html-edit-generator.php', array( 'generator' => $generator ) ); + } else { + Admin::view( 'html-list-generators.php' ); + } + } + /** * Add activations menu. * @@ -242,10 +280,10 @@ public function output_activations_page() { 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' ), + 'general' => __( 'General', '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. @@ -366,21 +404,14 @@ public static function export_tab() { } /** - * Getnerators tab content. + * Generators tab content. + * + * @since 3.0.0 * - * @since 1.4.6 * @return void */ - public static function generators_tab() { - ?> -
-
-

- -
- <?php esc_attr_e( 'Generators', 'wc-serial-numbers' ); ?> -
- +
+

+ exists() ) : ?> + + + + + + + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +

+ +

+
+ + + +

+ +

+
+ + + +

+ +

+
+ + + +

+ +

+
+ + + +

+ +

+
+ + + +

+ +

+
+ + + + exists() ? esc_html__( 'Save Changes', 'wc-serial-numbers' ) : esc_html__( 'Create', 'wc-serial-numbers' ), 'primary' ); ?> +
+
+
diff --git a/src/Admin/views/html-list-generators.php b/src/Admin/views/html-list-generators.php new file mode 100644 index 00000000..cca1e41a --- /dev/null +++ b/src/Admin/views/html-list-generators.php @@ -0,0 +1,33 @@ +current_action(); +$list_table->process_bulk_actions( $action ); +?> + +
+

+ + + + +
+ prepare_items(); + $list_table->views(); + $list_table->display(); + ?> + + +
+
diff --git a/src/Admin/views/html-list-keys.php b/src/Admin/views/html-list-keys.php index eab9cdfd..608a61bc 100644 --- a/src/Admin/views/html-list-keys.php +++ b/src/Admin/views/html-list-keys.php @@ -29,7 +29,7 @@ - + diff --git a/src/Admin/views/tools/general.php b/src/Admin/views/tools/general.php new file mode 100644 index 00000000..40005cbe --- /dev/null +++ b/src/Admin/views/tools/general.php @@ -0,0 +1,77 @@ + + +
+
+

+
+
+

+ +
+ + ', '' ); ?> +

+ +
+
+ + +

+ +

+
+ +
+ + +

+ +

+ +
+ + +
+ + +

+ +

+
+ + +
+
+ +
diff --git a/src/Deprecated/Functions.php b/src/Deprecated/Functions.php index 9d1317cf..e5dcaa80 100644 --- a/src/Deprecated/Functions.php +++ b/src/Deprecated/Functions.php @@ -376,7 +376,7 @@ function wc_serial_numbers_get_order_table_columns() { */ function wc_serial_numbers_get_stock_quantity( $product_id ) { $source = get_post_meta( $product_id, '_serial_key_source', true ); - if ( 'custom_source' == get_post_meta( $product_id, '_serial_key_source', true ) || empty( $source ) ) { + if ( 'preset' == get_post_meta( $product_id, '_serial_key_source', true ) || empty( $source ) ) { $stocks = wcsn_get_stocks_count(); if ( isset( $stocks[ $product_id ] ) ) { return absint( $stocks[ $product_id ] ); diff --git a/src/Installer.php b/src/Installer.php index 39c2cb4d..b003798d 100644 --- a/src/Installer.php +++ b/src/Installer.php @@ -153,7 +153,7 @@ public static function create_tables() { expire_date DATETIME NULL DEFAULT NULL, order_date DATETIME NULL DEFAULT NULL, uuid varchar(50) DEFAULT NULL, - source varchar(50) DEFAULT 'custom_source', + source varchar(50) DEFAULT 'preset', created_date DATETIME NULL DEFAULT NULL, PRIMARY KEY (id), key product_id (product_id), @@ -247,7 +247,7 @@ protected function update_120() { $wpdb->query( "ALTER TABLE {$wpdb->prefix}serial_numbers ADD vendor_id bigint(20) NOT NULL DEFAULT 0" ); $wpdb->query( "ALTER TABLE {$wpdb->prefix}serial_numbers ADD activation_count int(9) NOT NULL DEFAULT 0" ); $wpdb->query( "ALTER TABLE {$wpdb->prefix}serial_numbers ADD KEY vendor_id(`vendor_id`)" ); - $wpdb->query( "ALTER TABLE {$wpdb->prefix}serial_numbers ADD source varchar(200) NOT NULL default 'custom_source'" ); + $wpdb->query( "ALTER TABLE {$wpdb->prefix}serial_numbers ADD source varchar(200) NOT NULL default 'preset'" ); $wpdb->query( "ALTER TABLE {$wpdb->prefix}serial_numbers_activations CHANGE platform platform varchar(200) DEFAULT NULL" ); // status update. $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}serial_numbers set status=%s WHERE status=%s AND order_id=0", 'available', 'new' ) ); diff --git a/src/Models/Key.php b/src/Models/Key.php index fb86d722..aecb09dd 100644 --- a/src/Models/Key.php +++ b/src/Models/Key.php @@ -45,7 +45,7 @@ class Key extends Model { 'status' => 'available', 'validity' => 0, 'order_date' => '', - 'source' => 'custom_source', + 'source' => 'preset', 'created_date' => '', ); diff --git a/src/Orders.php b/src/Orders.php index f0011ec2..dfa4b33b 100644 --- a/src/Orders.php +++ b/src/Orders.php @@ -28,7 +28,6 @@ public function __construct() { add_action( 'woocommerce_order_status_changed', array( __CLASS__, 'handle_order_status_changed' ) ); // TODO: handle order status change and order remove scenario. // TODO: handle order again feature. - add_action( 'woocommerce_email_after_order_table', array( __CLASS__, 'order_email_keys' ), PHP_INT_MAX ); add_action( 'woocommerce_order_details_after_order_table', array( __CLASS__, 'order_display_keys' ), 9 ); } @@ -53,8 +52,8 @@ public static function validate_checkout() { if ( wcsn_is_product_enabled( $product_id ) && ! $allow_backorder ) { $per_item_quantity = absint( apply_filters( 'wc_serial_numbers_per_product_delivery_qty', 1, $product_id ) ); $needed_quantity = $quantity * ( empty( $per_item_quantity ) ? 1 : absint( $per_item_quantity ) ); - $source = apply_filters( 'wc_serial_numbers_product_serial_source', 'custom_source', $product_id, $needed_quantity ); - if ( 'custom_source' === $source ) { + $source = apply_filters( 'wc_serial_numbers_product_serial_source', 'preset', $product_id, $needed_quantity ); + if ( 'preset' === $source ) { $args = array( 'product_id' => $product_id, 'status' => 'available', diff --git a/src/Plugin.php b/src/Plugin.php index 50649400..dab3d633 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -2,6 +2,8 @@ namespace WooCommerceSerialNumbers; +use WooCommerceSerialNumbers\Models\Generator; + defined( 'ABSPATH' ) || exit; /** @@ -107,10 +109,88 @@ public function init() { $this->services['admin'] = new Admin\Admin(); } + // Filters. + add_filter( 'wc_serial_numbers_pre_order_item_connect_serial_numbers', array( __CLASS__, 'generate_serials' ), 10, 3 ); + add_filter( 'wc_serial_numbers_product_serial_source', array( __CLASS__, 'define_serials_source' ), 10, 2 ); + // Init action. do_action( 'wc_serial_numbers_loaded' ); } + /** + * Generate serial numbers. + * + * @param int $product_id Product ID. + * @param int $total_delivery_qty Total delivery quantity. + * @param string $source Source. + * + * @since 1.2.0 + * @return bool|int + */ + public static function generate_serials( $product_id, $total_delivery_qty, $source ) { + if ( empty( $product_id ) || empty( $total_delivery_qty ) || empty( $source ) || 'preset' === $source ) { + return false; + } + $count = (int) wcsn_get_keys( + array( + 'product_id' => $product_id, + 'status' => 'available', + 'source' => $source, + 'count' => true, + ) + ); + + $needed_quantity = ceil( $total_delivery_qty - $count ); + + if ( $needed_quantity < 1 ) { + return false; + } + + // Check if source is not changed. + if ( get_post_meta( $product_id, '_serial_key_source', true ) !== $source ) { + return false; + } + + $total_generated = 0; + if ( 'automatic' === $source ) { + $generator_id = (int) get_post_meta( $product_id, '_generator_id', true ); + $generator = wcsn_get_generator( $generator_id ); + + $data = array( + 'product_id' => $product_id, + 'quantity' => $needed_quantity, + 'source' => 'automatic', + ); + + // If generator is found then generate keys from generator. + if ( $generator ) { + $data['generator_id'] = $generator_id; + } + + // Generate keys. + $keys = wcsn_generate_keys( $data ); + + $total_generated = count( $keys ); + } + + return $total_generated; + } + + /** + * Define serial number source. + * + * @param string $source Source. + * @param int $product_id Product ID. + * + * @since 1.2.0 + * @return bool|mixed|string + */ + public static function define_serials_source( $source, $product_id ) { + $key_source = get_post_meta( $product_id, '_serial_key_source', true ); + + return 'automatic' === $key_source ? 'automatic' : $source; + } + /** * Determines if the pro version active. * diff --git a/src/functions.php b/src/functions.php index e13c80ec..ecb0ff72 100644 --- a/src/functions.php +++ b/src/functions.php @@ -9,6 +9,7 @@ use WooCommerceSerialNumbers\Encryption; use WooCommerceSerialNumbers\Models\Activation; use WooCommerceSerialNumbers\Models\Key; +use WooCommerceSerialNumbers\Models\Generator; defined( 'ABSPATH' ) || exit; @@ -88,7 +89,8 @@ function wcsn_get_revoke_statuses() { */ function wcsn_get_key_sources() { $sources = array( - 'custom_source' => __( 'Manually added', 'wc-serial-numbers' ), + 'automatic' => __( 'Automatic', 'wc-serial-numbers' ), + 'preset' => __( 'Preset', 'wc-serial-numbers' ), ); return apply_filters( 'wc_serial_numbers_key_sources', $sources ); @@ -328,7 +330,7 @@ function wcsn_get_order_line_items_data( $order_id, $order_item_id = null ) { $refund_qty = $order->get_qty_refunded_for_item( $item_id ); // Deprecated filter. // todo: remove this filter in the future. - $sources = apply_filters( 'wc_serial_numbers_product_serial_source', 'custom_source', $product_id ); + $sources = apply_filters( 'wc_serial_numbers_product_serial_source', 'preset', $product_id ); $quantity = $quantity * apply_filters( 'wc_serial_numbers_per_product_delivery_qty', 1, $product_id, $order_id ); $data = array( @@ -472,7 +474,6 @@ function wcsn_order_update_keys( $order_id ) { $do_add = apply_filters( 'wc_serial_numbers_add_order_keys', true, $order_id, $line_items, $order_status ); if ( in_array( $order_status, array( 'processing', 'completed' ), true ) && ! wcsn_order_is_fullfilled( $order_id ) && $do_add ) { - /** * Action hook to pre add order keys. * @@ -901,7 +902,7 @@ function wcsn_get_stocks_count( $stock_limit = null, $force = true ) { 'relation' => 'AND', array( 'key' => '_serial_key_source', - 'value' => 'custom_source', + 'value' => 'preset', 'compare' => '=', ), ), @@ -1066,3 +1067,142 @@ function ( $a, $b ) { return $properties; } + +/** + * Get generators. + * + * @param array $args Query args. + * + * @since 1.2.1 + * @return Generator[]|int + */ +function wcsn_get_generators( $args = array() ) { + return Generator::results( $args ); +} + +/** + * Get the generator. + * + * @param mixed $data Generator ID or object. + * + * @since 1.2.1 + * @return Generator|null + */ +function wcsn_get_generator( $data ) { + return Generator::find( $data ); +} + +/** + * Generate keys based on the settings of the product. + * + * @param array $args Arguments. + * + * @since 3.0.0 + */ +function wcsn_generate_keys( $args = array() ) { + // Default arguments. + $defaults = array( + 'product_id' => 0, + 'generator_id' => 0, + 'quantity' => 1, + ); + // Parse arguments. + $args = wp_parse_args( $args, $defaults ); + + $product_id = ! empty( $args['product_id'] ) ? absint( $args['product_id'] ) : 0; + $generator_id = ! empty( $args['generator_id'] ) ? absint( $args['generator_id'] ) : 0; + $quantity = ! empty( $args['quantity'] ) ? absint( $args['quantity'] ) : 1; + $product = wc_get_product( $product_id ); + + // If product ID is not set, then return false. + if ( empty( $product ) ) { + return false; + } + + // If generator is set, we will check if product has a generator set otherwise we will use default settings. + if ( empty( $generator_id ) ) { + $generator_id = get_post_meta( $product_id, '_generator_id', true ); + } + + // If we get the generator ID and generator is found, then we will use the generator settings. + $generator = null; + if ( ! empty( $generator_id ) ) { + $generator = Generator::find( $generator_id ); + } + + // Prepare required variables. + $pattern = $generator ? $generator->pattern : get_option( 'wcsn_pattern', '####-####-####-####' ); + $charset = $generator ? $generator->charset : get_option( 'wcsn_charset', '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' ); + $valid_for = $generator ? $generator->valid_for : get_option( 'wcsn_valid_for', 0 ); + $activation_limit = $generator ? $generator->activation_limit : get_option( 'wcsn_activation_limit', 0 ); + $source = ! empty( $args['source'] ) ? $args['source'] : 'preset'; + $mask_count = substr_count( $pattern, '#' ); + + // Prepare replacers array. + $replacers = array( + 'product_id' => $product->get_id(), + 'product_sku' => $product->get_sku(), + 'y' => wp_date( 'Y' ), + 'm' => wp_date( 'm' ), + 'd' => wp_date( 'd' ), + 'h' => wp_date( 'H' ), + 'i' => wp_date( 'i' ), + 's' => wp_date( 's' ), + ); + + // Replace placeholders with actual values. + $pattern = preg_replace_callback( + '/{(\w+)}/', + function ( $matches ) use ( $replacers ) { + return $replacers[ $matches[1] ] ?? ''; + }, + $pattern + ); + + $keys = array(); + foreach ( range( 1, $quantity ) as $index ) { + $code = $pattern; + $chars = array_map( + function () use ( $charset ) { + return $charset[ wp_rand( 0, strlen( $charset ) - 1 ) ]; + }, + range( 1, $mask_count ) + ); + + // Replace '#' with characters. + foreach ( $chars as $char ) { + if ( strpos( $code, '#' ) !== false ) { + $pos = strpos( $code, '#' ); + $code = substr_replace( $code, $char, $pos, 1 ); + } else { + $code .= $char; + } + } + + $data = array( + 'product_id' => $product->get_id(), + 'serial_key' => $code, + 'activation_limit' => $activation_limit, + 'validity' => $valid_for, + 'source' => $source, + ); + + /** + * Filter hook to alter the key data before inserting. + * + * @param array $data The key data. + * @param array $args The arguments. + * @param Generator|null $generator The generator object. + */ + $data = apply_filters( 'wc_serial_numbers_generated_key_data', $data, $args, $generator ); + + $key = wcsn_insert_key( $data ); + + if ( ! is_wp_error( $key ) ) { + $keys[] = $key; + } + } + + // Return the generated keys. + return $keys; +} diff --git a/webpack.config.js b/webpack.config.js index 8f5ff7a9..b6a71490 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -10,6 +10,7 @@ const mode = isProduction ? 'production' : 'development'; module.exports = { ...defaultConfig, entry: { + 'js/admin-product': './assets/js/admin-product.js', 'js/admin-script': './assets/js/admin-script.js', 'js/frontend-script': './assets/js/frontend-script.js', 'css/admin-style': './assets/css/admin-style.scss',