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