Commit 7f5b59a3 authored by Mahmoud Aglan's avatar Mahmoud Aglan

xorg

parent 69f666e7
...@@ -556,71 +556,69 @@ class TutorialController extends Controller ...@@ -556,71 +556,69 @@ class TutorialController extends Controller
]); ]);
} }
public function exportStart(Request $request): Response public function exportPdf(Request $request): Response
{ {
$storageDir = __DIR__ . '/../../../../storage/cache'; set_time_limit(480);
$lockFile = $storageDir . '/book-export.lock';
$pdfFile = $storageDir . '/book-export.pdf';
if (file_exists($lockFile) && (time() - filemtime($lockFile)) < 300) { $screenshotsPath = realpath(__DIR__ . '/../../../../public/assets/tutorials/screenshots');
return $this->json(['status' => 'processing']); $viewsPath = realpath(__DIR__ . '/../Views');
}
file_put_contents($lockFile, 'started');
@unlink($pdfFile);
$phpBinary = PHP_BINARY; $bookData = $this->collectBookData($screenshotsPath, $viewsPath);
$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']); ob_start();
} $data = $bookData;
include __DIR__ . '/../Views/export_pdf.php';
public function exportStatus(Request $request): Response $html = ob_get_clean();
{
$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]);
}
if (file_exists($pdfFile)) { $wkhtmltopdf = null;
@unlink($lockFile); foreach (['/usr/local/bin/wkhtmltopdf', '/usr/bin/wkhtmltopdf'] as $path) {
return $this->json(['status' => 'done', 'size' => filesize($pdfFile)]); if (file_exists($path) && is_executable($path)) {
$wkhtmltopdf = $path;
break;
}
} }
if (!$wkhtmltopdf) {
if (file_exists($lockFile)) { $wkhtmltopdf = trim((string) shell_exec('which wkhtmltopdf 2>/dev/null'));
return $this->json(['status' => 'processing']);
} }
return $this->json(['status' => 'idle']); if ($wkhtmltopdf) {
} $tmpInput = tempnam(sys_get_temp_dir(), 'pdf_in_') . '.html';
$tmpOutput = tempnam(sys_get_temp_dir(), 'pdf_out_') . '.pdf';
public function exportDownload(Request $request): Response file_put_contents($tmpInput, $html);
{
$pdfFile = realpath(__DIR__ . '/../../../../storage/cache') . '/book-export.pdf'; $cmd = escapeshellarg($wkhtmltopdf)
. ' --encoding utf-8 --page-size A4'
if (!file_exists($pdfFile)) { . ' --margin-top 15 --margin-bottom 15 --margin-left 12 --margin-right 12'
return $this->json(['error' => 'File not found'], 404); . ' --enable-local-file-access'
. ' --no-stop-slow-scripts'
. ' --javascript-delay 200'
. ' --image-quality 85'
. ' --print-media-type'
. ' ' . escapeshellarg($tmpInput)
. ' ' . escapeshellarg($tmpOutput)
. ' 2>&1';
exec($cmd, $output, $returnCode);
@unlink($tmpInput);
if (($returnCode === 0 || $returnCode === 1) && file_exists($tmpOutput) && filesize($tmpOutput) > 0) {
$pdfContent = file_get_contents($tmpOutput);
@unlink($tmpOutput);
$response = new Response();
return $response->html($pdfContent, 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="Book-of-the-ERP.pdf"',
'Content-Length' => (string) strlen($pdfContent),
]);
}
@unlink($tmpOutput);
} }
$content = file_get_contents($pdfFile);
$response = new Response(); $response = new Response();
return $response->html($content, 200, [ return $response->html($html, 200, [
'Content-Type' => 'application/pdf', 'Content-Type' => 'text/html; charset=utf-8',
'Content-Disposition' => 'attachment; filename="Book-of-the-ERP.pdf"', 'Content-Disposition' => 'attachment; filename="Book-of-the-ERP.html"',
'Content-Length' => (string) strlen($content),
]); ]);
} }
......
...@@ -4,9 +4,7 @@ declare(strict_types=1); ...@@ -4,9 +4,7 @@ declare(strict_types=1);
return [ return [
['GET', '/tutorials', 'Tutorials\Controllers\TutorialController@index', ['auth'], 'tutorials.view'], ['GET', '/tutorials', 'Tutorials\Controllers\TutorialController@index', ['auth'], 'tutorials.view'],
['GET', '/tutorials/book', 'Tutorials\Controllers\TutorialController@bookPage', ['auth'], 'tutorials.view'], ['GET', '/tutorials/book', 'Tutorials\Controllers\TutorialController@bookPage', ['auth'], 'tutorials.view'],
['POST', '/tutorials/export-start', 'Tutorials\Controllers\TutorialController@exportStart', ['auth'], 'tutorials.view'], ['GET', '/tutorials/export-pdf', 'Tutorials\Controllers\TutorialController@exportPdf', ['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', 'Tutorials\Controllers\TutorialController@sportsActivity', ['auth'], 'tutorials.view'],
['GET', '/tutorials/sports-activity/{slug}', 'Tutorials\Controllers\TutorialController@show', ['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'], ['GET', '/tutorials/membership', 'Tutorials\Controllers\TutorialController@membership', ['auth'], 'tutorials.view'],
......
...@@ -126,17 +126,15 @@ ...@@ -126,17 +126,15 @@
<script> <script>
var exportSteps = [ var exportSteps = [
{ pct: 10, label: 'تحضير البيانات...' }, { pct: 5, label: 'تحضير البيانات...' },
{ pct: 25, label: 'تجميع شروحات العضوية...' }, { pct: 15, label: 'تجميع شروحات العضوية...' },
{ pct: 40, label: 'تجميع شروحات الأنشطة الرياضية...' }, { pct: 30, label: 'تجميع شروحات الأنشطة الرياضية...' },
{ pct: 55, label: 'تجميع شروحات الخزنة...' }, { pct: 45, label: 'تجميع شروحات الخزنة...' },
{ pct: 65, label: 'تجميع باقي الأقسام...' }, { pct: 55, label: 'تجميع باقي الأقسام...' },
{ pct: 75, label: 'تضمين لقطات الشاشة...' }, { pct: 70, label: 'تضمين لقطات الشاشة...' },
{ pct: 85, label: 'توليد ملف PDF...' }, { pct: 85, label: 'توليد ملف PDF...' },
{ pct: 92, label: 'تجهيز التحميل...' }, { pct: 92, label: 'تجهيز التحميل...' },
]; ];
var pollTimer = null;
var stepTimer = null;
function startExport() { function startExport() {
document.getElementById('beforeExport').style.display = 'none'; document.getElementById('beforeExport').style.display = 'none';
...@@ -157,81 +155,44 @@ function startExport() { ...@@ -157,81 +155,44 @@ function startExport() {
progressStep.textContent = step.label; progressStep.textContent = step.label;
stepIndex++; stepIndex++;
if (stepIndex < exportSteps.length) { if (stepIndex < exportSteps.length) {
stepTimer = setTimeout(animateStep, 4000); setTimeout(animateStep, 8000);
} }
} }
animateStep(); animateStep();
fetch('/tutorials/export-start', { fetch('/tutorials/export-pdf')
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) { .then(function(response) {
if (!response.ok) throw new Error('HTTP ' + response.status); if (!response.ok) throw new Error('HTTP ' + response.status);
return response.blob(); return response.blob();
}) })
.then(function(blob) { .then(function(blob) {
progressBar.style.width = '100%';
progressLabel.textContent = '100%';
progressStep.textContent = 'اكتمل!';
var filename = 'Book-of-the-ERP.pdf';
if (blob.type && blob.type.indexOf('html') !== -1) {
filename = 'Book-of-the-ERP.html';
}
var url = window.URL.createObjectURL(blob); var url = window.URL.createObjectURL(blob);
var a = document.createElement('a'); var a = document.createElement('a');
a.href = url; a.href = url;
a.download = 'Book-of-the-ERP.pdf'; a.download = filename;
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
setTimeout(function() { setTimeout(function() {
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
document.body.removeChild(a); document.body.removeChild(a);
}, 100); }, 100);
setTimeout(function() { finishExport(true); }, 600); setTimeout(function() { finishExport(true); }, 600);
}) })
.catch(function(err) { .catch(function(err) {
showError('فشل التحميل: ' + err.message); document.getElementById('errorMessage').textContent = 'فشل التصدير: ' + err.message;
finishExport(false);
}); });
} }
function showError(msg) {
clearInterval(pollTimer);
clearTimeout(stepTimer);
document.getElementById('errorMessage').textContent = msg;
finishExport(false);
}
function finishExport(success) { function finishExport(success) {
document.getElementById('duringExport').style.display = 'none'; document.getElementById('duringExport').style.display = 'none';
if (success) { if (success) {
......
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