PluginProbe
AI Builder – Generate pages, blocks, images & translate with AI / 2.7.2
AI Builder – Generate pages, blocks, images & translate with AI v2.7.2
2.7.9 2.7.8 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.3.10 2.3.11 All 121 releases
ai-builder / includes / class-generations-storage.php

class-generations-storage.php in AI Builder – Generate pages, blocks, images & translate with AI 2.7.2, at includes/class-generations-storage.php

406 lines 13.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class to handle Multi-Page Generator storage using JSON files
4 * Stores generations in wp-content/uploads/ai-builder-generations/
5 */
6
7 if (!defined('ABSPATH'))
8 exit;
9
10 class AIBUI_Generations_Storage
11 {
12 private $base_dir;
13
14 public function __construct()
15 {
16 $upload_dir = wp_upload_dir();
17 $this->base_dir = $upload_dir['basedir'] . '/ai-builder-generations';
18 $this->ensure_directories_exist();
19 }
20
21 /**
22 * Ensure base directory and .htaccess exist
23 */
24 private function ensure_directories_exist()
25 {
26 if (!file_exists($this->base_dir)) {
27 wp_mkdir_p($this->base_dir);
28
29 // Create .htaccess to protect files
30 $htaccess_content = "deny from all\n";
31 file_put_contents($this->base_dir . '/.htaccess', $htaccess_content);
32 }
33 }
34
35 /**
36 * Get user-specific directory for generations
37 */
38 private function get_user_dir($user_id = null)
39 {
40 if (!$user_id) {
41 $user_id = get_current_user_id();
42 }
43
44 $user_dir = $this->base_dir . '/user-' . intval($user_id);
45
46 if (!file_exists($user_dir)) {
47 wp_mkdir_p($user_dir);
48 }
49
50 return $user_dir;
51 }
52
53 /**
54 * Generate filename for a generation
55 */
56 private function get_filename($id)
57 {
58 $sanitized_id = sanitize_file_name($id);
59 return 'gen-' . date('Ymd-His') . '-' . $sanitized_id . '.json';
60 }
61
62 /**
63 * Find file by generation ID
64 */
65 private function find_file_by_id($id, $user_id = null)
66 {
67 if (!$user_id) {
68 $user_id = get_current_user_id();
69 }
70
71 $user_dir = $this->get_user_dir($user_id);
72 $pattern = $user_dir . '/gen-*-' . sanitize_file_name($id) . '.json';
73 $files = glob($pattern);
74
75 return !empty($files) ? $files[0] : null;
76 }
77
78 /**
79 * Save a generation to file
80 */
81 public function save($payload)
82 {
83 $user_id = get_current_user_id();
84 $user_dir = $this->get_user_dir($user_id);
85
86 $id = isset($payload['id']) && is_string($payload['id']) ? $payload['id'] : wp_generate_uuid4();
87 $filename = $this->get_filename($id);
88 $filepath = $user_dir . '/' . $filename;
89
90 // Prepare data structure
91 $data = array(
92 'id' => $id,
93 'title' => sanitize_text_field($payload['title'] ?? ''),
94 'metaDesc' => sanitize_textarea_field($payload['metaDesc'] ?? ''),
95 'cssContent' => wp_kses_post($payload['cssContent'] ?? ''),
96 'jsContent' => wp_kses_post($payload['jsContent'] ?? ''),
97 'blocksJson' => isset($payload['blocksJson']) && is_array($payload['blocksJson']) ? $payload['blocksJson'] : array(),
98 'status' => 'Pending review',
99 'createdAt' => current_time('mysql'),
100 'applied' => false,
101 'pageId' => 0,
102 'userId' => $user_id,
103 );
104
105 // Write JSON file
106 $json_content = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
107
108 if (file_put_contents($filepath, $json_content) === false) {
109 return new WP_Error('file_write_error', 'Failed to save generation file');
110 }
111
112 // Update index for quick listing
113 $this->update_index($user_id, $id, array(
114 'filename' => $filename,
115 'title' => $data['title'],
116 'status' => $data['status'],
117 'createdAt' => $data['createdAt'],
118 ));
119
120 return $data;
121 }
122
123 /**
124 * Get a generation by ID
125 */
126 public function get($id, $user_id = null)
127 {
128 $filepath = $this->find_file_by_id($id, $user_id);
129
130 if (!$filepath || !file_exists($filepath)) {
131 return new WP_Error('not_found', 'Generation not found');
132 }
133
134 $json_content = file_get_contents($filepath);
135
136 if ($json_content === false) {
137 return new WP_Error('file_read_error', 'Failed to read generation file');
138 }
139
140 $data = json_decode($json_content, true);
141
142 if (!$data || !is_array($data)) {
143 return new WP_Error('invalid_json', 'Invalid JSON in generation file');
144 }
145
146 return $data;
147 }
148
149 /**
150 * Get all generations for a user
151 */
152 public function get_all($user_id = null)
153 {
154 if (!$user_id) {
155 $user_id = get_current_user_id();
156 }
157
158 $user_dir = $this->get_user_dir($user_id);
159 $files = glob($user_dir . '/gen-*.json');
160
161 $items = array();
162
163 foreach ($files as $filepath) {
164 $json_content = file_get_contents($filepath);
165 if ($json_content === false) continue;
166
167 $data = json_decode($json_content, true);
168 if (!$data || !is_array($data)) continue;
169
170 // Return only essential info (not full blocks JSON)
171 $items[] = array(
172 'id' => $data['id'] ?? '',
173 'title' => $data['title'] ?? '',
174 'status' => $data['status'] ?? 'Pending review',
175 'createdAt' => $data['createdAt'] ?? '',
176 'applied' => $data['applied'] ?? false,
177 'pageId' => $data['pageId'] ?? 0,
178 );
179 }
180
181 // Sort by date (newest first)
182 usort($items, function($a, $b) {
183 return strcmp($b['createdAt'] ?? '', $a['createdAt'] ?? '');
184 });
185
186 return $items;
187 }
188
189 /**
190 * Mark a generation as applied
191 */
192 public function mark_applied($id, $page_id = 0, $user_id = null)
193 {
194 $filepath = $this->find_file_by_id($id, $user_id);
195
196 if (!$filepath || !file_exists($filepath)) {
197 return new WP_Error('not_found', 'Generation not found');
198 }
199
200 $json_content = file_get_contents($filepath);
201 $data = json_decode($json_content, true);
202
203 if (!$data || !is_array($data)) {
204 return new WP_Error('invalid_json', 'Invalid JSON in generation file');
205 }
206
207 $data['status'] = 'Applied';
208 $data['applied'] = true;
209 if ($page_id > 0) {
210 $data['pageId'] = intval($page_id);
211 }
212
213 // Rewrite file
214 $updated_json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
215
216 if (file_put_contents($filepath, $updated_json) === false) {
217 return new WP_Error('file_write_error', 'Failed to update generation file');
218 }
219
220 // Update index
221 if (!$user_id) {
222 $user_id = get_current_user_id();
223 }
224 $this->update_index($user_id, $id, array(
225 'filename' => basename($filepath),
226 'title' => $data['title'],
227 'status' => 'Applied',
228 'createdAt' => $data['createdAt'],
229 ));
230
231 return $data;
232 }
233
234 /**
235 * Delete a generation file
236 */
237 public function delete($id, $user_id = null)
238 {
239 $filepath = $this->find_file_by_id($id, $user_id);
240
241 if (!$filepath || !file_exists($filepath)) {
242 return new WP_Error('not_found', 'Generation not found');
243 }
244
245 if (unlink($filepath) === false) {
246 return new WP_Error('file_delete_error', 'Failed to delete generation file');
247 }
248
249 // Update index
250 if (!$user_id) {
251 $user_id = get_current_user_id();
252 }
253 $this->remove_from_index($user_id, $id);
254
255 return true;
256 }
257
258 /**
259 * Update index for quick listing
260 */
261 private function update_index($user_id, $id, $info)
262 {
263 $index_key = 'aibui_gen_index_' . intval($user_id);
264 $index = get_option($index_key, array());
265
266 if (!is_array($index)) {
267 $index = array();
268 }
269
270 $index[$id] = $info;
271 update_option($index_key, $index, false);
272 }
273
274 /**
275 * Remove from index
276 */
277 private function remove_from_index($user_id, $id)
278 {
279 $index_key = 'aibui_gen_index_' . intval($user_id);
280 $index = get_option($index_key, array());
281
282 if (is_array($index) && isset($index[$id])) {
283 unset($index[$id]);
284 update_option($index_key, $index, false);
285 }
286 }
287
288 /**
289 * Cleanup old applied generations (older than X days)
290 */
291 public function cleanup_old($days = 30)
292 {
293 if (!file_exists($this->base_dir)) {
294 return 0;
295 }
296
297 $deleted_count = 0;
298 $user_dirs = glob($this->base_dir . '/user-*', GLOB_ONLYDIR);
299
300 foreach ($user_dirs as $user_dir) {
301 $files = glob($user_dir . '/gen-*.json');
302
303 foreach ($files as $filepath) {
304 $json_content = file_get_contents($filepath);
305 if ($json_content === false) continue;
306
307 $data = json_decode($json_content, true);
308 if (!$data || !is_array($data)) continue;
309
310 // Delete if applied and older than X days
311 if (($data['applied'] ?? false) && isset($data['createdAt'])) {
312 $created = strtotime($data['createdAt']);
313 if ($created === false) continue;
314
315 $age_days = (time() - $created) / DAY_IN_SECONDS;
316
317 if ($age_days > $days) {
318 if (unlink($filepath)) {
319 $deleted_count++;
320
321 // Update index
322 $user_id = $data['userId'] ?? 0;
323 if ($user_id) {
324 $this->remove_from_index($user_id, $data['id'] ?? '');
325 }
326 }
327 }
328 }
329 }
330 }
331
332 return $deleted_count;
333 }
334
335 /**
336 * Migrate old wp_options data to files (one-time migration)
337 */
338 public function migrate_from_options()
339 {
340 $old_data = get_option('aibui_multi_page_generations', array());
341
342 if (empty($old_data) || !is_array($old_data)) {
343 return 0;
344 }
345
346 $migrated_count = 0;
347
348 foreach ($old_data as $id => $generation) {
349 if (!is_array($generation)) continue;
350
351 // Try to determine user ID from generation data or use current user
352 $user_id = isset($generation['userId']) ? intval($generation['userId']) : get_current_user_id();
353
354 // Temporarily set user context for migration
355 $original_user = get_current_user_id();
356 if ($user_id !== $original_user) {
357 // Note: This is a limitation - we can't easily switch user context
358 // So we'll use the current user's directory
359 $user_id = $original_user;
360 }
361
362 $user_dir = $this->get_user_dir($user_id);
363 $filename = 'gen-' . date('Ymd-His', strtotime($generation['createdAt'] ?? 'now')) . '-' . sanitize_file_name($id) . '.json';
364 $filepath = $user_dir . '/' . $filename;
365
366 // Check if already migrated
367 if (file_exists($filepath)) {
368 continue;
369 }
370
371 // Prepare data
372 $data = array(
373 'id' => $id,
374 'title' => sanitize_text_field($generation['title'] ?? ''),
375 'metaDesc' => sanitize_textarea_field($generation['metaDesc'] ?? ''),
376 'cssContent' => wp_kses_post($generation['cssContent'] ?? ''),
377 'jsContent' => wp_kses_post($generation['jsContent'] ?? ''),
378 'blocksJson' => isset($generation['blocksJson']) && is_array($generation['blocksJson']) ? $generation['blocksJson'] : array(),
379 'status' => $generation['status'] ?? 'Pending review',
380 'createdAt' => $generation['createdAt'] ?? current_time('mysql'),
381 'applied' => $generation['applied'] ?? false,
382 'pageId' => isset($generation['pageId']) ? intval($generation['pageId']) : 0,
383 'userId' => $user_id,
384 );
385
386 // Write file
387 $json_content = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
388
389 if (file_put_contents($filepath, $json_content) !== false) {
390 $migrated_count++;
391
392 // Update index
393 $this->update_index($user_id, $id, array(
394 'filename' => $filename,
395 'title' => $data['title'],
396 'status' => $data['status'],
397 'createdAt' => $data['createdAt'],
398 ));
399 }
400 }
401
402 return $migrated_count;
403 }
404 }
405
406