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