PluginProbe
AI Builder – Generate pages, blocks, images & translate with AI / 2.3.10
AI Builder – Generate pages, blocks, images & translate with AI v2.3.10
2.7.10 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 All 122 releases
ai-builder / includes / class-generations-storage.php

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

404 lines 12.9 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 'blocksJson' => isset($payload['blocksJson']) && is_array($payload['blocksJson']) ? $payload['blocksJson'] : array(),
97 'status' => 'Pending review',
98 'createdAt' => current_time('mysql'),
99 'applied' => false,
100 'pageId' => 0,
101 'userId' => $user_id,
102 );
103
104 // Write JSON file
105 $json_content = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
106
107 if (file_put_contents($filepath, $json_content) === false) {
108 return new WP_Error('file_write_error', 'Failed to save generation file');
109 }
110
111 // Update index for quick listing
112 $this->update_index($user_id, $id, array(
113 'filename' => $filename,
114 'title' => $data['title'],
115 'status' => $data['status'],
116 'createdAt' => $data['createdAt'],
117 ));
118
119 return $data;
120 }
121
122 /**
123 * Get a generation by ID
124 */
125 public function get($id, $user_id = null)
126 {
127 $filepath = $this->find_file_by_id($id, $user_id);
128
129 if (!$filepath || !file_exists($filepath)) {
130 return new WP_Error('not_found', 'Generation not found');
131 }
132
133 $json_content = file_get_contents($filepath);
134
135 if ($json_content === false) {
136 return new WP_Error('file_read_error', 'Failed to read generation file');
137 }
138
139 $data = json_decode($json_content, true);
140
141 if (!$data || !is_array($data)) {
142 return new WP_Error('invalid_json', 'Invalid JSON in generation file');
143 }
144
145 return $data;
146 }
147
148 /**
149 * Get all generations for a user
150 */
151 public function get_all($user_id = null)
152 {
153 if (!$user_id) {
154 $user_id = get_current_user_id();
155 }
156
157 $user_dir = $this->get_user_dir($user_id);
158 $files = glob($user_dir . '/gen-*.json');
159
160 $items = array();
161
162 foreach ($files as $filepath) {
163 $json_content = file_get_contents($filepath);
164 if ($json_content === false) continue;
165
166 $data = json_decode($json_content, true);
167 if (!$data || !is_array($data)) continue;
168
169 // Return only essential info (not full blocks JSON)
170 $items[] = array(
171 'id' => $data['id'] ?? '',
172 'title' => $data['title'] ?? '',
173 'status' => $data['status'] ?? 'Pending review',
174 'createdAt' => $data['createdAt'] ?? '',
175 'applied' => $data['applied'] ?? false,
176 'pageId' => $data['pageId'] ?? 0,
177 );
178 }
179
180 // Sort by date (newest first)
181 usort($items, function($a, $b) {
182 return strcmp($b['createdAt'] ?? '', $a['createdAt'] ?? '');
183 });
184
185 return $items;
186 }
187
188 /**
189 * Mark a generation as applied
190 */
191 public function mark_applied($id, $page_id = 0, $user_id = null)
192 {
193 $filepath = $this->find_file_by_id($id, $user_id);
194
195 if (!$filepath || !file_exists($filepath)) {
196 return new WP_Error('not_found', 'Generation not found');
197 }
198
199 $json_content = file_get_contents($filepath);
200 $data = json_decode($json_content, true);
201
202 if (!$data || !is_array($data)) {
203 return new WP_Error('invalid_json', 'Invalid JSON in generation file');
204 }
205
206 $data['status'] = 'Applied';
207 $data['applied'] = true;
208 if ($page_id > 0) {
209 $data['pageId'] = intval($page_id);
210 }
211
212 // Rewrite file
213 $updated_json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
214
215 if (file_put_contents($filepath, $updated_json) === false) {
216 return new WP_Error('file_write_error', 'Failed to update generation file');
217 }
218
219 // Update index
220 if (!$user_id) {
221 $user_id = get_current_user_id();
222 }
223 $this->update_index($user_id, $id, array(
224 'filename' => basename($filepath),
225 'title' => $data['title'],
226 'status' => 'Applied',
227 'createdAt' => $data['createdAt'],
228 ));
229
230 return $data;
231 }
232
233 /**
234 * Delete a generation file
235 */
236 public function delete($id, $user_id = null)
237 {
238 $filepath = $this->find_file_by_id($id, $user_id);
239
240 if (!$filepath || !file_exists($filepath)) {
241 return new WP_Error('not_found', 'Generation not found');
242 }
243
244 if (unlink($filepath) === false) {
245 return new WP_Error('file_delete_error', 'Failed to delete generation file');
246 }
247
248 // Update index
249 if (!$user_id) {
250 $user_id = get_current_user_id();
251 }
252 $this->remove_from_index($user_id, $id);
253
254 return true;
255 }
256
257 /**
258 * Update index for quick listing
259 */
260 private function update_index($user_id, $id, $info)
261 {
262 $index_key = 'aibui_gen_index_' . intval($user_id);
263 $index = get_option($index_key, array());
264
265 if (!is_array($index)) {
266 $index = array();
267 }
268
269 $index[$id] = $info;
270 update_option($index_key, $index, false);
271 }
272
273 /**
274 * Remove from index
275 */
276 private function remove_from_index($user_id, $id)
277 {
278 $index_key = 'aibui_gen_index_' . intval($user_id);
279 $index = get_option($index_key, array());
280
281 if (is_array($index) && isset($index[$id])) {
282 unset($index[$id]);
283 update_option($index_key, $index, false);
284 }
285 }
286
287 /**
288 * Cleanup old applied generations (older than X days)
289 */
290 public function cleanup_old($days = 30)
291 {
292 if (!file_exists($this->base_dir)) {
293 return 0;
294 }
295
296 $deleted_count = 0;
297 $user_dirs = glob($this->base_dir . '/user-*', GLOB_ONLYDIR);
298
299 foreach ($user_dirs as $user_dir) {
300 $files = glob($user_dir . '/gen-*.json');
301
302 foreach ($files as $filepath) {
303 $json_content = file_get_contents($filepath);
304 if ($json_content === false) continue;
305
306 $data = json_decode($json_content, true);
307 if (!$data || !is_array($data)) continue;
308
309 // Delete if applied and older than X days
310 if (($data['applied'] ?? false) && isset($data['createdAt'])) {
311 $created = strtotime($data['createdAt']);
312 if ($created === false) continue;
313
314 $age_days = (time() - $created) / DAY_IN_SECONDS;
315
316 if ($age_days > $days) {
317 if (unlink($filepath)) {
318 $deleted_count++;
319
320 // Update index
321 $user_id = $data['userId'] ?? 0;
322 if ($user_id) {
323 $this->remove_from_index($user_id, $data['id'] ?? '');
324 }
325 }
326 }
327 }
328 }
329 }
330
331 return $deleted_count;
332 }
333
334 /**
335 * Migrate old wp_options data to files (one-time migration)
336 */
337 public function migrate_from_options()
338 {
339 $old_data = get_option('aibui_multi_page_generations', array());
340
341 if (empty($old_data) || !is_array($old_data)) {
342 return 0;
343 }
344
345 $migrated_count = 0;
346
347 foreach ($old_data as $id => $generation) {
348 if (!is_array($generation)) continue;
349
350 // Try to determine user ID from generation data or use current user
351 $user_id = isset($generation['userId']) ? intval($generation['userId']) : get_current_user_id();
352
353 // Temporarily set user context for migration
354 $original_user = get_current_user_id();
355 if ($user_id !== $original_user) {
356 // Note: This is a limitation - we can't easily switch user context
357 // So we'll use the current user's directory
358 $user_id = $original_user;
359 }
360
361 $user_dir = $this->get_user_dir($user_id);
362 $filename = 'gen-' . date('Ymd-His', strtotime($generation['createdAt'] ?? 'now')) . '-' . sanitize_file_name($id) . '.json';
363 $filepath = $user_dir . '/' . $filename;
364
365 // Check if already migrated
366 if (file_exists($filepath)) {
367 continue;
368 }
369
370 // Prepare data
371 $data = array(
372 'id' => $id,
373 'title' => sanitize_text_field($generation['title'] ?? ''),
374 'metaDesc' => sanitize_textarea_field($generation['metaDesc'] ?? ''),
375 'cssContent' => wp_kses_post($generation['cssContent'] ?? ''),
376 'blocksJson' => isset($generation['blocksJson']) && is_array($generation['blocksJson']) ? $generation['blocksJson'] : array(),
377 'status' => $generation['status'] ?? 'Pending review',
378 'createdAt' => $generation['createdAt'] ?? current_time('mysql'),
379 'applied' => $generation['applied'] ?? false,
380 'pageId' => isset($generation['pageId']) ? intval($generation['pageId']) : 0,
381 'userId' => $user_id,
382 );
383
384 // Write file
385 $json_content = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
386
387 if (file_put_contents($filepath, $json_content) !== false) {
388 $migrated_count++;
389
390 // Update index
391 $this->update_index($user_id, $id, array(
392 'filename' => $filename,
393 'title' => $data['title'],
394 'status' => $data['status'],
395 'createdAt' => $data['createdAt'],
396 ));
397 }
398 }
399
400 return $migrated_count;
401 }
402 }
403
404