if ( ! defined( 'ABSPATH' ) ) { exit; } add_action( 'wp_abilities_api_init', function () { if ( ! function_exists( 'wp_register_ability' ) ) { return; } /* * Search Media * Read-only search by media title or filename. */ wp_register_ability( 'hameyave/search-media', array( 'label' => 'Search Media', 'description' => 'Search the WordPress Media Library by image title or filename. Read-only. Returns attachment IDs, URLs, filenames, titles, ALT text and MIME types.', 'category' => 'product-management', 'input_schema' => array( 'type' => 'object', 'additionalProperties' => false, 'properties' => array( 'search' => array( 'type' => 'string', 'description' => 'Text to search for in the media title or filename.', 'minLength' => 1, ), 'limit' => array( 'type' => 'integer', 'description' => 'Maximum number of results.', 'minimum' => 1, 'maximum' => 50, 'default' => 20, ), ), 'required' => array( 'search' ), ), 'permission_callback' => function ( $input ) { return current_user_can( 'upload_files' ); }, 'execute_callback' => function ( $input ) { global $wpdb; $search = sanitize_text_field( $input['search'] ); $limit = isset( $input['limit'] ) ? absint( $input['limit'] ) : 20; $limit = max( 1, min( 50, $limit ) ); $like = '%' . $wpdb->esc_like( $search ) . '%'; $sql = $wpdb->prepare( " SELECT DISTINCT p.ID FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_wp_attached_file' WHERE p.post_type = 'attachment' AND p.post_status = 'inherit' AND ( p.post_title LIKE %s OR pm.meta_value LIKE %s ) ORDER BY p.ID DESC LIMIT %d ", $like, $like, $limit ); $ids = $wpdb->get_col( $sql ); $results = array(); foreach ( $ids as $id ) { $id = absint( $id ); $file = get_attached_file( $id ); $results[] = array( 'id' => $id, 'title' => get_the_title( $id ), 'filename' => $file ? wp_basename( $file ) : '', 'url' => wp_get_attachment_url( $id ), 'alt_text' => (string) get_post_meta( $id, '_wp_attachment_image_alt', true ), 'mime_type' => (string) get_post_mime_type( $id ), ); } return array( 'search' => $search, 'count' => count( $results ), 'results' => $results, ); }, 'meta' => array( 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true, ), 'public' => true, ), ) ); /* * Update Media Metadata * Update title and/or ALT text of one existing attachment. */ wp_register_ability( 'hameyave/update-media-meta', array( 'label' => 'Update Media Metadata', 'description' => 'Safely update the title and/or ALT text of one existing WordPress media attachment by exact attachment ID. Requires explicit confirmation.', 'category' => 'product-management', 'input_schema' => array( 'type' => 'object', 'additionalProperties' => false, 'properties' => array( 'attachment_id' => array( 'type' => 'integer', 'description' => 'Exact WordPress media attachment ID.', 'minimum' => 1, ), 'title' => array( 'type' => 'string', 'description' => 'Optional new Media Library title.', 'minLength' => 1, ), 'alt_text' => array( 'type' => 'string', 'description' => 'Optional new ALT text. May be empty to clear ALT text.', ), 'confirm_update' => array( 'type' => 'boolean', 'description' => 'Must be true after explicit user confirmation.', ), ), 'required' => array( 'attachment_id', 'confirm_update', ), ), 'permission_callback' => function ( $input ) { $id = isset( $input['attachment_id'] ) ? absint( $input['attachment_id'] ) : 0; return $id && current_user_can( 'upload_files' ) && current_user_can( 'edit_post', $id ); }, 'execute_callback' => function ( $input ) { $id = absint( $input['attachment_id'] ); if ( true !== $input['confirm_update'] ) { return new WP_Error( 'confirmation_required', 'Explicit confirmation is required before updating media metadata.' ); } $attachment = get_post( $id ); if ( ! $attachment || 'attachment' !== $attachment->post_type ) { return new WP_Error( 'invalid_attachment', 'The supplied ID is not a valid media attachment.' ); } $updated_fields = array(); if ( array_key_exists( 'title', $input ) ) { $new_title = sanitize_text_field( $input['title'] ); if ( '' === $new_title ) { return new WP_Error( 'invalid_title', 'Media title cannot be empty.' ); } $result = wp_update_post( array( 'ID' => $id, 'post_title' => $new_title, ), true ); if ( is_wp_error( $result ) ) { return $result; } $updated_fields[] = 'title'; } if ( array_key_exists( 'alt_text', $input ) ) { update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $input['alt_text'] ) ); $updated_fields[] = 'alt_text'; } if ( empty( $updated_fields ) ) { return new WP_Error( 'nothing_to_update', 'Provide title and/or alt_text to update.' ); } return array( 'updated' => true, 'attachment_id' => $id, 'title' => get_the_title( $id ), 'alt_text' => (string) get_post_meta( $id, '_wp_attachment_image_alt', true ), 'url' => wp_get_attachment_url( $id ), 'updated_fields' => $updated_fields, ); }, 'meta' => array( 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => true, ), 'public' => true, ), ) ); } );