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