diff --git a/includes/slm-blocks.php b/includes/slm-blocks.php new file mode 100644 index 0000000..5216ec1 --- /dev/null +++ b/includes/slm-blocks.php @@ -0,0 +1,25 @@ +options} WHERE option_name LIKE '_transient_$like_pattern' OR option_name LIKE '_transient_timeout_$like_pattern'"; + $wpdb->query($sql); + + add_action('admin_notices', function () { + echo '

All rate-limiting transients have been cleared.

'; + }); + } +}); + + +// Custom Hooks +// Hooks added to customize the text or logic: + +// slm_invalid_email_message: Customize the invalid email message. +// slm_success_message: Customize the success message. +// slm_no_license_message: Customize the "no license found" message. +// slm_license_email_message: Modify the email message body. +// slm_license_email_subject: Modify the email subject. +// slm_form_label: Change the form label text. +// slm_form_button_text: Change the form button text. + +class SLM_Forgot_License { + /** + * Initialize the class and hooks. + */ + public function __construct() { + // Register shortcode. + add_shortcode('slm_forgot_license', [$this, 'render_shortcode']); + } + + /** + * Render the shortcode. + * + * @return string + */ + public function render_shortcode() { + // Check if form is submitted. + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['slm_forgot_license_nonce'])) { + return $this->handle_form_submission(); + } + + // Output the form. + return $this->render_form(); + } + + /** + * Handle form submission. + * + * @return string + */ + protected function handle_form_submission() { + // Verify nonce for security. + if (!isset($_POST['slm_forgot_license_nonce']) || + !wp_verify_nonce($_POST['slm_forgot_license_nonce'], 'slm_forgot_license_action')) { + return '

Invalid request. Please try again.

'; + } + + // Sanitize and validate email. + $email = sanitize_email($_POST['slm_forgot_license_email']); + if (!is_email($email)) { + return apply_filters('slm_invalid_email_message', '

Invalid email address.

'); + } + + // Rate limiting: prevent repeated submissions from the same IP. + $ip = $_SERVER['REMOTE_ADDR']; + if ($this->is_rate_limited($ip)) { + return '

You are submitting requests too quickly. Please try again later.

'; + } + + // Retrieve licenses. + $licenses = SLM_Utility::get_licenses_by_email($email); + + if (!empty($licenses)) { + // Prepare and send the email. + $message = apply_filters('slm_license_email_message', $this->generate_email_message($licenses), $licenses); + wp_mail($email, apply_filters('slm_license_email_subject', 'Your Licenses'), $message); + + return apply_filters('slm_success_message', '

Your licenses have been sent to your email.

'); + } + + return apply_filters('slm_no_license_message', '

No licenses found for the provided email.

'); + } + + /** + * Check if the IP is rate-limited. + * + * @param string $ip + * @return bool + */ + protected function is_rate_limited($ip) { + // Allow administrators to bypass rate limiting and clear transients. + if (is_user_logged_in() && current_user_can('manage_options')) { + $this->clear_transients(); + return false; // Admins are not rate-limited. + } + + $key = 'slm_rate_limit_' . $ip; + $limit = 3; // Max submissions allowed per hour. + $time_frame = HOUR_IN_SECONDS; + + $attempts = get_transient($key); + + if ($attempts === false) { + set_transient($key, 1, $time_frame); + return false; + } + + if ($attempts >= $limit) { + return true; + } + + set_transient($key, $attempts + 1, $time_frame); + return false; + } + + /** + * Clear all relevant transients. + */ + protected function clear_transients() { + global $wpdb; + + // Search and delete all transients related to rate limiting. + $like_pattern = '%slm_rate_limit_%'; + $sql = "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_$like_pattern' OR option_name LIKE '_transient_timeout_$like_pattern'"; + $wpdb->query($sql); + } + + + + + /** + * Generate the email message. + * + * @param array $licenses + * @return string + */ + protected function generate_email_message($licenses) { + $message = "Here are your licenses:\n\n"; + + foreach ($licenses as $license) { + $message .= "License Key: {$license['license_key']}\n"; + // $message .= "Product: {$license['product_ref']}\n"; + $message .= "Status: {$license['lic_status']}\n\n"; + } + + return $message; + } + + /** + * Render the form HTML. + * + * @return string + */ + protected function render_form() { + ob_start(); + ?> +
+ + + + +
+ ' . __('You must be logged in to view your licenses.', 'slm-plus') . '

'; + } + + // Get the current user's email. + $current_user = wp_get_current_user(); + $email = $current_user->user_email; + + // Retrieve licenses for the user. + $licenses = $this->get_licenses_by_email($email); + + // Render the licenses table or a message if none are found. + if (empty($licenses)) { + return apply_filters('slm_no_license_message', '

' . __('No licenses found for your account.', 'slm-plus') . '

'); + } + + return $this->render_licenses_table($licenses); + } + + /** + * Retrieve licenses by email from the database. + * + * @param string $email + * @return array + */ + protected function get_licenses_by_email($email) { + global $wpdb; + + $table_name = $wpdb->prefix . 'lic_key_tbl'; // Update to match your actual table name. + $query = $wpdb->prepare( + "SELECT license_key, product_ref, lic_status, date_expiry, date_activated, max_allowed_domains, max_allowed_devices + FROM $table_name WHERE email = %s", + $email + ); + + return $wpdb->get_results($query, ARRAY_A); + } + + /** + * Render the licenses table HTML. + * + * @param array $licenses + * @return string + */ + protected function render_licenses_table($licenses) { + ob_start(); + ?> + + + + + + + + + + + + + + + + + + + + + + + + + +
+ prepare( + "SELECT license_key, product_ref, lic_status FROM $table_name WHERE email = %s", + $email + ); + + return $wpdb->get_results($query, ARRAY_A); + } + /** * Saves a backup of the plugin's database tables in a secure folder. */ diff --git a/public/assets/css/slm-blocks.css b/public/assets/css/slm-blocks.css new file mode 100644 index 0000000..bf7a5ef --- /dev/null +++ b/public/assets/css/slm-blocks.css @@ -0,0 +1,84 @@ +.wp-block-slm-forgot-license { + background-color: #f9f9f9; + border: 1px dashed #ddd; + padding: 10px; + text-align: center; + font-size: 14px; + font-style: italic; +} +/* Style for the block preview */ +.slm-forgot-license-preview { + padding: 20px; + background-color: #f9f9f9; + border: 1px solid #ddd; + border-radius: 4px; + max-width: 400px; + font-family: Arial, sans-serif; +} + +/* Vertically stacked form */ +.slm-forgot-license-form { + display: flex; + flex-direction: column; /* Stack items vertically */ + gap: 10px; /* Adds spacing between form elements */ + width: 100%; /* Ensure the form takes full width */ +} + +.slm-forgot-license-form label { + font-weight: bold; + margin-bottom: 5px; +} + +.slm-forgot-license-form input { + width: 100%; + padding: 10px; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; /* Ensures padding doesn't affect width */ +} + +.slm-forgot-license-form button { + padding: 10px; + background-color: #0073aa; + color: #fff; + border: none; + border-radius: 4px; + cursor: not-allowed; /* Indicate the button is disabled in preview */ + text-align: center; + font-size: 16px; +} + +/* Add hover styling for better preview (if enabled) */ +.slm-forgot-license-form button:hover { + background-color: #005f8d; +} + +/* Style for the List Licenses block preview */ +.slm-list-licenses-preview { + padding: 20px; + background-color: #f9f9f9; + border: 1px solid #ddd; + border-radius: 4px; + max-width: 400px; + font-family: Arial, sans-serif; +} + +.slm-list-licenses-preview-message { + font-weight: bold; + margin-bottom: 10px; +} + +.slm-list-licenses-placeholder { + list-style-type: none; + padding: 0; + margin: 0; +} + +.slm-list-licenses-placeholder li { + padding: 5px 0; + border-bottom: 1px solid #ddd; +} + +.slm-list-licenses-placeholder li:last-child { + border-bottom: none; +} diff --git a/public/assets/css/slm-front-end.css b/public/assets/css/slm-front-end.css index 0e26ff1..ce8a4b3 100644 --- a/public/assets/css/slm-front-end.css +++ b/public/assets/css/slm-front-end.css @@ -24,3 +24,30 @@ .slm-status-badge.status-expired { background-color: #6c757d; /* Gray */ } + +.slm-licenses-table { + width: 100%; + border-collapse: collapse; + margin: 20px 0; + font-size: 14px; + font-family: Arial, sans-serif; + text-align: left; +} + +.slm-licenses-table th, .slm-licenses-table td { + border: 1px solid #ddd; + padding: 10px; +} + +.slm-licenses-table th { + background-color: #f4f4f4; + font-weight: bold; +} + +.slm-licenses-table tr:nth-child(even) { + background-color: #f9f9f9; +} + +.slm-licenses-table tr:hover { + background-color: #f1f1f1; +} diff --git a/public/assets/js/slm-blocks.js b/public/assets/js/slm-blocks.js new file mode 100644 index 0000000..c27c138 --- /dev/null +++ b/public/assets/js/slm-blocks.js @@ -0,0 +1,97 @@ +const { registerBlockType } = wp.blocks; +const { createElement } = wp.element; +const { __ } = wp.i18n; + +// Register a custom block category. +wp.domReady(() => { + // Check if the category already exists. + const categories = wp.blocks.getCategories(); + + if (!categories.some((category) => category.slug === 'slm-plus')) { + // Add the custom category. + wp.blocks.setCategories([ + ...categories, + { + slug: 'slm-plus', + title: __('SLM Plus', 'slm-plus'), + icon: 'admin-network', // Dashicon or a custom icon. + }, + ]); + } +}); + + +// Register the block in the "SLM Plus" category. +registerBlockType('slm-plus/forgot-license', { + title: __('Forgot License', 'slm-plus'), + icon: 'lock', // Dashicon or custom icon for the block. + category: 'slm-plus', // Assign to the SLM Plus category. + attributes: {}, + + edit: () => { + // Create a more realistic preview for the editor. + return createElement( + 'div', + { className: 'slm-forgot-license-preview' }, + createElement( + 'form', + { className: 'slm-forgot-license-form' }, + createElement( + 'label', + { htmlFor: 'slm-email' }, + __('Enter your email address:', 'slm-plus') + ), + createElement('input', { + type: 'email', + id: 'slm-email', + placeholder: __('example@domain.com', 'slm-plus'), + disabled: true, // Disable interaction in the editor. + }), + createElement( + 'button', + { type: 'button', disabled: true }, + __('Retrieve License', 'slm-plus') + ) + ) + ); + }, + + save: () => { + // The saved output will still be the shortcode for frontend rendering. + return createElement('p', {}, '[slm_forgot_license]'); + }, +}); + + +// Register the "List Licenses" block. +registerBlockType('slm-plus/list-licenses', { + title: __('List Licenses', 'slm-plus'), + icon: 'list-view', // Dashicon for the block. + category: 'slm-plus', // Assign to the SLM Plus category. + attributes: {}, + + edit: () => { + return createElement( + 'div', + { className: 'slm-list-licenses-preview' }, + createElement( + 'p', + { className: 'slm-list-licenses-preview-message' }, + __('This block will display a list of licenses associated with the logged-in user.', 'slm-plus') + ), + createElement( + 'ul', + { className: 'slm-list-licenses-placeholder' }, + createElement('li', {}, __('License Key: ************', 'slm-plus')), + createElement('li', {}, __('Product: Example Product', 'slm-plus')), + createElement('li', {}, __('Status: Active', 'slm-plus')), + createElement('li', {}, __('Expiry Date: 2024-12-31', 'slm-plus')) + ) + ); + }, + + save: () => { + // Outputs the shortcode for rendering on the frontend. + return createElement('p', {}, '[slm_list_licenses]'); + }, +}); diff --git a/slm-plus.php b/slm-plus.php index dde9daf..7707ddc 100644 --- a/slm-plus.php +++ b/slm-plus.php @@ -1,7 +1,7 @@