Files
deepseek beaf0e1f37 Toolvana: production-ready media tools platform (CodeIgniter 4)
- 134-tool registry with programmatic SEO (unique titles/H1/descriptions,
  JSON-LD graphs, sitemap index, canonical 301 enforcement via required filter)
- DB-backed job queue (SKIP LOCKED) with drivers: Ffmpeg, Images (GD),
  Pdf (qpdf/gs/poppler), Youtube (thumbnails), Qr (server-side PNG)
- Security: SSRF guard, MIME validation, rate limits, API-key auth,
  bcrypt admin login, security headers
- Admin panel: dashboard, tools/categories/guides CRUD, SEO audit,
  analytics, job inspector with retry, system health, feature flags
- Docker deployment (nginx + web/api FPM pools + scalable workers),
  PHPUnit suite (19 tests / 1139 assertions), PWA manifest + service worker
2026-08-23 07:10:30 +00:00

93 lines
3.9 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Controllers\Admin;
use App\Controllers\BaseController;
use App\Libraries\CatalogBuilder;
use App\Models\CategoryModel;
use App\Models\GuideModel;
use App\Models\ToolModel;
/**
* Admin base: shared layout rendering + nav context.
* All children sit behind the admin-auth filter.
*/
abstract class AdminBase extends BaseController
{
protected function render(string $view, array $data = []): string
{
$data['viewContent'] = view('admin/' . $view, $data);
return view('layout', ['seo' => $this->seo] + $data);
}
/** Shared tool-form field list. */
protected function toolFields(): array
{
return [
'slug', 'name', 'short_description', 'description', 'icon',
'seo_title', 'seo_description', 'h1', 'intro', 'popularity', 'status',
];
}
/** Build a registry row from POST for admin-created tools. */
protected function toolFromInput(array $post, int $categoryId): array
{
// reuse the builder's content generator so admin-created tools get
// complete SEO fields even when left blank
$generated = null;
if (! empty($post['format_in']) && ! empty($post['format_out'])) {
foreach (CatalogBuilder::build() as $candidate) {
if ($candidate['slug'] === strtolower($post['format_in']) . '-to-' . strtolower($post['format_out'])) {
$generated = $candidate;
break;
}
}
}
return [
'category_id' => $categoryId,
'kind' => $post['kind'] ?? ($generated['kind'] ?? 'upload'),
'operation' => $post['operation'] ?? ($generated['operation'] ?? 'convert'),
'slug' => $post['slug'] ?? ($generated['slug'] ?? ''),
'name' => $post['name'],
'short_description' => $post['short_description'] ?? '',
'description' => $post['description'] ?? null,
'input_formats' => json_encode($generated['input_formats'] ?? []),
'output_formats' => json_encode($generated['output_formats'] ?? []),
'primary_input_format' => $post['format_in'] ?? ($generated['primary_input_format'] ?? null),
'primary_output_format' => $post['format_out'] ?? ($generated['primary_output_format'] ?? null),
'icon' => $post['icon'] ?? 'file',
'seo_title' => $post['seo_title'] ?: ($generated['seo_title'] ?? ($post['name'] . ' Free Online Tool')),
'seo_description' => $post['seo_description'] ?: mb_substr((string) ($post['short_description'] ?? ''), 0, 300),
'h1' => $post['h1'] ?: $post['name'],
'intro' => $post['intro'] ?: ($generated['intro'] ?? null),
'how_to' => json_encode($generated['how_to'] ?? []),
'faqs' => json_encode($generated['faqs'] ?? []),
'related_slugs' => json_encode([]),
'aliases' => json_encode(array_filter(array_map('trim', explode(',', (string) ($post['aliases'] ?? ''))))),
'requires_binaries' => json_encode($generated['requires_binaries'] ?? []),
'accepts_mimes' => json_encode($generated['accepts_mimes'] ?? []),
'popularity' => (int) ($post['popularity'] ?? 1000),
'status' => in_array(($post['status'] ?? ''), ['active', 'draft', 'inactive'], true) ? $post['status'] : 'active',
];
}
protected function categories(): array
{
return model(CategoryModel::class)->findAll();
}
protected function toolsModel(): ToolModel
{
return model(ToolModel::class);
}
protected function guidesModel(): GuideModel
{
return model(GuideModel::class);
}
}