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