161 lines
4.6 KiB
PHP
161 lines
4.6 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Core;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* NovaCore View Renderer
|
||
|
|
* Simple PHP template engine
|
||
|
|
*/
|
||
|
|
class View
|
||
|
|
{
|
||
|
|
private string $viewPath;
|
||
|
|
private array $sharedData = [];
|
||
|
|
|
||
|
|
public function __construct()
|
||
|
|
{
|
||
|
|
$this->viewPath = __DIR__ . '/../Modules';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render a view
|
||
|
|
*/
|
||
|
|
public function render(string $view, array $data = []): string
|
||
|
|
{
|
||
|
|
$data = array_merge($this->sharedData, $data);
|
||
|
|
|
||
|
|
// Get view file path
|
||
|
|
$viewFile = $this->getViewFile($view);
|
||
|
|
if (!file_exists($viewFile)) {
|
||
|
|
throw new \Exception("View '{$view}' not found");
|
||
|
|
}
|
||
|
|
|
||
|
|
// Read view file content
|
||
|
|
$content = file_get_contents($viewFile);
|
||
|
|
|
||
|
|
// Process template syntax first
|
||
|
|
$content = $this->processTemplate($content);
|
||
|
|
|
||
|
|
// Debug: Log processed content
|
||
|
|
if (getenv('APP_DEBUG') === 'true') {
|
||
|
|
error_log("Processed template content: " . substr($content, 0, 500));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Extract data to variables
|
||
|
|
extract($data);
|
||
|
|
|
||
|
|
// Start output buffering
|
||
|
|
ob_start();
|
||
|
|
|
||
|
|
// Evaluate processed content
|
||
|
|
try {
|
||
|
|
eval('?>' . $content);
|
||
|
|
} catch (ParseError $e) {
|
||
|
|
// Log the problematic content for debugging
|
||
|
|
error_log("Template parse error: " . $e->getMessage());
|
||
|
|
error_log("Problematic content around line " . $e->getLine() . ": " . substr($content, max(0, $e->getLine() - 10) * 50, 1000));
|
||
|
|
throw $e;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get content and clean buffer
|
||
|
|
return ob_get_clean();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get view file path
|
||
|
|
*/
|
||
|
|
private function getViewFile(string $view): string
|
||
|
|
{
|
||
|
|
// Convert dot notation to path
|
||
|
|
$view = str_replace('.', '/', $view);
|
||
|
|
|
||
|
|
// Add .php extension if not present
|
||
|
|
if (!str_ends_with($view, '.php')) {
|
||
|
|
$view .= '.php';
|
||
|
|
}
|
||
|
|
|
||
|
|
return $this->viewPath . '/' . $view;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Process template syntax
|
||
|
|
*/
|
||
|
|
private function processTemplate(string $content): string
|
||
|
|
{
|
||
|
|
// Process {{ }} syntax (auto-escape)
|
||
|
|
$content = preg_replace_callback('/\{\{\s*(.+?)\s*\}\}/', function ($matches) {
|
||
|
|
$expression = trim($matches[1]);
|
||
|
|
return '<?php echo htmlspecialchars(' . $expression . ', ENT_QUOTES, \'UTF-8\'); ?>';
|
||
|
|
}, $content);
|
||
|
|
|
||
|
|
// Process {!! !!} syntax (raw output)
|
||
|
|
$content = preg_replace_callback('/\{!!\s*(.+?)\s*!!\}/', function ($matches) {
|
||
|
|
$expression = trim($matches[1]);
|
||
|
|
return '<?php echo ' . $expression . '; ?>';
|
||
|
|
}, $content);
|
||
|
|
|
||
|
|
// Process @if statements
|
||
|
|
$content = preg_replace('/@if\s*\(\s*([^)]+)\s*\)/', '<?php if ($1): ?>', $content);
|
||
|
|
$content = preg_replace('/@elseif\s*\(\s*([^)]+)\s*\)/', '<?php elseif ($1): ?>', $content);
|
||
|
|
$content = preg_replace('/@else/', '<?php else: ?>', $content);
|
||
|
|
$content = preg_replace('/@endif/', '<?php endif; ?>', $content);
|
||
|
|
|
||
|
|
// Debug: Log processed @if statements
|
||
|
|
if (getenv('APP_DEBUG') === 'true') {
|
||
|
|
error_log("After @if processing: " . substr($content, 0, 1000));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Process @foreach loops
|
||
|
|
$content = preg_replace('/@foreach\s*\((.+?)\)/', '<?php foreach ($1): ?>', $content);
|
||
|
|
$content = preg_replace('/@endforeach/', '<?php endforeach; ?>', $content);
|
||
|
|
|
||
|
|
// Process @for loops
|
||
|
|
$content = preg_replace('/@for\s*\((.+?)\)/', '<?php for ($1): ?>', $content);
|
||
|
|
$content = preg_replace('/@endfor/', '<?php endfor; ?>', $content);
|
||
|
|
|
||
|
|
// Process @while loops
|
||
|
|
$content = preg_replace('/@while\s*\((.+?)\)/', '<?php while ($1): ?>', $content);
|
||
|
|
$content = preg_replace('/@endwhile/', '<?php endwhile; ?>', $content);
|
||
|
|
|
||
|
|
// Process @csrf directive
|
||
|
|
$content = preg_replace('/@csrf/', '<?php echo csrf_token(); ?>', $content);
|
||
|
|
|
||
|
|
// Process @method directive
|
||
|
|
$content = preg_replace('/@method\s*\((.+?)\)/', '<?php echo method_field($1); ?>', $content);
|
||
|
|
|
||
|
|
return $content;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Share data with all views
|
||
|
|
*/
|
||
|
|
public function share(string $key, $value): void
|
||
|
|
{
|
||
|
|
$this->sharedData[$key] = $value;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if view exists
|
||
|
|
*/
|
||
|
|
public function exists(string $view): bool
|
||
|
|
{
|
||
|
|
$viewFile = $this->getViewFile($view);
|
||
|
|
return file_exists($viewFile);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get view path
|
||
|
|
*/
|
||
|
|
public function getViewPath(): string
|
||
|
|
{
|
||
|
|
return $this->viewPath;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set view path
|
||
|
|
*/
|
||
|
|
public function setViewPath(string $path): void
|
||
|
|
{
|
||
|
|
$this->viewPath = $path;
|
||
|
|
}
|
||
|
|
}
|