first commit

This commit is contained in:
Yuri
2025-11-16 21:47:01 +01:00
commit 271960206c
8 changed files with 1681 additions and 0 deletions
@@ -0,0 +1,551 @@
<?php
/**
* Admin Settings
*
* Handles plugin settings page in WordPress admin
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
class WHMCS_Pricing_Admin {
/**
* Single instance
*/
private static $instance = null;
/**
* Get instance
*/
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_init', array($this, 'register_settings'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_styles'));
add_action('admin_post_whmcs_clear_cache', array($this, 'handle_clear_cache'));
}
/**
* Add admin menu
*/
public function add_admin_menu() {
add_options_page(
__('WHMCS Pricing Settings', 'whmcs-pricing'),
__('WHMCS Pricing', 'whmcs-pricing'),
'manage_options',
'whmcs-pricing',
array($this, 'render_settings_page')
);
}
/**
* Register settings
*/
public function register_settings() {
register_setting(
'whmcs_pricing_settings',
'whmcs_pricing_settings',
array($this, 'sanitize_settings')
);
// API Settings Section
add_settings_section(
'whmcs_pricing_api_section',
__('WHMCS API Configuration', 'whmcs-pricing'),
array($this, 'render_api_section'),
'whmcs-pricing'
);
add_settings_field(
'api_url',
__('WHMCS URL', 'whmcs-pricing'),
array($this, 'render_text_field'),
'whmcs-pricing',
'whmcs_pricing_api_section',
array(
'name' => 'api_url',
'placeholder' => 'https://your-whmcs-domain.com',
'description' => __('Your WHMCS installation URL (without /includes/api.php)', 'whmcs-pricing')
)
);
add_settings_field(
'api_identifier',
__('API Identifier', 'whmcs-pricing'),
array($this, 'render_text_field'),
'whmcs-pricing',
'whmcs_pricing_api_section',
array(
'name' => 'api_identifier',
'description' => __('Your WHMCS API identifier', 'whmcs-pricing')
)
);
add_settings_field(
'api_secret',
__('API Secret', 'whmcs-pricing'),
array($this, 'render_password_field'),
'whmcs-pricing',
'whmcs_pricing_api_section',
array(
'name' => 'api_secret',
'description' => __('Your WHMCS API secret', 'whmcs-pricing')
)
);
// Display Settings Section
add_settings_section(
'whmcs_pricing_display_section',
__('Display Settings', 'whmcs-pricing'),
array($this, 'render_display_section'),
'whmcs-pricing'
);
add_settings_field(
'default_currency',
__('Default Currency', 'whmcs-pricing'),
array($this, 'render_currency_field'),
'whmcs-pricing',
'whmcs_pricing_display_section',
array(
'name' => 'default_currency',
'description' => __('Default currency for pricing display', 'whmcs-pricing')
)
);
add_settings_field(
'show_decimals',
__('Show Decimals', 'whmcs-pricing'),
array($this, 'render_checkbox_field'),
'whmcs-pricing',
'whmcs_pricing_display_section',
array(
'name' => 'show_decimals',
'label' => __('Display prices with decimal places (e.g., €67.00 instead of €67)', 'whmcs-pricing')
)
);
// Performance Settings Section
add_settings_section(
'whmcs_pricing_performance_section',
__('Performance Settings', 'whmcs-pricing'),
array($this, 'render_performance_section'),
'whmcs-pricing'
);
add_settings_field(
'cache_duration',
__('Cache Duration', 'whmcs-pricing'),
array($this, 'render_number_field'),
'whmcs-pricing',
'whmcs_pricing_performance_section',
array(
'name' => 'cache_duration',
'min' => 300,
'max' => 86400,
'step' => 300,
'description' => __('How long to cache API responses (in seconds). Default: 3600 (1 hour)', 'whmcs-pricing')
)
);
// Advanced Settings Section
add_settings_section(
'whmcs_pricing_advanced_section',
__('Advanced Settings', 'whmcs-pricing'),
array($this, 'render_advanced_section'),
'whmcs-pricing'
);
add_settings_field(
'debug_mode',
__('Debug Mode', 'whmcs-pricing'),
array($this, 'render_checkbox_field'),
'whmcs-pricing',
'whmcs_pricing_advanced_section',
array(
'name' => 'debug_mode',
'label' => __('Enable debug logging and disable cache', 'whmcs-pricing')
)
);
}
/**
* Render settings page
*/
public function render_settings_page() {
if (!current_user_can('manage_options')) {
return;
}
// Show success message
if (isset($_GET['settings-updated'])) {
add_settings_error(
'whmcs_pricing_messages',
'whmcs_pricing_message',
__('Settings saved successfully.', 'whmcs-pricing'),
'updated'
);
}
if (isset($_GET['cache-cleared'])) {
add_settings_error(
'whmcs_pricing_messages',
'whmcs_pricing_message',
__('Cache cleared successfully.', 'whmcs-pricing'),
'updated'
);
}
settings_errors('whmcs_pricing_messages');
?>
<div class="wrap whmcs-pricing-admin">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<div class="whmcs-pricing-container">
<div class="whmcs-pricing-main">
<form action="options.php" method="post">
<?php
settings_fields('whmcs_pricing_settings');
do_settings_sections('whmcs-pricing');
submit_button(__('Save Settings', 'whmcs-pricing'));
?>
</form>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
<?php wp_nonce_field('whmcs_clear_cache', 'whmcs_cache_nonce'); ?>
<input type="hidden" name="action" value="whmcs_clear_cache">
<?php submit_button(__('Clear All Cache', 'whmcs-pricing'), 'secondary', 'clear_cache', false); ?>
</form>
</div>
<div class="whmcs-pricing-sidebar">
<div class="whmcs-pricing-box">
<h3 class="whmcs-toggle" style="cursor: pointer;">
<?php _e('Shortcode Usage', 'whmcs-pricing'); ?> <span style="float: right;">▲</span>
</h3>
<p class="whmcs-preview" style="margin: 10px 0; color: #666; font-size: 13px; display: none;">
<?php _e('Expand to view shortcode examples and parameters...', 'whmcs-pricing'); ?>
</p>
<div class="whmcs-toggle-content">
<p><?php _e('Use these shortcodes in your posts, pages, or Elementor:', 'whmcs-pricing'); ?></p>
<h4><?php _e('Basic Usage:', 'whmcs-pricing'); ?></h4>
<code>[whmcs_price product_id="123"]</code>
<h4><?php _e('With Billing Cycle:', 'whmcs-pricing'); ?></h4>
<code>[whmcs_price product_id="123" billing_cycle="monthly"]</code>
<h4><?php _e('With Currency:', 'whmcs-pricing'); ?></h4>
<code>[whmcs_price product_id="123" currency="EUR"]</code>
<h4><?php _e('Without Decimals:', 'whmcs-pricing'); ?></h4>
<code>[whmcs_price product_id="123" format="no_decimals"]</code>
<h4><?php _e('With Language:', 'whmcs-pricing'); ?></h4>
<code>[whmcs_price product_id="123" lang="dutch"]</code>
<h4><?php _e('Available Billing Cycles:', 'whmcs-pricing'); ?></h4>
<ul>
<li>monthly</li>
<li>quarterly</li>
<li>semiannually</li>
<li>annually</li>
<li>biennially</li>
<li>triennially</li>
</ul>
</div>
</div>
<div class="whmcs-pricing-box">
<h3 class="whmcs-toggle" style="cursor: pointer;">
<?php _e('CSS Styling Guide', 'whmcs-pricing'); ?> <span style="float: right;">▼</span>
</h3>
<p class="whmcs-preview" style="margin: 10px 0; color: #666; font-size: 13px;">
<?php _e('Expand to view CSS examples and styling options...', 'whmcs-pricing'); ?>
</p>
<div class="whmcs-toggle-content" style="display: none;">
<p><?php _e('Add custom CSS to style your prices. Go to Appearance > Customize > Additional CSS:', 'whmcs-pricing'); ?></p>
<h4><?php _e('HTML Structure:', 'whmcs-pricing'); ?></h4>
<code style="white-space: pre-wrap;">&lt;span class="whmcs-price"&gt;
&lt;span class="currency-symbol"&gt;€&lt;/span&gt;
&lt;span class="price-amount"&gt;67.00&lt;/span&gt;
&lt;span class="billing-cycle"&gt;per month&lt;/span&gt;
&lt;/span&gt;</code>
<h4><?php _e('Example CSS:', 'whmcs-pricing'); ?></h4>
<code style="white-space: pre-wrap;">.whmcs-price {
font-size: 24px;
font-weight: bold;
color: #2271b1;
}
.whmcs-price .price-amount {
font-size: 32px;
}
.whmcs-price .billing-cycle {
font-size: 14px;
color: #666;
}</code>
<h4><?php _e('Using Custom Class:', 'whmcs-pricing'); ?></h4>
<code>[whmcs_price product_id="123" class="featured"]</code>
<p style="margin-top: 8px; font-size: 13px;"><?php _e('Then style with: .whmcs-price.featured { }', 'whmcs-pricing'); ?></p>
</div>
</div>
<div class="whmcs-pricing-box">
<h3><?php _e('Plugin Information', 'whmcs-pricing'); ?></h3>
<p><strong><?php _e('Version:', 'whmcs-pricing'); ?></strong> <?php echo WHMCS_PRICING_VERSION; ?></p>
<p><strong><?php _e('Author:', 'whmcs-pricing'); ?></strong> <a href="https://arkhost.com" target="_blank" rel="noopener noreferrer">ArkHost</a></p>
<p><strong><?php _e('Sponsored by:', 'whmcs-pricing'); ?></strong> <a href="https://duplika.com" target="_blank" rel="noopener noreferrer">Duplika</a></p>
</div>
</div>
</div>
</div>
<script>
jQuery(document).ready(function($) {
$('.whmcs-toggle').on('click', function() {
var clickedBox = $(this).parent();
var clickedPreview = clickedBox.find('.whmcs-preview');
var clickedContent = clickedBox.find('.whmcs-toggle-content');
var clickedArrow = $(this).find('span');
// Close all other boxes first
$('.whmcs-pricing-box').not(clickedBox).each(function() {
var otherPreview = $(this).find('.whmcs-preview');
var otherContent = $(this).find('.whmcs-toggle-content');
var otherArrow = $(this).find('.whmcs-toggle span');
if (otherContent.is(':visible')) {
otherPreview.slideDown(200);
otherContent.slideUp(200);
otherArrow.html('▼');
}
});
// Toggle clicked box
clickedPreview.slideToggle(200);
clickedContent.slideToggle(200);
if (clickedContent.is(':visible')) {
clickedArrow.html('▲');
} else {
clickedArrow.html('▼');
}
});
});
</script>
<?php
}
/**
* Section callbacks
*/
public function render_api_section() {
echo '<p>' . esc_html__('Configure your WHMCS API credentials. You can generate API credentials in WHMCS Admin > System Settings > API Credentials.', 'whmcs-pricing') . '</p>';
}
public function render_display_section() {
echo '<p>' . esc_html__('Configure how prices are displayed on your website.', 'whmcs-pricing') . '</p>';
}
public function render_performance_section() {
echo '<p>' . esc_html__('Optimize plugin performance with caching.', 'whmcs-pricing') . '</p>';
}
public function render_advanced_section() {
echo '<p>' . esc_html__('Advanced options for troubleshooting and development.', 'whmcs-pricing') . '</p>';
}
/**
* Field rendering methods
*/
public function render_text_field($args) {
$settings = get_option('whmcs_pricing_settings', array());
$value = isset($settings[$args['name']]) ? $settings[$args['name']] : '';
$placeholder = isset($args['placeholder']) ? $args['placeholder'] : '';
?>
<input type="text"
name="whmcs_pricing_settings[<?php echo esc_attr($args['name']); ?>]"
value="<?php echo esc_attr($value); ?>"
placeholder="<?php echo esc_attr($placeholder); ?>"
class="regular-text">
<?php if (isset($args['description'])): ?>
<p class="description"><?php echo esc_html($args['description']); ?></p>
<?php endif; ?>
<?php
}
public function render_password_field($args) {
$settings = get_option('whmcs_pricing_settings', array());
$value = isset($settings[$args['name']]) ? $settings[$args['name']] : '';
?>
<input type="password"
name="whmcs_pricing_settings[<?php echo esc_attr($args['name']); ?>]"
value="<?php echo esc_attr($value); ?>"
class="regular-text">
<?php if (isset($args['description'])): ?>
<p class="description"><?php echo esc_html($args['description']); ?></p>
<?php endif; ?>
<?php
}
public function render_number_field($args) {
$settings = get_option('whmcs_pricing_settings', array());
$value = isset($settings[$args['name']]) ? $settings[$args['name']] : '';
$min = isset($args['min']) ? $args['min'] : 0;
$max = isset($args['max']) ? $args['max'] : 100000;
$step = isset($args['step']) ? $args['step'] : 1;
?>
<input type="number"
name="whmcs_pricing_settings[<?php echo esc_attr($args['name']); ?>]"
value="<?php echo esc_attr($value); ?>"
min="<?php echo esc_attr($min); ?>"
max="<?php echo esc_attr($max); ?>"
step="<?php echo esc_attr($step); ?>"
class="regular-text">
<?php if (isset($args['description'])): ?>
<p class="description"><?php echo esc_html($args['description']); ?></p>
<?php endif; ?>
<?php
}
public function render_checkbox_field($args) {
$settings = get_option('whmcs_pricing_settings', array());
$checked = isset($settings[$args['name']]) && $settings[$args['name']] ? 'checked' : '';
?>
<label>
<input type="checkbox"
name="whmcs_pricing_settings[<?php echo esc_attr($args['name']); ?>]"
value="1"
<?php echo $checked; ?>>
<?php echo esc_html($args['label']); ?>
</label>
<?php
}
public function render_currency_field($args) {
$settings = get_option('whmcs_pricing_settings', array());
$value = isset($settings[$args['name']]) ? $settings[$args['name']] : 'USD';
$currencies = array(
'USD' => 'USD - US Dollar ($)',
'EUR' => 'EUR - Euro (€)',
'GBP' => 'GBP - British Pound (£)',
'AUD' => 'AUD - Australian Dollar (A$)',
'CAD' => 'CAD - Canadian Dollar (C$)',
'JPY' => 'JPY - Japanese Yen (¥)',
'CNY' => 'CNY - Chinese Yuan (¥)',
'INR' => 'INR - Indian Rupee (₹)',
'BRL' => 'BRL - Brazilian Real (R$)',
'RUB' => 'RUB - Russian Ruble (₽)',
'CHF' => 'CHF - Swiss Franc (Fr)',
'SEK' => 'SEK - Swedish Krona (kr)',
'NOK' => 'NOK - Norwegian Krone (kr)',
'DKK' => 'DKK - Danish Krone (kr)',
'PLN' => 'PLN - Polish Zloty (zł)',
'THB' => 'THB - Thai Baht (฿)',
'MYR' => 'MYR - Malaysian Ringgit (RM)',
'SGD' => 'SGD - Singapore Dollar (S$)',
'NZD' => 'NZD - New Zealand Dollar (NZ$)',
'MXN' => 'MXN - Mexican Peso (Mex$)',
'ZAR' => 'ZAR - South African Rand (R)'
);
?>
<select name="whmcs_pricing_settings[<?php echo esc_attr($args['name']); ?>]" class="regular-text">
<?php foreach ($currencies as $code => $label): ?>
<option value="<?php echo esc_attr($code); ?>" <?php selected($value, $code); ?>>
<?php echo esc_html($label); ?>
</option>
<?php endforeach; ?>
</select>
<?php if (isset($args['description'])): ?>
<p class="description"><?php echo esc_html($args['description']); ?></p>
<?php endif; ?>
<?php
}
/**
* Sanitize settings
*/
public function sanitize_settings($input) {
$sanitized = array();
// Sanitize URL
if (isset($input['api_url'])) {
$sanitized['api_url'] = esc_url_raw($input['api_url']);
}
// Sanitize text fields
$text_fields = array('api_identifier', 'api_secret', 'default_currency');
foreach ($text_fields as $field) {
if (isset($input[$field])) {
$sanitized[$field] = sanitize_text_field($input[$field]);
}
}
// Sanitize number
if (isset($input['cache_duration'])) {
$sanitized['cache_duration'] = absint($input['cache_duration']);
}
// Sanitize checkboxes
$sanitized['show_decimals'] = isset($input['show_decimals']) ? true : false;
$sanitized['debug_mode'] = isset($input['debug_mode']) ? true : false;
return $sanitized;
}
/**
* Handle clear cache action
*/
public function handle_clear_cache() {
if (!current_user_can('manage_options')) {
wp_die(__('Unauthorized', 'whmcs-pricing'));
}
check_admin_referer('whmcs_clear_cache', 'whmcs_cache_nonce');
WHMCS_Pricing_API::clear_all_cache();
wp_redirect(add_query_arg(
array(
'page' => 'whmcs-pricing',
'cache-cleared' => 'true'
),
admin_url('options-general.php')
));
exit;
}
/**
* Enqueue admin styles
*/
public function enqueue_admin_styles($hook) {
if ('settings_page_whmcs-pricing' !== $hook) {
return;
}
wp_enqueue_style(
'whmcs-pricing-admin',
WHMCS_PRICING_PLUGIN_URL . 'assets/css/admin.css',
array(),
WHMCS_PRICING_VERSION
);
}
}
@@ -0,0 +1,316 @@
<?php
/**
* WHMCS API Handler
*
* Handles all communication with WHMCS API
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
class WHMCS_Pricing_API {
/**
* Get settings
*/
private static function get_settings() {
$defaults = array(
'api_url' => '',
'api_identifier' => '',
'api_secret' => '',
'cache_duration' => 3600,
'default_currency' => 'USD',
'show_decimals' => true,
'debug_mode' => false
);
$settings = get_option('whmcs_pricing_settings', $defaults);
return wp_parse_args($settings, $defaults);
}
/**
* Make API request to WHMCS
*/
private static function make_request($action, $params = array()) {
$settings = self::get_settings();
// Validate settings
if (empty($settings['api_url']) || empty($settings['api_identifier']) || empty($settings['api_secret'])) {
self::log_error('API credentials not configured');
return new WP_Error('missing_credentials', __('WHMCS API credentials not configured', 'whmcs-pricing'));
}
// Prepare API request
$api_url = rtrim($settings['api_url'], '/') . '/includes/api.php';
$post_data = array_merge(array(
'action' => $action,
'identifier' => $settings['api_identifier'],
'secret' => $settings['api_secret'],
'responsetype' => 'json'
), $params);
// Make request
$response = wp_remote_post($api_url, array(
'timeout' => 15,
'body' => $post_data,
'sslverify' => true
));
// Check for errors
if (is_wp_error($response)) {
self::log_error('API request failed: ' . $response->get_error_message());
return $response;
}
// Parse response
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if (json_last_error() !== JSON_ERROR_NONE) {
self::log_error('Invalid JSON response from WHMCS API');
return new WP_Error('invalid_response', __('Invalid response from WHMCS API', 'whmcs-pricing'));
}
// Check API result
if (isset($data['result']) && $data['result'] === 'error') {
$error_message = isset($data['message']) ? $data['message'] : __('Unknown API error', 'whmcs-pricing');
self::log_error('WHMCS API error: ' . $error_message);
return new WP_Error('api_error', $error_message);
}
return $data;
}
/**
* Get product details
*/
public static function get_product($product_id) {
$cache_key = 'whmcs_product_' . $product_id;
$cached_data = get_transient($cache_key);
if (false !== $cached_data && !self::is_debug_mode()) {
return $cached_data;
}
// Make API request
$response = self::make_request('GetProducts', array(
'pid' => $product_id
));
if (is_wp_error($response)) {
return $response;
}
// Extract product data
if (!isset($response['products']) || !isset($response['products']['product'])) {
self::log_error('Product not found: ' . $product_id);
return new WP_Error('product_not_found', __('Product not found', 'whmcs-pricing'));
}
$product_data = is_array($response['products']['product']) && isset($response['products']['product'][0])
? $response['products']['product'][0]
: $response['products']['product'];
// Debug log the product data structure
if (self::is_debug_mode()) {
self::log_error('Product data for ID ' . $product_id . ': ' . print_r($product_data, true));
}
// Cache the result
$settings = self::get_settings();
set_transient($cache_key, $product_data, absint($settings['cache_duration']));
return $product_data;
}
/**
* Get product price
*/
public static function get_price($product_id, $args = array()) {
$defaults = array(
'billing_cycle' => 'monthly',
'currency' => null,
'format' => 'default'
);
$args = wp_parse_args($args, $defaults);
$settings = self::get_settings();
// Use default currency if not specified
if (empty($args['currency'])) {
$args['currency'] = $settings['default_currency'];
}
// Get product data
$product = self::get_product($product_id);
if (is_wp_error($product)) {
return $product;
}
// Find pricing for the requested currency and billing cycle
$price = self::extract_price($product, $args['billing_cycle'], $args['currency']);
if (is_wp_error($price)) {
return $price;
}
// Format the price
return self::format_price($price, $args['currency'], $args['format']);
}
/**
* Extract price from product data
*/
private static function extract_price($product, $billing_cycle, $currency) {
// Normalize billing cycle
$cycle_map = array(
'monthly' => 'monthly',
'quarterly' => 'quarterly',
'semiannually' => 'semiannually',
'annually' => 'annually',
'biennially' => 'biennially',
'triennially' => 'triennially',
'month' => 'monthly',
'quarter' => 'quarterly',
'year' => 'annually',
'annual' => 'annually'
);
$billing_cycle = strtolower($billing_cycle);
$billing_cycle = isset($cycle_map[$billing_cycle]) ? $cycle_map[$billing_cycle] : $billing_cycle;
// Debug pricing structure
if (self::is_debug_mode()) {
self::log_error("Looking for price: currency={$currency}, cycle={$billing_cycle}");
if (isset($product['pricing'])) {
self::log_error('Available pricing: ' . print_r($product['pricing'], true));
}
}
// Look for pricing in product data
if (isset($product['pricing'][$currency][$billing_cycle])) {
$price = floatval($product['pricing'][$currency][$billing_cycle]);
// WHMCS uses -1.00 to indicate billing cycle not available
if ($price < 0) {
if (self::is_debug_mode()) {
self::log_error("Price is -1 (not available) for cycle: {$billing_cycle}");
}
return new WP_Error('price_not_available', __('This billing cycle is not available for this product', 'whmcs-pricing'));
}
if (self::is_debug_mode()) {
self::log_error("Found price: {$price}");
}
return $price;
}
// Try to find any available pricing and log it
if (self::is_debug_mode() && isset($product['pricing'])) {
self::log_error("Available currencies: " . implode(', ', array_keys($product['pricing'])));
if (isset($product['pricing'][$currency])) {
self::log_error("Available cycles for {$currency}: " . implode(', ', array_keys($product['pricing'][$currency])));
}
}
self::log_error("Price not found for product {$product['pid']}, cycle: {$billing_cycle}, currency: {$currency}");
return new WP_Error('price_not_found', __('Price not available for selected options', 'whmcs-pricing'));
}
/**
* Format price
*/
private static function format_price($price, $currency, $format) {
$settings = self::get_settings();
// Apply decimal formatting
if ($format === 'no_decimals' || !$settings['show_decimals']) {
$price = round($price);
$formatted_price = number_format($price, 0);
} else {
$formatted_price = number_format($price, 2);
}
// Get currency symbol
$currency_symbol = self::get_currency_symbol($currency);
return array(
'raw' => $price,
'formatted' => $formatted_price,
'currency' => $currency,
'symbol' => $currency_symbol
);
}
/**
* Get currency symbol
*/
private static function get_currency_symbol($currency) {
$symbols = array(
'USD' => '$',
'EUR' => '€',
'GBP' => '£',
'AUD' => 'A$',
'CAD' => 'C$',
'JPY' => '¥',
'CNY' => '¥',
'INR' => '₹',
'BRL' => 'R$',
'RUB' => '₽',
'CHF' => 'Fr',
'SEK' => 'kr',
'NOK' => 'kr',
'DKK' => 'kr',
'PLN' => 'zł',
'THB' => '฿',
'MYR' => 'RM',
'SGD' => 'S$',
'NZD' => 'NZ$',
'MXN' => 'Mex$',
'ZAR' => 'R'
);
return isset($symbols[$currency]) ? $symbols[$currency] : $currency;
}
/**
* Clear cache for specific product
*/
public static function clear_cache($product_id) {
delete_transient('whmcs_product_' . $product_id);
}
/**
* Clear all cached products
*/
public static function clear_all_cache() {
global $wpdb;
$wpdb->query(
"DELETE FROM {$wpdb->options}
WHERE option_name LIKE '_transient_whmcs_product_%'
OR option_name LIKE '_transient_timeout_whmcs_product_%'"
);
}
/**
* Check if debug mode is enabled
*/
private static function is_debug_mode() {
$settings = self::get_settings();
return !empty($settings['debug_mode']);
}
/**
* Log error message
*/
private static function log_error($message) {
if (self::is_debug_mode() && function_exists('error_log')) {
error_log('[WHMCS Pricing] ' . $message);
}
}
}
@@ -0,0 +1,217 @@
<?php
/**
* Shortcode Handler
*
* Registers and processes shortcodes
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
class WHMCS_Pricing_Shortcodes {
/**
* Single instance
*/
private static $instance = null;
/**
* Translations cache
*/
private static $translations = null;
/**
* Get instance
*/
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
add_shortcode('whmcs_price', array($this, 'render_price_shortcode'));
}
/**
* Load translations from JSON file
*/
private static function load_translations() {
if (null !== self::$translations) {
return self::$translations;
}
$json_file = WHMCS_PRICING_PLUGIN_DIR . 'languages.json';
if (file_exists($json_file)) {
$json_content = file_get_contents($json_file);
self::$translations = json_decode($json_content, true);
}
if (!is_array(self::$translations)) {
self::$translations = array();
}
return self::$translations;
}
/**
* Render price shortcode
*
* @param array $atts Shortcode attributes
* @return string HTML output
*/
public function render_price_shortcode($atts) {
// Parse attributes
$atts = shortcode_atts(array(
'product_id' => '',
'billing_cycle' => 'monthly',
'currency' => '',
'format' => 'default',
'show_cycle' => 'true',
'class' => '',
'lang' => 'english',
'language' => '' // Support both lang and language
), $atts, 'whmcs_price');
// Validate product ID
if (empty($atts['product_id'])) {
return $this->render_error(__('Product ID is required', 'whmcs-pricing'));
}
// Sanitize attributes
$product_id = absint($atts['product_id']);
$billing_cycle = sanitize_text_field($atts['billing_cycle']);
$currency = !empty($atts['currency']) ? sanitize_text_field($atts['currency']) : '';
$format = sanitize_text_field($atts['format']);
$show_cycle = filter_var($atts['show_cycle'], FILTER_VALIDATE_BOOLEAN);
$custom_class = sanitize_html_class($atts['class']);
// Support both 'lang' and 'language' parameters
$lang = !empty($atts['language']) ? sanitize_text_field($atts['language']) : sanitize_text_field($atts['lang']);
// Get price from API
$price_data = WHMCS_Pricing_API::get_price($product_id, array(
'billing_cycle' => $billing_cycle,
'currency' => $currency,
'format' => $format
));
// Check for errors
if (is_wp_error($price_data)) {
return $this->render_error($price_data->get_error_message());
}
// Render the price
return $this->render_price_html($price_data, $billing_cycle, $product_id, $show_cycle, $custom_class, $lang);
}
/**
* Render price HTML
*
* @param array $price_data Price data from API
* @param string $billing_cycle Billing cycle
* @param int $product_id Product ID
* @param bool $show_cycle Whether to show billing cycle
* @param string $custom_class Custom CSS class
* @param string $lang Language code
* @return string HTML output
*/
private function render_price_html($price_data, $billing_cycle, $product_id, $show_cycle, $custom_class, $lang = 'english') {
$classes = array('whmcs-price');
if (!empty($custom_class)) {
$classes[] = $custom_class;
}
$cycle_text = $show_cycle ? $this->get_cycle_text($billing_cycle, $lang) : '';
$html = sprintf(
'<span class="%s" data-product-id="%d" data-cycle="%s">',
esc_attr(implode(' ', $classes)),
esc_attr($product_id),
esc_attr($billing_cycle)
);
$html .= sprintf(
'<span class="currency-symbol">%s</span>',
esc_html($price_data['symbol'])
);
$html .= sprintf(
'<span class="price-amount">%s</span>',
esc_html($price_data['formatted'])
);
if (!empty($cycle_text)) {
$html .= sprintf(
'<span class="billing-cycle">%s</span>',
esc_html($cycle_text)
);
}
$html .= '</span>';
return $html;
}
/**
* Get billing cycle text
*
* @param string $cycle Billing cycle
* @param string $lang Language code
* @return string Human-readable cycle text
*/
private function get_cycle_text($cycle, $lang = 'english') {
$cycle = strtolower($cycle);
// Load translations
$translations = self::load_translations();
// Check if language exists, fallback to English
if (!isset($translations[$lang])) {
$lang = 'english';
}
// Get translated text
if (isset($translations[$lang][$cycle])) {
return ' ' . $translations[$lang][$cycle];
}
// Final fallback to English defaults
$defaults = array(
'monthly' => 'per month',
'quarterly' => 'per quarter',
'semiannually' => 'per 6 months',
'annually' => 'per year',
'biennially' => 'per 2 years',
'triennially' => 'per 3 years'
);
return isset($defaults[$cycle]) ? ' ' . $defaults[$cycle] : '';
}
/**
* Render error message
*
* @param string $message Error message
* @return string HTML output
*/
private function render_error($message) {
// Only show errors to administrators
if (!current_user_can('manage_options')) {
return '<span class="whmcs-price whmcs-price-error" style="display:none;"></span>';
}
return sprintf(
'<span class="whmcs-price whmcs-price-error" style="color: #dc3232; font-style: italic;">[WHMCS Pricing Error: %s]</span>',
esc_html($message)
);
}
}