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