db = $db ?? $class_database; $this->smarty = $smarty; $this->user_id = isset($_SESSION['USER_ID']) ? (int)$_SESSION['USER_ID'] : 0; } /** * Create a new template * * @param array $data Template data * @return array Result with success status and template_id */ public function createTemplate($data) { // Validate input if (empty($data['template_name'])) { return ['success' => false, 'error' => 'Template name is required']; } if ($this->user_id === 0) { return ['success' => false, 'error' => 'User not authenticated']; } // Generate slug $slug = $this->generateSlug($data['template_name'], $this->user_id); // Default structure if not provided $default_structure = json_encode([ 'sections' => [], 'layout_type' => 'flex', 'max_width' => 1200 ]); // Prepare data $insert_data = [ 'user_id' => $this->user_id, 'template_name' => VDatabase::sanitizeInput($data['template_name']), 'template_slug' => $slug, 'template_type' => $data['template_type'] ?? 'custom_page', 'template_structure' => $data['template_structure'] ?? $default_structure, 'template_settings' => $data['template_settings'] ?? json_encode([]), 'custom_css' => $data['custom_css'] ?? '', 'custom_js' => $data['custom_js'] ?? '', 'is_active' => isset($data['is_active']) ? (int)$data['is_active'] : 0 ]; // Insert into database $sql = "INSERT INTO `db_templatebuilder_templates` SET " . VDatabase::build_insert_update($insert_data); $result = $this->db->execute($sql); if ($result) { $template_id = $this->db->insert_id(); // Create initial version $this->createVersion($template_id, $insert_data, 'Initial version'); VLogger::log('INFO', "Template created: ID {$template_id}, Name: {$data['template_name']}", ['user_id' => $this->user_id]); return [ 'success' => true, 'template_id' => $template_id, 'slug' => $slug ]; } return ['success' => false, 'error' => 'Failed to create template']; } /** * Update an existing template * * @param int $template_id Template ID * @param array $data Update data * @param string $change_note Optional change note for version history * @return array Result with success status */ public function updateTemplate($template_id, $data, $change_note = null) { $template_id = (int)$template_id; // Verify ownership if (!$this->verifyOwnership($template_id)) { return ['success' => false, 'error' => 'Unauthorized']; } // Prepare update data $update_data = []; $allowed_fields = [ 'template_name', 'template_type', 'template_structure', 'template_settings', 'custom_css', 'custom_js', 'is_active', 'is_default' ]; foreach ($allowed_fields as $field) { if (isset($data[$field])) { if ($field === 'template_name') { $update_data[$field] = VDatabase::sanitizeInput($data[$field]); } else { $update_data[$field] = $data[$field]; } } } if (empty($update_data)) { return ['success' => false, 'error' => 'No valid fields to update']; } // Update database $sql = "UPDATE `db_templatebuilder_templates` SET " . VDatabase::build_insert_update($update_data) . " WHERE `template_id` = '{$template_id}'"; $result = $this->db->execute($sql); if ($result) { // Create version history entry $this->createVersion($template_id, $update_data, $change_note); VLogger::log('INFO', "Template updated: ID {$template_id}", ['user_id' => $this->user_id, 'changes' => array_keys($update_data)]); return ['success' => true]; } return ['success' => false, 'error' => 'Failed to update template']; } /** * Delete a template * * @param int $template_id Template ID * @return array Result with success status */ public function deleteTemplate($template_id) { $template_id = (int)$template_id; // Verify ownership if (!$this->verifyOwnership($template_id)) { return ['success' => false, 'error' => 'Unauthorized']; } $sql = "DELETE FROM `db_templatebuilder_templates` WHERE `template_id` = '{$template_id}'"; $result = $this->db->execute($sql); if ($result) { VLogger::log('INFO', "Template deleted: ID {$template_id}", ['user_id' => $this->user_id]); return ['success' => true]; } return ['success' => false, 'error' => 'Failed to delete template']; } /** * Get template by ID * * @param int $template_id Template ID * @param bool $check_ownership Whether to verify ownership * @return array|null Template data or null if not found */ public function getTemplate($template_id, $check_ownership = true) { $template_id = (int)$template_id; $sql = "SELECT * FROM `db_templatebuilder_templates` WHERE `template_id` = '{$template_id}'"; if ($check_ownership && $this->user_id > 0) { $sql .= " AND `user_id` = '{$this->user_id}'"; } $result = $this->db->execute($sql); if ($result && $result->recordcount() > 0) { $template = $result->fields; // Decode JSON fields $template['template_structure'] = json_decode($template['template_structure'], true); $template['template_settings'] = json_decode($template['template_settings'], true); return $template; } return null; } /** * Get template by slug * * @param string $slug Template slug * @return array|null Template data or null if not found */ public function getTemplateBySlug($slug) { $slug = VDatabase::sanitizeInput($slug); $sql = "SELECT * FROM `db_templatebuilder_templates` WHERE `template_slug` = '{$slug}' AND `is_active` = 1 LIMIT 1"; $result = $this->db->execute($sql); if ($result && $result->recordcount() > 0) { $template = $result->fields; // Decode JSON fields $template['template_structure'] = json_decode($template['template_structure'], true); $template['template_settings'] = json_decode($template['template_settings'], true); return $template; } return null; } /** * Get all templates for current user * * @param array $filters Optional filters * @return array Array of templates */ public function getUserTemplates($filters = []) { if ($this->user_id === 0) { return []; } $sql = "SELECT * FROM `db_templatebuilder_templates` WHERE `user_id` = '{$this->user_id}'"; // Apply filters if (!empty($filters['template_type'])) { $type = VDatabase::sanitizeInput($filters['template_type']); $sql .= " AND `template_type` = '{$type}'"; } if (isset($filters['is_active'])) { $active = (int)$filters['is_active']; $sql .= " AND `is_active` = '{$active}'"; } $sql .= " ORDER BY `updated_at` DESC"; if (!empty($filters['limit'])) { $limit = (int)$filters['limit']; $sql .= " LIMIT {$limit}"; } $result = $this->db->execute($sql); $templates = []; if ($result) { foreach ($result->getRows() as $row) { // Don't decode JSON for listing (performance) $templates[] = $row; } } return $templates; } /** * Render a template * * @param int|string $template_identifier Template ID or slug * @param array $data Data to pass to template * @return string Rendered HTML */ public function renderTemplate($template_identifier, $data = []) { // Get template if (is_numeric($template_identifier)) { $template = $this->getTemplate($template_identifier, false); } else { $template = $this->getTemplateBySlug($template_identifier); } if (!$template) { return ''; } // Increment views $this->incrementViews($template['template_id']); // Build HTML from structure $html = $this->buildHtmlFromStructure($template['template_structure'], $data); // Wrap with custom CSS if present if (!empty($template['custom_css'])) { $html = "\n" . $html; } // Add custom JS if present (sanitized) if (!empty($template['custom_js'])) { $html .= "\n"; } return $html; } /** * Build HTML from template structure * * @param array $structure Template structure * @param array $data Data to pass to components * @return string Generated HTML */ private function buildHtmlFromStructure($structure, $data = []) { if (empty($structure['sections'])) { return ''; } $html = ''; $max_width = $structure['max_width'] ?? 1200; $layout_type = $structure['layout_type'] ?? 'flex'; // Container wrapper $html .= "
\n"; foreach ($structure['sections'] as $section) { $html .= $this->buildSection($section, $data); } $html .= "
\n"; return $html; } /** * Build a section * * @param array $section Section data * @param array $data Global data * @return string Section HTML */ private function buildSection($section, $data) { $section_id = $section['id'] ?? 'section-' . uniqid(); $section_class = $section['class'] ?? ''; $columns = $section['columns'] ?? 1; $html = "
\n"; // Apply section styles if present if (!empty($section['styles'])) { $style_str = $this->buildStyleString($section['styles']); $html = str_replace('
\n"; foreach ($section['blocks'] as $block) { $html .= $this->buildBlock($block, $data); } $html .= "
\n"; } $html .= "
\n"; return $html; } /** * Build a block (component) * * @param array $block Block data * @param array $data Global data * @return string Block HTML */ private function buildBlock($block, $data) { $block_id = $block['id'] ?? 'block-' . uniqid(); $component_slug = $block['component'] ?? null; if (!$component_slug) { return ''; } // Get component definition $component = $this->getComponent($component_slug); if (!$component) { return ""; } // Merge block settings with component defaults $settings = array_merge( json_decode($component['component_settings_schema'], true) ?? [], $block['settings'] ?? [] ); // Build HTML from component template $html = $component['component_html']; // Replace placeholders with settings values $html = $this->replacePlaceholders($html, $settings, $data); // Apply component CSS if present if (!empty($component['component_css'])) { $css = $this->replacePlaceholders($component['component_css'], $settings, $data); $html = "\n" . $html; } // Wrap in block container $block_html = "
\n"; $block_html .= $html; $block_html .= "
\n"; return $block_html; } /** * Replace placeholders in template string * * @param string $template Template string * @param array $settings Settings values * @param array $data Global data * @return string Processed string */ private function replacePlaceholders($template, $settings, $data) { // Replace {{variable}} with actual values $template = preg_replace_callback('/\{\{(\w+)\}\}/', function($matches) use ($settings, $data) { $key = $matches[1]; // Check settings first if (isset($settings[$key])) { $value = $settings[$key]; // Get default value if it's an array if (is_array($value) && isset($value['default'])) { return $value['default']; } return $value; } // Check global data if (isset($data[$key])) { return $data[$key]; } return $matches[0]; // Return original if not found }, $template); return $template; } /** * Get component by slug * * @param string $slug Component slug * @return array|null Component data */ private function getComponent($slug) { $slug = VDatabase::sanitizeInput($slug); $sql = "SELECT * FROM `db_templatebuilder_components` WHERE `component_slug` = '{$slug}' LIMIT 1"; $result = $this->db->execute($sql); if ($result && $result->recordcount() > 0) { return $result->fields; } return null; } /** * Get all available components * * @param string $category Optional category filter * @return array Array of components */ public function getComponents($category = null) { $sql = "SELECT * FROM `db_templatebuilder_components`"; if ($category) { $category = VDatabase::sanitizeInput($category); $sql .= " WHERE `component_category` = '{$category}'"; } $sql .= " ORDER BY `component_category`, `component_name`"; $result = $this->db->execute($sql); $components = []; if ($result) { foreach ($result->getRows() as $row) { $row['component_settings_schema'] = json_decode($row['component_settings_schema'], true); $components[] = $row; } } return $components; } /** * Create a version history entry * * @param int $template_id Template ID * @param array $data Template data * @param string $change_note Optional change note * @return bool Success status */ private function createVersion($template_id, $data, $change_note = null) { // Get current version number $sql = "SELECT MAX(`version_number`) as max_version FROM `db_templatebuilder_versions` WHERE `template_id` = '{$template_id}'"; $result = $this->db->execute($sql); $max_version = 0; if ($result && $result->recordcount() > 0) { $row = $result->fields; $max_version = (int)$row['max_version']; } $new_version = $max_version + 1; $version_data = [ 'template_id' => $template_id, 'version_number' => $new_version, 'template_structure' => $data['template_structure'] ?? '{}', 'template_settings' => $data['template_settings'] ?? '{}', 'custom_css' => $data['custom_css'] ?? '', 'custom_js' => $data['custom_js'] ?? '', 'change_note' => $change_note ? VDatabase::sanitizeInput($change_note) : null ]; $sql = "INSERT INTO `db_templatebuilder_versions` SET " . VDatabase::build_insert_update($version_data); return $this->db->execute($sql); } /** * Get user preferences * * @param int $user_id Optional user ID (defaults to current user) * @return array User preferences */ public function getUserPreferences($user_id = null) { $user_id = $user_id ?? $this->user_id; if ($user_id === 0) { return $this->getDefaultPreferences(); } $sql = "SELECT * FROM `db_templatebuilder_user_prefs` WHERE `user_id` = '{$user_id}' LIMIT 1"; $result = $this->db->execute($sql); if ($result && $result->recordcount() > 0) { $prefs = $result->fields; $prefs['preferences'] = json_decode($prefs['preferences'], true); return $prefs; } return $this->getDefaultPreferences(); } /** * Update user preferences * * @param array $preferences Preferences to update * @return bool Success status */ public function updateUserPreferences($preferences) { if ($this->user_id === 0) { return false; } // Check if preferences exist $existing = $this->getUserPreferences(); $allowed_fields = [ 'active_template_homepage', 'active_template_channel', 'active_template_browse', 'builder_mode', 'auto_save', 'show_grid', 'preferences' ]; $update_data = []; foreach ($allowed_fields as $field) { if (isset($preferences[$field])) { if ($field === 'preferences') { $update_data[$field] = json_encode($preferences[$field]); } else { $update_data[$field] = $preferences[$field]; } } } if (empty($update_data)) { return false; } if (!empty($existing['pref_id'])) { // Update existing $sql = "UPDATE `db_templatebuilder_user_prefs` SET " . VDatabase::build_insert_update($update_data) . " WHERE `user_id` = '{$this->user_id}'"; } else { // Insert new $update_data['user_id'] = $this->user_id; $sql = "INSERT INTO `db_templatebuilder_user_prefs` SET " . VDatabase::build_insert_update($update_data); } return $this->db->execute($sql); } /** * Get default preferences * * @return array Default preferences */ private function getDefaultPreferences() { return [ 'builder_mode' => 'simple', 'auto_save' => 1, 'show_grid' => 1, 'preferences' => [] ]; } /** * Verify template ownership * * @param int $template_id Template ID * @return bool True if user owns template */ private function verifyOwnership($template_id) { if ($this->user_id === 0) { return false; } $sql = "SELECT `user_id` FROM `db_templatebuilder_templates` WHERE `template_id` = '{$template_id}' LIMIT 1"; $result = $this->db->execute($sql); if ($result && $result->recordcount() > 0) { $row = $result->fields; return ((int)$row['user_id'] === $this->user_id); } return false; } /** * Generate unique slug * * @param string $name Template name * @param int $user_id User ID * @return string Unique slug */ private function generateSlug($name, $user_id) { // Basic slug generation $slug = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $name))); $slug = $user_id . '-' . $slug; // Check uniqueness $original_slug = $slug; $counter = 1; while ($this->slugExists($slug)) { $slug = $original_slug . '-' . $counter; $counter++; } return $slug; } /** * Check if slug exists * * @param string $slug Slug to check * @return bool True if exists */ private function slugExists($slug) { $slug = VDatabase::sanitizeInput($slug); $sql = "SELECT COUNT(*) as count FROM `db_templatebuilder_templates` WHERE `template_slug` = '{$slug}'"; $result = $this->db->execute($sql); if ($result) { $row = $result->fields; return ((int)$row['count'] > 0); } return false; } /** * Increment template views * * @param int $template_id Template ID * @return bool Success status */ private function incrementViews($template_id) { $sql = "UPDATE `db_templatebuilder_templates` SET `views` = `views` + 1 WHERE `template_id` = '{$template_id}'"; return $this->db->execute($sql); } /** * Build CSS style string from array * * @param array $styles Style array * @return string CSS string */ private function buildStyleString($styles) { $style_parts = []; foreach ($styles as $property => $value) { $property = str_replace('_', '-', $property); $style_parts[] = "{$property}: {$value}"; } return implode('; ', $style_parts); } /** * Duplicate a template * * @param int $template_id Template ID to duplicate * @param string $new_name Optional new name * @return array Result with success status and new template_id */ public function duplicateTemplate($template_id, $new_name = null) { $template = $this->getTemplate($template_id, true); if (!$template) { return ['success' => false, 'error' => 'Template not found']; } // Prepare new template data $new_template = [ 'template_name' => $new_name ?? ($template['template_name'] . ' (Copy)'), 'template_type' => $template['template_type'], 'template_structure' => json_encode($template['template_structure']), 'template_settings' => json_encode($template['template_settings']), 'custom_css' => $template['custom_css'], 'custom_js' => $template['custom_js'], 'is_active' => 0 ]; return $this->createTemplate($new_template); } }