<?php
/*
Plugin Name: Secure Dynamic Handler
Description: Example of secure WordPress plugin practices
Version: 2.0
Author: Security Team
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// SECURE: WordPress plugin with proper security measures
class SecureDynamicPlugin {
private const PLUGIN_SLUG = 'secure-dynamic';
private const VERSION = '2.0.0';
private const REQUIRED_CAPABILITY = 'manage_options';
// Allowed mathematical operations
private const ALLOWED_OPERATIONS = ['+', '-', '*', '/', '%', '**'];
private const MAX_NUMBER = 999999;
// Allowed data operations
private const ALLOWED_DATA_OPERATIONS = [
'get_user_count',
'get_post_count',
'get_plugin_status',
'validate_settings'
];
public function __construct() {
add_action('init', [$this, 'init']);
add_action('wp_ajax_secure_calculate', [$this, 'handle_calculation']);
add_action('wp_ajax_secure_data_op', [$this, 'handle_data_operation']);
add_shortcode('secure_calc', [$this, 'calculator_shortcode']);
add_action('admin_menu', [$this, 'add_admin_menu']);
// Security hooks
register_activation_hook(__FILE__, [$this, 'activation_check']);
add_action('wp_loaded', [$this, 'security_init']);
}
public function init(): void {
// Load translations
load_plugin_textdomain(self::PLUGIN_SLUG, false,
dirname(plugin_basename(__FILE__)) . '/languages/');
// Enqueue admin scripts securely
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
}
public function security_init(): void {
// Security headers
if (is_admin()) {
add_action('admin_head', [$this, 'add_security_headers']);
}
}
public function activation_check(): void {
// Check WordPress version
if (version_compare(get_bloginfo('version'), '5.0', '<')) {
deactivate_plugins(plugin_basename(__FILE__));
wp_die(__('This plugin requires WordPress 5.0 or higher.', self::PLUGIN_SLUG));
}
// Check PHP version
if (version_compare(PHP_VERSION, '7.4', '<')) {
deactivate_plugins(plugin_basename(__FILE__));
wp_die(__('This plugin requires PHP 7.4 or higher.', self::PLUGIN_SLUG));
}
}
// SECURE: Safe calculation without eval()
public function handle_calculation(): void {
try {
// Security checks
$this->verify_ajax_security('secure_calc_nonce');
// Rate limiting
if (!$this->check_rate_limit('calculation')) {
wp_send_json_error(__('Too many requests. Please wait.', self::PLUGIN_SLUG));
}
// Get and validate inputs
$num1 = $this->validate_number($_POST['num1'] ?? '');
$operator = $this->validate_operator($_POST['operator'] ?? '');
$num2 = $this->validate_number($_POST['num2'] ?? '');
// Perform safe calculation
$result = $this->calculate_safely($num1, $operator, $num2);
wp_send_json_success([
'result' => $result,
'expression' => "$num1 $operator $num2 = $result"
]);
} catch (Exception $e) {
error_log(sprintf(
'Secure Plugin calculation error: %s',
$e->getMessage()
));
wp_send_json_error(__('Calculation failed.', self::PLUGIN_SLUG));
}
}
// SECURE: Allowlisted data operations
public function handle_data_operation(): void {
try {
// Security checks
$this->verify_ajax_security('secure_data_nonce');
if (!current_user_can(self::REQUIRED_CAPABILITY)) {
wp_send_json_error(__('Insufficient permissions.', self::PLUGIN_SLUG));
}
$operation = sanitize_key($_POST['operation'] ?? '');
if (!in_array($operation, self::ALLOWED_DATA_OPERATIONS, true)) {
wp_send_json_error(__('Operation not allowed.', self::PLUGIN_SLUG));
}
$result = $this->execute_data_operation($operation);
wp_send_json_success($result);
} catch (Exception $e) {
error_log(sprintf(
'Secure Plugin data operation error: %s',
$e->getMessage()
));
wp_send_json_error(__('Operation failed.', self::PLUGIN_SLUG));
}
}
// SECURE: Shortcode with validation
public function calculator_shortcode($atts): string {
$atts = shortcode_atts([
'num1' => '0',
'operator' => '+',
'num2' => '0'
], $atts, 'secure_calc');
try {
$num1 = $this->validate_number($atts['num1']);
$operator = $this->validate_operator($atts['operator']);
$num2 = $this->validate_number($atts['num2']);
$result = $this->calculate_safely($num1, $operator, $num2);
return sprintf(
'<span class="secure-calc-result">%s %s %s = %s</span>',
esc_html($num1),
esc_html($operator),
esc_html($num2),
esc_html($result)
);
} catch (Exception $e) {
return '<span class="secure-calc-error">' .
esc_html__('Invalid calculation parameters.', self::PLUGIN_SLUG) .
'</span>';
}
}
private function verify_ajax_security(string $nonce_action): void {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'] ?? '', $nonce_action)) {
wp_send_json_error(__('Security check failed.', self::PLUGIN_SLUG));
}
// Check referer
if (!wp_get_referer()) {
wp_send_json_error(__('Invalid request source.', self::PLUGIN_SLUG));
}
}
private function validate_number(string $input): float {
if (!is_numeric($input)) {
throw new InvalidArgumentException('Invalid number format');
}
$number = floatval($input);
if (abs($number) > self::MAX_NUMBER) {
throw new InvalidArgumentException('Number exceeds maximum allowed value');
}
if (!is_finite($number)) {
throw new InvalidArgumentException('Number must be finite');
}
return $number;
}
private function validate_operator(string $operator): string {
if (!in_array($operator, self::ALLOWED_OPERATIONS, true)) {
throw new InvalidArgumentException('Invalid operator');
}
return $operator;
}
private function calculate_safely(float $num1, string $operator, float $num2): float {
switch ($operator) {
case '+':
return $num1 + $num2;
case '-':
return $num1 - $num2;
case '*':
return $num1 * $num2;
case '/':
if ($num2 == 0) {
throw new InvalidArgumentException('Division by zero');
}
return $num1 / $num2;
case '%':
if ($num2 == 0) {
throw new InvalidArgumentException('Modulo by zero');
}
return fmod($num1, $num2);
case '**':
if ($num2 > 10) {
throw new InvalidArgumentException('Exponent too large');
}
return pow($num1, $num2);
default:
throw new InvalidArgumentException('Unknown operator');
}
}
private function execute_data_operation(string $operation): array {
switch ($operation) {
case 'get_user_count':
return ['count' => count_users()['total_users']];
case 'get_post_count':
return ['count' => wp_count_posts()->publish];
case 'get_plugin_status':
return [
'active' => is_plugin_active(plugin_basename(__FILE__)),
'version' => self::VERSION
];
case 'validate_settings':
$settings = get_option(self::PLUGIN_SLUG . '_settings', []);
return ['valid' => $this->validate_plugin_settings($settings)];
default:
throw new InvalidArgumentException('Unknown operation');
}
}
private function validate_plugin_settings(array $settings): bool {
// Implement settings validation logic
return true;
}
private function check_rate_limit(string $action): bool {
$user_id = get_current_user_id() ?: $_SERVER['REMOTE_ADDR'];
$transient_key = self::PLUGIN_SLUG . '_rate_' . $action . '_' . md5($user_id);
$count = get_transient($transient_key) ?: 0;
if ($count >= 20) { // 20 requests per minute
return false;
}
set_transient($transient_key, $count + 1, 60);
return true;
}
public function add_security_headers(): void {
echo '<meta http-equiv="X-Content-Type-Options" content="nosniff">' . "\n";
echo '<meta http-equiv="X-Frame-Options" content="DENY">' . "\n";
}
public function enqueue_admin_scripts($hook): void {
if (strpos($hook, self::PLUGIN_SLUG) === false) {
return;
}
wp_enqueue_script(
self::PLUGIN_SLUG . '-admin',
plugins_url('assets/admin.js', __FILE__),
['jquery'],
self::VERSION,
true
);
// Pass nonces to JavaScript
wp_localize_script(self::PLUGIN_SLUG . '-admin', 'securePlugin', [
'calcNonce' => wp_create_nonce('secure_calc_nonce'),
'dataNonce' => wp_create_nonce('secure_data_nonce'),
'ajaxUrl' => admin_url('admin-ajax.php')
]);
}
public function add_admin_menu(): void {
add_options_page(
__('Secure Dynamic Plugin', self::PLUGIN_SLUG),
__('Secure Plugin', self::PLUGIN_SLUG),
self::REQUIRED_CAPABILITY,
self::PLUGIN_SLUG,
[$this, 'admin_page']
);
}
public function admin_page(): void {
if (!current_user_can(self::REQUIRED_CAPABILITY)) {
wp_die(__('Insufficient permissions.', self::PLUGIN_SLUG));
}
include plugin_dir_path(__FILE__) . 'templates/admin-page.php';
}
}
// Initialize the plugin
new SecureDynamicPlugin();