Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 312
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
Media
0.00% covered (danger)
0.00%
0 / 312
0.00% covered (danger)
0.00%
0 / 2
210
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 register_abilities
0.00% covered (danger)
0.00%
0 / 311
0.00% covered (danger)
0.00%
0 / 1
182
1<?php
2declare( strict_types=1 );
3
4namespace BLU\Abilities;
5
6/**
7 * Media abilities for WordPress media library.
8 */
9class Media {
10
11    /**
12     * Constructor - registers all media-related abilities.
13     */
14    public function __construct() {
15        $this->register_abilities();
16    }
17
18    /**
19     * Register media abilities.
20     */
21    private function register_abilities(): void {
22        // List media
23        blu_register_ability(
24            'blu/list-media',
25            array(
26                'label'               => 'List Media',
27                'description'         => 'List WordPress media items with pagination and filtering',
28                'category'            => 'blu-mcp',
29                'input_schema'        => array(
30                    'type'       => 'object',
31                    'properties' => array(
32                        'page'     => array(
33                            'type'        => 'integer',
34                            'description' => 'Page number',
35                        ),
36                        'per_page' => array(
37                            'type'        => 'integer',
38                            'description' => 'Items per page',
39                        ),
40                    ),
41                ),
42                'execute_callback'    => function ( $input = null ) {
43                    $request = new \WP_REST_Request( 'GET', '/wp/v2/media' );
44                    if ( $input ) {
45                        $request->set_query_params( $input );
46                    }
47                    $response = rest_do_request( $request );
48                    return blu_standardize_rest_response( $response );
49                },
50                'permission_callback' => fn() => current_user_can( 'upload_files' ),
51                'meta'                => array(
52                    'annotations' => array(
53                        'readonly'     => true,
54                        'destructive'  => false,
55                        'idempotent'   => true,
56                    ),
57                ),
58            )
59        );
60
61        // Get media
62        blu_register_ability(
63            'blu/get-media',
64            array(
65                'label'               => 'Get Media',
66                'description'         => 'Get a WordPress media item details by ID',
67                'category'            => 'blu-mcp',
68                'input_schema'        => array(
69                    'type'       => 'object',
70                    'properties' => array(
71                        'id' => array(
72                            'type'        => 'integer',
73                            'description' => 'Media ID',
74                        ),
75                    ),
76                    'required'   => array( 'id' ),
77                ),
78                'execute_callback'    => function ( $input ) {
79                    $request = new \WP_REST_Request( 'GET', '/wp/v2/media/' . $input['id'] );
80                    $response = rest_do_request( $request );
81                    return blu_standardize_rest_response( $response );
82                },
83                'permission_callback' => fn() => current_user_can( 'upload_files' ),
84                'meta'                => array(
85                    'annotations' => array(
86                        'readonly'     => true,
87                        'destructive'  => false,
88                        'idempotent'   => true,
89                    ),
90                ),
91            )
92        );
93
94        // Get media file (binary content)
95        blu_register_ability(
96            'blu/get-media-file',
97            array(
98                'label'               => 'Get Media File',
99                'description'         => 'Get the actual file content (blob) of a WordPress media item',
100                'category'            => 'blu-mcp',
101                'input_schema'        => array(
102                    'type'       => 'object',
103                    'properties' => array(
104                        'id'   => array(
105                            'type'        => 'integer',
106                            'description' => 'Media ID',
107                        ),
108                        'size' => array(
109                            'type'        => 'string',
110                            'description' => 'Image size (thumbnail, medium, large, full)',
111                        ),
112                    ),
113                    'required'   => array( 'id' ),
114                ),
115                'execute_callback'    => function ( $input ) {
116                    $id        = $input['id'];
117                    $size      = $input['size'] ?? 'full';
118                    $file_path = get_attached_file( $id );
119
120                    if ( ! file_exists( $file_path ) ) {
121
122                        return blu_prepare_ability_response( 404, 'File not found' );
123                    }
124
125                    if ( 'full' !== $size && 'original' !== $size ) {
126                        $meta = wp_get_attachment_metadata( $id );
127                        if ( isset( $meta['sizes'][ $size ]['file'] ) ) {
128                            $base_dir  = pathinfo( $file_path, PATHINFO_DIRNAME );
129                            $file_path = $base_dir . '/' . $meta['sizes'][ $size ]['file'];
130                        }
131                    }
132
133                    if ( ! file_exists( $file_path ) ) {
134
135                        return blu_prepare_ability_response( 404, 'Requested size not found' );
136                    }
137
138                    $mime_type = get_post_mime_type( $id );
139                    $file_data = file_get_contents( $file_path );
140
141                    return blu_prepare_ability_response( 200, array(
142                        'results'  => $file_data,
143                        'type'     => 'image',
144                        'mimeType' => $mime_type,
145                    ));
146                },
147                'permission_callback' => fn() => current_user_can( 'upload_files' ),
148                'meta'                => array(
149                    'annotations' => array(
150                        'readonly'     => true,
151                        'destructive'  => false,
152                        'idempotent'   => true,
153                    ),
154                ),
155            )
156        );
157
158        // Upload media
159        blu_register_ability(
160            'blu/upload-media',
161            array(
162                'label'               => 'Upload Media',
163                'description'         => 'Upload a new media file to WordPress',
164                'category'            => 'blu-mcp',
165                'input_schema'        => array(
166                    'type'       => 'object',
167                    'properties' => array(
168                        'file'        => array(
169                            'type'        => 'string',
170                            'description' => 'Base64 encoded file data',
171                        ),
172                        'title'       => array(
173                            'type'        => 'string',
174                            'description' => 'Media title',
175                        ),
176                        'caption'     => array(
177                            'type'        => 'string',
178                            'description' => 'Media caption',
179                        ),
180                        'description' => array(
181                            'type'        => 'string',
182                            'description' => 'Media description',
183                        ),
184                        'alt_text'    => array(
185                            'type'        => 'string',
186                            'description' => 'Alt text',
187                        ),
188                    ),
189                    'required'   => array( 'file' ),
190                ),
191                'execute_callback'    => function ( $input ) {
192                    // Process base64 file
193                    $base64_data = $input['file'];
194                    if ( strpos( $base64_data, 'data:' ) === 0 ) {
195                        $base64_data = preg_replace( '/^data:.*?;base64,/', '', $base64_data );
196                    }
197
198                    $file_data = base64_decode( $base64_data, true );
199                    if ( false === $file_data ) {
200                        return blu_prepare_ability_response( 400, 'Invalid base64 data' );
201                    }
202
203                    // Detect mime type
204                    $finfo     = finfo_open( FILEINFO_MIME_TYPE );
205                    $mime_type = finfo_buffer( $finfo, $file_data );
206                    finfo_close( $finfo );
207
208                    // Create temp file
209                    $filename = isset( $input['title'] ) ? sanitize_file_name( $input['title'] ) : 'upload';
210                    $upload   = wp_upload_bits( $filename, null, $file_data );
211
212                    if ( $upload['error'] ) {
213
214                        return blu_prepare_ability_response( 500, 'File upload error: ' . $upload['error'] );
215                    }
216
217                    // Create attachment
218                    $attachment = array(
219                        'post_mime_type' => $mime_type,
220                        'post_title'     => $input['title'] ?? '',
221                        'post_content'   => $input['description'] ?? '',
222                        'post_excerpt'   => $input['caption'] ?? '',
223                        'post_status'    => 'inherit',
224                    );
225
226                    $attach_id = wp_insert_attachment( $attachment, $upload['file'] );
227                    require_once ABSPATH . 'wp-admin/includes/image.php';
228                    $attach_data = wp_generate_attachment_metadata( $attach_id, $upload['file'] );
229                    wp_update_attachment_metadata( $attach_id, $attach_data );
230
231                    if ( ! empty( $input['alt_text'] ) ) {
232                        update_post_meta( $attach_id, '_wp_attachment_image_alt', $input['alt_text'] );
233                    }
234                    
235                    return blu_prepare_ability_response( 201, get_post( $attach_id ) );
236                },
237                'permission_callback' => fn() => current_user_can( 'upload_files' ),
238                'meta'                => array(
239                    'annotations' => array(
240                        'readonly'     => false,
241                        'destructive'  => false,
242                        'idempotent'   => false,
243                    ),
244                ),
245            )
246        );
247
248        // Update media
249        blu_register_ability(
250            'blu/update-media',
251            array(
252                'label'               => 'Update Media',
253                'description'         => 'Update a WordPress media item',
254                'category'            => 'blu-mcp',
255                'input_schema'        => array(
256                    'type'       => 'object',
257                    'properties' => array(
258                        'id'          => array(
259                            'type'        => 'integer',
260                            'description' => 'Media ID',
261                        ),
262                        'title'       => array(
263                            'type'        => 'string',
264                            'description' => 'Media title',
265                        ),
266                        'caption'     => array(
267                            'type'        => 'string',
268                            'description' => 'Media caption',
269                        ),
270                        'description' => array(
271                            'type'        => 'string',
272                            'description' => 'Media description',
273                        ),
274                        'alt_text'    => array(
275                            'type'        => 'string',
276                            'description' => 'Alt text',
277                        ),
278                    ),
279                    'required'   => array( 'id' ),
280                ),
281                'execute_callback'    => function ( $input ) {
282                    $id = $input['id'];
283                    unset( $input['id'] );
284                    $request = new \WP_REST_Request( 'POST', '/wp/v2/media/' . $id );
285                    $request->set_body_params( $input );
286                    $response = rest_do_request( $request );
287                    return blu_standardize_rest_response( $response );
288                },
289                'permission_callback' => fn() => current_user_can( 'upload_files' ),
290                'meta'                => array(
291                    'annotations' => array(
292                        'readonly'     => false,
293                        'destructive'  => false,
294                        'idempotent'   => true,
295                    ),
296                ),
297            )
298        );
299
300        // Delete media
301        blu_register_ability(
302            'blu/delete-media',
303            array(
304                'label'               => 'Delete Media',
305                'description'         => 'Delete a WordPress media item permanently',
306                'category'            => 'blu-mcp',
307                'input_schema'        => array(
308                    'type'       => 'object',
309                    'properties' => array(
310                        'id' => array(
311                            'type'        => 'integer',
312                            'description' => 'Media ID',
313                        ),
314                    ),
315                    'required'   => array( 'id' ),
316                ),
317                'execute_callback'    => function ( $input ) {
318                    $request = new \WP_REST_Request( 'DELETE', '/wp/v2/media/' . $input['id'] );
319                    $request->set_param( 'force', true );
320                    $response = rest_do_request( $request );
321                    return blu_standardize_rest_response( $response );
322                },
323                'permission_callback' => fn() => current_user_can( 'delete_posts' ),
324                'meta'                => array(
325                    'annotations' => array(
326                        'readonly'     => false,
327                        'destructive'  => true,
328                        'idempotent'   => true,
329                    ),
330                ),
331            )
332        );
333
334        // Search media
335        blu_register_ability(
336            'blu/search-media',
337            array(
338                'label'               => 'Search Media',
339                'description'         => 'Search WordPress media items by title, caption, or description',
340                'category'            => 'blu-mcp',
341                'input_schema'        => array(
342                    'type'       => 'object',
343                    'properties' => array(
344                        'search'     => array(
345                            'type'        => 'string',
346                            'description' => 'Search term',
347                        ),
348                        'media_type' => array(
349                            'type'        => 'string',
350                            'description' => 'Media type filter (image, video, audio, application)',
351                        ),
352                        'mime_type'  => array(
353                            'type'        => 'string',
354                            'description' => 'MIME type filter',
355                        ),
356                    ),
357                ),
358                'execute_callback'    => function ( $input = null ) {
359                    $request = new \WP_REST_Request( 'GET', '/wp/v2/media' );
360                    if ( $input ) {
361                        $request->set_query_params( $input );
362                    }
363                    $response = rest_do_request( $request );
364                    return blu_standardize_rest_response( $response );
365                },
366                'permission_callback' => fn() => current_user_can( 'upload_files' ),
367                'meta'                => array(
368                    'annotations' => array(
369                        'readonly'     => true,
370                        'destructive'  => false,
371                        'idempotent'   => true,
372                    ),
373                ),
374            )
375        );
376    }
377}