monorepo/native/wordpress/maple-icons-wp/includes/class-mi-ajax-handler.php
2026-02-02 14:17:16 -05:00

376 lines
12 KiB
PHP

<?php
/**
* AJAX Handler class - Handles all AJAX requests.
*
* @package MapleIcons
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class MI_Ajax_Handler
*
* Handles all AJAX requests for the plugin.
*/
class MI_Ajax_Handler {
/**
* Constructor - Register AJAX handlers.
*/
public function __construct() {
// Admin AJAX handlers.
add_action( 'wp_ajax_mi_download_set', array( $this, 'handle_download_set' ) );
add_action( 'wp_ajax_mi_delete_set', array( $this, 'handle_delete_set' ) );
add_action( 'wp_ajax_mi_set_active', array( $this, 'handle_set_active' ) );
add_action( 'wp_ajax_mi_get_progress', array( $this, 'handle_get_progress' ) );
// Block editor AJAX handlers.
add_action( 'wp_ajax_mi_search_icons', array( $this, 'handle_search_icons' ) );
add_action( 'wp_ajax_mi_get_icon_svg', array( $this, 'handle_get_icon_svg' ) );
// No nopriv handlers - all functionality requires login.
}
/**
* Handle download set request.
*/
public function handle_download_set() {
// 1. Nonce verification.
if ( ! check_ajax_referer( 'mi_admin_nonce', 'nonce', false ) ) {
wp_send_json_error(
array( 'message' => __( 'Security check failed.', 'maple-icons' ) ),
403
);
}
// 2. Capability check.
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error(
array( 'message' => __( 'You do not have permission to do this.', 'maple-icons' ) ),
403
);
}
// 3. Input validation.
$slug = isset( $_POST['slug'] ) ? sanitize_key( $_POST['slug'] ) : '';
if ( empty( $slug ) ) {
wp_send_json_error(
array( 'message' => __( 'Icon set slug is required.', 'maple-icons' ) )
);
}
if ( ! MI_Icon_Sets::is_valid_slug( $slug ) ) {
wp_send_json_error(
array( 'message' => __( 'Invalid icon set.', 'maple-icons' ) )
);
}
// Check if already downloaded.
$registry = MI_Icon_Registry::get_instance();
if ( $registry->is_downloaded( $slug ) ) {
wp_send_json_error(
array( 'message' => __( 'This icon set is already downloaded.', 'maple-icons' ) )
);
}
// 4. Process download.
// Increase time limit for large downloads.
set_time_limit( 600 ); // 10 minutes.
$downloader = new MI_Downloader();
$result = $downloader->download_set( $slug );
if ( is_wp_error( $result ) ) {
wp_send_json_error(
array( 'message' => $result->get_error_message() )
);
}
if ( ! $result['success'] ) {
wp_send_json_error(
array(
'message' => sprintf(
/* translators: %1$d: downloaded count, %2$d: failed count */
__( 'Download completed with errors. %1$d downloaded, %2$d failed.', 'maple-icons' ),
$result['downloaded'],
$result['failed']
),
'errors' => array_slice( $result['errors'], 0, 10 ), // Limit errors shown.
'downloaded' => $result['downloaded'],
'failed' => $result['failed'],
)
);
}
// Mark as downloaded.
$registry->mark_downloaded( $slug, $result['downloaded'] );
// If no active set, make this one active.
if ( ! $registry->get_active_set() ) {
$registry->set_active( $slug );
}
$registry->refresh();
wp_send_json_success(
array(
'message' => sprintf(
/* translators: %d: number of icons */
__( 'Successfully downloaded %d icons.', 'maple-icons' ),
$result['downloaded']
),
'icon_count' => $result['downloaded'],
'is_active' => $registry->get_active_set() === $slug,
)
);
}
/**
* Handle delete set request.
*/
public function handle_delete_set() {
// 1. Nonce verification.
if ( ! check_ajax_referer( 'mi_admin_nonce', 'nonce', false ) ) {
wp_send_json_error(
array( 'message' => __( 'Security check failed.', 'maple-icons' ) ),
403
);
}
// 2. Capability check.
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error(
array( 'message' => __( 'You do not have permission to do this.', 'maple-icons' ) ),
403
);
}
// 3. Input validation.
$slug = isset( $_POST['slug'] ) ? sanitize_key( $_POST['slug'] ) : '';
if ( empty( $slug ) ) {
wp_send_json_error(
array( 'message' => __( 'Icon set slug is required.', 'maple-icons' ) )
);
}
if ( ! MI_Icon_Sets::is_valid_slug( $slug ) ) {
wp_send_json_error(
array( 'message' => __( 'Invalid icon set.', 'maple-icons' ) )
);
}
// 4. Delete the set.
$registry = MI_Icon_Registry::get_instance();
$result = $registry->delete_set( $slug );
if ( is_wp_error( $result ) ) {
wp_send_json_error(
array( 'message' => $result->get_error_message() )
);
}
wp_send_json_success(
array(
'message' => __( 'Icon set deleted successfully.', 'maple-icons' ),
)
);
}
/**
* Handle set active request.
*/
public function handle_set_active() {
// 1. Nonce verification.
if ( ! check_ajax_referer( 'mi_admin_nonce', 'nonce', false ) ) {
wp_send_json_error(
array( 'message' => __( 'Security check failed.', 'maple-icons' ) ),
403
);
}
// 2. Capability check.
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error(
array( 'message' => __( 'You do not have permission to do this.', 'maple-icons' ) ),
403
);
}
// 3. Input validation.
$slug = isset( $_POST['slug'] ) ? sanitize_key( $_POST['slug'] ) : '';
// Empty slug is allowed (to deactivate).
if ( ! empty( $slug ) && ! MI_Icon_Sets::is_valid_slug( $slug ) ) {
wp_send_json_error(
array( 'message' => __( 'Invalid icon set.', 'maple-icons' ) )
);
}
// 4. Set active.
$registry = MI_Icon_Registry::get_instance();
$result = $registry->set_active( $slug );
if ( is_wp_error( $result ) ) {
wp_send_json_error(
array( 'message' => $result->get_error_message() )
);
}
wp_send_json_success(
array(
'message' => empty( $slug )
? __( 'No icon set is now active.', 'maple-icons' )
: __( 'Icon set activated.', 'maple-icons' ),
'active_set' => $slug,
)
);
}
/**
* Handle get progress request.
*/
public function handle_get_progress() {
// 1. Nonce verification.
if ( ! check_ajax_referer( 'mi_admin_nonce', 'nonce', false ) ) {
wp_send_json_error(
array( 'message' => __( 'Security check failed.', 'maple-icons' ) ),
403
);
}
// 2. Capability check.
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error(
array( 'message' => __( 'You do not have permission to do this.', 'maple-icons' ) ),
403
);
}
// 3. Input validation.
$slug = isset( $_POST['slug'] ) ? sanitize_key( $_POST['slug'] ) : '';
if ( empty( $slug ) || ! MI_Icon_Sets::is_valid_slug( $slug ) ) {
wp_send_json_error(
array( 'message' => __( 'Invalid icon set.', 'maple-icons' ) )
);
}
// 4. Get progress.
$downloader = new MI_Downloader();
$progress = $downloader->get_progress( $slug );
if ( false === $progress ) {
wp_send_json_success(
array(
'downloading' => false,
'completed' => 0,
'total' => 0,
)
);
}
wp_send_json_success(
array(
'downloading' => true,
'completed' => $progress['completed'],
'total' => $progress['total'],
'percentage' => $progress['total'] > 0
? round( ( $progress['completed'] / $progress['total'] ) * 100 )
: 0,
)
);
}
/**
* Handle search icons request (for block editor).
*/
public function handle_search_icons() {
// 1. Nonce verification.
if ( ! check_ajax_referer( 'mi_block_nonce', 'nonce', false ) ) {
wp_send_json_error(
array( 'message' => __( 'Security check failed.', 'maple-icons' ) ),
403
);
}
// 2. Capability check (edit_posts for block usage).
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error(
array( 'message' => __( 'You do not have permission to do this.', 'maple-icons' ) ),
403
);
}
// 3. Input validation.
$query = isset( $_POST['query'] ) ? sanitize_text_field( $_POST['query'] ) : '';
$style = isset( $_POST['style'] ) ? sanitize_key( $_POST['style'] ) : '';
$limit = isset( $_POST['limit'] ) ? absint( $_POST['limit'] ) : MI_SEARCH_LIMIT;
$offset = isset( $_POST['offset'] ) ? absint( $_POST['offset'] ) : 0;
// Limit the limit.
if ( $limit > 100 ) {
$limit = 100;
}
// 4. Search.
$registry = MI_Icon_Registry::get_instance();
$results = $registry->search_icons( $query, $style, $limit, $offset );
wp_send_json_success( $results );
}
/**
* Handle get icon SVG request (for block editor).
*/
public function handle_get_icon_svg() {
// 1. Nonce verification.
if ( ! check_ajax_referer( 'mi_block_nonce', 'nonce', false ) ) {
wp_send_json_error(
array( 'message' => __( 'Security check failed.', 'maple-icons' ) ),
403
);
}
// 2. Capability check.
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error(
array( 'message' => __( 'You do not have permission to do this.', 'maple-icons' ) ),
403
);
}
// 3. Input validation.
$slug = isset( $_POST['set'] ) ? sanitize_key( $_POST['set'] ) : '';
$style = isset( $_POST['style'] ) ? sanitize_key( $_POST['style'] ) : '';
$name = isset( $_POST['name'] ) ? sanitize_file_name( $_POST['name'] ) : '';
if ( empty( $slug ) || empty( $style ) || empty( $name ) ) {
wp_send_json_error(
array( 'message' => __( 'Missing required parameters.', 'maple-icons' ) )
);
}
// 4. Get SVG.
$registry = MI_Icon_Registry::get_instance();
$svg = $registry->get_icon_svg( $slug, $style, $name );
if ( is_wp_error( $svg ) ) {
wp_send_json_error(
array( 'message' => $svg->get_error_message() )
);
}
wp_send_json_success(
array(
'svg' => $svg,
'set' => $slug,
'style' => $style,
'name' => $name,
)
);
}
}