base_dir = $upload_dir['basedir'] . '/ai-builder-generations'; $this->ensure_directories_exist(); } /** * Ensure base directory and .htaccess exist */ private function ensure_directories_exist() { if (!file_exists($this->base_dir)) { wp_mkdir_p($this->base_dir); // Create .htaccess to protect files $htaccess_content = "deny from all\n"; file_put_contents($this->base_dir . '/.htaccess', $htaccess_content); } } /** * Get user-specific directory for generations */ private function get_user_dir($user_id = null) { if (!$user_id) { $user_id = get_current_user_id(); } $user_dir = $this->base_dir . '/user-' . intval($user_id); if (!file_exists($user_dir)) { wp_mkdir_p($user_dir); } return $user_dir; } /** * Generate filename for a generation */ private function get_filename($id) { $sanitized_id = sanitize_file_name($id); return 'gen-' . date('Ymd-His') . '-' . $sanitized_id . '.json'; } /** * Find file by generation ID */ private function find_file_by_id($id, $user_id = null) { if (!$user_id) { $user_id = get_current_user_id(); } $user_dir = $this->get_user_dir($user_id); $pattern = $user_dir . '/gen-*-' . sanitize_file_name($id) . '.json'; $files = glob($pattern); return !empty($files) ? $files[0] : null; } /** * Save a generation to file */ public function save($payload) { $user_id = get_current_user_id(); $user_dir = $this->get_user_dir($user_id); $id = isset($payload['id']) && is_string($payload['id']) ? $payload['id'] : wp_generate_uuid4(); $filename = $this->get_filename($id); $filepath = $user_dir . '/' . $filename; // Prepare data structure $data = array( 'id' => $id, 'title' => sanitize_text_field($payload['title'] ?? ''), 'metaDesc' => sanitize_textarea_field($payload['metaDesc'] ?? ''), 'cssContent' => wp_kses_post($payload['cssContent'] ?? ''), 'blocksJson' => isset($payload['blocksJson']) && is_array($payload['blocksJson']) ? $payload['blocksJson'] : array(), 'status' => 'Pending review', 'createdAt' => current_time('mysql'), 'applied' => false, 'pageId' => 0, 'userId' => $user_id, ); // Write JSON file $json_content = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if (file_put_contents($filepath, $json_content) === false) { return new WP_Error('file_write_error', 'Failed to save generation file'); } // Update index for quick listing $this->update_index($user_id, $id, array( 'filename' => $filename, 'title' => $data['title'], 'status' => $data['status'], 'createdAt' => $data['createdAt'], )); return $data; } /** * Get a generation by ID */ public function get($id, $user_id = null) { $filepath = $this->find_file_by_id($id, $user_id); if (!$filepath || !file_exists($filepath)) { return new WP_Error('not_found', 'Generation not found'); } $json_content = file_get_contents($filepath); if ($json_content === false) { return new WP_Error('file_read_error', 'Failed to read generation file'); } $data = json_decode($json_content, true); if (!$data || !is_array($data)) { return new WP_Error('invalid_json', 'Invalid JSON in generation file'); } return $data; } /** * Get all generations for a user */ public function get_all($user_id = null) { if (!$user_id) { $user_id = get_current_user_id(); } $user_dir = $this->get_user_dir($user_id); $files = glob($user_dir . '/gen-*.json'); $items = array(); foreach ($files as $filepath) { $json_content = file_get_contents($filepath); if ($json_content === false) continue; $data = json_decode($json_content, true); if (!$data || !is_array($data)) continue; // Return only essential info (not full blocks JSON) $items[] = array( 'id' => $data['id'] ?? '', 'title' => $data['title'] ?? '', 'status' => $data['status'] ?? 'Pending review', 'createdAt' => $data['createdAt'] ?? '', 'applied' => $data['applied'] ?? false, 'pageId' => $data['pageId'] ?? 0, ); } // Sort by date (newest first) usort($items, function($a, $b) { return strcmp($b['createdAt'] ?? '', $a['createdAt'] ?? ''); }); return $items; } /** * Mark a generation as applied */ public function mark_applied($id, $page_id = 0, $user_id = null) { $filepath = $this->find_file_by_id($id, $user_id); if (!$filepath || !file_exists($filepath)) { return new WP_Error('not_found', 'Generation not found'); } $json_content = file_get_contents($filepath); $data = json_decode($json_content, true); if (!$data || !is_array($data)) { return new WP_Error('invalid_json', 'Invalid JSON in generation file'); } $data['status'] = 'Applied'; $data['applied'] = true; if ($page_id > 0) { $data['pageId'] = intval($page_id); } // Rewrite file $updated_json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if (file_put_contents($filepath, $updated_json) === false) { return new WP_Error('file_write_error', 'Failed to update generation file'); } // Update index if (!$user_id) { $user_id = get_current_user_id(); } $this->update_index($user_id, $id, array( 'filename' => basename($filepath), 'title' => $data['title'], 'status' => 'Applied', 'createdAt' => $data['createdAt'], )); return $data; } /** * Delete a generation file */ public function delete($id, $user_id = null) { $filepath = $this->find_file_by_id($id, $user_id); if (!$filepath || !file_exists($filepath)) { return new WP_Error('not_found', 'Generation not found'); } if (unlink($filepath) === false) { return new WP_Error('file_delete_error', 'Failed to delete generation file'); } // Update index if (!$user_id) { $user_id = get_current_user_id(); } $this->remove_from_index($user_id, $id); return true; } /** * Update index for quick listing */ private function update_index($user_id, $id, $info) { $index_key = 'aibui_gen_index_' . intval($user_id); $index = get_option($index_key, array()); if (!is_array($index)) { $index = array(); } $index[$id] = $info; update_option($index_key, $index, false); } /** * Remove from index */ private function remove_from_index($user_id, $id) { $index_key = 'aibui_gen_index_' . intval($user_id); $index = get_option($index_key, array()); if (is_array($index) && isset($index[$id])) { unset($index[$id]); update_option($index_key, $index, false); } } /** * Cleanup old applied generations (older than X days) */ public function cleanup_old($days = 30) { if (!file_exists($this->base_dir)) { return 0; } $deleted_count = 0; $user_dirs = glob($this->base_dir . '/user-*', GLOB_ONLYDIR); foreach ($user_dirs as $user_dir) { $files = glob($user_dir . '/gen-*.json'); foreach ($files as $filepath) { $json_content = file_get_contents($filepath); if ($json_content === false) continue; $data = json_decode($json_content, true); if (!$data || !is_array($data)) continue; // Delete if applied and older than X days if (($data['applied'] ?? false) && isset($data['createdAt'])) { $created = strtotime($data['createdAt']); if ($created === false) continue; $age_days = (time() - $created) / DAY_IN_SECONDS; if ($age_days > $days) { if (unlink($filepath)) { $deleted_count++; // Update index $user_id = $data['userId'] ?? 0; if ($user_id) { $this->remove_from_index($user_id, $data['id'] ?? ''); } } } } } } return $deleted_count; } /** * Migrate old wp_options data to files (one-time migration) */ public function migrate_from_options() { $old_data = get_option('aibui_multi_page_generations', array()); if (empty($old_data) || !is_array($old_data)) { return 0; } $migrated_count = 0; foreach ($old_data as $id => $generation) { if (!is_array($generation)) continue; // Try to determine user ID from generation data or use current user $user_id = isset($generation['userId']) ? intval($generation['userId']) : get_current_user_id(); // Temporarily set user context for migration $original_user = get_current_user_id(); if ($user_id !== $original_user) { // Note: This is a limitation - we can't easily switch user context // So we'll use the current user's directory $user_id = $original_user; } $user_dir = $this->get_user_dir($user_id); $filename = 'gen-' . date('Ymd-His', strtotime($generation['createdAt'] ?? 'now')) . '-' . sanitize_file_name($id) . '.json'; $filepath = $user_dir . '/' . $filename; // Check if already migrated if (file_exists($filepath)) { continue; } // Prepare data $data = array( 'id' => $id, 'title' => sanitize_text_field($generation['title'] ?? ''), 'metaDesc' => sanitize_textarea_field($generation['metaDesc'] ?? ''), 'cssContent' => wp_kses_post($generation['cssContent'] ?? ''), 'blocksJson' => isset($generation['blocksJson']) && is_array($generation['blocksJson']) ? $generation['blocksJson'] : array(), 'status' => $generation['status'] ?? 'Pending review', 'createdAt' => $generation['createdAt'] ?? current_time('mysql'), 'applied' => $generation['applied'] ?? false, 'pageId' => isset($generation['pageId']) ? intval($generation['pageId']) : 0, 'userId' => $user_id, ); // Write file $json_content = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if (file_put_contents($filepath, $json_content) !== false) { $migrated_count++; // Update index $this->update_index($user_id, $id, array( 'filename' => $filename, 'title' => $data['title'], 'status' => $data['status'], 'createdAt' => $data['createdAt'], )); } } return $migrated_count; } }