PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.5.4
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.5.4
3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / classes / services / image.php
ai-engine / classes / services Last commit date
image.php 7 months ago message-builder.php 3 weeks ago model-environment.php 7 months ago response-id-manager.php 3 months ago session.php 11 months ago usage-stats.php 1 month ago
image.php
256 lines
1 <?php
2
3 class Meow_MWAI_Services_Image {
4 private $core;
5
6 public function __construct( $core ) {
7 $this->core = $core;
8 }
9
10 public function is_image( $mimeType ) {
11 return strpos( $mimeType, 'image/' ) === 0;
12 }
13
14 public function get_image_resolution( $imageData ) {
15 try {
16 $tempFile = tmpfile();
17 $tempFilePath = stream_get_meta_data( $tempFile )['uri'];
18 fwrite( $tempFile, $imageData );
19 $imageSize = getimagesize( $tempFilePath );
20 fclose( $tempFile );
21 if ( $imageSize !== false ) {
22 return $imageSize[0] . 'x' . $imageSize[1];
23 }
24 }
25 catch ( Exception $e ) {
26 throw new Exception( 'Failed to get image resolution.' );
27 }
28 return null;
29 }
30
31 public function get_mime_type( $file, $fileData = null ) {
32 $mimeType = null;
33
34 // If we have file data, let's use it
35 if ( !empty( $fileData ) ) {
36 $f = finfo_open();
37 $mimeType = finfo_buffer( $f, $fileData, FILEINFO_MIME_TYPE );
38 }
39
40 // Try to use mime_content_type for local files
41 if ( !$mimeType ) {
42 $isUrl = filter_var( $file, FILTER_VALIDATE_URL );
43 if ( !$isUrl && function_exists( 'mime_content_type' ) ) {
44 try {
45 // Sanitize file path to prevent PHAR deserialization attacks
46 $sanitized_file = Meow_MWAI_Core::sanitize_file_path( $file );
47 if ( file_exists( $sanitized_file ) ) {
48 $mimeType = mime_content_type( $sanitized_file );
49 }
50 }
51 catch ( Exception $e ) {
52 // If sanitization fails, fall through to extension-based detection
53 Meow_MWAI_Logging::warn( 'File path sanitization failed: ' . $e->getMessage() );
54 }
55 }
56 }
57
58 // Otherwise, let's check the file extension (which can actually also be an URL)
59 if ( !$mimeType ) {
60 $extension = pathinfo( $file, PATHINFO_EXTENSION );
61 $extension = strtolower( $extension );
62 $mimeTypes = [
63 'jpg' => 'image/jpeg',
64 'jpeg' => 'image/jpeg',
65 'png' => 'image/png',
66 'gif' => 'image/gif',
67 'webp' => 'image/webp',
68 'bmp' => 'image/bmp',
69 'tiff' => 'image/tiff',
70 'tif' => 'image/tiff',
71 'svg' => 'image/svg+xml',
72 'ico' => 'image/x-icon',
73 'pdf' => 'application/pdf',
74 ];
75 $mimeType = isset( $mimeTypes[$extension] ) ? $mimeTypes[$extension] : null;
76 }
77
78 return $mimeType;
79 }
80
81 public function download_image( $url ) {
82 // Handle data URLs (base64-encoded images from Google Gemini, etc.)
83 if ( strpos( $url, 'data:' ) === 0 ) {
84 // Extract base64 data from data URL
85 // Format: data:image/png;base64,iVBORw0KGgoAAAANS...
86 $parts = explode( ',', $url, 2 );
87 if ( count( $parts ) !== 2 ) {
88 throw new Exception( 'Invalid data URL format.' );
89 }
90
91 // Validate it's an image data URL
92 if ( stripos( $parts[0], 'image/' ) === false ) {
93 throw new Exception( 'Data URL is not an image.' );
94 }
95
96 // Decode base64 data
97 $image_data = base64_decode( $parts[1] );
98 if ( $image_data === false ) {
99 throw new Exception( 'Failed to decode base64 image data.' );
100 }
101
102 return $image_data;
103 }
104
105 // Validate URL scheme (only allow http/https)
106 $parsed_url = parse_url( $url );
107 if ( empty( $parsed_url['scheme'] ) || !in_array( $parsed_url['scheme'], [ 'http', 'https' ] ) ) {
108 throw new Exception( 'Invalid URL scheme. Only HTTP and HTTPS are allowed.' );
109 }
110
111 // Check against banned IPs to prevent SSRF attacks
112 $host = $parsed_url['host'] ?? '';
113 if ( empty( $host ) ) {
114 throw new Exception( 'Invalid URL: no host specified.' );
115 }
116
117 // Resolve hostname to IP
118 $ip = gethostbyname( $host );
119
120 // Check if the resolved IP is in the banned IPs list
121 $banned_ips = $this->core->get_option( 'banned_ips' );
122 if ( !empty( $banned_ips ) && !empty( $this->core->security ) ) {
123 if ( $this->core->security->is_blocked_ip( $ip, $banned_ips ) ) {
124 throw new Exception( 'Access to this IP address is not allowed.' );
125 }
126 }
127
128 // Disable redirects to prevent bypass attacks
129 $response = wp_safe_remote_get( $url, [
130 'timeout' => 60,
131 'redirection' => 0 // Prevent redirect-based bypass
132 ] );
133
134 if ( is_wp_error( $response ) ) {
135 throw new Exception( $response->get_error_message() );
136 }
137
138 // Validate response is actually an image
139 $content_type = wp_remote_retrieve_header( $response, 'content-type' );
140 if ( empty( $content_type ) || stripos( $content_type, 'image/' ) !== 0 ) {
141 throw new Exception( 'URL did not return an image. Content-Type: ' . $content_type );
142 }
143
144 return wp_remote_retrieve_body( $response );
145 }
146
147 /**
148 * Add an image from a URL to the Media Library.
149 * @param string $url The URL of the image to be downloaded.
150 * @param string $filename The filename of the image, if not set, it will be the basename of the URL.
151 * @param string $title The title of the image.
152 * @param string $description The description of the image.
153 * @param string $caption The caption of the image.
154 * @param string $alt The alt text of the image.
155 * @param array $ai_metadata AI-related metadata (model, latency, env_id).
156 * @return int The attachment ID of the image.
157 */
158 public function add_image_from_url( $url, $filename = null, $title = null, $description = null, $caption = null, $alt = null, $attachedPost = null, $post_status = 'inherit', $post_type = 'attachment', $ai_metadata = [] ) {
159 $path_parts = pathinfo( parse_url( $url, PHP_URL_PATH ) );
160 $url_filename = $path_parts['basename'];
161 $file_type = wp_check_filetype( $url_filename, null );
162 $allowed_types = get_allowed_mime_types();
163
164 // For URLs without file extensions (like Google Gemini), default to PNG
165 $extension = 'png';
166 if ( $file_type && $file_type['ext'] && in_array( $file_type['type'], $allowed_types ) ) {
167 $extension = $file_type['ext'];
168 }
169
170 if ( !empty( $filename ) ) {
171 $custom_file_type = wp_check_filetype( $filename, null );
172 if ( !$custom_file_type || !in_array( $custom_file_type['type'], $allowed_types ) ) {
173 throw new Exception( 'Invalid custom file type.' );
174 }
175 // Use the extension from the custom filename if valid
176 $extension = $custom_file_type['ext'];
177 }
178
179 $image_data = $this->download_image( $url );
180 if ( !$image_data ) {
181 throw new Exception( 'Could not download the image.' );
182 }
183 $upload_dir = wp_upload_dir();
184
185 // Filename handling including 'generated_' prefix scenario
186 if ( empty( $filename ) ) {
187 $filename = sanitize_file_name( $url_filename );
188 if ( empty( $extension ) ) { // This condition might now be redundant
189 $extension = $file_type['ext'];
190 }
191 // Filename length check and prepend if conditions met
192 if ( strlen( $filename ) > 32 || strlen( $filename ) < 4 || strpos( $filename, 'generated_' ) === 0 ) {
193 $filename = uniqid( 'ai_', true ) . '.' . $extension;
194 }
195 if ( strpos( $filename, '.' ) === false ) {
196 $filename .= '.' . $extension;
197 }
198 }
199
200 // Directory and file path handling
201 if ( wp_mkdir_p( $upload_dir['path'] ) ) {
202 $file = $upload_dir['path'] . '/' . $filename;
203 }
204 else {
205 $file = $upload_dir['basedir'] . '/' . $filename;
206 }
207
208 // Ensure file name uniqueness in the directory
209 $i = 1;
210 $parts = pathinfo( $file );
211 while ( file_exists( $file ) ) {
212 $file = $parts['dirname'] . '/' . $parts['filename'] . '-' . $i . '.' . $parts['extension'];
213 $i++;
214 }
215
216 // Write file to filesystem
217 file_put_contents( $file, $image_data );
218
219 // Prepare and insert attachment
220 $wp_filetype = wp_check_filetype( basename( $file ), null );
221 $attachment = [
222 'post_mime_type' => $wp_filetype['type'],
223 'post_title' => !is_null( $title ) ? $title : preg_replace( '/\.[^.]+$/', '', basename( $file ) ),
224 'post_content' => !is_null( $description ) ? $description : '',
225 'post_status' => $post_status,
226 'post_excerpt' => !is_null( $caption ) ? $caption : '',
227 'post_type' => $post_type,
228 ];
229
230 // Use wp_insert_post instead of wp_insert_attachment to allow custom post types
231 $attach_id = wp_insert_post( $attachment );
232
233 // Set the attached file manually since we're not using wp_insert_attachment
234 update_attached_file( $attach_id, $file );
235 require_once( ABSPATH . 'wp-admin/includes/image.php' );
236 $attach_data = wp_generate_attachment_metadata( $attach_id, $file );
237 wp_update_attachment_metadata( $attach_id, $attach_data );
238 if ( !is_null( $alt ) ) {
239 update_post_meta( $attach_id, '_wp_attachment_image_alt', $alt );
240 }
241
242 // Store AI-related metadata
243 if ( !empty( $ai_metadata['model'] ) ) {
244 update_post_meta( $attach_id, 'mwai_model', sanitize_text_field( $ai_metadata['model'] ) );
245 }
246 if ( !empty( $ai_metadata['latency'] ) ) {
247 update_post_meta( $attach_id, 'mwai_latency', floatval( $ai_metadata['latency'] ) );
248 }
249 if ( !empty( $ai_metadata['env_id'] ) ) {
250 update_post_meta( $attach_id, 'mwai_env_id', sanitize_text_field( $ai_metadata['env_id'] ) );
251 }
252
253 return $attach_id;
254 }
255 }
256