PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.8.4
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.8.4
3.5.8 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 / modules / files.php
ai-engine / classes / modules Last commit date
advisor.php 2 years ago chatbot.php 1 year ago discussions.php 1 year ago files.php 1 year ago gdpr.php 1 year ago search.php 1 year ago security.php 1 year ago tasks.php 1 year ago wand.php 1 year ago
files.php
766 lines
1 <?php
2
3 class Meow_MWAI_Modules_Files {
4 private $core = null;
5 private $wpdb = null;
6 private $namespace = 'mwai-ui/v1';
7 private $db_check = false;
8 private $table_files = null;
9 private $table_filemeta = null;
10
11 public function __construct( $core ) {
12 global $wpdb;
13 $this->core = $core;
14 $this->wpdb = $wpdb;
15 $this->table_files = $this->wpdb->prefix . 'mwai_files';
16 $this->table_filemeta = $this->wpdb->prefix . 'mwai_filemeta';
17 add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
18 if ( !wp_next_scheduled( 'mwai_files_cleanup' ) ) {
19 wp_schedule_event( time(), 'hourly', 'mwai_files_cleanup' );
20 }
21 add_action( 'mwai_files_cleanup', [ $this, 'cleanup_expired_files' ] );
22 }
23
24 public function cleanup_expired_files() {
25 $current_time = current_time( 'mysql' );
26 $expired_files = [];
27 if ( $this->check_db() ) {
28 $expired_files = $this->wpdb->get_results(
29 "SELECT * FROM $this->table_files WHERE expires IS NOT NULL AND expires < '{$current_time}'"
30 );
31 }
32 $expired_posts = get_posts( [
33 'post_type' => 'attachment',
34 'meta_key' => '_mwai_file_expires',
35 'meta_value' => $current_time,
36 'meta_compare' => '<'
37 ] );
38 $fileRefs = [];
39 foreach ( $expired_files as $file ) {
40 $fileRefs[] = $file->refId;
41 }
42 foreach ( $expired_posts as $post ) {
43 $fileRefs[] = get_post_meta( $post->ID, '_mwai_file_id', true );
44 }
45 $this->delete_expired_files( $fileRefs );
46 }
47
48 public function delete_expired_files( $fileRefs ) {
49
50 // Give a chance to other process to delete the files (for example, in the case of files hosted by Assistants)
51 $fileRefs = apply_filters( 'mwai_files_delete', $fileRefs );
52
53 if ( !is_array( $fileRefs ) ) {
54 $fileRefs = [ $fileRefs ];
55 }
56 foreach ( $fileRefs as $refId ) {
57 $file = null;
58 if ( $this->check_db() ) {
59 $file = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT *
60 FROM $this->table_files
61 WHERE refId = %s", $refId
62 ) );
63 }
64 if ( $file ) {
65 $this->wpdb->delete( $this->table_files, [ 'refId' => $refId ] );
66 $this->wpdb->delete( $this->table_filemeta, [ 'file_id' => $file->id ] );
67 if ( file_exists( $file->path ) ) {
68 unlink( $file->path );
69 }
70 }
71 else {
72 $posts = get_posts( [ 'post_type' => 'attachment', 'meta_key' => '_mwai_file_id', 'meta_value' => $refId ] );
73 if ( $posts ) {
74 foreach ( $posts as $post ) {
75 wp_delete_attachment( $post->ID, true );
76 }
77 }
78 }
79 }
80 }
81
82 public function get_path( $refId ) {
83 $file = null;
84 if ( $this->check_db() ) {
85 $file = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT *
86 FROM $this->table_files
87 WHERE refId = %s", $refId
88 ) );
89 }
90 if ( $file ) {
91 return $file->path;
92 }
93 else {
94 $posts = get_posts( [ 'post_type' => 'attachment', 'meta_key' => '_mwai_file_id', 'meta_value' => $refId ] );
95 if ( $posts ) {
96 foreach ( $posts as $post ) {
97 return get_attached_file( $post->ID );
98 }
99 }
100 }
101 return null;
102 }
103
104 public function get_base64_data( $refId ) {
105 $path = $this->get_path( $refId );
106 if ( $path ) {
107 $content = file_get_contents( $path );
108 $data = base64_encode( $content );
109 return $data;
110 }
111 return null;
112 }
113
114 public function is_image( $refId ) {
115 $info = $this->get_info( $refId );
116 return $info['type'] === 'image';
117 }
118
119 public function get_mime_type( $refId ) {
120 $path = $this->get_path( $refId );
121 if ( $path ) {
122 return Meow_MWAI_Core::get_mime_type( $path );
123 }
124 $url = $this->get_url( $refId );
125 if ( $url ) {
126 return Meow_MWAI_Core::get_mime_type( $url );
127 }
128 return null;
129 }
130
131 public function get_data( $refId ) {
132 $path = $this->get_path( $refId );
133 if ( $path ) {
134 $content = file_get_contents( $path );
135 return $content;
136 }
137 return null;
138 }
139
140 public function get_info( $refId ) {
141 $info = null;
142 if ( $this->check_db() ) {
143 $info = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT *
144 FROM $this->table_files
145 WHERE refId = %s", $refId
146 ), ARRAY_A );
147 }
148 if ( !$info ) {
149 $posts = get_posts( [ 'post_type' => 'attachment', 'meta_key' => '_mwai_file_id', 'meta_value' => $refId ] );
150 if ( $posts ) {
151 $post = $posts[0];
152 $info = [
153 'refId' => $refId,
154 'url' => wp_get_attachment_url( $post->ID ),
155 'path' => get_attached_file( $post->ID )
156 ];
157 }
158 }
159 if ( $info ) {
160 $info['metadata'] = $this->get_metadata( $refId );
161 }
162 return $info;
163 }
164
165 public function get_url( $refId ) {
166 $file = null;
167 if ( $this->check_db() ) {
168 $file = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT *
169 FROM $this->table_files
170 WHERE refId = %s", $refId
171 ) );
172 }
173 if ( $file ) {
174 return $file->url;
175 }
176 else {
177 $posts = get_posts( [ 'post_type' => 'attachment', 'meta_key' => '_mwai_file_id', 'meta_value' => $refId ] );
178 if ( $posts ) {
179 foreach ( $posts as $post ) {
180 return wp_get_attachment_url( $post->ID );
181 }
182 }
183 }
184 return null;
185 }
186
187 /**
188 * Handle a base-64 PNG returned by gpt-image-1: save as a temp file,
189 * register it in the Files DB, and give back a public URL.
190 *
191 * @param string $b64_json Raw base-64 image payload from OpenAI.
192 * @param string $purpose Optional purpose flag. Default 'generated'.
193 * @param int $ttl Time-to-live in seconds. Default 1 hour.
194 * @param string $target Target location: 'uploads' or 'library'. Default 'uploads'.
195 * @param array $metadata Additional metadata to store with the file.
196 *
197 * @return string|WP_Error Public URL or WP_Error on failure.
198 */
199 public function save_temp_image_from_b64( string $b64_json,
200 string $purpose = 'generated', int $ttl = HOUR_IN_SECONDS, string $target = 'uploads', array $metadata = [] )
201 {
202 // 1) Decode → binary.
203 $binary = base64_decode( $b64_json );
204 if ( !$binary ) {
205 return new WP_Error( 'mwai_bad_b64', 'Invalid base-64 payload.' );
206 }
207
208 // 2) Make a transient file in the server tmp dir.
209 $tmp_path = wp_tempnam( 'mwai-image' ); // Creates an empty file.
210 $filename = 'mwai-generated-' . time() . '-' . wp_generate_password( 8, false ) . '.png';
211 file_put_contents( $tmp_path, $binary );
212
213 // 3) Reuse the normal upload flow (target based on user preference, expiry = $ttl).
214 try {
215 // Extract envId from metadata if available
216 $envId = isset( $metadata['query_envId'] ) ? $metadata['query_envId'] : null;
217
218 $refId = $this->upload_file(
219 $tmp_path, // path on disk
220 $filename, // desired filename
221 $purpose, // purpose
222 $metadata, // metadata (now includes query info)
223 $envId, // envId from query
224 $target, // target (uploads or library based on user settings)
225 $ttl // expiry in seconds
226 );
227 // 4) Clean up temp file if it was uploaded to library (but not if uploads)
228 // For uploads target, the temp file IS the final file
229 if ( $target === 'library' && file_exists( $tmp_path ) ) {
230 @unlink( $tmp_path );
231 }
232
233 // 5) Turn refId → URL.
234 return $this->get_url( $refId );
235 }
236 catch ( Exception $e ) {
237 // Clean up temp file on error
238 if ( file_exists( $tmp_path ) ) {
239 @unlink( $tmp_path );
240 }
241 return new WP_Error( 'mwai_upload_failed', $e->getMessage() );
242 }
243 }
244
245 #region REST endpoints
246
247 public function rest_api_init() {
248 register_rest_route( $this->namespace, '/files/upload', array(
249 'methods' => 'POST',
250 'callback' => array( $this, 'rest_upload' ),
251 'permission_callback' => array( $this->core, 'check_rest_nonce' )
252 ) );
253 register_rest_route( $this->namespace, '/files/list', array(
254 'methods' => 'POST',
255 'callback' => array( $this, 'rest_list' ),
256 'permission_callback' => array( $this->core, 'check_rest_nonce' )
257 ) );
258 register_rest_route( $this->namespace, '/files/delete', array(
259 'methods' => 'POST',
260 'callback' => array( $this, 'rest_delete' ),
261 'permission_callback' => array( $this->core, 'check_rest_nonce' )
262 ) );
263 }
264
265
266 /*
267 * Record a new file in the Files database.
268 * This doesn't handle the upload or anything.
269 */
270 public function commit_file( $fileInfo ) {
271 if ( !$this->check_db() ) {
272 throw new Exception( 'Could not create database table.' );
273 }
274 $now = date( 'Y-m-d H:i:s' );
275 if ( empty( $fileInfo['refId'] ) ) {
276 if ( !empty( $fileInfo['url'] ) ) {
277 $fileInfo['refId'] = $this->generate_refId( $fileInfo['url'] );
278 }
279 else {
280 throw new Exception( 'File ID (or URL) is required.' );
281 }
282 }
283 if ( empty( $fileInfo['type'] ) ) {
284 $fileInfo['type'] = Meow_MWAI_Core::is_image( $fileInfo['url'] ) ? 'image' : 'file';
285 }
286 $success = $this->wpdb->insert( $this->table_files, [
287 'refId' => $fileInfo['refId'],
288 'envId' => empty( $fileInfo['envId'] ) ? null : $fileInfo['envId'],
289 'userId' => empty( $fileInfo['userId'] ) ? $this->core->get_user_id() : $fileInfo['userId'],
290 'purpose' => empty( $fileInfo['purpose'] ) ? null : $fileInfo['purpose'],
291 'type' => empty( $fileInfo['type'] ) ? null : $fileInfo['type'],
292 'status' => empty( $fileInfo['status'] ) ? null : $fileInfo['status'],
293 'created' => empty( $fileInfo['created'] ) ? $now : $fileInfo['created'],
294 'updated' => empty( $fileInfo['updated'] ) ? $now : $fileInfo['updated'],
295 'expires' => empty( $fileInfo['expires'] ) ? null : $fileInfo['expires'],
296 'path' => empty( $fileInfo['path'] ) ? null : $fileInfo['path'],
297 'url' => empty( $fileInfo['url'] ) ? null : $fileInfo['url']
298 ] );
299 // check for error
300 if ( !$success ) {
301 throw new Exception( 'Error while adding file in the DB (' . $this->wpdb->last_error . ')' );
302 }
303 return $this->wpdb->insert_id;
304 }
305
306 // Generate a refId from a URL or random, and make sure it's unique
307 public function generate_refId( $attempts = 0 ) {
308 $refId = md5( date( 'Y-m-d H:i:s' ) . '-' . $attempts );
309 $file = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT *
310 FROM $this->table_files
311 WHERE refId = %s", $refId
312 ) );
313 if ( $file ) {
314 return $this->generate_refId( $attempts + 1 );
315 }
316 return $refId;
317 }
318
319 public function upload_file( $path, $filename = null, $purpose = null,
320 $metadata = null, $envId = null, $target = null, $expiry = null ) {
321 require_once( ABSPATH . 'wp-admin/includes/image.php' );
322 require_once( ABSPATH . 'wp-admin/includes/file.php' );
323 require_once( ABSPATH . 'wp-admin/includes/media.php' );
324
325 $target = empty( $target ) ? $this->core->get_option( 'image_local_upload' ) : $target;
326 $expiry = empty( $expiry ) ? $this->core->get_option( 'image_expires' ) : $expiry;
327
328 $expires = ( $expiry === 'never' || empty( $expiry ) ) ? null : date( 'Y-m-d H:i:s', time() + intval( $expiry ) );
329 $refId = $this->generate_refId();
330 $url = null;
331 if ( empty( $filename ) ) {
332 $parsed_url = parse_url( $path, PHP_URL_PATH );
333 $filename = basename( $parsed_url );
334 $extension = pathinfo( $filename, PATHINFO_EXTENSION );
335 }
336 else {
337 $extension = pathinfo( $filename, PATHINFO_EXTENSION );
338 }
339 $newFilename = $refId . '.' . $extension;
340 $unique_filename = wp_unique_filename( wp_upload_dir()['path'], $newFilename );
341 $destination = wp_upload_dir()['path'] . '/' . $unique_filename;
342
343 if ( $target === 'uploads' ) {
344 if ( !$this->check_db() ) {
345 throw new Exception( 'Could not create database table.' );
346 }
347 if ( !copy( $path, $destination ) ) {
348 throw new Exception( 'Could not move the file.' );
349 }
350 $url = wp_upload_dir()['url'] . '/' . $unique_filename;
351
352 $now = date( 'Y-m-d H:i:s' );
353 $fileId = $this->commit_file( [
354 'refId' => $refId,
355 'envId' => $envId,
356 'purpose' => $purpose,
357 'type' => null,
358 'status' => 'uploaded',
359 'created' => $now,
360 'updated' => $now,
361 'expires' => $expires,
362 'path' => $destination,
363 'url' => $url
364 ] );
365 if ( $metadata && is_array( $metadata ) ) {
366 foreach ( $metadata as $metaKey => $metaValue ) {
367 $this->add_metadata( $fileId, $metaKey, $metaValue );
368 }
369 }
370
371 }
372 else if ( $target === 'library' ) {
373
374 if ( filter_var( $path, FILTER_VALIDATE_URL ) ) {
375 $tmp = download_url( $path );
376 if ( is_wp_error( $tmp ) ) {
377 throw new Exception( $tmp->get_error_message() );
378 }
379 $file_array = [ 'name' => $unique_filename, 'tmp_name' => $tmp ];
380 }
381 else {
382 $file_array = [ 'name' => $unique_filename, 'tmp_name' => $path ];
383 }
384
385
386 $id = media_handle_sideload( $file_array, 0 );
387 if ( is_wp_error( $id ) ) {
388 throw new Exception( $id->get_error_message() );
389 }
390
391 $url = wp_get_attachment_url( $id );
392 update_post_meta( $id, '_mwai_file_id', $refId );
393 update_post_meta( $id, '_mwai_file_expires', $expires );
394
395 // Store additional metadata
396 if ( $metadata && is_array( $metadata ) ) {
397 foreach ( $metadata as $metaKey => $metaValue ) {
398 update_post_meta( $id, '_mwai_' . $metaKey, $metaValue );
399 }
400 }
401
402 // Store purpose and envId as post meta
403 if ( $purpose ) {
404 update_post_meta( $id, '_mwai_purpose', $purpose );
405 }
406 if ( $envId ) {
407 update_post_meta( $id, '_mwai_envId', $envId );
408 }
409 }
410
411 return $refId;
412 }
413
414 public function add_metadata( $fileId, $metaKey, $metaValue ) {
415 $data = [
416 'file_id' => $fileId,
417 'meta_key' => $metaKey,
418 'meta_value' => $metaValue
419 ];
420 $res = $this->wpdb->insert( $this->table_filemeta, $data );
421 if ( $res === false ) {
422 Meow_MWAI_Logging::warn( "Error while writing files metadata (" . $this->wpdb->last_error . ")" );
423 return false;
424 }
425 return $this->wpdb->insert_id;
426 }
427
428 public function update_refId( $fileId, $refId ) {
429 if ( $this->check_db() ) {
430 $this->wpdb->update( $this->table_files, [ 'refId' => $refId ], [ 'id' => $fileId ] );
431 }
432 }
433
434 public function update_purpose( $fileId, $purpose ) {
435 if ( $this->check_db() ) {
436 $this->wpdb->update( $this->table_files, [ 'purpose' => $purpose ], [ 'id' => $fileId ] );
437 }
438 }
439
440 public function update_envId( $fileId, $envId ) {
441 if ( $this->check_db() ) {
442 $this->wpdb->update( $this->table_files, [ 'envId' => $envId ], [ 'id' => $fileId ] );
443 }
444 }
445
446 public function get_metadata( $refId, $fileId = null ) {
447 if ( !$fileId ) {
448 $fileId = $this->get_id_from_refId( $refId );
449 }
450 if ( $fileId ) {
451 $sql = $this->wpdb->prepare( "SELECT * FROM $this->table_filemeta WHERE file_id = %d", $fileId );
452 $metadata = $this->wpdb->get_results( $sql, ARRAY_A );
453 $meta = [];
454 foreach ( $metadata as $metaItem ) {
455 $meta[$metaItem['meta_key']] = $metaItem['meta_value'];
456 }
457 return $meta;
458 }
459 return null;
460 }
461
462 public function search( $userId = null, $purpose = null, $metadata = [], $envId = null ) {
463 list( $sql, $params ) = $this->_buildQuery( $userId, $purpose, $metadata, $envId, true );
464 $finalQuery = $this->wpdb->prepare( $sql, $params );
465 $files = $this->wpdb->get_results( $finalQuery, ARRAY_A );
466 foreach ( $files as &$file ) {
467 $file['metadata'] = $this->get_metadata( $file['refId'] );
468 }
469 return $files;
470 }
471
472 public function list( $userId = null, $purpose = null, $metadata = [],
473 $envId = null, $limit = 10, $offset = 0 )
474 {
475 list( $countSql, $countParams ) = $this->_buildQuery( $userId, $purpose, $metadata, $envId, false );
476 $total = $this->wpdb->get_var( $this->wpdb->prepare( $countSql, $countParams ) );
477
478 list( $fileSql, $fileParams ) = $this->_buildQuery( $userId, $purpose, $metadata, $envId, true );
479 if ( $limit ) {
480 $fileSql .= " LIMIT %d";
481 $fileParams[] = $limit;
482 }
483 if ( $offset ) {
484 $fileSql .= " OFFSET %d";
485 $fileParams[] = $offset;
486 }
487 $files = $this->wpdb->get_results( $this->wpdb->prepare( $fileSql, $fileParams ), ARRAY_A );
488 foreach ( $files as &$file ) {
489 $file['metadata'] = $this->get_metadata( $file['refId'] );
490 }
491 return [ 'files' => $files, 'total' => $total ];
492 }
493
494 private function _buildQuery( $userId, $purpose, $metadata, $envId, $selectStar ) {
495 $sql = $selectStar ? "SELECT * FROM $this->table_files WHERE 1=1" : "SELECT COUNT(*) FROM $this->table_files WHERE 1=1";
496 $params = [];
497
498 // Based on the old "search" function
499 $actualUserId = $this->core->get_user_id();
500 $canAdmin = $this->core->can_access_settings();
501 if ( $userId !== $actualUserId ) {
502 if ( !$canAdmin ) {
503 throw new Exception( 'You are not allowed to access files from another user.' );
504 }
505 }
506 if ( $userId ) {
507 $sql .= " AND userId = %d";
508 $params[] = $userId;
509 }
510 if ( $purpose ) {
511 if ( is_array( $purpose ) ) {
512 $sql .= " AND (";
513 foreach ( $purpose as $p ) {
514 $sql .= " purpose = %s OR";
515 $params[] = $p;
516 }
517 $sql = rtrim( $sql, 'OR' );
518 $sql .= ")";
519 }
520 else {
521 $sql .= " AND purpose = %s";
522 $params[] = $purpose;
523 }
524 }
525 if ( $metadata ) {
526 foreach ( $metadata as $metaKey => $metaValue ) {
527 $sql .= " AND EXISTS ( SELECT * FROM $this->table_filemeta
528 WHERE file_id = $this->table_files.id AND meta_key = %s AND meta_value = %s )";
529 $params[] = $metaKey;
530 $params[] = $metaValue;
531 }
532 }
533 if ( $envId ) {
534 $sql .= " AND envId = %s";
535 $params[] = $envId;
536 }
537 $sql .= " ORDER BY updated DESC";
538 return [ $sql, $params ];
539 }
540
541 // public function search( $userId = null, $purpose = null, $metadata = [], $limit = 10, $offset = 0 ) {
542 // $sql = "SELECT * FROM $this->table_files WHERE 1=1";
543 // $actualUserId = $this->core->get_user_id();
544 // $canAdmin = $this->core->can_access_settings();
545 // if ( $userId !== $actualUserId ) {
546 // if ( !$canAdmin ) {
547 // throw new Exception( 'You are not allowed to access files from another user.' );
548 // }
549 // }
550 // if ( $userId ) {
551 // $sql .= $this->wpdb->prepare( " AND userId = %d", $userId );
552 // }
553 // if ( $purpose ) {
554 // if ( is_array( $purpose ) ) {
555 // $sql .= " AND (";
556 // foreach ( $purpose as $p ) {
557 // $sql .= $this->wpdb->prepare( " purpose = %s OR", $p );
558 // }
559 // $sql = rtrim( $sql, 'OR' );
560 // $sql .= ")";
561 // }
562 // else {
563 // $sql .= $this->wpdb->prepare( " AND purpose = %s", $purpose );
564 // }
565 // }
566 // if ( $metadata ) {
567 // foreach ( $metadata as $metaKey => $metaValue ) {
568 // $sql .= $this->wpdb->prepare( " AND EXISTS ( SELECT * FROM $this->table_filemeta
569 // WHERE file_id = $this->table_files.id AND meta_key = %s AND meta_value = %s )",
570 // $metaKey, $metaValue
571 // );
572 // }
573 // }
574 // $sql .= " ORDER BY updated DESC";
575 // if ( $limit ) {
576 // $sql .= $this->wpdb->prepare( " LIMIT %d", $limit );
577 // }
578 // if ( $offset ) {
579 // $sql .= $this->wpdb->prepare( " OFFSET %d", $offset );
580 // }
581 // $files = $this->wpdb->get_results( $sql, ARRAY_A );
582
583 // // Add metadata
584 // foreach ( $files as &$file ) {
585 // $file['metadata'] = $this->get_metadata( $file['refId'] );
586 // }
587
588 // return $files;
589 // }
590
591 public function get_id_from_refId( $refId ) {
592 $file = null;
593 if ( $this->check_db() ) {
594 $file = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT *
595 FROM $this->table_files
596 WHERE refId = %s", $refId
597 ) );
598 }
599 if ( $file ) {
600 return $file->id;
601 }
602 return null;
603 }
604
605 public function add_metadata_from_refId( $refId, $metaKey, $metaValue ) {
606 $fileId = $this->get_id_from_refId( $refId );
607 if ( $fileId ) {
608 return $this->add_metadata( $fileId, $metaKey, $metaValue );
609 }
610 return false;
611 }
612
613 public function rest_list( $request ) {
614 $params = $request->get_json_params();
615 $userId = empty( $params['userId'] ) ? null : $params['userId'];
616 $envId = empty( $params['envId'] ) ? null : $params['envId'];
617 $purpose = empty( $params['purpose'] ) ? null : $params['purpose'];
618 $metadata = empty( $params['metadata'] ) ? null : json_decode( $params['metadata'], true );
619 $limit = empty( $params['limit'] ) ? 10 : intval( $params['limit'] );
620 $offset = empty( $params['page'] ) ? 0 : ( intval( $params['page'] ) - 1) * $limit;
621 $files = $this->list( $userId, $purpose, $metadata, $envId, $limit, $offset );
622 return new WP_REST_Response( [ 'success' => true, 'data' => $files ], 200 );
623 }
624
625 public function rest_delete( $request ) {
626 $params = $request->get_json_params();
627 $fileIds = empty( $params['files'] ) ? [] : $params['files'];
628 $this->delete_files( $fileIds );
629 return new WP_REST_Response( [ 'success' => true ], 200 );
630 }
631
632 public function delete_files( $fileIds ) {
633 $query = "SELECT refId, path FROM $this->table_files WHERE id IN (";
634 $params = [];
635 foreach ( $fileIds as $fileId ) {
636 $query .= "%s,";
637 $params[] = $fileId;
638 }
639 $query = rtrim( $query, ',' );
640 $query .= ")";
641 $files = $this->wpdb->get_results( $this->wpdb->prepare( $query, $params ), ARRAY_A );
642 $refIds = apply_filters( 'mwai_files_delete', array_column( $files, 'refId' ) );
643 foreach ( $files as $file ) {
644 if ( in_array( $file['refId'], $refIds ) ) {
645 $this->wpdb->delete( $this->table_files, [ 'refId' => $file['refId'] ] );
646 if ( file_exists( $file['path'] ) ) {
647 unlink( $file['path'] );
648 }
649 }
650 }
651 }
652
653 public function rest_upload() {
654 if ( empty( $_FILES['file'] ) ) {
655 return new WP_REST_Response( [ 'success' => false, 'message' => 'No file provided.' ], 400 );
656 }
657 $file = $_FILES['file'];
658 $purpose = empty( $_POST['purpose'] ) ? null : $_POST['purpose'];
659 $metadata = empty( $_POST['metadata'] ) ? null : json_decode( $_POST['metadata'], true );
660 $envId = empty( $_POST['envId'] ) ? null : $_POST['envId'];
661 if ( !$purpose ) {
662 return new WP_REST_Response( [ 'success' => false, 'message' => 'Purpose is required.' ], 400 );
663 }
664 $fileTypeCheck = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'] );
665 if ( !$fileTypeCheck['type'] ) {
666 return new WP_REST_Response( [ 'success' => false, 'message' => 'Invalid file type.' ], 400 );
667 }
668
669 try {
670 $refId = $this->upload_file( $file['tmp_name'], $file['name'], $purpose, $metadata, $envId );
671 $url = $this->get_url( $refId );
672 return new WP_REST_Response( [
673 'success' => true,
674 'data' => [ 'id' => $refId, 'url' => $url ]
675 ], 200 );
676 }
677 catch ( Exception $e ) {
678 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
679 }
680 }
681
682 #endregion
683
684 #region Database functions
685
686 function create_db() {
687 $charset_collate = $this->wpdb->get_charset_collate();
688 $sql = "CREATE TABLE $this->table_files (
689 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
690 refId VARCHAR(64) NOT NULL,
691 envId VARCHAR(128) NULL,
692 userId BIGINT(20) UNSIGNED NULL,
693 type VARCHAR(32) NULL,
694 status VARCHAR(32) NULL,
695 purpose VARCHAR(32) NULL,
696 created DATETIME NOT NULL,
697 updated DATETIME NOT NULL,
698 expires DATETIME NULL,
699 path TEXT NULL,
700 url TEXT NULL,
701 PRIMARY KEY (id),
702 UNIQUE KEY unique_file_id (refId)
703 ) $charset_collate;";
704
705 $sqlFileMeta = "CREATE TABLE $this->table_filemeta (
706 meta_id BIGINT(20) NOT NULL AUTO_INCREMENT,
707 file_id BIGINT(20) NOT NULL,
708 meta_key varchar(255) NULL,
709 meta_value longtext NULL,
710 PRIMARY KEY (meta_id)
711 ) $charset_collate;";
712
713 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
714 dbDelta( $sql );
715 dbDelta( $sqlFileMeta );
716 }
717
718 function check_db() {
719 if ( $this->db_check ) {
720 return true;
721 }
722
723 // Check if table_files exists
724 $sql = $this->wpdb->prepare( "SHOW TABLES LIKE %s", $this->table_files );
725 $table_files_exists = strtolower( $this->wpdb->get_var( $sql )) === strtolower( $this->table_files );
726
727 // Check if table_filemeta exists
728 $sqlMeta = $this->wpdb->prepare( "SHOW TABLES LIKE %s", $this->table_filemeta );
729 $table_filemeta_exists = strtolower( $this->wpdb->get_var( $sqlMeta )) === strtolower( $this->table_filemeta );
730
731 // If either table does not exist, create them
732 if ( !$table_files_exists || !$table_filemeta_exists ) {
733 $this->create_db();
734 }
735
736 // Update db_check for both tables
737 $this->db_check = $table_files_exists && $table_filemeta_exists;
738
739 // LATER: REMOVE THIS AFTER MARCH 2024
740 // $this->db_check = $this->db_check && $this->wpdb->get_var( "SHOW COLUMNS FROM $this->table_files LIKE 'userId'" );
741 // if ( !$this->db_check ) {
742 // $this->wpdb->query( "ALTER TABLE $this->table_files ADD COLUMN userId BIGINT(20) UNSIGNED NULL" );
743 // $this->wpdb->query( "ALTER TABLE $this->table_files ADD COLUMN purpose VARCHAR(32) NULL" );
744 // $this->wpdb->query( "ALTER TABLE $this->table_files MODIFY COLUMN path TEXT NULL" );
745 // $this->wpdb->query( "ALTER TABLE $this->table_files DROP COLUMN metadata" );
746 // $this->db_check = true;
747 // }
748 // // LATER: REMOVE THIS AFTER MARCH 2024
749 // $this->db_check = $this->db_check && !$this->wpdb->get_var( "SHOW COLUMNS FROM $this->table_files LIKE 'fileId'" );
750 // if ( !$this->db_check ) {
751 // $this->wpdb->query( "ALTER TABLE $this->table_files ADD COLUMN refId VARCHAR(64) NOT NULL" );
752 // $this->wpdb->query( "ALTER TABLE $this->table_files DROP COLUMN fileId" );
753 // $this->db_check = true;
754 // }
755 // // LATER: REMOVE THIS AFTER MARCH 2024
756 // $this->db_check = $this->db_check && $this->wpdb->get_var( "SHOW COLUMNS FROM $this->table_files LIKE 'envId'" );
757 // if ( !$this->db_check ) {
758 // $this->wpdb->query( "ALTER TABLE $this->table_files ADD COLUMN envId VARCHAR(128) NULL" );
759 // $this->db_check = true;
760 // }
761
762 return $this->db_check;
763 }
764
765 #endregion
766 }