auth_start(); break; case 'auth_check': $this->auth_check(); break; case 'logout': $this->logout(); break; case 'status': $this->status(); break; case 'variants': $this->variants(); break; case 'icons': $this->icons(); break; case 'track': $this->track(); break; case 'upload': $this->upload(); break; default: wp_send_json_error('Unknown action'); break; } } private function icons() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : ''; if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'lordicon_action' ) ) { wp_send_json_error('Security check failed.'); return; } $family = sanitize_text_field( wp_unslash( $_POST['family'] ?? '' ) ); $style = sanitize_text_field( wp_unslash( $_POST['style'] ?? '' ) ); $search = sanitize_text_field( wp_unslash( $_POST['search'] ?? '' ) ); $index = sanitize_text_field( wp_unslash( $_POST['index'] ?? '' ) ); $page = isset( $_POST['page'] ) ? intval( wp_unslash( $_POST['page'] ) ) : 1; $per_page = isset( $_POST['per_page'] ) ? intval( wp_unslash( $_POST['per_page'] ) ) : 100; $result = API::get_instance()->icons([ 'family' => $family, 'style' => $style, 'search' => $search, 'index' => $index, 'page' => $page, 'per_page' => $per_page, ]); if ( isset( $result['error'] ) ) { wp_send_json_error( $result['error'] ); } wp_send_json_success([ 'icons' => $result['data'] ?? [], 'params' => [ 'link' => $result['headers']['link'] ?? '', ], ]); } private function track() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : ''; if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'lordicon_action' ) ) { wp_send_json_error('Security check failed.'); return; } $family = sanitize_text_field( wp_unslash( $_POST['family'] ?? '' ) ); $style = sanitize_text_field( wp_unslash( $_POST['style'] ?? '' ) ); $index = isset( $_POST['index'] ) ? intval( wp_unslash( $_POST['index'] ) ) : 0; $params = [ 'family' => $family, 'style' => $style, 'index' => $index, ]; $result = API::get_instance()->track( $params ); if ( isset( $result['error'] ) ) { wp_send_json_error( $result['error'] ); } wp_send_json_success(); } private function status() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : ''; if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'lordicon_action' ) ) { wp_send_json_error('Security check failed.'); return; } $result = API::get_instance()->status(); if (isset($result['error'])) { wp_send_json_error($result['error']); } else { wp_send_json_success($result['data']); } } private function variants() { $cache_key = 'lordicon_variants'; $cached_result = get_transient($cache_key); if ($cached_result !== false) { wp_send_json_success($cached_result); return; } $result = API::get_instance()->variants(); if (isset($result['error'])) { wp_send_json_error($result['error']); } else { set_transient($cache_key, $result['data'], HOUR_IN_SECONDS); wp_send_json_success($result['data']); } } private function auth_start() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : ''; if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'lordicon_action' ) ) { wp_send_json_error('Security check failed.'); return; } $email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ); if ( empty( $email ) ) { wp_send_json_error( 'Email is required' ); return; } $result = API::get_instance()->auth_start( $email ); if ( isset( $result['error'] ) ) { wp_send_json_error( $result['error'] ); } wp_send_json_success( $result['data'] ?? [] ); } private function auth_check() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : ''; if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'lordicon_action' ) ) { wp_send_json_error('Security check failed.'); return; } $email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ); $code = sanitize_text_field( wp_unslash( $_POST['code'] ?? '' ) ); if ( empty( $email ) || empty( $code ) ) { wp_send_json_error( 'Email and code are required' ); return; } $result = API::get_instance()->auth_check( $email, $code ); if ( isset( $result['error'] ) ) { wp_send_json_error( $result['error'] ); } $data = $result['data'] ?? []; if ( isset( $data['token'] ) ) { $settings_json = get_option( 'lordicon_settings', '{}' ); $settings = json_decode( $settings_json ); if ( ! is_object( $settings ) ) { $settings = new \stdClass(); } $settings->token = $data['token']; update_option( 'lordicon_settings', wp_json_encode( $settings ) ); } wp_send_json_success(); } private function logout() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : ''; if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'lordicon_action' ) ) { wp_send_json_error('Security check failed.'); return; } delete_option('lordicon_settings'); wp_send_json_success(); } private function upload() { $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if (empty($nonce) || ! wp_verify_nonce($nonce, 'lordicon_action')) { wp_send_json_error('Security check failed.'); return; } // Check if the current user has permission to upload files if (!current_user_can('upload_files')) { wp_send_json_error('Insufficient permissions'); } // Sanitize input fields from POST to prevent malicious input $post_id = isset($_POST['post_id']) ? intval(wp_unslash($_POST['post_id'])) : 0; $family = sanitize_text_field(wp_unslash($_POST['family'] ?? '')); $style = sanitize_text_field(wp_unslash($_POST['style'] ?? '')); $index = sanitize_text_field(wp_unslash($_POST['index'] ?? '')); $name = sanitize_text_field(wp_unslash($_POST['name'] ?? '')); $hash = sanitize_text_field(wp_unslash($_POST['hash'] ?? '')); // Ensure required fields are provided before proceeding if (empty($family) || empty($style) || empty($index) || empty($name)) { wp_send_json_error('Family, style, index, and name are required'); } // Include WordPress media functions require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; $result = [ 'svgAttachmentId' => 0, 'jsonAttachmentId' => 0, ]; // Check existing attachments $existing_svg = $this->find_existing_attachment('svg', $family, $style, $index, $name, $post_id, $hash); $existing_json = $this->find_existing_attachment('json', $family, $style, $index, $name, $post_id); if ($existing_svg) $result['svgAttachmentId'] = $existing_svg->ID; if ($existing_json) $result['jsonAttachmentId'] = $existing_json->ID; // Handle SVG and JSON file uploads in a loop for cleaner code foreach ([ 'svg_file' => ['type' => 'svg', 'mime' => 'image/svg+xml'], 'json_file' => ['type' => 'json', 'mime' => 'application/json'] ] as $input_name => $props) { // Proceed only if a file is uploaded and there is no existing attachment if (isset($_FILES[$input_name], $_FILES[$input_name]['tmp_name']) && $result[$props['type'].'AttachmentId'] <= 0) { // Sanitize file name $filename = isset($_FILES[$input_name]['name']) ? sanitize_file_name(wp_unslash($_FILES[$input_name]['name'])) : ''; $_FILES[$input_name]['name'] = $filename; $tmp_name = isset($_FILES[$input_name]['tmp_name']) ? wp_normalize_path(sanitize_text_field($_FILES[$input_name]['tmp_name'])) : ''; if ( $tmp_name && isset($_FILES[$input_name]['error']) && $_FILES[$input_name]['error'] === UPLOAD_ERR_OK && is_uploaded_file($tmp_name) ) { // Verify file type and extension to match expected type $filetype = wp_check_filetype_and_ext($tmp_name, $filename); if ($filetype['ext'] !== $props['type'] || $filetype['type'] !== $props['mime']) { wp_send_json_error('Invalid ' . strtoupper($props['type']) . ' file type.'); } // Use WordPress media handling to store the uploaded file properly $attachment_id = media_handle_upload($input_name, $post_id); if (is_wp_error($attachment_id)) { wp_send_json_error($attachment_id->get_error_message()); } // Update post title for easier identification in Media Library $title = sprintf('%s-%s-%s-%s', $family, $style, $index, $name); wp_update_post(['ID' => $attachment_id, 'post_title' => $title]); // Store custom meta for later identification of uploaded icons foreach ([ '_lordicon_type' => $props['type'], '_lordicon_family' => $family, '_lordicon_style' => $style, '_lordicon_index' => $index, '_lordicon_name' => $name, '_lordicon_hash' => $hash, ] as $meta_key => $meta_value) { update_post_meta($attachment_id, $meta_key, $meta_value); } // Store attachment ID in result for response $result[$props['type'].'AttachmentId'] = $attachment_id; } } } wp_send_json_success($result); } private function find_existing_attachment($type, $family, $style, $index, $name, $post_id = 0, $hash = null) { $meta_query = array( array( 'key' => '_lordicon_type', 'value' => $type ), array( 'key' => '_lordicon_family', 'value' => $family ), array( 'key' => '_lordicon_style', 'value' => $style ), array( 'key' => '_lordicon_index', 'value' => $index ), array( 'key' => '_lordicon_name', 'value' => $name ) ); if (!empty($hash)) { $meta_query[] = array( 'key' => '_lordicon_hash', 'value' => $hash ); } // We use a meta query here to find attachments that match specific metadata fields. // Each Lordicon file (SVG or JSON) is stored as a WordPress attachment with custom meta // describing its type, family, style, index, name, and optionally hash. By querying // these meta fields, we can reliably locate the exact attachment corresponding to // the uploaded icon without relying solely on file names or post titles. $args = array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_parent' => $post_id, 'meta_query' => $meta_query ); $attachments = get_posts($args); return !empty($attachments) ? $attachments[0] : null; } }