initial commit
This commit is contained in:
parent
d066133bd4
commit
e6f71e3706
55 changed files with 11928 additions and 0 deletions
|
|
@ -0,0 +1,227 @@
|
|||
<?php
|
||||
/**
|
||||
* AJAX Handler for Maple Local Fonts.
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class MLF_Ajax_Handler
|
||||
*
|
||||
* Handles all AJAX requests for font download and deletion.
|
||||
*/
|
||||
class MLF_Ajax_Handler {
|
||||
|
||||
/**
|
||||
* Rate limiter instance.
|
||||
*
|
||||
* @var MLF_Rate_Limiter
|
||||
*/
|
||||
private $rate_limiter;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action('wp_ajax_mlf_download_font', [$this, 'handle_download']);
|
||||
add_action('wp_ajax_mlf_delete_font', [$this, 'handle_delete']);
|
||||
// NEVER add wp_ajax_nopriv_ - admin only functionality
|
||||
|
||||
// Initialize rate limiter: 10 requests per minute
|
||||
$this->rate_limiter = new MLF_Rate_Limiter(10, 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle font download AJAX request.
|
||||
*/
|
||||
public function handle_download() {
|
||||
// 1. NONCE CHECK
|
||||
if (!check_ajax_referer('mlf_download_font', 'nonce', false)) {
|
||||
wp_send_json_error(['message' => __('Security check failed.', 'maple-local-fonts')], 403);
|
||||
}
|
||||
|
||||
// 2. CAPABILITY CHECK
|
||||
$capability = function_exists('mlf_get_capability') ? mlf_get_capability() : 'edit_theme_options';
|
||||
if (!current_user_can($capability)) {
|
||||
wp_send_json_error(['message' => __('Unauthorized.', 'maple-local-fonts')], 403);
|
||||
}
|
||||
|
||||
// 3. RATE LIMIT CHECK
|
||||
if (!$this->rate_limiter->check_and_record('download')) {
|
||||
wp_send_json_error([
|
||||
'message' => __('Too many requests. Please wait a moment and try again.', 'maple-local-fonts'),
|
||||
], 429);
|
||||
}
|
||||
|
||||
// 4. INPUT VALIDATION
|
||||
// Validate font name
|
||||
$font_name = isset($_POST['font_name']) ? sanitize_text_field(wp_unslash($_POST['font_name'])) : '';
|
||||
|
||||
if (empty($font_name)) {
|
||||
wp_send_json_error(['message' => __('Font name is required.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
// Strict allowlist pattern - alphanumeric, spaces, hyphens only
|
||||
if (!preg_match('/^[a-zA-Z0-9\s\-]+$/', $font_name)) {
|
||||
wp_send_json_error(['message' => __('Invalid font name: only letters, numbers, spaces, and hyphens allowed.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
if (strlen($font_name) > 100) {
|
||||
wp_send_json_error(['message' => __('Font name is too long.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
// Validate weights
|
||||
$weights = isset($_POST['weights']) ? array_map('absint', (array) $_POST['weights']) : [];
|
||||
$allowed_weights = [100, 200, 300, 400, 500, 600, 700, 800, 900];
|
||||
$weights = array_intersect($weights, $allowed_weights);
|
||||
|
||||
if (empty($weights)) {
|
||||
wp_send_json_error(['message' => __('At least one weight is required.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
if (count($weights) > MLF_MAX_WEIGHTS_PER_FONT) {
|
||||
wp_send_json_error(['message' => __('Too many weights selected.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
// Validate styles
|
||||
$styles = isset($_POST['styles']) ? (array) $_POST['styles'] : [];
|
||||
$allowed_styles = ['normal', 'italic'];
|
||||
// Sanitize each style value before filtering
|
||||
$styles = array_map('sanitize_text_field', $styles);
|
||||
$styles = array_filter($styles, function($style) use ($allowed_styles) {
|
||||
return in_array($style, $allowed_styles, true);
|
||||
});
|
||||
|
||||
if (empty($styles)) {
|
||||
wp_send_json_error(['message' => __('At least one style is required.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
// 5. PROCESS REQUEST
|
||||
try {
|
||||
$downloader = new MLF_Font_Downloader();
|
||||
$download_result = $downloader->download($font_name, $weights, $styles);
|
||||
|
||||
if (is_wp_error($download_result)) {
|
||||
wp_send_json_error(['message' => $this->get_user_error_message($download_result)]);
|
||||
}
|
||||
|
||||
// Register font with WordPress
|
||||
$registry = new MLF_Font_Registry();
|
||||
$result = $registry->register_font(
|
||||
$download_result['font_name'],
|
||||
$download_result['font_slug'],
|
||||
$download_result['files']
|
||||
);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
wp_send_json_error(['message' => $this->get_user_error_message($result)]);
|
||||
}
|
||||
|
||||
wp_send_json_success([
|
||||
'message' => sprintf(
|
||||
/* translators: %s: font name */
|
||||
__('Successfully installed %s.', 'maple-local-fonts'),
|
||||
esc_html($font_name)
|
||||
),
|
||||
'font_id' => $result,
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
// Sanitize exception message before logging (defense in depth)
|
||||
error_log('MLF Download Error: ' . sanitize_text_field($e->getMessage()));
|
||||
wp_send_json_error(['message' => __('An unexpected error occurred.', 'maple-local-fonts')]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle font deletion AJAX request.
|
||||
*/
|
||||
public function handle_delete() {
|
||||
// 1. NONCE CHECK
|
||||
if (!check_ajax_referer('mlf_delete_font', 'nonce', false)) {
|
||||
wp_send_json_error(['message' => __('Security check failed.', 'maple-local-fonts')], 403);
|
||||
}
|
||||
|
||||
// 2. CAPABILITY CHECK
|
||||
$capability = function_exists('mlf_get_capability') ? mlf_get_capability() : 'edit_theme_options';
|
||||
if (!current_user_can($capability)) {
|
||||
wp_send_json_error(['message' => __('Unauthorized.', 'maple-local-fonts')], 403);
|
||||
}
|
||||
|
||||
// 3. RATE LIMIT CHECK
|
||||
if (!$this->rate_limiter->check_and_record('delete')) {
|
||||
wp_send_json_error([
|
||||
'message' => __('Too many requests. Please wait a moment and try again.', 'maple-local-fonts'),
|
||||
], 429);
|
||||
}
|
||||
|
||||
// 4. INPUT VALIDATION
|
||||
$font_id = isset($_POST['font_id']) ? absint($_POST['font_id']) : 0;
|
||||
|
||||
if ($font_id < 1) {
|
||||
wp_send_json_error(['message' => __('Invalid font ID.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
// Verify font exists and is a font family
|
||||
$font = get_post($font_id);
|
||||
if (!$font || $font->post_type !== 'wp_font_family') {
|
||||
wp_send_json_error(['message' => __('Font not found.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
// Verify it's one we imported (not a theme font)
|
||||
if (get_post_meta($font_id, '_mlf_imported', true) !== '1') {
|
||||
wp_send_json_error(['message' => __('Cannot delete fonts not imported by this plugin.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
// 5. PROCESS REQUEST
|
||||
try {
|
||||
$registry = new MLF_Font_Registry();
|
||||
$result = $registry->delete_font($font_id);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
wp_send_json_error(['message' => $this->get_user_error_message($result)]);
|
||||
}
|
||||
|
||||
wp_send_json_success(['message' => __('Font deleted successfully.', 'maple-local-fonts')]);
|
||||
} catch (Exception $e) {
|
||||
// Sanitize exception message before logging (defense in depth)
|
||||
error_log('MLF Delete Error: ' . sanitize_text_field($e->getMessage()));
|
||||
wp_send_json_error(['message' => __('An unexpected error occurred.', 'maple-local-fonts')]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert internal error codes to user-friendly messages.
|
||||
*
|
||||
* @param WP_Error $error The error object.
|
||||
* @return string User-friendly message.
|
||||
*/
|
||||
private function get_user_error_message($error) {
|
||||
$code = $error->get_error_code();
|
||||
|
||||
$messages = [
|
||||
'font_not_found' => __('Font not found on Google Fonts. Please check the spelling and try again.', 'maple-local-fonts'),
|
||||
'font_exists' => __('This font is already installed.', 'maple-local-fonts'),
|
||||
'request_failed' => __('Could not connect to Google Fonts. Please check your internet connection and try again.', 'maple-local-fonts'),
|
||||
'http_error' => __('Google Fonts returned an error. Please try again later.', 'maple-local-fonts'),
|
||||
'parse_failed' => __('Could not process the font data. The font may not be available.', 'maple-local-fonts'),
|
||||
'download_failed' => __('Could not download the font files. Please try again.', 'maple-local-fonts'),
|
||||
'write_failed' => __('Could not save font files. Please check that wp-content/fonts is writable.', 'maple-local-fonts'),
|
||||
'mkdir_failed' => __('Could not create fonts directory. Please check file permissions.', 'maple-local-fonts'),
|
||||
'invalid_path' => __('Invalid file path.', 'maple-local-fonts'),
|
||||
'invalid_url' => __('Invalid font URL.', 'maple-local-fonts'),
|
||||
'invalid_name' => __('Invalid font name.', 'maple-local-fonts'),
|
||||
'invalid_weights' => __('No valid weights specified.', 'maple-local-fonts'),
|
||||
'invalid_styles' => __('No valid styles specified.', 'maple-local-fonts'),
|
||||
'not_found' => __('Font not found.', 'maple-local-fonts'),
|
||||
'not_ours' => __('Cannot delete fonts not imported by this plugin.', 'maple-local-fonts'),
|
||||
'response_too_large' => __('The font data is too large to process. Please try selecting fewer weights.', 'maple-local-fonts'),
|
||||
'file_too_large' => __('The font file is too large to download.', 'maple-local-fonts'),
|
||||
];
|
||||
|
||||
return $messages[$code] ?? __('An unexpected error occurred. Please try again.', 'maple-local-fonts');
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue