where('is_published', 1)->findAll(); foreach ($themes as &$theme) { $meta = json_decode($theme['metadata'] ?? '{}', true); $theme['colors'] = $meta['colors'] ?? []; $theme['tags'] = $meta['tags'] ?? []; $theme['vars'] = $meta['vars'] ?? []; // Provide a preview array compatible with the frontend $theme['preview'] = !empty($theme['colors']) ? array_values($theme['colors']) : ['#ffffff', '#f0f0f0', '#007acc']; } if ($this->request->isAJAX() || $this->request->hasHeader('Fetch') || str_contains($this->request->getHeaderLine('Accept'), 'application/json')) { $origin = $this->request->getHeaderLine('Origin') ?: '*'; header('Access-Control-Allow-Origin: ' . $origin); header('Vary: Origin'); return $this->response->setJSON($themes); } return view('theme_store', [ 'themes' => $themes, 'flash_success' => session()->getFlashdata('success'), 'flash_error' => session()->getFlashdata('error'), ]); } public function upload(): Response { $origin = $this->request->getHeaderLine('Origin') ?: '*'; header('Access-Control-Allow-Origin: ' . $origin); header('Vary: Origin'); $file = $this->request->getFile('theme_css'); $displayName = trim($this->request->getPost('display_name') ?? ''); $description = trim($this->request->getPost('description') ?? ''); if ($displayName === '') { if ($this->request->isAJAX() || $this->request->hasHeader('Fetch')) { return $this->response->setStatusCode(400)->setJSON(['error' => 'Display name is required.']); } return redirect()->to('themes')->with('error', 'Display name is required.'); } if (! $file || ! $file->isValid() || $file->hasMoved()) { if ($this->request->isAJAX() || $this->request->hasHeader('Fetch')) { return $this->response->setStatusCode(400)->setJSON(['error' => 'Please upload a valid CSS file.']); } return redirect()->to('themes')->with('error', 'Please upload a valid CSS file.'); } if (strtolower($file->getExtension()) !== 'css') { if ($this->request->isAJAX() || $this->request->hasHeader('Fetch')) { return $this->response->setStatusCode(400)->setJSON(['error' => 'Only .css files are allowed.']); } return redirect()->to('themes')->with('error', 'Only .css files are allowed.'); } $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $displayName)); $slug = trim($slug, '-'); $filename = $slug . '-' . substr(bin2hex(random_bytes(3)), 0, 6) . '.css'; $file->move(FCPATH . 'themes', $filename, true); // Extract CSS variables and colors from the uploaded file $cssContent = file_get_contents(FCPATH . 'themes/' . $filename); preg_match_all('/(--[a-zA-Z0-9-]+)\s*:\s*([^;]+);/', $cssContent, $matches); $vars = []; if (!empty($matches[1])) { foreach ($matches[1] as $index => $key) { $vars[$key] = trim($matches[2][$index]); } } // Try to generate 3-color preview based on standard variables $colors = []; if (isset($vars['--bg'])) $colors['bg'] = $vars['--bg']; if (isset($vars['--surface'])) $colors['surface'] = $vars['--surface']; if (isset($vars['--accent'])) $colors['accent'] = $vars['--accent']; $model = new MarketplaceThemeModel(); $model->insert([ 'id' => $this->uuid4(), 'name' => $slug, 'display_name' => $displayName, 'description' => $description ?: 'Custom community theme.', 'author' => 'Community', 'version' => '1.0.0', 'thumbnail_url' => null, 'download_url' => '/themes/' . $filename, 'price' => 0, 'is_published' => true, 'metadata' => json_encode([ 'tags' => ['custom', 'community'], 'colors' => $colors, 'vars' => $vars ]), 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), ]); if ($this->request->isAJAX() || $this->request->hasHeader('Fetch')) { return $this->response->setJSON([ 'success' => true, 'message' => '"' . esc($displayName) . '" uploaded successfully!' ]); } return redirect()->to('themes')->with('success', '"' . esc($displayName) . '" uploaded successfully!'); } public function preview(string $id): Response { $model = new MarketplaceThemeModel(); $theme = $model->find($id); if (! $theme) { return $this->response->setStatusCode(404)->setBody('
Theme not found.
'); } $distIndex = '/home/came/Nextcloud/arch-work/Projects/Todo-App/dist/index.html'; if (! file_exists($distIndex)) { return $this->response->setBody( '' . 'Todo app dist not found.