'Insufficient permissions.' ), 403 ); } } /* ------------------------------------------------------------------ ADMIN MENÜ & ASSETS ------------------------------------------------------------------ */ public function add_settings_link( $links ) { $settings_link = 'Settings'; array_unshift( $links, $settings_link ); return $links; } public function create_menu() { add_menu_page( 'Erdo Image Optimizer', 'Image Optimizer', 'manage_options', 'erdo-image-optimizer', array( $this, 'render_page' ), 'dashicons-performance', 11 ); } public function assets( $hook ) { if ( 'toplevel_page_erdo-image-optimizer' !== $hook ) { return; } wp_enqueue_media(); $ver = ERDO_VERSION; wp_enqueue_style( 'erdo-css', ERDO_URL . 'assets/css/admin.css', array(), $ver ); wp_enqueue_script( 'erdo-js', ERDO_URL . 'assets/js/admin.js', array( 'jquery' ), $ver, true ); wp_localize_script( 'erdo-js', 'erdo_vars', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'erdo_nonce' ), ) ); } /* ------------------------------------------------------------------ ANA SAYFA HTML ------------------------------------------------------------------ */ public function render_page() { $format = strtoupper( get_option( 'erdo_convert_format', 'webp' ) ); ?>

v

—
0%

—
0%

—

—

—
verify_capability(); $paged = isset( $_POST['page'] ) ? intval( $_POST['page'] ) : 1; $search = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : ''; $query = new WP_Query( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => 'image', 'posts_per_page' => 100, 'paged' => $paged, 's' => $search, 'fields' => 'ids', ) ); $buckets = array( 'red' => '', 'orange' => '', 'green' => '', 'gray' => '' ); $counts = array( 'red' => 0, 'orange' => 0, 'green' => 0, 'gray' => 0 ); if ( ! empty( $query->posts ) ) { $results = ERDO_Auditor::bulk_analyze( $query->posts ); foreach ( $results as $id => $res ) { $cat = ( isset( $res['is_used'] ) && false === $res['is_used'] ) ? 'gray' : $res['status']; $counts[ $cat ]++; $format_label = strtoupper( str_replace( 'image/', '', $res['mime'] ?? '' ) ); $score_label = isset( $res['score'] ) ? intval( $res['score'] ) . '/6' : ''; // Alt text durumu bulk_analyze() checklist'inde zaten hesaplanmış — // "Missing Alt" filtresi bunu kart üzerinden okur, kart başına // ayrı bir AJAX isteği atmasına gerek kalmaz. $has_alt = true; foreach ( $res['checklist'] ?? array() as $item ) { if ( 'Alt Text' === $item['label'] ) { $has_alt = ! empty( $item['pass'] ); break; } } $html = '
'; $html .= '
'; $html .= ''; if ( $format_label ) { $html .= '' . esc_html( $format_label ) . ''; } $html .= '
'; $html .= ''; $html .= '
'; $buckets[ $cat ] .= $html; } } wp_send_json_success( array( 'html' => $buckets, 'counts' => $counts, 'max_pages' => $query->max_num_pages, 'found' => $query->found_posts, ) ); } /* ------------------------------------------------------------------ AJAX: Dashboard istatistikleri ------------------------------------------------------------------ */ public function ajax_get_stats() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); $stats_cache_key = 'erdo_stats_cache'; $cache_group = 'erdo_image_optimizer'; $cached = wp_cache_get( $stats_cache_key, $cache_group ); if ( false !== $cached ) { wp_send_json_success( $cached ); return; } global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $total = (int) $wpdb->get_var( "SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_type = 'attachment' AND post_mime_type LIKE 'image/%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $count_optimized = (int) $wpdb->get_var( "SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_type = 'attachment' AND post_mime_type IN ('image/webp','image/avif')" ); $seo_cache_key = 'erdo_count_seo'; $count_seo = wp_cache_get( $seo_cache_key, $cache_group ); if ( false === $count_seo ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $count_seo = (int) $wpdb->get_var( "SELECT COUNT(p.ID) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE p.post_type = 'attachment' AND pm.meta_key = '_wp_attachment_image_alt' AND pm.meta_value != ''" ); wp_cache_set( $seo_cache_key, $count_seo, $cache_group, 300 ); } $opt_score = ( $total > 0 ) ? round( ( $count_optimized / $total ) * 100 ) : 0; $seo_score = ( $total > 0 ) ? round( ( $count_seo / $total ) * 100 ) : 0; $saved = (int) get_option( 'erdo_total_saved_bytes', 0 ); $result = array( 'total' => $total, 'opt_score' => $opt_score, 'seo_score' => $seo_score, 'total_saved' => size_format( $saved, 2 ), ); wp_cache_set( $stats_cache_key, $result, $cache_group, 300 ); // library_version cache'e dahil edilmez — her istekte taze okunur $result['library_version'] = (int) get_option( 'erdo_library_version', 0 ); wp_send_json_success( $result ); } /* ------------------------------------------------------------------ AJAX: Görsel detay modali (HTML döndürür) ------------------------------------------------------------------ */ public function ajax_get_detail() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); if ( ! isset( $_POST['id'] ) ) { wp_send_json_error( array( 'message' => 'Missing image ID.' ) ); } $id = intval( $_POST['id'] ); $data = ERDO_Auditor::analyze( $id ); ob_start(); $help = array( 'Filename' => __( 'Search engines prefer descriptive file names with hyphens (-) as separators. Avoid underscores or spaces.', 'erdo-image-optimizer' ), 'Alt Text' => __( 'Alt text helps search engines understand the image and is read by screen readers for accessibility.', 'erdo-image-optimizer' ), 'Image Title' => __( 'A clean, readable title helps users and search engines. Avoid slug-style formatting.', 'erdo-image-optimizer' ), 'Dimensions' => __( 'Oversized images slow down your site. Resize to the maximum width your theme needs (usually 1920px or less).', 'erdo-image-optimizer' ), 'Format' => __( 'WebP and AVIF offer better compression than JPEG/PNG without quality loss, making your site faster.', 'erdo-image-optimizer' ), 'File Size' => __( 'Large images are the #1 cause of slow websites. Aim to keep images under 250 KB.', 'erdo-image-optimizer' ), ); $usage_msg = ( isset( $data['is_used'] ) && ! $data['is_used'] ) ? '
⚠ Unused Image
' : ''; $format = strtoupper( get_option( 'erdo_convert_format', 'webp' ) ); $show_comparison = false; if ( ! empty( $data['saved_bytes'] ) && ! empty( $data['original_size'] ) ) { $show_comparison = true; $orig_size_fmt = size_format( $data['original_size'], 1 ); $curr_size_fmt = size_format( $data['current_size'], 1 ); $saved_fmt = size_format( $data['saved_bytes'], 1 ); $saved_percent = ( $data['original_size'] > 0 ) ? round( ( $data['saved_bytes'] / $data['original_size'] ) * 100 ) : 0; $bar_curr = ( $data['original_size'] > 0 ) ? round( ( $data['current_size'] / $data['original_size'] ) * 100 ) . '%' : '100%'; } $score = $data['score'] ?? 0; $score_class = $score >= 5 ? 'score-high' : ( $score >= 3 ? 'score-mid' : 'score-low' ); ?>

  • i
    x
verify_capability(); $id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0; if ( ! $id ) { wp_send_json_error( array( 'message' => 'Invalid ID.' ) ); } ERDO_Auditor::save_image_data( $id, wp_unslash( $_POST ) ); $new_data = ERDO_Auditor::analyze( $id ); wp_send_json_success( $new_data ); } /* ------------------------------------------------------------------ AJAX: WebP/AVIF dönüşümü — artık bağımsız JSON yanıt döndürüyor. JS tarafı dönüşüm sonrası ayrı bir erdo_get_detail çağrısı yapar. ------------------------------------------------------------------ */ public function ajax_convert_webp() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); if ( ! isset( $_POST['id'] ) ) { wp_send_json_error( array( 'message' => 'Missing ID.' ) ); } $id = intval( wp_unslash( $_POST['id'] ) ); $result = ERDO_Auditor::convert_to_webp( $id ); if ( ERDO_Auditor::ALREADY_OPTIMIZED === $result ) { wp_send_json_error( array( 'message' => 'Image is already optimized.', 'already_optimized' => true, ) ); } elseif ( $result ) { wp_send_json_success( array( 'id' => $id ) ); } else { wp_send_json_error( array( 'message' => 'Conversion failed.' ) ); } } /* ------------------------------------------------------------------ AJAX: Toplu dönüşüm için ID listesi ------------------------------------------------------------------ */ public function ajax_get_bulk_list() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); $query = new WP_Query( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => array( 'image/jpeg', 'image/png' ), 'posts_per_page' => -1, 'fields' => 'ids', 'no_found_rows' => true, ) ); wp_send_json_success( $query->posts ); } /* ------------------------------------------------------------------ AJAX: Toplu dönüşüm - tek adım ------------------------------------------------------------------ */ public function ajax_bulk_step() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); if ( ! isset( $_POST['id'] ) ) { wp_send_json_error(); } $id = intval( wp_unslash( $_POST['id'] ) ); $res = ERDO_Auditor::convert_to_webp( $id ); if ( ERDO_Auditor::ALREADY_OPTIMIZED === $res || $res ) { wp_send_json_success(); } else { wp_send_json_error(); } } /* ------------------------------------------------------------------ AJAX: Kullanılmayan görsel listesi bulk_analyze() ile tüm ID'ler TEK sorguda analiz edilir. Eski kod: 500 görsel × 6 sorgu = 3000 sorgu → timeout Yeni kod: 500 görsel → 4-5 sorgu → ~saniyeler içinde ------------------------------------------------------------------ */ public function ajax_get_unused_list() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); $query = new WP_Query( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => 'image', 'posts_per_page' => -1, 'fields' => 'ids', 'no_found_rows' => true, ) ); if ( empty( $query->posts ) ) { wp_send_json_success( array() ); return; } // Tüm görselleri TEK seferde analiz et (toplu sorgu) $all_results = ERDO_Auditor::bulk_analyze( $query->posts ); $unused = array(); foreach ( $all_results as $id => $data ) { if ( isset( $data['is_used'] ) && ! $data['is_used'] ) { $unused[] = $id; } } wp_send_json_success( $unused ); } /* ------------------------------------------------------------------ AJAX: Görsel sil ------------------------------------------------------------------ */ public function ajax_delete_image() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); if ( ! isset( $_POST['id'] ) ) { wp_send_json_error(); } $id = intval( wp_unslash( $_POST['id'] ) ); ERDO_Auditor::delete_image( $id ); wp_send_json_success(); } /* ------------------------------------------------------------------ AJAX: Manuel yeniden boyutlandır ------------------------------------------------------------------ */ public function ajax_manual_resize() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); if ( ! isset( $_POST['id'], $_POST['w'], $_POST['h'] ) ) { wp_send_json_error(); } $id = intval( wp_unslash( $_POST['id'] ) ); $w = intval( wp_unslash( $_POST['w'] ) ); $h = intval( wp_unslash( $_POST['h'] ) ); $res = ERDO_Auditor::manual_resize( $id, $w, $h ); if ( $res ) { wp_send_json_success(); } else { wp_send_json_error(); } } /* ------------------------------------------------------------------ AJAX: Orijinal görseli yedeğinden geri yükle ------------------------------------------------------------------ */ public function ajax_restore_original() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); if ( ! isset( $_POST['id'] ) ) { wp_send_json_error( array( 'message' => 'Missing ID.' ) ); } $id = intval( wp_unslash( $_POST['id'] ) ); $result = ERDO_Auditor::restore_original( $id ); if ( $result ) { wp_send_json_success( array( 'id' => $id ) ); } else { wp_send_json_error( array( 'message' => 'No backup found for this image.' ) ); } } /* ------------------------------------------------------------------ AJAX: Tüm audit cache'i temizle ve yenile ------------------------------------------------------------------ */ public function ajax_refresh_all() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); ERDO_Auditor::clear_all_cache(); wp_send_json_success(); } /* ------------------------------------------------------------------ AJAX: Eksik featured image olan yazıları tara ------------------------------------------------------------------ */ public function ajax_scan_thumbnails() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); global $wpdb; $cache_key = 'erdo_missing_thumbnails'; $cache_group = 'erdo_image_optimizer'; $results = wp_cache_get( $cache_key, $cache_group ); if ( false === $results ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $results = $wpdb->get_results( "SELECT p.ID, p.post_title FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_thumbnail_id' WHERE p.post_type = 'post' AND p.post_status = 'publish' AND pm.post_id IS NULL LIMIT 50", ARRAY_A ); wp_cache_set( $cache_key, $results, $cache_group, 300 ); } $missing = array(); if ( ! empty( $results ) ) { foreach ( $results as $row ) { $missing[] = array( 'id' => (int) $row['ID'], 'title' => $row['post_title'], 'link' => get_edit_post_link( $row['ID'] ), ); } } wp_send_json_success( $missing ); } /* ------------------------------------------------------------------ AJAX: Ayarları kaydet ------------------------------------------------------------------ */ public function ajax_save_settings() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); update_option( 'erdo_auto_webp', isset( $_POST['erdo_auto_webp'] ) ? 1 : 0 ); update_option( 'erdo_auto_sanitize', isset( $_POST['erdo_auto_sanitize'] ) ? 1 : 0 ); update_option( 'erdo_remove_exif', isset( $_POST['erdo_remove_exif'] ) ? 1 : 0 ); update_option( 'erdo_lossless', isset( $_POST['erdo_lossless'] ) ? 1 : 0 ); update_option( 'erdo_keep_original', isset( $_POST['erdo_keep_original'] ) ? 1 : 0 ); update_option( 'erdo_serve_webp', isset( $_POST['erdo_serve_webp'] ) ? 1 : 0 ); update_option( 'erdo_lazy_loading', isset( $_POST['erdo_lazy_loading'] ) ? 1 : 0 ); update_option( 'erdo_smart_quality', isset( $_POST['erdo_smart_quality'] ) ? 1 : 0 ); update_option( 'erdo_allow_svg', isset( $_POST['erdo_allow_svg'] ) ? 1 : 0 ); update_option( 'erdo_append_original_filename', isset( $_POST['erdo_append_original_filename'] ) ? 1 : 0 ); if ( isset( $_POST['erdo_webp_serve_method'] ) ) { $meth = sanitize_text_field( wp_unslash( $_POST['erdo_webp_serve_method'] ) ); update_option( 'erdo_webp_serve_method', in_array( $meth, array( 'picture', 'htaccess' ), true ) ? $meth : 'picture' ); } if ( isset( $_POST['erdo_max_width'] ) ) { update_option( 'erdo_max_width', max( 0, intval( wp_unslash( $_POST['erdo_max_width'] ) ) ) ); } if ( isset( $_POST['erdo_convert_format'] ) ) { update_option( 'erdo_convert_format', sanitize_text_field( wp_unslash( $_POST['erdo_convert_format'] ) ) ); } if ( isset( $_POST['erdo_quality'] ) ) { $q = intval( wp_unslash( $_POST['erdo_quality'] ) ); $q = max( 10, min( 100, $q ) ); update_option( 'erdo_quality', $q ); } // BunnyCDN update_option( 'erdo_bunny_enabled', isset( $_POST['erdo_bunny_enabled'] ) ? 1 : 0 ); update_option( 'erdo_bunny_api_key', sanitize_text_field( wp_unslash( $_POST['erdo_bunny_api_key'] ?? '' ) ) ); update_option( 'erdo_bunny_storage_zone', sanitize_text_field( wp_unslash( $_POST['erdo_bunny_storage_zone'] ?? '' ) ) ); update_option( 'erdo_bunny_hostname', sanitize_text_field( wp_unslash( $_POST['erdo_bunny_hostname'] ?? '' ) ) ); wp_send_json_success(); } /* ------------------------------------------------------------------ AJAX: Ayarlar modali (HTML döndürür) ------------------------------------------------------------------ */ public function ajax_get_settings() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); $aw = get_option( 'erdo_auto_webp' ); $as = get_option( 'erdo_auto_sanitize' ); $re = get_option( 'erdo_remove_exif' ); $fmt = get_option( 'erdo_convert_format', 'webp' ); $q = get_option( 'erdo_quality', 80 ); $ll = get_option( 'erdo_lossless', 0 ); $ko = get_option( 'erdo_keep_original', 0 ); $aof = get_option( 'erdo_append_original_filename', 0 ); $avif = function_exists( 'imageavif' ); ?>

verify_capability(); $ids = get_posts( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => array( 'image/jpeg', 'image/png', 'image/gif' ), 'posts_per_page' => -1, 'fields' => 'ids', 'no_found_rows' => true, ) ); if ( empty( $ids ) ) { wp_send_json_error( array( 'message' => 'No convertible images found.' ) ); } ERDO_Queue::start( $ids, 'convert' ); wp_send_json_success( ERDO_Queue::get_status() ); } /* ------------------------------------------------------------------ AJAX: Kuyruğu iptal et ------------------------------------------------------------------ */ public function ajax_cancel_queue() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); ERDO_Queue::cancel(); wp_send_json_success( array( 'status' => 'cancelled' ) ); } /* ------------------------------------------------------------------ AJAX: Kuyruk durumunu sorgula ------------------------------------------------------------------ */ public function ajax_queue_status() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); wp_send_json_success( ERDO_Queue::get_status() ); } /* ------------------------------------------------------------------ AJAX: .htaccess kurallarını yaz ------------------------------------------------------------------ */ public function ajax_write_htaccess() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); ERDO_WebP_Server::write_htaccess_rules(); wp_send_json_success( array( 'active' => ERDO_WebP_Server::htaccess_has_rules() ) ); } /* ------------------------------------------------------------------ AJAX: .htaccess kurallarını kaldır ------------------------------------------------------------------ */ public function ajax_remove_htaccess() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); ERDO_WebP_Server::remove_htaccess_rules(); wp_send_json_success( array( 'active' => false ) ); } /* ------------------------------------------------------------------ AJAX: Tek görsel — dosya adını yeniden adlandır ------------------------------------------------------------------ */ public function ajax_rename_file() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); $id = isset( $_POST['id'] ) ? intval( $_POST['id'] ) : 0; if ( ! $id ) { wp_send_json_error( array( 'message' => 'Invalid ID.' ) ); } $result = ERDO_Auditor::rename_file( $id ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } $new_data = ERDO_Auditor::analyze( $id ); wp_send_json_success( array_merge( $result, array( 'audit' => $new_data ) ) ); } /* ------------------------------------------------------------------ AJAX: Toplu dosya adı yeniden adlandırma — ID listesi işle ------------------------------------------------------------------ */ public function ajax_bulk_rename() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); // ID yoksa tüm kütüphaneyi tara $ids = isset( $_POST['ids'] ) ? array_map( 'intval', (array) wp_unslash( $_POST['ids'] ) ) : array(); if ( empty( $ids ) ) { $ids = get_posts( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => 'image', 'posts_per_page' => -1, 'fields' => 'ids', 'no_found_rows' => true, ) ); } $renamed = 0; $skipped = 0; foreach ( $ids as $id ) { $result = ERDO_Auditor::rename_file( $id ); if ( is_wp_error( $result ) ) { $skipped++; } else { $renamed++; } } wp_send_json_success( array( 'renamed' => $renamed, 'skipped' => $skipped ) ); } /* ------------------------------------------------------------------ AJAX: SEO audit sonuçlarını CSV olarak indir ------------------------------------------------------------------ */ public function ajax_export_csv() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); $status_filter = isset( $_POST['status'] ) ? sanitize_text_field( wp_unslash( $_POST['status'] ) ) : ''; $rows = ERDO_Auditor::get_csv_rows( $status_filter ); $csv = "ID,Filename,Score,Status,Format,Alt Text,Title,Edit URL\n"; foreach ( $rows as $r ) { $csv .= implode( ',', array_map( function( $v ) { return '"' . str_replace( '"', '""', $v ) . '"'; }, $r ) ) . "\n"; } wp_send_json_success( array( 'csv' => $csv, 'count' => count( $rows ) ) ); } /* ------------------------------------------------------------------ AJAX: Seçili görseller için dosya adından alt text üret ------------------------------------------------------------------ */ public function ajax_bulk_alt_text() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); $ids = isset( $_POST['ids'] ) ? array_map( 'intval', (array) wp_unslash( $_POST['ids'] ) ) : array(); // ID verilmezse sadece alt text eksik olanları işle if ( empty( $ids ) ) { $ids = get_posts( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => 'image', 'posts_per_page' => -1, 'fields' => 'ids', 'no_found_rows' => true, 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query array( 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS', ), ), ) ); } $updated = 0; foreach ( $ids as $id ) { $alt = ERDO_Auditor::set_alt_from_filename( $id ); if ( $alt ) { $updated++; } } wp_send_json_success( array( 'updated' => $updated ) ); } /* ------------------------------------------------------------------ AJAX: BunnyCDN'e görsel yükle ------------------------------------------------------------------ */ public function ajax_cdn_upload() { check_ajax_referer( 'erdo_nonce', 'nonce' ); $this->verify_capability(); $id = isset( $_POST['id'] ) ? intval( $_POST['id'] ) : 0; if ( ! $id ) { wp_send_json_error( array( 'message' => 'Invalid ID.' ) ); } $result = $this->upload_to_bunny( $id ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'cdn_url' => $result ) ); } /* ------------------------------------------------------------------ BunnyCDN: Tek görsel yükle — CDN URL'sini döndürür ------------------------------------------------------------------ */ private function upload_to_bunny( $post_id ) { $api_key = get_option( 'erdo_bunny_api_key', '' ); $storage_zone = get_option( 'erdo_bunny_storage_zone', '' ); $cdn_hostname = get_option( 'erdo_bunny_hostname', '' ); if ( ! $api_key || ! $storage_zone ) { return new WP_Error( 'no_config', 'BunnyCDN API key or storage zone not configured.' ); } $file_path = get_attached_file( $post_id ); if ( ! $file_path || ! file_exists( $file_path ) ) { return new WP_Error( 'not_found', 'File not found.' ); } $upload_dir = wp_upload_dir(); $rel_path = ltrim( str_replace( $upload_dir['basedir'], '', $file_path ), '/' ); $cdn_path = $storage_zone . '/' . $rel_path; $endpoint = 'https://storage.bunnycdn.com/' . $cdn_path; // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $file_data = file_get_contents( $file_path ); if ( false === $file_data ) { return new WP_Error( 'read_failed', 'Could not read file.' ); } $response = wp_remote_request( $endpoint, array( 'method' => 'PUT', 'headers' => array( 'AccessKey' => $api_key, 'Content-Type' => 'application/octet-stream', ), 'body' => $file_data, 'timeout' => 60, ) ); if ( is_wp_error( $response ) ) { return $response; } $code = wp_remote_retrieve_response_code( $response ); if ( $code < 200 || $code >= 300 ) { return new WP_Error( 'upload_failed', 'BunnyCDN upload failed. HTTP ' . $code ); } $hostname = $cdn_hostname ?: ( $storage_zone . '.b-cdn.net' ); $cdn_url = 'https://' . trailingslashit( $hostname ) . $rel_path; update_post_meta( $post_id, '_erdo_cdn_url', $cdn_url ); return $cdn_url; } /* ------------------------------------------------------------------ DASHBOARD WIDGET ------------------------------------------------------------------ */ public function register_dashboard_widget() { wp_add_dashboard_widget( 'erdo_image_optimizer_widget', __( 'Image Optimizer', 'erdo-image-optimizer' ), array( $this, 'render_dashboard_widget' ) ); } public function render_dashboard_widget() { if ( ! current_user_can( 'manage_options' ) ) { return; } $cache_key = 'erdo_stats_cache'; $stats = wp_cache_get( $cache_key, 'erdo_image_optimizer' ); if ( false === $stats ) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $total = (int) $wpdb->get_var( "SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_type='attachment' AND post_mime_type LIKE 'image/%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $opt = (int) $wpdb->get_var( "SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_type='attachment' AND post_mime_type IN ('image/webp','image/avif')" ); $stats = array( 'total' => $total, 'optimized' => $opt, 'opt_score' => $total > 0 ? round( ( $opt / $total ) * 100 ) : 0, 'total_saved' => size_format( (int) get_option( 'erdo_total_saved_bytes', 0 ), 1 ), ); } $url = admin_url( 'admin.php?page=erdo-image-optimizer' ); ?>
%
WP_REST_Server::READABLE, 'callback' => array( $this, 'rest_stats' ), 'permission_callback' => array( $this, 'rest_permission' ), ) ); register_rest_route( 'erdo/v1', '/audit', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'rest_audit' ), 'permission_callback' => array( $this, 'rest_permission' ), 'args' => array( 'page' => array( 'default' => 1, 'sanitize_callback' => 'absint' ), 'status' => array( 'default' => '', 'sanitize_callback' => 'sanitize_text_field' ), ), ) ); } public function rest_permission() { return current_user_can( 'manage_options' ); } public function rest_stats( WP_REST_Request $request ) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $total = (int) $wpdb->get_var( "SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_type='attachment' AND post_mime_type LIKE 'image/%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $opt = (int) $wpdb->get_var( "SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_type='attachment' AND post_mime_type IN ('image/webp','image/avif')" ); return rest_ensure_response( array( 'total' => $total, 'optimized' => $opt, 'opt_rate' => $total > 0 ? round( ( $opt / $total ) * 100 ) : 0, 'total_saved' => (int) get_option( 'erdo_total_saved_bytes', 0 ), 'format' => get_option( 'erdo_convert_format', 'webp' ), ) ); } public function rest_audit( WP_REST_Request $request ) { $page = $request->get_param( 'page' ); $status_filter = $request->get_param( 'status' ); $per_page = 50; $query = new WP_Query( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => 'image', 'posts_per_page' => $per_page, 'paged' => $page, 'fields' => 'ids', ) ); $results = ERDO_Auditor::bulk_analyze( $query->posts ); $items = array(); foreach ( $results as $id => $r ) { $cat = ( isset( $r['is_used'] ) && false === $r['is_used'] ) ? 'unused' : $r['status']; if ( $status_filter && $status_filter !== $cat ) { continue; } $items[] = array( 'id' => $id, 'filename' => $r['filename'], 'score' => $r['score'], 'status' => $cat, 'mime' => $r['mime'] ?? '', 'is_used' => $r['is_used'], 'edit_url' => get_edit_post_link( $id, 'raw' ), ); } return rest_ensure_response( array( 'items' => $items, 'page' => $page, 'max_pages' => $query->max_num_pages, 'total' => $query->found_posts, ) ); } }