| |
| <?php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| define('CORTEX_IMAGE_CACHE_TTL', HOUR_IN_SECONDS);
|
| define('CORTEX_IMAGE_BULK_LIMIT', 50);
|
| define('CORTEX_IMAGE_PREVIEW_LIMIT', 25);
|
| define('CORTEX_AUDIT_SAMPLE_LIMIT', 50);
|
| define('CORTEX_LICENSE_PAGE', '/image-licensing/');
|
|
|
|
|
|
|
|
|
|
|
| add_action('rest_api_init', function() {
|
|
|
|
|
| register_rest_route('cortex/v1', '/images', array(
|
| 'methods' => 'GET',
|
| 'callback' => 'cortex_get_images',
|
| 'permission_callback' => 'cortex_image_permission_check',
|
| ));
|
|
|
|
|
| register_rest_route('cortex/v1', '/images/(?P<id>\d+)', array(
|
| 'methods' => 'GET',
|
| 'callback' => 'cortex_get_image',
|
| 'permission_callback' => 'cortex_image_permission_check',
|
| ));
|
|
|
|
|
| register_rest_route('cortex/v1', '/images/(?P<id>\d+)', array(
|
| 'methods' => 'POST',
|
| 'callback' => 'cortex_update_image',
|
| 'permission_callback' => 'cortex_image_permission_check',
|
| ));
|
|
|
|
|
| register_rest_route('cortex/v1', '/images/bulk', array(
|
| 'methods' => 'POST',
|
| 'callback' => 'cortex_bulk_update_images',
|
| 'permission_callback' => 'cortex_image_permission_check',
|
| ));
|
|
|
|
|
| register_rest_route('cortex/v1', '/images/audit', array(
|
| 'methods' => 'GET',
|
| 'callback' => 'cortex_audit_images',
|
| 'permission_callback' => 'cortex_image_permission_check',
|
| ));
|
|
|
|
|
| register_rest_route('cortex/v1', '/images/bulk-preview', array(
|
| 'methods' => 'POST',
|
| 'callback' => 'cortex_bulk_preview_images',
|
| 'permission_callback' => 'cortex_image_permission_check',
|
| ));
|
|
|
|
|
| register_rest_route('cortex/v1', '/images/bulk-get', array(
|
| 'methods' => 'POST',
|
| 'callback' => 'cortex_bulk_get_images',
|
| 'permission_callback' => 'cortex_image_permission_check',
|
| ));
|
|
|
|
|
| register_rest_route('cortex/v1', '/images/audit-url', array(
|
| 'methods' => 'GET',
|
| 'callback' => 'cortex_audit_images_by_url',
|
| 'permission_callback' => 'cortex_image_permission_check',
|
| ));
|
|
|
|
|
| register_rest_route('cortex/v1', '/images/audit-url', array(
|
| 'methods' => 'POST',
|
| 'callback' => 'cortex_audit_images_by_url_post',
|
| 'permission_callback' => 'cortex_image_permission_check',
|
| 'args' => array(
|
| 'url' => array(
|
| 'required' => true,
|
| 'type' => 'string',
|
| 'description' => 'The page URL to audit images for',
|
| 'sanitize_callback' => 'esc_url_raw',
|
| ),
|
| 'include_preview' => array(
|
| 'required' => false,
|
| 'type' => 'boolean',
|
| 'default' => false,
|
| ),
|
| 'preview_size' => array(
|
| 'required' => false,
|
| 'type' => 'string',
|
| 'default' => 'thumbnail',
|
| 'enum' => array('thumbnail', 'medium', 'large'),
|
| ),
|
| ),
|
| ));
|
| });
|
|
|
|
|
|
|
|
|
|
|
| function cortex_image_permission_check() {
|
| return current_user_can('edit_posts');
|
| }
|
|
|
|
|
|
|
|
|
|
|
| function cortex_image_cache_key($attachment_id) {
|
| return 'cortex_image_meta_' . $attachment_id;
|
| }
|
|
|
|
|
| if (is_admin()) {
|
| add_action('edit_attachment', function($attachment_id) {
|
| wp_cache_delete(cortex_image_cache_key($attachment_id));
|
| });
|
| }
|
|
|
|
|
|
|
|
|
| function cortex_get_in_use_image_ids() {
|
| global $wpdb;
|
|
|
| $cache_key = 'cortex_in_use_image_ids';
|
| $cached = wp_cache_get($cache_key);
|
| if ($cached !== false) {
|
| return $cached;
|
| }
|
|
|
| $all_ids = array();
|
|
|
|
|
| $featured_ids = $wpdb->get_col("
|
| SELECT DISTINCT pm.meta_value
|
| FROM {$wpdb->postmeta} pm
|
| INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID
|
| WHERE pm.meta_key = '_thumbnail_id'
|
| AND p.post_status = 'publish'
|
| AND pm.meta_value > 0
|
| ");
|
| $all_ids = array_merge($all_ids, array_map('intval', $featured_ids));
|
|
|
|
|
| $published_posts = $wpdb->get_results("
|
| SELECT ID, post_content
|
| FROM {$wpdb->posts}
|
| WHERE post_status = 'publish'
|
| AND post_content LIKE '%wp-image-%'
|
| ");
|
|
|
| foreach ($published_posts as $post) {
|
| if (preg_match_all('/wp-image-(\d+)/', $post->post_content, $matches)) {
|
| $all_ids = array_merge($all_ids, array_map('intval', $matches[1]));
|
| }
|
| }
|
|
|
|
|
| $parent_ids = $wpdb->get_col("
|
| SELECT a.ID
|
| FROM {$wpdb->posts} a
|
| INNER JOIN {$wpdb->posts} p ON a.post_parent = p.ID
|
| WHERE a.post_type = 'attachment'
|
| AND a.post_mime_type LIKE 'image/%'
|
| AND p.post_status = 'publish'
|
| ");
|
| $all_ids = array_merge($all_ids, array_map('intval', $parent_ids));
|
|
|
| $unique_ids = array_unique(array_filter($all_ids));
|
|
|
| if (!empty($unique_ids)) {
|
| $ids_string = implode(',', $unique_ids);
|
| $verified_ids = $wpdb->get_col("
|
| SELECT ID FROM {$wpdb->posts}
|
| WHERE ID IN ({$ids_string})
|
| AND post_type = 'attachment'
|
| AND post_mime_type LIKE 'image/%'
|
| ");
|
| $unique_ids = array_map('intval', $verified_ids);
|
| }
|
|
|
| wp_cache_set($cache_key, $unique_ids, '', 300);
|
|
|
| return $unique_ids;
|
| }
|
|
|
|
|
|
|
|
|
| function cortex_classify_image($attachment_id, $metadata = null) {
|
| $filename = basename(get_attached_file($attachment_id));
|
| $filename_lower = strtolower($filename);
|
|
|
| $ui_patterns = array('arrow', 'divider', 'icon', 'spacer', 'bullet', 'separator', 'border', 'shadow', 'bg-', 'background-');
|
| foreach ($ui_patterns as $pattern) {
|
| if (strpos($filename_lower, $pattern) !== false) return 'ui';
|
| }
|
|
|
| if ($metadata === null) {
|
| $metadata = wp_get_attachment_metadata($attachment_id);
|
| }
|
| if ($metadata && isset($metadata['width']) && isset($metadata['height'])) {
|
| if ($metadata['width'] < 100 || $metadata['height'] < 100) return 'ui';
|
| }
|
|
|
| global $wpdb;
|
| $is_featured = $wpdb->get_var($wpdb->prepare(
|
| "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_thumbnail_id' AND meta_value = %d",
|
| $attachment_id
|
| ));
|
| if ($is_featured > 0) return 'content';
|
|
|
| $in_content = $wpdb->get_var($wpdb->prepare(
|
| "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_content LIKE %s",
|
| '%wp-image-' . $attachment_id . '%'
|
| ));
|
| if ($in_content > 0) return 'content';
|
|
|
| return 'decorative';
|
| }
|
|
|
|
|
|
|
|
|
| function cortex_calculate_image_score($attachment_id, $alt_text, $title, $caption, $metadata, $parent_focus_keyword = '') {
|
| $score = 0;
|
| $issues = array();
|
| $filename = basename(get_attached_file($attachment_id));
|
| $mime_type = get_post_mime_type($attachment_id);
|
|
|
|
|
| if (!empty($alt_text)) {
|
| $score += 15;
|
| $alt_len = strlen($alt_text);
|
| if ($alt_len >= 25 && $alt_len <= 125) {
|
| $score += 10;
|
| } else {
|
| $issues[] = $alt_len < 25 ? 'alt_too_short' : 'alt_too_long';
|
| }
|
|
|
| if (!empty($parent_focus_keyword)) {
|
| $keywords = array_map('trim', explode(',', $parent_focus_keyword));
|
| $keyword_found = false;
|
| foreach ($keywords as $keyword) {
|
| if (!empty($keyword) && stripos($alt_text, $keyword) !== false) {
|
| $keyword_found = true;
|
| break;
|
| }
|
| }
|
| if ($keyword_found) {
|
| $score += 10;
|
| } else {
|
| $issues[] = 'alt_missing_keyword';
|
| }
|
| }
|
| } else {
|
| $issues[] = 'missing_alt';
|
| }
|
|
|
|
|
| if (!empty($title)) {
|
| $score += 10;
|
| if (!preg_match('/^(image|img|photo|pic|picture|untitled|dsc|dcim|\d+)/i', $title) && strlen($title) > 5) {
|
| $score += 5;
|
| } else {
|
| $issues[] = 'title_generic';
|
| }
|
| } else {
|
| $issues[] = 'missing_title';
|
| }
|
|
|
|
|
| if (!preg_match('/^(IMG_|DSC_|DCIM|Photo|Screenshot|Screen Shot|\d{4,})/i', $filename)) {
|
| $score += 15;
|
| } else {
|
| $issues[] = 'filename_generic';
|
| }
|
|
|
|
|
| if ($mime_type === 'image/webp') {
|
| $score += 10;
|
| } else {
|
| $issues[] = 'not_webp';
|
| }
|
|
|
|
|
| if ($metadata && isset($metadata['width']) && isset($metadata['height'])) {
|
| if ($metadata['width'] <= 2000 && $metadata['height'] <= 2000) {
|
| $score += 10;
|
| } else {
|
| $issues[] = 'oversized_dimensions';
|
| }
|
| }
|
|
|
|
|
| $file_path = get_attached_file($attachment_id);
|
| if ($file_path && file_exists($file_path)) {
|
| $file_size = filesize($file_path);
|
| $is_hero = $metadata && isset($metadata['width']) && $metadata['width'] >= 1200;
|
| $size_limit = $is_hero ? 500 * 1024 : 200 * 1024;
|
|
|
| if ($file_size <= $size_limit) {
|
| $score += 10;
|
| } else {
|
| $issues[] = 'file_too_large';
|
| }
|
| }
|
|
|
|
|
| if (!empty($caption)) {
|
| $score += 5;
|
| }
|
|
|
| return array(
|
| 'score' => min($score, 100),
|
| 'issues' => $issues
|
| );
|
| }
|
|
|
|
|
|
|
|
|
| function cortex_get_image_parent_posts($attachment_id) {
|
| global $wpdb;
|
| $parents = array();
|
|
|
|
|
| $featured_posts = $wpdb->get_results($wpdb->prepare(
|
| "SELECT p.ID, p.post_title, p.post_excerpt, p.post_type
|
| FROM {$wpdb->posts} p
|
| INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
|
| WHERE pm.meta_key = '_thumbnail_id' AND pm.meta_value = %d AND p.post_status = 'publish'",
|
| $attachment_id
|
| ));
|
|
|
| foreach ($featured_posts as $post) {
|
| $parents[] = array(
|
| 'id' => $post->ID,
|
| 'title' => $post->post_title,
|
| 'url' => get_permalink($post->ID),
|
| 'focus_keyword' => get_post_meta($post->ID, 'rank_math_focus_keyword', true),
|
| 'post_type' => $post->post_type,
|
| 'relationship' => 'featured_image'
|
| );
|
| }
|
|
|
|
|
| $content_posts = $wpdb->get_results($wpdb->prepare(
|
| "SELECT ID, post_title, post_excerpt, post_type
|
| FROM {$wpdb->posts}
|
| WHERE post_status = 'publish' AND post_content LIKE %s",
|
| '%wp-image-' . $attachment_id . '%'
|
| ));
|
|
|
| foreach ($content_posts as $post) {
|
| $exists = false;
|
| foreach ($parents as $p) {
|
| if ($p['id'] == $post->ID) { $exists = true; break; }
|
| }
|
| if ($exists) continue;
|
|
|
| $parents[] = array(
|
| 'id' => $post->ID,
|
| 'title' => $post->post_title,
|
| 'url' => get_permalink($post->ID),
|
| 'focus_keyword' => get_post_meta($post->ID, 'rank_math_focus_keyword', true),
|
| 'post_type' => $post->post_type,
|
| 'relationship' => 'in_content'
|
| );
|
| }
|
|
|
|
|
| $attachment = get_post($attachment_id);
|
| if ($attachment && $attachment->post_parent > 0) {
|
| $parent = get_post($attachment->post_parent);
|
| if ($parent && $parent->post_status === 'publish') {
|
| $exists = false;
|
| foreach ($parents as $p) {
|
| if ($p['id'] == $parent->ID) { $exists = true; break; }
|
| }
|
| if (!$exists) {
|
| $parents[] = array(
|
| 'id' => $parent->ID,
|
| 'title' => $parent->post_title,
|
| 'url' => get_permalink($parent->ID),
|
| 'focus_keyword' => get_post_meta($parent->ID, 'rank_math_focus_keyword', true),
|
| 'post_type' => $parent->post_type,
|
| 'relationship' => 'upload_parent'
|
| );
|
| }
|
| }
|
| }
|
|
|
| return $parents;
|
| }
|
|
|
|
|
|
|
|
|
| function cortex_build_image_data($attachment_id, $include_parents = true) {
|
| $cached = wp_cache_get(cortex_image_cache_key($attachment_id));
|
| if ($cached !== false && !$include_parents) return $cached;
|
|
|
| $attachment = get_post($attachment_id);
|
| if (!$attachment || $attachment->post_type !== 'attachment') return null;
|
| if (!wp_attachment_is_image($attachment_id)) return null;
|
|
|
| $metadata = wp_get_attachment_metadata($attachment_id);
|
| $alt_text = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
|
| $file_path = get_attached_file($attachment_id);
|
|
|
| $parent_posts = $include_parents ? cortex_get_image_parent_posts($attachment_id) : array();
|
| $primary_keyword = '';
|
| if (!empty($parent_posts) && !empty($parent_posts[0]['focus_keyword'])) {
|
| $primary_keyword = $parent_posts[0]['focus_keyword'];
|
| }
|
|
|
| $score_data = cortex_calculate_image_score(
|
| $attachment_id,
|
| $alt_text,
|
| $attachment->post_title,
|
| $attachment->post_excerpt,
|
| $metadata,
|
| $primary_keyword
|
| );
|
|
|
| $data = array(
|
| 'id' => $attachment_id,
|
| 'url' => wp_get_attachment_url($attachment_id),
|
| 'filename' => basename($file_path),
|
| 'alt_text' => $alt_text,
|
| 'title' => $attachment->post_title,
|
| 'caption' => $attachment->post_excerpt,
|
| 'description' => $attachment->post_content,
|
| 'dimensions' => array(
|
| 'width' => $metadata['width'] ?? 0,
|
| 'height' => $metadata['height'] ?? 0
|
| ),
|
| 'file_size' => file_exists($file_path) ? filesize($file_path) : 0,
|
| 'file_size_formatted' => file_exists($file_path) ? size_format(filesize($file_path)) : '0 B',
|
| 'mime_type' => get_post_mime_type($attachment_id),
|
| 'optimization_score' => $score_data['score'],
|
| 'issues' => $score_data['issues'],
|
| 'image_type' => cortex_classify_image($attachment_id, $metadata),
|
| 'parent_posts' => $parent_posts,
|
| 'date_uploaded' => $attachment->post_date,
|
| 'date_modified' => $attachment->post_modified
|
| );
|
|
|
| if (!$include_parents) {
|
| wp_cache_set(cortex_image_cache_key($attachment_id), $data, '', CORTEX_IMAGE_CACHE_TTL);
|
| }
|
|
|
| return $data;
|
| }
|
|
|
|
|
|
|
|
|
| function cortex_get_image_base64($attachment_id, $size = 'thumbnail') {
|
| $image_src = wp_get_attachment_image_src($attachment_id, $size);
|
|
|
| if (!$image_src) return null;
|
|
|
| $image_url = $image_src[0];
|
| $upload_dir = wp_upload_dir();
|
| $file_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $image_url);
|
|
|
| if (!file_exists($file_path)) {
|
| $file_path = get_attached_file($attachment_id);
|
| }
|
|
|
| if (!$file_path || !file_exists($file_path)) return null;
|
|
|
| $mime_type = get_post_mime_type($attachment_id);
|
| $image_data = file_get_contents($file_path);
|
| if ($image_data === false) return null;
|
|
|
| return 'data:' . $mime_type . ';base64,' . base64_encode($image_data);
|
| }
|
|
|
|
|
|
|
|
|
|
|
| function cortex_get_images($request) {
|
| $per_page = $request->get_param('per_page') ? min(intval($request->get_param('per_page')), 100) : 50;
|
| $page = $request->get_param('page') ? intval($request->get_param('page')) : 1;
|
| $in_use_only = $request->get_param('in_use_only');
|
| $min_score = $request->get_param('min_score');
|
| $max_score = $request->get_param('max_score');
|
| $image_type = $request->get_param('image_type');
|
| $has_issues = $request->get_param('has_issues');
|
|
|
| $filter_in_use = ($in_use_only !== 'false' && $in_use_only !== '0');
|
|
|
| if ($filter_in_use) {
|
| $in_use_ids = cortex_get_in_use_image_ids();
|
|
|
| if (empty($in_use_ids)) {
|
| return array(
|
| 'items' => array(),
|
| 'pagination' => array('total' => 0, 'per_page' => $per_page, 'current_page' => $page, 'total_pages' => 0),
|
| 'count' => 0
|
| );
|
| }
|
|
|
| $total = count($in_use_ids);
|
| $offset = ($page - 1) * $per_page;
|
| $paged_ids = array_slice($in_use_ids, $offset, $per_page);
|
|
|
| $images = array();
|
| foreach ($paged_ids as $attachment_id) {
|
| $image_data = cortex_build_image_data($attachment_id);
|
| if (!$image_data) continue;
|
|
|
| if ($min_score !== null && $image_data['optimization_score'] < intval($min_score)) continue;
|
| if ($max_score !== null && $image_data['optimization_score'] > intval($max_score)) continue;
|
| if ($image_type && $image_data['image_type'] !== $image_type) continue;
|
| if ($has_issues === 'true' && empty($image_data['issues'])) continue;
|
| if ($has_issues === 'false' && !empty($image_data['issues'])) continue;
|
|
|
| $images[] = $image_data;
|
| }
|
|
|
| return array(
|
| 'items' => $images,
|
| 'pagination' => array('total' => $total, 'per_page' => $per_page, 'current_page' => $page, 'total_pages' => ceil($total / $per_page)),
|
| 'count' => count($images),
|
| 'filter' => 'in_use_only'
|
| );
|
| }
|
|
|
| $args = array(
|
| 'post_type' => 'attachment',
|
| 'post_mime_type' => 'image',
|
| 'post_status' => 'inherit',
|
| 'posts_per_page' => $per_page,
|
| 'paged' => $page,
|
| 'orderby' => 'date',
|
| 'order' => 'DESC'
|
| );
|
|
|
| $query = new WP_Query($args);
|
| $images = array();
|
|
|
| foreach ($query->posts as $attachment) {
|
| $image_data = cortex_build_image_data($attachment->ID);
|
| if (!$image_data) continue;
|
|
|
| if ($min_score !== null && $image_data['optimization_score'] < intval($min_score)) continue;
|
| if ($max_score !== null && $image_data['optimization_score'] > intval($max_score)) continue;
|
| if ($image_type && $image_data['image_type'] !== $image_type) continue;
|
| if ($has_issues === 'true' && empty($image_data['issues'])) continue;
|
| if ($has_issues === 'false' && !empty($image_data['issues'])) continue;
|
|
|
| $images[] = $image_data;
|
| }
|
|
|
| return array(
|
| 'items' => $images,
|
| 'pagination' => array('total' => $query->found_posts, 'per_page' => $per_page, 'current_page' => $page, 'total_pages' => $query->max_num_pages),
|
| 'count' => count($images),
|
| 'filter' => 'all_images'
|
| );
|
| }
|
|
|
| function cortex_get_image($request) {
|
| $attachment_id = intval($request->get_param('id'));
|
| $image_data = cortex_build_image_data($attachment_id, true);
|
|
|
| if (!$image_data) {
|
| return new WP_Error('not_found', 'Image not found', array('status' => 404));
|
| }
|
|
|
| return $image_data;
|
| }
|
|
|
| function cortex_update_image($request) {
|
| $attachment_id = intval($request->get_param('id'));
|
| $attachment = get_post($attachment_id);
|
|
|
| if (!$attachment || $attachment->post_type !== 'attachment') {
|
| return new WP_Error('not_found', 'Image not found', array('status' => 404));
|
| }
|
|
|
| $updated = array();
|
| $post_updates = array('ID' => $attachment_id);
|
|
|
| $alt_text = $request->get_param('alt_text');
|
| if ($alt_text !== null) {
|
| update_post_meta($attachment_id, '_wp_attachment_image_alt', sanitize_text_field($alt_text));
|
| $updated['alt_text'] = $alt_text;
|
| }
|
|
|
| $title = $request->get_param('title');
|
| if ($title !== null) {
|
| $post_updates['post_title'] = sanitize_text_field($title);
|
| $updated['title'] = $title;
|
| }
|
|
|
| $caption = $request->get_param('caption');
|
| if ($caption !== null) {
|
| $post_updates['post_excerpt'] = sanitize_textarea_field($caption);
|
| $updated['caption'] = $caption;
|
| }
|
|
|
| $description = $request->get_param('description');
|
| if ($description !== null) {
|
| $post_updates['post_content'] = sanitize_textarea_field($description);
|
| $updated['description'] = $description;
|
| }
|
|
|
| if (count($post_updates) > 1) {
|
| wp_update_post($post_updates);
|
| }
|
|
|
| if (empty($updated)) {
|
| return new WP_Error('no_fields', 'No valid fields provided to update', array('status' => 400));
|
| }
|
|
|
| wp_cache_delete(cortex_image_cache_key($attachment_id));
|
|
|
| return array(
|
| 'success' => true,
|
| 'id' => $attachment_id,
|
| 'updated_fields' => $updated,
|
| 'fields_count' => count($updated),
|
| 'current_data' => cortex_build_image_data($attachment_id),
|
| 'message' => count($updated) . ' field(s) updated successfully'
|
| );
|
| }
|
|
|
| function cortex_bulk_update_images($request) {
|
| $items = $request->get_param('items');
|
|
|
| if (!$items || !is_array($items)) {
|
| return new WP_Error('invalid_payload', 'Expected "items" array in request body', array('status' => 400));
|
| }
|
|
|
| if (count($items) > CORTEX_IMAGE_BULK_LIMIT) {
|
| return new WP_Error('too_many_items', 'Maximum ' . CORTEX_IMAGE_BULK_LIMIT . ' items per bulk request', array('status' => 400));
|
| }
|
|
|
| $results = array(
|
| 'success' => array(),
|
| 'failed' => array(),
|
| 'summary' => array('total' => count($items), 'succeeded' => 0, 'failed' => 0, 'fields_updated' => 0)
|
| );
|
|
|
| foreach ($items as $item) {
|
| if (!isset($item['id'])) {
|
| $results['failed'][] = array('error' => 'Missing id field', 'item' => $item);
|
| $results['summary']['failed']++;
|
| continue;
|
| }
|
|
|
| $attachment_id = intval($item['id']);
|
| $attachment = get_post($attachment_id);
|
|
|
| if (!$attachment || $attachment->post_type !== 'attachment') {
|
| $results['failed'][] = array('id' => $attachment_id, 'error' => 'Image not found');
|
| $results['summary']['failed']++;
|
| continue;
|
| }
|
|
|
| $updated = array();
|
| $post_updates = array('ID' => $attachment_id);
|
|
|
| if (isset($item['alt_text'])) {
|
| update_post_meta($attachment_id, '_wp_attachment_image_alt', sanitize_text_field($item['alt_text']));
|
| $updated['alt_text'] = $item['alt_text'];
|
| }
|
|
|
| if (isset($item['title'])) {
|
| $post_updates['post_title'] = sanitize_text_field($item['title']);
|
| $updated['title'] = $item['title'];
|
| }
|
|
|
| if (isset($item['caption'])) {
|
| $post_updates['post_excerpt'] = sanitize_textarea_field($item['caption']);
|
| $updated['caption'] = $item['caption'];
|
| }
|
|
|
| if (isset($item['description'])) {
|
| $post_updates['post_content'] = sanitize_textarea_field($item['description']);
|
| $updated['description'] = $item['description'];
|
| }
|
|
|
| if (count($post_updates) > 1) {
|
| wp_update_post($post_updates);
|
| }
|
|
|
| wp_cache_delete(cortex_image_cache_key($attachment_id));
|
|
|
| $results['success'][] = array('id' => $attachment_id, 'updated_fields' => $updated, 'fields_count' => count($updated));
|
| $results['summary']['succeeded']++;
|
| $results['summary']['fields_updated'] += count($updated);
|
| }
|
|
|
| return $results;
|
| }
|
|
|
| function cortex_bulk_preview_images($request) {
|
| $ids = $request->get_param('ids');
|
| $size = $request->get_param('size') ?: 'thumbnail';
|
|
|
| if (!$ids || !is_array($ids)) {
|
| return new WP_Error('invalid_payload', 'Expected "ids" array in request body', array('status' => 400));
|
| }
|
|
|
| if (count($ids) > CORTEX_IMAGE_PREVIEW_LIMIT) {
|
| return new WP_Error('too_many_ids', 'Maximum ' . CORTEX_IMAGE_PREVIEW_LIMIT . ' images per bulk preview', array('status' => 400));
|
| }
|
|
|
| $valid_sizes = array('thumbnail', 'medium', 'large');
|
| if (!in_array($size, $valid_sizes)) $size = 'thumbnail';
|
|
|
| $images = array();
|
| $failed = array();
|
|
|
| foreach ($ids as $id) {
|
| $attachment_id = intval($id);
|
| $image_data = cortex_build_image_data($attachment_id, true);
|
|
|
| if (!$image_data) {
|
| $failed[] = array('id' => $attachment_id, 'error' => 'Image not found');
|
| continue;
|
| }
|
|
|
| $base64 = cortex_get_image_base64($attachment_id, $size);
|
|
|
| if (!$base64) {
|
| $failed[] = array('id' => $attachment_id, 'error' => 'Could not generate preview');
|
| continue;
|
| }
|
|
|
| $preview = array(
|
| 'id' => $attachment_id,
|
| 'preview' => $base64,
|
| 'preview_size' => $size,
|
| 'filename' => $image_data['filename'],
|
| 'current_alt' => $image_data['alt_text'],
|
| 'current_title' => $image_data['title'],
|
| 'dimensions' => $image_data['dimensions'],
|
| 'optimization_score' => $image_data['optimization_score'],
|
| 'issues' => $image_data['issues'],
|
| 'image_type' => $image_data['image_type'],
|
| 'parent_focus_keyword' => '',
|
| 'parent_post_title' => ''
|
| );
|
|
|
| if (!empty($image_data['parent_posts'])) {
|
| $primary_parent = $image_data['parent_posts'][0];
|
| $preview['parent_focus_keyword'] = $primary_parent['focus_keyword'] ?? '';
|
| $preview['parent_post_title'] = $primary_parent['title'] ?? '';
|
| }
|
|
|
| $images[] = $preview;
|
| }
|
|
|
| return array(
|
| 'images' => $images,
|
| 'failed' => $failed,
|
| 'count' => count($images),
|
| 'size' => $size,
|
| 'summary' => array('requested' => count($ids), 'returned' => count($images), 'failed' => count($failed))
|
| );
|
| }
|
|
|
| function cortex_bulk_get_images($request) {
|
| $ids = $request->get_param('ids');
|
|
|
| if (!$ids || !is_array($ids)) {
|
| return new WP_Error('invalid_payload', 'Expected "ids" array in request body', array('status' => 400));
|
| }
|
|
|
| if (count($ids) > CORTEX_IMAGE_BULK_LIMIT) {
|
| return new WP_Error('too_many_ids', 'Maximum ' . CORTEX_IMAGE_BULK_LIMIT . ' images per request', array('status' => 400));
|
| }
|
|
|
| $images = array();
|
| $not_found = array();
|
|
|
| foreach ($ids as $id) {
|
| $attachment_id = intval($id);
|
| $image_data = cortex_build_image_data($attachment_id, true);
|
|
|
| if ($image_data) {
|
| $images[] = $image_data;
|
| } else {
|
| $not_found[] = $attachment_id;
|
| }
|
| }
|
|
|
| return array(
|
| 'images' => $images,
|
| 'not_found' => $not_found,
|
| 'count' => count($images),
|
| 'summary' => array('requested' => count($ids), 'returned' => count($images), 'not_found' => count($not_found))
|
| );
|
| }
|
|
|
| function cortex_audit_images_by_url_core($url, $include_preview = false, $preview_size = 'thumbnail') {
|
| if (empty($url)) {
|
| return new WP_Error('missing_url', 'URL parameter is required', array('status' => 400));
|
| }
|
|
|
| $url = esc_url_raw($url);
|
| $post_id = url_to_postid($url);
|
|
|
| if (!$post_id) {
|
| $url_without_trailing = rtrim($url, '/');
|
| $post_id = url_to_postid($url_without_trailing);
|
| }
|
|
|
| if (!$post_id) {
|
| $url_with_trailing = trailingslashit($url);
|
| $post_id = url_to_postid($url_with_trailing);
|
| }
|
|
|
| if (!$post_id) {
|
| $path = parse_url($url, PHP_URL_PATH);
|
| $slug = basename(rtrim($path, '/'));
|
|
|
| if (!empty($slug)) {
|
| global $wpdb;
|
| $post_id = $wpdb->get_var($wpdb->prepare(
|
| "SELECT ID FROM {$wpdb->posts} WHERE post_name = %s AND post_status = 'publish' LIMIT 1",
|
| $slug
|
| ));
|
| }
|
| }
|
|
|
| if (!$post_id) {
|
| return new WP_Error('post_not_found', 'Could not find a post/page for this URL: ' . $url, array('status' => 404));
|
| }
|
|
|
| $post = get_post($post_id);
|
|
|
| if (!$post || $post->post_status !== 'publish') {
|
| return new WP_Error('post_not_found', 'Post not found or not published', array('status' => 404));
|
| }
|
|
|
| $image_ids = array();
|
| $image_relationships = array();
|
|
|
|
|
| $featured_id = get_post_thumbnail_id($post_id);
|
| if ($featured_id) {
|
| $image_ids[] = intval($featured_id);
|
| $image_relationships[intval($featured_id)] = 'featured_image';
|
| }
|
|
|
|
|
| if (!empty($post->post_content)) {
|
| if (preg_match_all('/wp-image-(\d+)/', $post->post_content, $matches)) {
|
| foreach ($matches[1] as $img_id) {
|
| $img_id = intval($img_id);
|
| if (!in_array($img_id, $image_ids)) {
|
| $image_ids[] = $img_id;
|
| $image_relationships[$img_id] = 'in_content';
|
| } else if ($image_relationships[$img_id] === 'featured_image') {
|
| $image_relationships[$img_id] = 'featured_and_content';
|
| }
|
| }
|
| }
|
| }
|
|
|
|
|
| $meta_fields_to_check = array('_elementor_data', '_wpb_shortcodes_custom_css');
|
| foreach ($meta_fields_to_check as $meta_key) {
|
| $meta_value = get_post_meta($post_id, $meta_key, true);
|
| if (!empty($meta_value) && is_string($meta_value)) {
|
| if (preg_match_all('/wp-image-(\d+)/', $meta_value, $matches)) {
|
| foreach ($matches[1] as $img_id) {
|
| $img_id = intval($img_id);
|
| if (!in_array($img_id, $image_ids)) {
|
| $image_ids[] = $img_id;
|
| $image_relationships[$img_id] = 'in_page_builder';
|
| }
|
| }
|
| }
|
| if (preg_match_all('/"id"\s*:\s*(\d+)/', $meta_value, $id_matches)) {
|
| foreach ($id_matches[1] as $potential_id) {
|
| $potential_id = intval($potential_id);
|
| if (wp_attachment_is_image($potential_id) && !in_array($potential_id, $image_ids)) {
|
| $image_ids[] = $potential_id;
|
| $image_relationships[$potential_id] = 'in_page_builder';
|
| }
|
| }
|
| }
|
| }
|
| }
|
|
|
| if (empty($image_ids)) {
|
| return array(
|
| 'url' => $url,
|
| 'post_id' => $post_id,
|
| 'post_title' => $post->post_title,
|
| 'post_type' => $post->post_type,
|
| 'focus_keyword' => get_post_meta($post_id, 'rank_math_focus_keyword', true),
|
| 'message' => 'No images found on this page',
|
| 'images' => array(),
|
| 'summary' => array('total_images' => 0, 'average_score' => 0, 'images_needing_attention' => 0)
|
| );
|
| }
|
|
|
| $images = array();
|
| $total_score = 0;
|
| $needs_attention = 0;
|
| $issue_counts = array();
|
|
|
| foreach ($image_ids as $attachment_id) {
|
| $image_data = cortex_build_image_data($attachment_id, false);
|
|
|
| if (!$image_data) {
|
| $images[] = array(
|
| 'id' => $attachment_id,
|
| 'error' => 'Image not found (may have been deleted)',
|
| 'relationship' => $image_relationships[$attachment_id] ?? 'unknown'
|
| );
|
| continue;
|
| }
|
|
|
| $image_data['relationship'] = $image_relationships[$attachment_id] ?? 'unknown';
|
|
|
| if ($include_preview) {
|
| $base64 = cortex_get_image_base64($attachment_id, $preview_size);
|
| if ($base64) {
|
| $image_data['preview'] = $base64;
|
| $image_data['preview_size'] = $preview_size;
|
| }
|
| }
|
|
|
| $total_score += $image_data['optimization_score'];
|
|
|
| if ($image_data['optimization_score'] < 70) {
|
| $needs_attention++;
|
| }
|
|
|
| foreach ($image_data['issues'] as $issue) {
|
| if (!isset($issue_counts[$issue])) $issue_counts[$issue] = 0;
|
| $issue_counts[$issue]++;
|
| }
|
|
|
| $images[] = $image_data;
|
| }
|
|
|
| arsort($issue_counts);
|
|
|
| $found_images = array_filter($images, function($img) { return !isset($img['error']); });
|
| $average_score = count($found_images) > 0 ? round($total_score / count($found_images), 1) : 0;
|
|
|
| return array(
|
| 'url' => $url,
|
| 'post_id' => $post_id,
|
| 'post_title' => $post->post_title,
|
| 'post_type' => $post->post_type,
|
| 'focus_keyword' => get_post_meta($post_id, 'rank_math_focus_keyword', true),
|
| 'images' => $images,
|
| 'summary' => array(
|
| 'total_images' => count($image_ids),
|
| 'images_found' => count($found_images),
|
| 'average_score' => $average_score,
|
| 'images_needing_attention' => $needs_attention,
|
| 'issue_counts' => $issue_counts
|
| ),
|
| 'image_ids' => $image_ids
|
| );
|
| }
|
|
|
| function cortex_audit_images_by_url($request) {
|
| $url = $request->get_param('url');
|
| $include_preview = $request->get_param('include_preview') === 'true';
|
| $preview_size = $request->get_param('preview_size') ?: 'thumbnail';
|
|
|
| return cortex_audit_images_by_url_core($url, $include_preview, $preview_size);
|
| }
|
|
|
| function cortex_audit_images_by_url_post($request) {
|
| $url = $request->get_param('url');
|
| $include_preview = $request->get_param('include_preview') === true || $request->get_param('include_preview') === 'true';
|
| $preview_size = $request->get_param('preview_size') ?: 'thumbnail';
|
|
|
| return cortex_audit_images_by_url_core($url, $include_preview, $preview_size);
|
| }
|
|
|
| function cortex_audit_images($request) {
|
| $sample_limit = $request->get_param('sample_limit')
|
| ? min(intval($request->get_param('sample_limit')), 100)
|
| : CORTEX_AUDIT_SAMPLE_LIMIT;
|
|
|
| $in_use_ids = cortex_get_in_use_image_ids();
|
|
|
| $audit = array(
|
| 'total_images' => count($in_use_ids),
|
| 'scope' => 'in_use_only',
|
| 'score_distribution' => array('excellent' => 0, 'good' => 0, 'needs_work' => 0, 'poor' => 0),
|
| 'type_distribution' => array('content' => 0, 'decorative' => 0, 'ui' => 0),
|
| 'issue_counts' => array(),
|
| 'average_score' => 0,
|
| 'images_needing_attention' => array(),
|
| 'format_distribution' => array(),
|
| 'total_file_size' => 0
|
| );
|
|
|
| if (empty($in_use_ids)) {
|
| $audit['message'] = 'No images found in use on published content';
|
| return $audit;
|
| }
|
|
|
| $total_score = 0;
|
| $needs_attention = array();
|
|
|
| foreach ($in_use_ids as $attachment_id) {
|
| $image_data = cortex_build_image_data($attachment_id, false);
|
| if (!$image_data) continue;
|
|
|
| $score = $image_data['optimization_score'];
|
| $total_score += $score;
|
|
|
| if ($score >= 90) $audit['score_distribution']['excellent']++;
|
| else if ($score >= 70) $audit['score_distribution']['good']++;
|
| else if ($score >= 50) $audit['score_distribution']['needs_work']++;
|
| else $audit['score_distribution']['poor']++;
|
|
|
| $type = $image_data['image_type'];
|
| if (isset($audit['type_distribution'][$type])) $audit['type_distribution'][$type]++;
|
|
|
| foreach ($image_data['issues'] as $issue) {
|
| if (!isset($audit['issue_counts'][$issue])) $audit['issue_counts'][$issue] = 0;
|
| $audit['issue_counts'][$issue]++;
|
| }
|
|
|
| $format = $image_data['mime_type'];
|
| if (!isset($audit['format_distribution'][$format])) $audit['format_distribution'][$format] = 0;
|
| $audit['format_distribution'][$format]++;
|
|
|
| $audit['total_file_size'] += $image_data['file_size'];
|
|
|
| if ($score < 70) {
|
| $needs_attention[] = array(
|
| 'id' => $attachment_id,
|
| 'url' => $image_data['url'],
|
| 'filename' => $image_data['filename'],
|
| 'score' => $score,
|
| 'issues' => $image_data['issues'],
|
| 'image_type' => $image_data['image_type']
|
| );
|
| }
|
| }
|
|
|
| usort($needs_attention, function($a, $b) { return $a['score'] - $b['score']; });
|
| $audit['images_needing_attention'] = array_slice($needs_attention, 0, $sample_limit);
|
| $audit['total_needing_attention'] = count($needs_attention);
|
|
|
| $audit['average_score'] = $audit['total_images'] > 0 ? round($total_score / $audit['total_images'], 1) : 0;
|
| $audit['total_file_size_formatted'] = size_format($audit['total_file_size']);
|
|
|
| arsort($audit['issue_counts']);
|
|
|
|
|
| $audit['recommendations'] = array();
|
|
|
| if (($audit['issue_counts']['missing_alt'] ?? 0) > 0) {
|
| $count = $audit['issue_counts']['missing_alt'];
|
| $audit['recommendations'][] = array(
|
| 'priority' => 'high',
|
| 'issue' => 'missing_alt',
|
| 'count' => $count,
|
| 'recommendation' => 'Add descriptive alt text (25-125 chars) with relevant keywords.'
|
| );
|
| }
|
|
|
| if (($audit['issue_counts']['not_webp'] ?? 0) > 0) {
|
| $count = $audit['issue_counts']['not_webp'];
|
| $audit['recommendations'][] = array(
|
| 'priority' => 'medium',
|
| 'issue' => 'not_webp',
|
| 'count' => $count,
|
| 'recommendation' => 'Convert to WebP format for better compression.'
|
| );
|
| }
|
|
|
| if (($audit['issue_counts']['file_too_large'] ?? 0) > 0) {
|
| $count = $audit['issue_counts']['file_too_large'];
|
| $audit['recommendations'][] = array(
|
| 'priority' => 'high',
|
| 'issue' => 'file_too_large',
|
| 'count' => $count,
|
| 'recommendation' => 'Compress images. Target <200KB standard, <500KB hero.'
|
| );
|
| }
|
|
|
| return $audit;
|
| }
|
|
|
|
|
|
|
|
|
|
|
| if (!is_admin() && !wp_doing_ajax() && (!defined('REST_REQUEST') || !REST_REQUEST)) {
|
| add_filter('rank_math/json_ld', 'cortex_enhance_image_schema', 99, 2);
|
| }
|
|
|
| function cortex_enhance_image_schema($data, $jsonld) {
|
| if (!is_singular()) return $data;
|
|
|
| $post_id = get_the_ID();
|
| $site_name = get_bloginfo('name');
|
| $site_url = get_bloginfo('url');
|
| $current_year = date('Y');
|
| $license_page = $site_url . CORTEX_LICENSE_PAGE;
|
|
|
| $creator = array(
|
| '@type' => 'Organization',
|
| 'name' => $site_name,
|
| 'url' => $site_url
|
| );
|
|
|
| if (isset($data['@graph']) && is_array($data['@graph'])) {
|
| foreach ($data['@graph'] as &$item) {
|
| if (isset($item['@type']) && $item['@type'] === 'ImageObject') {
|
| cortex_add_licensing_fields($item, $site_name, $current_year, $creator, $license_page);
|
| }
|
| }
|
| }
|
|
|
| return $data;
|
| }
|
|
|
| function cortex_add_licensing_fields(&$image, $site_name, $year, $creator, $license_page) {
|
| if (!isset($image['creditText'])) $image['creditText'] = $site_name;
|
| if (!isset($image['copyrightNotice'])) $image['copyrightNotice'] = '© ' . $year . ' ' . $site_name;
|
| if (!isset($image['creator'])) $image['creator'] = $creator;
|
| if (!isset($image['acquireLicensePage'])) $image['acquireLicensePage'] = $license_page;
|
| }
|
| |
| |
Comments