PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.4.8
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.4.8
3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / classes / modules / files.php
ai-engine / classes / modules Last commit date
advisor.php 2 years ago chatbot.php 1 year ago discussions.php 1 year ago files.php 2 years ago security.php 2 years ago tasks.php 2 years ago wand.php 2 years ago
files.php
687 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 $info = $this->get_info( $refId );
115 return $info['type'] === 'image';
116 }
117
118 public function get_mime_type( $refId ) {
119 $path = $this->get_path( $refId );
120 if ( $path ) {
121 return Meow_MWAI_Core::get_mime_type( $path );
122 }
123 $url = $this->get_url( $refId );
124 if ( $url ) {
125 return Meow_MWAI_Core::get_mime_type( $url );
126 }
127 return null;
128 }
129
130 public function get_data( $refId ) {
131 $path = $this->get_path( $refId );
132 if ( $path ) {
133 $content = file_get_contents( $path );
134 return $content;
135 }
136 return null;
137 }
138
139 public function get_info( $refId ) {
140 $info = null;
141 if ( $this->check_db() ) {
142 $info = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT *
143 FROM $this->table_files
144 WHERE refId = %s", $refId
145 ), ARRAY_A );
146 }
147 if ( !$info ) {
148 $posts = get_posts( [ 'post_type' => 'attachment', 'meta_key' => '_mwai_file_id', 'meta_value' => $refId ] );
149 if ( $posts ) {
150 $post = $posts[0];
151 $info = [
152 'refId' => $refId,
153 'url' => wp_get_attachment_url( $post->ID ),
154 'path' => get_attached_file( $post->ID )
155 ];
156 }
157 }
158 if ( $info ) {
159 $info['metadata'] = $this->get_metadata( $refId );
160 }
161 return $info;
162 }
163
164 public function get_url( $refId ) {
165 $file = null;
166 if ( $this->check_db() ) {
167 $file = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT *
168 FROM $this->table_files
169 WHERE refId = %s", $refId
170 ) );
171 }
172 if ( $file ) {
173 return $file->url;
174 }
175 else {
176 $posts = get_posts( [ 'post_type' => 'attachment', 'meta_key' => '_mwai_file_id', 'meta_value' => $refId ] );
177 if ( $posts ) {
178 foreach ( $posts as $post ) {
179 return wp_get_attachment_url( $post->ID );
180 }
181 }
182 }
183 return null;
184 }
185
186 #region REST endpoints
187
188 public function rest_api_init() {
189 register_rest_route( $this->namespace, '/files/upload', array(
190 'methods' => 'POST',
191 'callback' => array( $this, 'rest_upload' ),
192 'permission_callback' => array( $this->core, 'check_rest_nonce' )
193 ) );
194 register_rest_route( $this->namespace, '/files/list', array(
195 'methods' => 'POST',
196 'callback' => array( $this, 'rest_list' ),
197 'permission_callback' => array( $this->core, 'check_rest_nonce' )
198 ) );
199 register_rest_route( $this->namespace, '/files/delete', array(
200 'methods' => 'POST',
201 'callback' => array( $this, 'rest_delete' ),
202 'permission_callback' => array( $this->core, 'check_rest_nonce' )
203 ) );
204 }
205
206
207 /*
208 * Record a new file in the Files database.
209 * This doesn't handle the upload or anything.
210 */
211 public function commit_file( $fileInfo ) {
212 if ( !$this->check_db() ) {
213 throw new Exception( 'Could not create database table.' );
214 }
215 $now = date( 'Y-m-d H:i:s' );
216 if ( empty( $fileInfo['refId'] ) ) {
217 if ( !empty( $fileInfo['url'] ) ) {
218 $fileInfo['refId'] = $this->generate_refId( $fileInfo['url'] );
219 }
220 else {
221 throw new Exception( 'File ID (or URL) is required.' );
222 }
223 }
224 if ( empty( $fileInfo['type'] ) ) {
225 $fileInfo['type'] = Meow_MWAI_Core::is_image( $fileInfo['url'] ) ? 'image' : 'file';
226 }
227 $success = $this->wpdb->insert( $this->table_files, [
228 'refId' => $fileInfo['refId'],
229 'envId' => empty( $fileInfo['envId'] ) ? null : $fileInfo['envId'],
230 'userId' => empty( $fileInfo['userId'] ) ? $this->core->get_user_id() : $fileInfo['userId'],
231 'purpose' => empty( $fileInfo['purpose'] ) ? null : $fileInfo['purpose'],
232 'type' => empty( $fileInfo['type'] ) ? null : $fileInfo['type'],
233 'status' => empty( $fileInfo['status'] ) ? null : $fileInfo['status'],
234 'created' => empty( $fileInfo['created'] ) ? $now : $fileInfo['created'],
235 'updated' => empty( $fileInfo['updated'] ) ? $now : $fileInfo['updated'],
236 'expires' => empty( $fileInfo['expires'] ) ? null : $fileInfo['expires'],
237 'path' => empty( $fileInfo['path'] ) ? null : $fileInfo['path'],
238 'url' => empty( $fileInfo['url'] ) ? null : $fileInfo['url']
239 ] );
240 // check for error
241 if ( !$success ) {
242 throw new Exception( 'Error while adding file in the DB (' . $this->wpdb->last_error . ')' );
243 }
244 return $this->wpdb->insert_id;
245 }
246
247 // Generate a refId from a URL or random, and make sure it's unique
248 public function generate_refId( $attempts = 0 ) {
249 $refId = md5( date( 'Y-m-d H:i:s' ) . '-' . $attempts );
250 $file = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT *
251 FROM $this->table_files
252 WHERE refId = %s", $refId
253 ) );
254 if ( $file ) {
255 return $this->generate_refId( $attempts + 1 );
256 }
257 return $refId;
258 }
259
260 public function upload_file( $path, $filename = null, $purpose = null,
261 $metadata = null, $envId = null, $target = null, $expiry = null ) {
262 require_once( ABSPATH . 'wp-admin/includes/image.php' );
263 require_once( ABSPATH . 'wp-admin/includes/file.php' );
264 require_once( ABSPATH . 'wp-admin/includes/media.php' );
265
266 $target = empty( $target ) ? $this->core->get_option( 'image_local_upload' ) : $target;
267 $expiry = empty( $expiry ) ? $this->core->get_option( 'image_expires' ) : $expiry;
268
269 $expires = ( $expiry === 'never' || empty( $expiry ) ) ? null : date( 'Y-m-d H:i:s', time() + intval( $expiry ) );
270 $refId = $this->generate_refId();
271 $url = null;
272 if ( empty( $filename ) ) {
273 $parsed_url = parse_url( $path, PHP_URL_PATH );
274 $filename = basename( $parsed_url );
275 $extension = pathinfo( $filename, PATHINFO_EXTENSION );
276 }
277 else {
278 $extension = pathinfo( $filename, PATHINFO_EXTENSION );
279 }
280 $newFilename = $refId . '.' . $extension;
281 $unique_filename = wp_unique_filename( wp_upload_dir()['path'], $newFilename );
282 $destination = wp_upload_dir()['path'] . '/' . $unique_filename;
283
284 if ( $target === 'uploads' ) {
285 if ( !$this->check_db() ) {
286 throw new Exception( 'Could not create database table.' );
287 }
288 if ( !copy( $path, $destination ) ) {
289 throw new Exception( 'Could not move the file.' );
290 }
291 $url = wp_upload_dir()['url'] . '/' . $unique_filename;
292
293 $now = date( 'Y-m-d H:i:s' );
294 $fileId = $this->commit_file( [
295 'refId' => $refId,
296 'envId' => $envId,
297 'purpose' => $purpose,
298 'type' => null,
299 'status' => 'uploaded',
300 'created' => $now,
301 'updated' => $now,
302 'expires' => $expires,
303 'path' => $destination,
304 'url' => $url
305 ] );
306 if ( $metadata && is_array( $metadata ) ) {
307 foreach ( $metadata as $metaKey => $metaValue ) {
308 $this->add_metadata( $fileId, $metaKey, $metaValue );
309 }
310 }
311
312 }
313 else if ( $target === 'library' ) {
314 if ( filter_var( $path, FILTER_VALIDATE_URL ) ) {
315 $tmp = download_url( $path );
316 if ( is_wp_error( $tmp ) ) {
317 throw new Exception( $tmp->get_error_message() );
318 }
319 $file_array = [ 'name' => $unique_filename, 'tmp_name' => $tmp ];
320 }
321 else {
322 $file_array = [ 'name' => $unique_filename, 'tmp_name' => $path ];
323 }
324 $id = media_handle_sideload( $file_array, 0 );
325 if ( is_wp_error( $id ) ) {
326 throw new Exception( $id->get_error_message() );
327 }
328 $url = wp_get_attachment_url( $id );
329 update_post_meta( $id, '_mwai_file_id', $refId );
330 update_post_meta( $id, '_mwai_file_expires', $expires );
331 }
332
333 return $refId;
334 }
335
336 public function add_metadata( $fileId, $metaKey, $metaValue ) {
337 $data = [
338 'file_id' => $fileId,
339 'meta_key' => $metaKey,
340 'meta_value' => $metaValue
341 ];
342 $res = $this->wpdb->insert( $this->table_filemeta, $data );
343 if ( $res === false ) {
344 $this->core->log( "⚠️ (Files) Error while writing files metadata (" . $this->wpdb->last_error . ")" );
345 return false;
346 }
347 return $this->wpdb->insert_id;
348 }
349
350 public function update_refId( $fileId, $refId ) {
351 if ( $this->check_db() ) {
352 $this->wpdb->update( $this->table_files, [ 'refId' => $refId ], [ 'id' => $fileId ] );
353 }
354 }
355
356 public function update_purpose( $fileId, $purpose ) {
357 if ( $this->check_db() ) {
358 $this->wpdb->update( $this->table_files, [ 'purpose' => $purpose ], [ 'id' => $fileId ] );
359 }
360 }
361
362 public function update_envId( $fileId, $envId ) {
363 if ( $this->check_db() ) {
364 $this->wpdb->update( $this->table_files, [ 'envId' => $envId ], [ 'id' => $fileId ] );
365 }
366 }
367
368 public function get_metadata( $refId, $fileId = null ) {
369 if ( !$fileId ) {
370 $fileId = $this->get_id_from_refId( $refId );
371 }
372 if ( $fileId ) {
373 $sql = $this->wpdb->prepare( "SELECT * FROM $this->table_filemeta WHERE file_id = %d", $fileId );
374 $metadata = $this->wpdb->get_results( $sql, ARRAY_A );
375 $meta = [];
376 foreach ( $metadata as $metaItem ) {
377 $meta[$metaItem['meta_key']] = $metaItem['meta_value'];
378 }
379 return $meta;
380 }
381 return null;
382 }
383
384 public function search( $userId = null, $purpose = null, $metadata = [], $envId = null ) {
385 list( $sql, $params ) = $this->_buildQuery( $userId, $purpose, $metadata, $envId, true );
386 $finalQuery = $this->wpdb->prepare( $sql, $params );
387 $files = $this->wpdb->get_results( $finalQuery, ARRAY_A );
388 foreach ( $files as &$file ) {
389 $file['metadata'] = $this->get_metadata( $file['refId'] );
390 }
391 return $files;
392 }
393
394 public function list( $userId = null, $purpose = null, $metadata = [],
395 $envId = null, $limit = 10, $offset = 0 )
396 {
397 list( $countSql, $countParams ) = $this->_buildQuery( $userId, $purpose, $metadata, $envId, false );
398 $total = $this->wpdb->get_var( $this->wpdb->prepare( $countSql, $countParams ) );
399
400 list( $fileSql, $fileParams ) = $this->_buildQuery( $userId, $purpose, $metadata, $envId, true );
401 if ( $limit ) {
402 $fileSql .= " LIMIT %d";
403 $fileParams[] = $limit;
404 }
405 if ( $offset ) {
406 $fileSql .= " OFFSET %d";
407 $fileParams[] = $offset;
408 }
409 $files = $this->wpdb->get_results( $this->wpdb->prepare( $fileSql, $fileParams ), ARRAY_A );
410 foreach ( $files as &$file ) {
411 $file['metadata'] = $this->get_metadata( $file['refId'] );
412 }
413 return [ 'files' => $files, 'total' => $total ];
414 }
415
416 private function _buildQuery( $userId, $purpose, $metadata, $envId, $selectStar ) {
417 $sql = $selectStar ? "SELECT * FROM $this->table_files WHERE 1=1" : "SELECT COUNT(*) FROM $this->table_files WHERE 1=1";
418 $params = [];
419
420 // Based on the old "search" function
421 $actualUserId = $this->core->get_user_id();
422 $canAdmin = $this->core->can_access_settings();
423 if ( $userId !== $actualUserId ) {
424 if ( !$canAdmin ) {
425 throw new Exception( 'You are not allowed to access files from another user.' );
426 }
427 }
428 if ( $userId ) {
429 $sql .= " AND userId = %d";
430 $params[] = $userId;
431 }
432 if ( $purpose ) {
433 if ( is_array( $purpose ) ) {
434 $sql .= " AND (";
435 foreach ( $purpose as $p ) {
436 $sql .= " purpose = %s OR";
437 $params[] = $p;
438 }
439 $sql = rtrim( $sql, 'OR' );
440 $sql .= ")";
441 }
442 else {
443 $sql .= " AND purpose = %s";
444 $params[] = $purpose;
445 }
446 }
447 if ( $metadata ) {
448 foreach ( $metadata as $metaKey => $metaValue ) {
449 $sql .= " AND EXISTS ( SELECT * FROM $this->table_filemeta
450 WHERE file_id = $this->table_files.id AND meta_key = %s AND meta_value = %s )";
451 $params[] = $metaKey;
452 $params[] = $metaValue;
453 }
454 }
455 if ( $envId ) {
456 $sql .= " AND envId = %s";
457 $params[] = $envId;
458 }
459 $sql .= " ORDER BY updated DESC";
460 return [ $sql, $params ];
461 }
462
463 // public function search( $userId = null, $purpose = null, $metadata = [], $limit = 10, $offset = 0 ) {
464 // $sql = "SELECT * FROM $this->table_files WHERE 1=1";
465 // $actualUserId = $this->core->get_user_id();
466 // $canAdmin = $this->core->can_access_settings();
467 // if ( $userId !== $actualUserId ) {
468 // if ( !$canAdmin ) {
469 // throw new Exception( 'You are not allowed to access files from another user.' );
470 // }
471 // }
472 // if ( $userId ) {
473 // $sql .= $this->wpdb->prepare( " AND userId = %d", $userId );
474 // }
475 // if ( $purpose ) {
476 // if ( is_array( $purpose ) ) {
477 // $sql .= " AND (";
478 // foreach ( $purpose as $p ) {
479 // $sql .= $this->wpdb->prepare( " purpose = %s OR", $p );
480 // }
481 // $sql = rtrim( $sql, 'OR' );
482 // $sql .= ")";
483 // }
484 // else {
485 // $sql .= $this->wpdb->prepare( " AND purpose = %s", $purpose );
486 // }
487 // }
488 // if ( $metadata ) {
489 // foreach ( $metadata as $metaKey => $metaValue ) {
490 // $sql .= $this->wpdb->prepare( " AND EXISTS ( SELECT * FROM $this->table_filemeta
491 // WHERE file_id = $this->table_files.id AND meta_key = %s AND meta_value = %s )",
492 // $metaKey, $metaValue
493 // );
494 // }
495 // }
496 // $sql .= " ORDER BY updated DESC";
497 // if ( $limit ) {
498 // $sql .= $this->wpdb->prepare( " LIMIT %d", $limit );
499 // }
500 // if ( $offset ) {
501 // $sql .= $this->wpdb->prepare( " OFFSET %d", $offset );
502 // }
503 // $files = $this->wpdb->get_results( $sql, ARRAY_A );
504
505 // // Add metadata
506 // foreach ( $files as &$file ) {
507 // $file['metadata'] = $this->get_metadata( $file['refId'] );
508 // }
509
510 // return $files;
511 // }
512
513 public function get_id_from_refId( $refId ) {
514 $file = null;
515 if ( $this->check_db() ) {
516 $file = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT *
517 FROM $this->table_files
518 WHERE refId = %s", $refId
519 ) );
520 }
521 if ( $file ) {
522 return $file->id;
523 }
524 return null;
525 }
526
527 public function add_metadata_from_refId( $refId, $metaKey, $metaValue ) {
528 $fileId = $this->get_id_from_refId( $refId );
529 if ( $fileId ) {
530 return $this->add_metadata( $fileId, $metaKey, $metaValue );
531 }
532 return false;
533 }
534
535 public function rest_list( $request ) {
536 $params = $request->get_json_params();
537 $userId = empty( $params['userId'] ) ? null : $params['userId'];
538 $envId = empty( $params['envId'] ) ? null : $params['envId'];
539 $purpose = empty( $params['purpose'] ) ? null : $params['purpose'];
540 $metadata = empty( $params['metadata'] ) ? null : json_decode( $params['metadata'], true );
541 $limit = empty( $params['limit'] ) ? 10 : intval( $params['limit'] );
542 $offset = empty( $params['page'] ) ? 0 : ( intval( $params['page'] ) - 1) * $limit;
543 $files = $this->list( $userId, $purpose, $metadata, $envId, $limit, $offset );
544 return new WP_REST_Response( [ 'success' => true, 'data' => $files ], 200 );
545 }
546
547 public function rest_delete( $request ) {
548 $params = $request->get_json_params();
549 $fileIds = empty( $params['files'] ) ? [] : $params['files'];
550 $this->delete_files( $fileIds );
551 return new WP_REST_Response( [ 'success' => true ], 200 );
552 }
553
554 public function delete_files( $fileIds ) {
555 $query = "SELECT refId, path FROM $this->table_files WHERE id IN (";
556 $params = [];
557 foreach ( $fileIds as $fileId ) {
558 $query .= "%s,";
559 $params[] = $fileId;
560 }
561 $query = rtrim( $query, ',' );
562 $query .= ")";
563 $files = $this->wpdb->get_results( $this->wpdb->prepare( $query, $params ), ARRAY_A );
564 $refIds = apply_filters( 'mwai_files_delete', array_column( $files, 'refId' ) );
565 foreach ( $files as $file ) {
566 if ( in_array( $file['refId'], $refIds ) ) {
567 $this->wpdb->delete( $this->table_files, [ 'refId' => $file['refId'] ] );
568 if ( file_exists( $file['path'] ) ) {
569 unlink( $file['path'] );
570 }
571 }
572 }
573 }
574
575 public function rest_upload() {
576 if ( empty( $_FILES['file'] ) ) {
577 return new WP_REST_Response( [ 'success' => false, 'message' => 'No file provided.' ], 400 );
578 }
579 $file = $_FILES['file'];
580 $purpose = empty( $_POST['purpose'] ) ? null : $_POST['purpose'];
581 $metadata = empty( $_POST['metadata'] ) ? null : json_decode( $_POST['metadata'], true );
582 $envId = empty( $_POST['envId'] ) ? null : $_POST['envId'];
583 if ( !$purpose ) {
584 return new WP_REST_Response( [ 'success' => false, 'message' => 'Purpose is required.' ], 400 );
585 }
586 $fileTypeCheck = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'] );
587 if ( !$fileTypeCheck['type'] ) {
588 return new WP_REST_Response( [ 'success' => false, 'message' => 'Invalid file type.' ], 400 );
589 }
590
591 try {
592 $refId = $this->upload_file( $file['tmp_name'], $file['name'], $purpose, $metadata, $envId );
593 $url = $this->get_url( $refId );
594 return new WP_REST_Response( [
595 'success' => true,
596 'data' => [ 'id' => $refId, 'url' => $url ]
597 ], 200 );
598 }
599 catch ( Exception $e ) {
600 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
601 }
602 }
603
604 #endregion
605
606 #region Database functions
607
608 function create_db() {
609 $charset_collate = $this->wpdb->get_charset_collate();
610 $sql = "CREATE TABLE $this->table_files (
611 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
612 refId VARCHAR(64) NOT NULL,
613 envId VARCHAR(128) NULL,
614 userId BIGINT(20) UNSIGNED NULL,
615 type VARCHAR(32) NULL,
616 status VARCHAR(32) NULL,
617 purpose VARCHAR(32) NULL,
618 created DATETIME NOT NULL,
619 updated DATETIME NOT NULL,
620 expires DATETIME NULL,
621 path TEXT NULL,
622 url TEXT NULL,
623 PRIMARY KEY (id),
624 UNIQUE KEY unique_file_id (refId)
625 ) $charset_collate;";
626
627 $sqlFileMeta = "CREATE TABLE $this->table_filemeta (
628 meta_id BIGINT(20) NOT NULL AUTO_INCREMENT,
629 file_id BIGINT(20) NOT NULL,
630 meta_key varchar(255) NULL,
631 meta_value longtext NULL,
632 PRIMARY KEY (meta_id)
633 ) $charset_collate;";
634
635 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
636 dbDelta( $sql );
637 dbDelta( $sqlFileMeta );
638 }
639
640 function check_db() {
641 if ( $this->db_check ) {
642 return true;
643 }
644
645 // Check if table_files exists
646 $sql = $this->wpdb->prepare( "SHOW TABLES LIKE %s", $this->table_files );
647 $table_files_exists = strtolower( $this->wpdb->get_var( $sql )) === strtolower( $this->table_files );
648
649 // Check if table_filemeta exists
650 $sqlMeta = $this->wpdb->prepare( "SHOW TABLES LIKE %s", $this->table_filemeta );
651 $table_filemeta_exists = strtolower( $this->wpdb->get_var( $sqlMeta )) === strtolower( $this->table_filemeta );
652
653 // If either table does not exist, create them
654 if ( !$table_files_exists || !$table_filemeta_exists ) {
655 $this->create_db();
656 }
657
658 // Update db_check for both tables
659 $this->db_check = $table_files_exists && $table_filemeta_exists;
660
661 // LATER: REMOVE THIS AFTER MARCH 2024
662 $this->db_check = $this->db_check && $this->wpdb->get_var( "SHOW COLUMNS FROM $this->table_files LIKE 'userId'" );
663 if ( !$this->db_check ) {
664 $this->wpdb->query( "ALTER TABLE $this->table_files ADD COLUMN userId BIGINT(20) UNSIGNED NULL" );
665 $this->wpdb->query( "ALTER TABLE $this->table_files ADD COLUMN purpose VARCHAR(32) NULL" );
666 $this->wpdb->query( "ALTER TABLE $this->table_files MODIFY COLUMN path TEXT NULL" );
667 $this->wpdb->query( "ALTER TABLE $this->table_files DROP COLUMN metadata" );
668 $this->db_check = true;
669 }
670 // LATER: REMOVE THIS AFTER MARCH 2024
671 $this->db_check = $this->db_check && !$this->wpdb->get_var( "SHOW COLUMNS FROM $this->table_files LIKE 'fileId'" );
672 if ( !$this->db_check ) {
673 $this->wpdb->query( "ALTER TABLE $this->table_files ADD COLUMN refId VARCHAR(64) NOT NULL" );
674 $this->wpdb->query( "ALTER TABLE $this->table_files DROP COLUMN fileId" );
675 $this->db_check = true;
676 }
677 // LATER: REMOVE THIS AFTER MARCH 2024
678 $this->db_check = $this->db_check && $this->wpdb->get_var( "SHOW COLUMNS FROM $this->table_files LIKE 'envId'" );
679 if ( !$this->db_check ) {
680 $this->wpdb->query( "ALTER TABLE $this->table_files ADD COLUMN envId VARCHAR(128) NULL" );
681 $this->db_check = true;
682 }
683 return $this->db_check;
684 }
685
686 #endregion
687 }