Commit d51f23be authored by Mahmoud Aglan's avatar Mahmoud Aglan

clot

parent 4a2f8851
......@@ -7,7 +7,6 @@ use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Modules\Tutorials\TutorialRegistry;
use App\Shared\Services\PdfExportService;
class TutorialController extends Controller
{
......@@ -557,22 +556,75 @@ class TutorialController extends Controller
]);
}
public function exportPdf(Request $request): Response
public function exportStart(Request $request): Response
{
$screenshotsPath = realpath(__DIR__ . '/../../../../public/assets/tutorials/screenshots');
$viewsPath = realpath(__DIR__ . '/../Views');
$storageDir = __DIR__ . '/../../../../storage/cache';
$lockFile = $storageDir . '/book-export.lock';
$pdfFile = $storageDir . '/book-export.pdf';
$bookData = $this->collectBookData($screenshotsPath, $viewsPath);
if (file_exists($lockFile) && (time() - filemtime($lockFile)) < 300) {
return $this->json(['status' => 'processing']);
}
file_put_contents($lockFile, 'started');
@unlink($pdfFile);
$phpBinary = PHP_BINARY;
$scriptPath = realpath(__DIR__ . '/../../../../cli.php');
$cmd = sprintf(
'%s %s export:book > /dev/null 2>&1 &',
escapeshellarg($phpBinary),
escapeshellarg($scriptPath)
);
exec($cmd);
return $this->json(['status' => 'started']);
}
public function exportStatus(Request $request): Response
{
$storageDir = __DIR__ . '/../../../../storage/cache';
$lockFile = $storageDir . '/book-export.lock';
$pdfFile = $storageDir . '/book-export.pdf';
$errorFile = $storageDir . '/book-export.error';
if (file_exists($errorFile)) {
$error = file_get_contents($errorFile);
@unlink($errorFile);
@unlink($lockFile);
return $this->json(['status' => 'error', 'message' => $error]);
}
ob_start();
$data = $bookData;
include __DIR__ . '/../Views/export_pdf.php';
$html = ob_get_clean();
if (file_exists($pdfFile)) {
@unlink($lockFile);
return $this->json(['status' => 'done', 'size' => filesize($pdfFile)]);
}
if (file_exists($lockFile)) {
return $this->json(['status' => 'processing']);
}
return $this->json(['status' => 'idle']);
}
return PdfExportService::renderToPdf($html, 'Book-of-the-ERP.pdf');
public function exportDownload(Request $request): Response
{
$pdfFile = realpath(__DIR__ . '/../../../../storage/cache') . '/book-export.pdf';
if (!file_exists($pdfFile)) {
return $this->json(['error' => 'File not found'], 404);
}
$content = file_get_contents($pdfFile);
$response = new Response();
return $response->html($content, 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="Book-of-the-ERP.pdf"',
'Content-Length' => (string) strlen($content),
]);
}
private function collectBookData(string $screenshotsPath, string $viewsPath): array
public function collectBookData(string $screenshotsPath, string $viewsPath): array
{
$book = [
'generatedAt' => date('Y-m-d H:i'),
......
......@@ -4,7 +4,9 @@ declare(strict_types=1);
return [
['GET', '/tutorials', 'Tutorials\Controllers\TutorialController@index', ['auth'], 'tutorials.view'],
['GET', '/tutorials/book', 'Tutorials\Controllers\TutorialController@bookPage', ['auth'], 'tutorials.view'],
['GET', '/tutorials/export-pdf', 'Tutorials\Controllers\TutorialController@exportPdf', ['auth'], 'tutorials.view'],
['POST', '/tutorials/export-start', 'Tutorials\Controllers\TutorialController@exportStart', ['auth'], 'tutorials.view'],
['GET', '/tutorials/export-status', 'Tutorials\Controllers\TutorialController@exportStatus', ['auth'], 'tutorials.view'],
['GET', '/tutorials/export-download', 'Tutorials\Controllers\TutorialController@exportDownload', ['auth'], 'tutorials.view'],
['GET', '/tutorials/sports-activity', 'Tutorials\Controllers\TutorialController@sportsActivity', ['auth'], 'tutorials.view'],
['GET', '/tutorials/sports-activity/{slug}', 'Tutorials\Controllers\TutorialController@show', ['auth'], 'tutorials.view'],
['GET', '/tutorials/membership', 'Tutorials\Controllers\TutorialController@membership', ['auth'], 'tutorials.view'],
......
......@@ -126,15 +126,17 @@
<script>
var exportSteps = [
{ pct: 5, label: 'تحضير البيانات...' },
{ pct: 15, label: 'تجميع شروحات العضوية...' },
{ pct: 30, label: 'تجميع شروحات الأنشطة الرياضية...' },
{ pct: 45, label: 'تجميع شروحات الخزنة...' },
{ pct: 55, label: 'تجميع باقي الأقسام...' },
{ pct: 70, label: 'تضمين لقطات الشاشة...' },
{ pct: 10, label: 'تحضير البيانات...' },
{ pct: 25, label: 'تجميع شروحات العضوية...' },
{ pct: 40, label: 'تجميع شروحات الأنشطة الرياضية...' },
{ pct: 55, label: 'تجميع شروحات الخزنة...' },
{ pct: 65, label: 'تجميع باقي الأقسام...' },
{ pct: 75, label: 'تضمين لقطات الشاشة...' },
{ pct: 85, label: 'توليد ملف PDF...' },
{ pct: 95, label: 'تجهيز التحميل...' },
{ pct: 92, label: 'تجهيز التحميل...' },
];
var pollTimer = null;
var stepTimer = null;
function startExport() {
document.getElementById('beforeExport').style.display = 'none';
......@@ -155,45 +157,81 @@ function startExport() {
progressStep.textContent = step.label;
stepIndex++;
if (stepIndex < exportSteps.length) {
setTimeout(animateStep, 600);
stepTimer = setTimeout(animateStep, 4000);
}
}
animateStep();
fetch('/tutorials/export-pdf')
fetch('/tutorials/export-start', {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.status === 'started' || data.status === 'processing') {
pollStatus();
} else {
showError('فشل بدء التصدير');
}
})
.catch(function(err) {
showError('فشل الاتصال: ' + err.message);
});
}
function pollStatus() {
pollTimer = setInterval(function() {
fetch('/tutorials/export-status')
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.status === 'done') {
clearInterval(pollTimer);
clearTimeout(stepTimer);
document.getElementById('progressBar').style.width = '100%';
document.getElementById('progressLabel').textContent = '100%';
document.getElementById('progressStep').textContent = 'اكتمل! جاري التحميل...';
downloadFile();
} else if (data.status === 'error') {
clearInterval(pollTimer);
clearTimeout(stepTimer);
showError(data.message || 'فشل التصدير');
}
})
.catch(function() {});
}, 3000);
}
function downloadFile() {
fetch('/tutorials/export-download')
.then(function(response) {
if (!response.ok) throw new Error('HTTP ' + response.status);
var contentType = response.headers.get('Content-Type') || '';
return response.blob().then(function(blob) {
return { blob: blob, contentType: contentType };
});
return response.blob();
})
.then(function(result) {
progressBar.style.width = '100%';
progressLabel.textContent = '100%';
progressStep.textContent = 'اكتمل!';
var ext = result.contentType.indexOf('pdf') !== -1 ? '.pdf' : '.html';
var filename = 'Book-of-the-ERP' + ext;
var url = window.URL.createObjectURL(result.blob);
.then(function(blob) {
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
a.download = 'Book-of-the-ERP.pdf';
document.body.appendChild(a);
a.click();
setTimeout(function() {
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}, 100);
setTimeout(function() { finishExport(true); }, 600);
})
.catch(function(err) {
document.getElementById('errorMessage').textContent = 'فشل التصدير: ' + err.message;
finishExport(false);
showError('فشل التحميل: ' + err.message);
});
}
function showError(msg) {
clearInterval(pollTimer);
clearTimeout(stepTimer);
document.getElementById('errorMessage').textContent = msg;
finishExport(false);
}
function finishExport(success) {
document.getElementById('duringExport').style.display = 'none';
if (success) {
......
......@@ -223,6 +223,84 @@ switch ($command) {
}
break;
case 'export:book':
echo "📖 Generating Book of the ERP PDF...\n";
$storageDir = __DIR__ . '/storage/cache';
$lockFile = $storageDir . '/book-export.lock';
$pdfFile = $storageDir . '/book-export.pdf';
$errorFile = $storageDir . '/book-export.error';
@unlink($errorFile);
file_put_contents($lockFile, 'processing');
try {
$screenshotsPath = realpath(__DIR__ . '/public/assets/tutorials/screenshots');
$viewsPath = realpath(__DIR__ . '/app/Modules/Tutorials/Views');
$app = \App\Core\App::getInstance();
$app->setDb($db);
$controller = new \App\Modules\Tutorials\Controllers\TutorialController();
$bookData = $controller->collectBookData($screenshotsPath, $viewsPath);
ob_start();
$data = $bookData;
include __DIR__ . '/app/Modules/Tutorials/Views/export_pdf.php';
$html = ob_get_clean();
echo " HTML generated (" . strlen($html) . " bytes)\n";
echo " Running wkhtmltopdf...\n";
$wkhtmltopdf = null;
foreach (['/usr/local/bin/wkhtmltopdf', '/usr/bin/wkhtmltopdf'] as $path) {
if (file_exists($path) && is_executable($path)) {
$wkhtmltopdf = $path;
break;
}
}
if (!$wkhtmltopdf) {
$wkhtmltopdf = trim((string) shell_exec('which wkhtmltopdf 2>/dev/null'));
}
if (!$wkhtmltopdf) {
file_put_contents($pdfFile, $html);
echo " ⚠️ wkhtmltopdf not found, saved as HTML\n";
} else {
$tmpInput = tempnam(sys_get_temp_dir(), 'pdf_in_') . '.html';
file_put_contents($tmpInput, $html);
$cmd = escapeshellarg($wkhtmltopdf)
. ' --encoding utf-8 --page-size A4'
. ' --margin-top 15 --margin-bottom 15 --margin-left 12 --margin-right 12'
. ' --enable-local-file-access'
. ' --no-stop-slow-scripts'
. ' --javascript-delay 200'
. ' --image-quality 85'
. ' --print-media-type'
. ' ' . escapeshellarg($tmpInput)
. ' ' . escapeshellarg($pdfFile)
. ' 2>&1';
$output = [];
exec($cmd, $output, $returnCode);
@unlink($tmpInput);
if (($returnCode === 0 || $returnCode === 1) && file_exists($pdfFile) && filesize($pdfFile) > 0) {
echo " ✅ PDF generated: " . round(filesize($pdfFile) / 1024 / 1024, 2) . " MB\n";
} else {
$errMsg = implode("\n", $output);
file_put_contents($errorFile, $errMsg ?: "wkhtmltopdf failed (code: {$returnCode})");
@unlink($pdfFile);
echo " ❌ wkhtmltopdf failed: {$errMsg}\n";
}
}
} catch (\Throwable $e) {
file_put_contents($errorFile, $e->getMessage());
echo " ❌ Error: " . $e->getMessage() . "\n";
}
@unlink($lockFile);
break;
case 'help':
default:
echo "THE CLUB ERP — CLI Commands\n";
......@@ -234,6 +312,7 @@ switch ($command) {
echo " php cli.php seed:run <Name> Run specific seed\n";
echo " php cli.php cron Run background jobs\n";
echo " php cli.php routes List all routes\n";
echo " php cli.php export:book Generate Book of the ERP PDF\n";
echo " php cli.php permissions:orphans Registered but unused permissions\n";
echo " php cli.php permissions:unprotected Routes without permissions\n";
echo " php cli.php permissions:stats Permission count per module\n";
......
......@@ -2,6 +2,10 @@
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html/public
# Allow long-running exports (PDF generation)
TimeOut 480
ProxyTimeout 480
<Directory /var/www/html/public>
Options -Indexes +FollowSymLinks
AllowOverride All
......
......@@ -2,9 +2,9 @@
upload_max_filesize = 20M
post_max_size = 25M
memory_limit = 256M
max_execution_time = 120
max_input_time = 60
memory_limit = 1024M
max_execution_time = 480
max_input_time = 240
max_input_vars = 5000
date.timezone = Africa/Cairo
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment