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