Working marketplace

This commit is contained in:
Cametendo
2026-05-13 15:29:47 +02:00
parent f27498dc26
commit caf81ea4e2
14 changed files with 577 additions and 32 deletions

View File

@@ -3,11 +3,12 @@
namespace App\Controllers;
use App\Models\MarketplaceThemeModel;
use App\Models\UserThemeModel;
use CodeIgniter\HTTP\Response;
class ThemeStore extends BaseController
{
public function index(): string
public function index()
{
$model = new MarketplaceThemeModel();
$themes = $model->where('is_published', 1)->findAll();
@@ -17,6 +18,15 @@ class ThemeStore extends BaseController
$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')) {
header('Access-Control-Allow-Origin: http://localhost:5173');
header('Access-Control-Allow-Credentials: true');
return $this->response->setJSON($themes);
}
return view('theme_store', [
@@ -28,20 +38,32 @@ class ThemeStore extends BaseController
public function upload(): Response
{
header('Access-Control-Allow-Origin: http://localhost:5173');
header('Access-Control-Allow-Credentials: true');
$file = $this->request->getFile('theme_css');
$displayName = trim($this->request->getPost('display_name') ?? '');
$description = trim($this->request->getPost('description') ?? '');
if ($displayName === '') {
return redirect()->to('/themes')->with('error', 'Display name is required.');
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()) {
return redirect()->to('/themes')->with('error', 'Please upload a valid CSS file.');
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') {
return redirect()->to('/themes')->with('error', 'Only .css files are allowed.');
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));
@@ -50,6 +72,23 @@ class ThemeStore extends BaseController
$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(),
@@ -62,12 +101,22 @@ class ThemeStore extends BaseController
'download_url' => '/themes/' . $filename,
'price' => 0,
'is_published' => true,
'metadata' => json_encode(['tags' => ['custom', 'community'], 'colors' => []]),
'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'),
]);
return redirect()->to('/themes')->with('success', '"' . esc($displayName) . '" uploaded successfully!');
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
@@ -117,6 +166,96 @@ class ThemeStore extends BaseController
->setBody($todoHtml);
}
public function install(string $id): Response
{
$model = new MarketplaceThemeModel();
$theme = $model->find($id);
if (! $theme) {
return $this->response->setStatusCode(404)->setJSON(['error' => 'Theme not found in the marketplace.']);
}
// Using session user_id or a default placeholder since standard auth might be configured separately
$userId = session()->get('user_id') ?? 'default-user-id';
$userThemeModel = new UserThemeModel();
if (! $userThemeModel->isInstalled($userId, $id)) {
$userThemeModel->installTheme($userId, $id);
}
return $this->response->setJSON([
'success' => true,
'message' => '"' . esc($theme['display_name']) . '" has been installed to your account.'
]);
}
public function activate(string $id): Response
{
$userId = session()->get('user_id') ?? 'default-user-id';
$userThemeModel = new UserThemeModel();
if (! $userThemeModel->isInstalled($userId, $id)) {
return $this->response->setStatusCode(400)->setJSON(['error' => 'Theme must be installed before it can be activated.']);
}
$userThemeModel->setActiveTheme($userId, $id);
return $this->response->setJSON(['success' => true, 'message' => 'Theme activated successfully.']);
}
public function uninstall(string $id): Response
{
$userId = session()->get('user_id') ?? 'default-user-id';
$userThemeModel = new UserThemeModel();
$userThemeModel->uninstallTheme($userId, $id);
return $this->response->setJSON(['success' => true, 'message' => 'Theme successfully uninstalled.']);
}
public function myThemes(): Response
{
$userId = session()->get('user_id') ?? 'default-user-id';
$userThemeModel = new UserThemeModel();
return $this->response->setJSON(['success' => true, 'data' => $userThemeModel->getUserThemes($userId)]);
}
public function serveCss(string $filename): Response
{
// Ensure it's just a file name (prevent directory traversal)
$filename = basename($filename);
$cssPath = FCPATH . 'themes/' . $filename;
// If the file actually exists on disk (e.g. newly uploaded themes)
if (file_exists($cssPath)) {
$css = file_get_contents($cssPath);
return $this->response->setContentType('text/css')->setBody($css);
}
// Generate dynamically for seeded themes that don't have a physical file
$model = new MarketplaceThemeModel();
$name = preg_replace('/\.css$/i', '', $filename);
$theme = $model->where('name', $name)->first();
if (! $theme) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
$meta = json_decode($theme['metadata'] ?? '{}', true);
$vars = $meta['vars'] ?? [];
$css = "/* Theme: {$theme['display_name']} */\n:root {\n";
foreach ($vars as $prop => $value) {
$css .= " {$prop}: {$value};\n";
}
$css .= "}\n";
return $this->response->setContentType('text/css')->setBody($css);
}
private function uuid4(): string
{
$data = random_bytes(16);