20if (!function_exists(
'str_contains')) {
21 function str_contains($haystack, $needle) {
22 return $needle !==
'' && mb_strpos($haystack, $needle) !==
false;
27if (isset($_GET[
'resetlang']) && $_SERVER[
'REQUEST_METHOD'] ===
'POST') {
28 header(
'Content-Type: application/json');
29 require_once dirname(__FILE__) .
'/mainfile.php';
31 $table = XOOPS_DB_PREFIX .
'_config';
32 $sql =
"UPDATE `$table` SET conf_value='english' WHERE conf_name='language' AND conf_modid=0";
33 $ok = $db->queryF($sql);
35 echo json_encode([
'success' =>
true]);
37 echo json_encode([
'success' =>
false,
'error' =>
'DB update failed']);
42require_once __DIR__ .
'/mainfile.php';
46if (!is_object($xoopsUser) || !in_array(1, $xoopsUser->getGroups())) {
47 header(
'HTTP/1.1 403 Forbidden');
48 echo
'<h1>403 Forbidden</h1><p>Admin access only.</p>';
53function extract_defines($file, $mydirname =
null) {
55 if (!file_exists($file))
return $defines;
56 $content = file_get_contents($file);
59 if (preg_match(
'/\$constpref\s*=\s*([\'\"])?(_MB_|_MI_|_MD_)?\.'
60 .
'\s*strtoupper\s*\(\s*\$mydirname\s*\)\s*([\'\"])?/i', $content)) {
64 if (preg_match(
'#modules/([^/]+)/language/#', str_replace(
'\\',
'/',$file), $m)) {
70 if (preg_match(
'/\$constpref\s*=\s*([\'\"])?(_MB_|_MI_|_MD_)?\.'
71 .
'\s*strtoupper\s*\(\s*\$mydirname\s*\)\s*([\'\"])?/i', $content, $pm)) {
72 $constpref = ($pm[2] ??
'') . strtoupper($mydirname) .
'_';
77 if ($constpref && preg_match_all(
'/define\s*\(\s*\$constpref\s*\.\s*[\'\"](_[A-Z0-9]+)[\'\"]\s*,/', $content, $matches)) {
78 foreach ($matches[1] as $suffix) {
79 $defines[] = $constpref . ltrim($suffix,
'_');
83 if (preg_match_all(
"/define\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*,/", $content, $matches2)) {
84 foreach ($matches2[1] as $c) {
86 if (!in_array($c, $defines)) $defines[] = $c;
93function get_all_used_constants($root_dirs) {
95 foreach ($root_dirs as $dir) {
96 $rii =
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir));
97 foreach ($rii as $file) {
98 if ($file->isFile() && strtolower($file->getExtension()) ===
'php') {
99 $content = file_get_contents($file->getPathname());
101 if (preg_match_all(
"/(['\"])(_[_A-Z0-9]+)\\1|\b(_[_A-Z0-9]+)\b/", $content, $matches)) {
102 foreach (array_merge($matches[2], $matches[3]) as $const) {
103 if ($const) $used[$const] =
true;
113 __DIR__ .
'/modules',
114 dirname(__DIR__) .
'/xoops_trust_path/modules'
118 dirname(__DIR__) .
'/xoops_trust_path'
120$used_constants = get_all_used_constants($code_roots);
124foreach ($roots as $base) {
125 foreach (glob($base .
'/*', GLOB_ONLYDIR) as $moddir) {
126 $module = basename($moddir);
127 $langbase = $moddir .
'/language';
128 if (!is_dir($langbase))
continue;
130 foreach (glob($langbase .
'/*', GLOB_ONLYDIR) as $langdir) {
131 $langcode = basename($langdir);
132 $languages[$langcode] =
true;
134 $englishdir = $langbase .
'/english';
135 if (!is_dir($englishdir))
continue;
136 foreach (glob($englishdir .
'/*.php') as $efile) {
137 $fname = basename($efile);
138 $edefs = extract_defines($efile);
139 foreach ($languages as $langcode => $_) {
140 if ($langcode ==
'english')
continue;
141 $tfile = $langbase .
'/' . $langcode .
'/' . $fname;
153 $tdefs = extract_defines($tfile);
154 $missing = array_diff($edefs, $tdefs);
155 $extra = array_diff($tdefs, $edefs);
156 $extra_unused = array_filter($extra,
function($const) use ($used_constants) {
157 return !isset($used_constants[$const]);
163 'missing' => $missing,
165 'extra_unused' => $extra_unused,
166 'ok' => empty($missing)
176foreach ($all_reports as $r) {
177 $modules[$r[
'module']] =
true;
178 $languages[$r[
'lang']] =
true;
180ksort($modules); ksort($languages);
184header(
'Content-Type: text/html; charset=utf-8');
189 <title>XOOPSCube
XCL - Recovery Language Audit</title>
190 <meta charset=
"UTF-8">
191 <meta name=
"viewport" content=
"width=device-width, initial-scale=1">
193 *::before,*::after {box-sizing: border-box;}
194 body{font:14px sans-serif;}
195 .ok{color:green;}.fail{color:red;}.flex{display:flex;}
196 .flex-1{padding:1rem;width: 50%;}ul{margin:0 0 1em 2em;}
197 li{margin:0.2em 0;}section {max-width: 1024px; margin:0
auto 2rem;}
198 .audit-header{background:#f5f5f5;padding:1em 2em 1em 2em;border-bottom:1px solid #ccc;}
199 .audit-btn{background:#0074d9;color:#fff;border:none;padding:0.5em 1.25em;margin: 1em 0 0;margin-top:1em;font-size:1.1em;border-radius:3px;cursor:pointer;}
200 .audit-btn:active{background:#005fa3;} #newauditbtn{background:#ff851b;}
201 .audit-result-wrap{margin:1em
auto 0;max-width:1100px;background:#fff;position:relative}
202 .audit-controls{position:sticky;top:0;z-index:10;background-color:#f8f8f8;padding:1em 1.5em;border-bottom:1px solid #ddd;margin:0
auto;max-width:1100px;box-sizing:border-box}
203 #audit-results-inner{height:480px;overflow-y:auto;padding:1.5em 2em;border:1px solid #eee;margin-top:1em}
204 .audit-export-btn{background:#1b932a;border:none;border-radius:3px;color:#fff;cursor:pointer;font-size:1em;margin:1em 0 0;padding:.65em}
205 .audit-export-btn:active{background:#229c2b}
206 input,select{max-width:180px;position:relative;appearance:none;background-color:#fff;border:1px solid #caced1;border-radius:4px;color:#111;cursor:pointer;padding:.5em;width:100%}
207 select{background:url(
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='m13 16.172l5.364-5.364l1.414 1.414L12 20l-7.778-7.778l1.414-1.414L11 16.172V4h2z'/%3E%3C/svg%3E") right 5% bottom 50%/.8em auto no-repeat
#fff}
208 .lang-reset-dialog{background:#b22222;border:1px solid #ffa9a9;border-radius:4px;color:#facfcf;margin:1em 0 1.5em;padding:1em 2em;text-align:center}
209 #lang-reset-english-btn{background:#d9534f;border:none;border-radius:4px;color:#fff;cursor:pointer;font-weight:700;padding:.5em 1.25em}
214 <div
class=
"audit-header">
215 <h1>XOOPSCube
XCL Language Audit Tool</h1>
216 <p>This tool scans all modules
for language files and compares constants between English and other languages.</p>
219 <button
id=
"runbtn" class=
"audit-btn" onclick=
"runAudit()">Start Audit</button>
220 <button
id=
"newauditbtn" class=
"audit-btn" onclick=
"newAudit()" style=
"display:none;">New Audit</button>
223 if (is_object($xoopsUser) && $xoopsUser->isAdmin(1)) {
224 echo
'<div class="flex-1 lang-reset-dialog">
225 <strong>Stuck with a broken language?</strong><br>
226 <button id="lang-reset-english-btn">Reset Site Language to English</button><br>
227 <small>(Emergency recovery)</small>
232 <div
id=
"audit-loading" style=
"display:none;margin-top:1em;">
233 <p>Analyzing language files... please wait.</p>
237<!-- Controls Section -->
238<div
id=
"audit-controls-section" class=
"audit-controls" style=
"display:none;">
239 <div style=
"display:flex;flex-wrap:wrap;gap:1em;align-items:center;">
240 <div style=
"flex:1;">
241 <label
for=
"audit-module"><b>Filter by Module:</b></label>
242 <select
id=
"audit-module" onchange=
"filterReports()" style=
"width:100%;">
243 <option value=
"">All Modules</option>
244 <?php
foreach (array_keys($modules) as $m): ?>
245 <option value=
"<?php echo htmlspecialchars($m); ?>"><?php echo htmlspecialchars($m); ?></option>
249 <div style=
"flex:1;">
250 <label
for=
"audit-lang"><b>Filter by Language:</b></label>
251 <select
id=
"audit-lang" onchange=
"filterReports()" style=
"width:100%;">
252 <option value=
"">All Languages</option>
253 <?php
foreach (array_keys($languages) as $l): ?>
254 <option value=
"<?php echo htmlspecialchars($l); ?>"><?php echo htmlspecialchars($l); ?></option>
258 <div style=
"flex:1;">
259 <label
for=
"audit-file"><b>Filter by Filename:</b></label>
260 <input type=
"text" id=
"audit-file" onkeyup=
"filterReports()" placeholder=
"e.g. admin.php" style=
"width:100%;">
262 <div style=
"flex:1;">
263 <label
for=
"audit-export-select"><b>Export:</b></label>
264 <select
id=
"audit-export-select" style=
"min-width:150px;">
265 <option value=
"">Export Options</option>
266 <option value=
"view-html">View as HTML</option>
267 <option value=
"download-html">Download HTML</option>
268 <option value=
"download-csv">Download CSV</option>
269 <option value=
"download-json">Download JSON</option>
272 <div><button
id=
"audit-send-email-btn" class=
"audit-export-btn">Send Report via Email</button></div>
276<div
id=
"audit-email-modal" style=
"display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0,0,0,0.5);z-index:9999;align-items:center;justify-content:center;">
277 <div style=
"background:firebrick;color:#fff;padding:2em 2.5em;border-radius:8px;max-width:350px;margin:auto;box-shadow:0 4px 18px #0002;">
278 <h>Send Report via Email</h3>
279 <form
id=
"audit-email-form" onsubmit=
"return false;">
280 <label>Email: <input type=
"email" id=
"audit-email-to" required style=
"width:100%;margin-bottom:1em;"></label>
281 <div style=
"text-align:right;">
282 <button type=
"button" onclick=
"closeAuditEmailModal()" style=
"margin-right:0.7em;">Cancel</button>
283 <button type=
"submit">Send</button>
285 <div
id=
"audit-email-status" style=
"color:#0074d9;margin-top:0.6em;"></div>
290<!-- Summary container -->
291<div
id=
"audit-summary-container"></div>
293<!-- Results Section -->
294<div
id=
"audit-results" class=
"audit-result-wrap" style=
"display:none;">
295 <div
id=
"audit-results-inner"></div>
299 let allReports = <?php echo json_encode($all_reports, JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT|JSON_UNESCAPED_UNICODE); ?>;
301 function filterReports() {
302 let mod = document.getElementById(
"audit-module").value;
303 let lang = document.getElementById(
"audit-lang").value;
304 let fileFilter = document.getElementById(
"audit-file").value.toLowerCase();
306 let results = allReports.filter(r =>
307 (mod ===
"" || r.module === mod) &&
308 (lang ===
"" || r.lang === lang) &&
309 (fileFilter ===
"" || r.file.toLowerCase().includes(fileFilter))
311 renderReports(results);
314 function renderReports(reports) {
316 let totalModules =
new Set(reports.map(r => r.module)).size;
317 let totalLangs =
new Set(reports.map(r => r.lang)).size;
318 let totalFiles = reports.length;
319 let totalMissing = reports.reduce((sum, r) => sum + (r.missing ? r.missing.length : 0), 0);
320 let totalExtra = reports.reduce((sum, r) => sum + (r.extra ? r.extra.length : 0), 0);
321 let totalUnused = reports.reduce((sum, r) => sum + (r.extra_unused ? r.extra_unused.length : 0), 0);
322 let totalOk = reports.filter(r => r.ok).length;
325 let summaryHtml = `<div
id=
"action-control" style=
"border:1px solid #eee; background:#f8f8f8; padding:1em 1.5em; margin-bottom:1.2em; border-radius:4px;">
327 <span title=
'Modules'><b>🧩 ${totalModules}</b> modules</span>
328 <span title=
'Languages'><b>🌐 ${totalLangs}</b> languages</span>
329 <span title=
'Files'><b>📄 ${totalFiles}</b> files</span>
330 <span title=
'OK'><b>✅ ${totalOk}</b> all present</span>
331 <span title=
'Missing'><b>❌ ${totalMissing}</b> missing</span>
332 <span title=
'Extra'><b>⚠️ ${totalExtra}</b> extra</span>
333 <span title=
'Unused'><b>🚫 ${totalUnused}</b> unused</span>
337 document.getElementById(
"audit-summary-container").innerHTML = summaryHtml;
341 for (let report of reports) {
342 let icon = report.ok ?
"✅" :
"❌";
343 reportHtml += `<h3>${icon} Module: <b>${report.module}</b> — Language: <b>${report.lang}</b> — File: <b>${report.file}</b></h3>`;
345 reportHtml += `<div
class=
"ok">All constants present.</div>`;
348 if (report.missing && report.missing.length) {
349 reportHtml += `<div
class=
"fail">Missing constants:<ul>`;
350 for (let c of report.missing) reportHtml += `<li>❌ <code>${c}</code></li>`;
351 reportHtml += `</ul></div>`;
355 if (report.extra && report.extra.length) {
356 reportHtml += `<div>Extra constants:<ul>`;
357 for (let c of report.extra) {
358 let warn = (report.extra_unused && report.extra_unused.includes(c)) ?
" <span style='color:red'>🚫 Not used in code</span>" :
"";
359 reportHtml += `<li>⚠️ <code>${c}</code>${warn}</li>`;
361 reportHtml += `</ul></div>`;
367 reportHtml += `<hr><div>✅ = All constants present<br>❌ = Missing constant<br>⚠️ = Extra constant in translation<br>🚫 = Extra constant not used anywhere in codebase</div>`;
370 document.getElementById(
"audit-results-inner").innerHTML = reportHtml;
373 function runAudit() {
374 const runBtn = document.getElementById(
"runbtn");
375 if (runBtn) runBtn.disabled =
true;
376 document.getElementById(
"audit-loading").style.display =
"block";
377 if (runBtn) runBtn.style.display =
"none";
381 document.getElementById(
"audit-controls-section").style.display =
"block";
382 document.getElementById(
"audit-results").style.display =
"block";
383 document.getElementById(
"audit-loading").style.display =
"none";
384 document.getElementById(
"newauditbtn").style.display =
"inline-block";
388 function newAudit() {
389 const runBtn = document.getElementById(
"runbtn");
390 if (runBtn) runBtn.disabled =
false;
391 document.getElementById(
"audit-controls-section").style.display =
"none";
392 document.getElementById(
"audit-results").style.display =
"none";
393 document.getElementById(
"audit-loading").style.display =
"none";
394 document.getElementById(
"audit-results-inner").innerHTML =
"";
397 localStorage.removeItem(
'auditModule');
398 localStorage.removeItem(
'auditLang');
399 localStorage.removeItem(
'auditFile');
402 document.getElementById(
"audit-module").value =
"";
403 document.getElementById(
"audit-lang").value =
"";
404 document.getElementById(
"audit-file").value =
"";
406 document.getElementById(
"newauditbtn").style.display =
"none";
407 if (runBtn) runBtn.style.display =
"inline-block";
412 function openAuditEmailModal() {
413 document.getElementById(
'audit-email-modal').style.display =
'flex';
414 document.getElementById(
'audit-email-to').focus();
415 document.getElementById(
'audit-email-status').textContent =
'';
417 function closeAuditEmailModal() {
418 document.getElementById(
'audit-email-modal').style.display =
'none';
420 window.addEventListener(
'DOMContentLoaded',
function() {
421 var sendBtn = document.getElementById(
'audit-send-email-btn');
422 if (sendBtn) sendBtn.addEventListener(
'click', openAuditEmailModal);
424 var emailForm = document.getElementById(
'audit-email-form');
425 if (emailForm) emailForm.addEventListener(
'submit',
function(e) {
427 var to = document.getElementById(
'audit-email-to').value.trim();
429 var mod = document.getElementById(
'audit-module').value;
430 var lang = document.getElementById(
'audit-lang').value;
431 var reports = allReports.filter(r =>
432 (mod ===
"" || r.module === mod) && (lang ===
"" || r.lang === lang)
434 var data = JSON.stringify(reports,
null, 2);
435 var xhr =
new XMLHttpRequest();
436 xhr.open(
'POST', window.location.pathname +
'?sendmail=1');
437 xhr.setRequestHeader(
'Content-Type',
'application/json');
438 xhr.onload =
function() {
439 var msg = (xhr.status === 200) ?
'✅ Email sent!' :
'❌ Failed to send: ' + xhr.responseText;
440 document.getElementById(
'audit-email-status').textContent = msg;
441 if (xhr.status === 200) setTimeout(closeAuditEmailModal, 1800);
443 xhr.send(JSON.stringify({ to: to, mod: mod, lang: lang, data: data }));
444 document.getElementById(
'audit-email-status').textContent =
'Sending...';
449 function exportAudit(format, action) {
450 let mod = document.getElementById(
"audit-module").value;
451 let lang = document.getElementById(
"audit-lang").value;
452 let fileFilter = document.getElementById(
"audit-file").value.toLowerCase();
455 let reports = allReports.filter(r =>
456 (mod ===
"" || r.module === mod) &&
457 (lang ===
"" || r.lang === lang) &&
458 (fileFilter ===
"" || r.file.toLowerCase().includes(fileFilter))
462 let filename =
'lang_audit_report';
463 let mimeType =
'text/plain';
466 if (mod) filename +=
'_' + mod;
467 if (lang) filename +=
'_' + lang;
470 if (format ===
'json') {
471 content = JSON.stringify(reports,
null, 2);
472 mimeType =
'application/json';
474 }
else if (format ===
'csv') {
476 content =
'Module,Language,File,Status,Missing Constants,Extra Constants,Unused Constants\n';
479 for (let r of reports) {
480 let status = r.ok ?
'OK' :
'Missing';
481 let missing = r.missing ? r.missing.join(
'|') :
'';
482 let extra = r.extra ? r.extra.join(
'|') :
'';
483 let unused = r.extra_unused ? r.extra_unused.join(
'|') :
'';
486 missing = missing.replace(/
"/g, '""');
487 extra = extra.replace(/"/g,
'""');
488 unused = unused.replace(/
"/g, '""');
490 content += `"${r.module}
","${r.lang}
","${r.file}
","${status}
","${missing}
","${extra}
","${unused}
"\n`;
492 mimeType = 'text/csv';
494 } else if (format === 'html') {
495 // Create HTML report
496 content = `<!DOCTYPE html>
499 <meta charset="UTF-8
">
500 <title>Language Audit Report</title>
502 body { font-family: Arial, sans-serif; margin: 20px; }
504 table { border-collapse: collapse; width: 100%; margin-top: 20px; }
505 th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
506 th { background-color: #f2f2f2; }
507 tr:nth-child(even) { background-color: #f9f9f9; }
508 .ok { color: green; }
509 .missing { color: red; }
510 .extra { color: orange; }
514 <h1>Language Audit Report</h1>
515 <p>Generated: ${new Date().toLocaleString()}</p>`;
518 content += '<p><strong>Filters:</strong> ';
519 if (mod) content += `Module: ${mod} `;
520 if (lang) content += `Language: ${lang}`;
531 <th>Missing Constants</th>
532 <th>Extra Constants</th>
535 for (let r of reports) {
536 let statusClass = r.ok ? 'ok' : 'missing';
537 let statusText = r.ok ? '✅ OK' : '❌ Missing';
544 <td class="${statusClass}
">${statusText}</td>
547 if (r.missing && r.missing.length) {
549 for (let c of r.missing) {
550 content += `<li class="missing
">${c}</li>`;
558 if (r.extra && r.extra.length) {
560 for (let c of r.extra) {
561 let unusedMark = (r.extra_unused && r.extra_unused.includes(c)) ? ' 🚫' : '';
562 content += `<li class="extra
">${c}${unusedMark}</li>`;
573 <p>✅ = All constants present<br>❌ = Missing constant<br>⚠️ = Extra constant in translation<br>🚫 = Extra constant not used anywhere in codebase</p>
576 mimeType = 'text/html';
579 // Handle view or download based on action
580 if (action === 'view') {
582 let blob = new Blob([content], {type: mimeType});
583 let url = URL.createObjectURL(blob);
584 window.open(url, '_blank');
585 } else if (action === 'download') {
587 let blob = new Blob([content], {type: mimeType});
588 let url = URL.createObjectURL(blob);
589 let a = document.createElement('a');
591 a.download = filename;
592 document.body.appendChild(a);
594 setTimeout(function() {
595 document.body.removeChild(a);
596 window.URL.revokeObjectURL(url);
601 window.addEventListener('DOMContentLoaded', function() {
602 // Export select handler
603 var exportSel = document.getElementById('audit-export-select');
605 exportSel.addEventListener('change', function(e) {
606 const val = e.target.value;
608 let [action, format] = val.split('-');
609 exportAudit(format, action);
614 // Restore filters from localStorage
615 const savedMod = localStorage.getItem('auditModule');
616 const savedLang = localStorage.getItem('auditLang');
617 const savedFile = localStorage.getItem('auditFile');
620 const modSel = document.getElementById('audit-module');
621 if (modSel) modSel.value = savedMod;
624 const langSel = document.getElementById('audit-lang');
625 if (langSel) langSel.value = savedLang;
628 const fileInput = document.getElementById('audit-file');
629 if (fileInput) fileInput.value = savedFile;
632 // If results are visible, apply filters
633 if (document.getElementById('audit-results').style.display === 'block') {
637 // Save filter selections to localStorage
638 document.addEventListener('change', function(e) {
639 if (e.target && e.target.id === 'audit-module') {
640 localStorage.setItem('auditModule', e.target.value);
642 if (e.target && e.target.id === 'audit-lang') {
643 localStorage.setItem('auditLang', e.target.value);
645 if (e.target && e.target.id === 'audit-file') {
646 localStorage.setItem('auditFile', e.target.value);
650 window.addEventListener('DOMContentLoaded', function() {
651 var resetBtn = document.getElementById('lang-reset-english-btn');
653 resetBtn.addEventListener('click', function() {
654 if (!confirm('Are you sure you want to reset the site language to English? This is for emergency recovery.')) return;
655 resetBtn.disabled = true;
656 resetBtn.textContent = 'Resetting...';
657 fetch(window.location.pathname + '?resetlang=1', {method:'POST'})
661 alert('Site language has been reset to English. Please reload the page.');
664 alert('Failed to reset language: ' + (resp.error || 'Unknown error'));
665 resetBtn.disabled = false;
666 resetBtn.textContent = 'Reset Site Language to English';
670 alert('Error: ' + e);
671 resetBtn.disabled = false;
672 resetBtn.textContent = 'Reset Site Language to English';
677const savedFile = localStorage.getItem('auditFile');
679 const fileInput = document.getElementById('audit-file');
680 if (fileInput) fileInput.value = savedFile;
682// Re-apply filters if needed (already there)
683// if (document.getElementById('audit-results').style.display === 'block') {
691// TODO check if any setup available
692// PHP: handle email send request using Mailer
693if (isset($_GET['sendmail']) && $_SERVER['REQUEST_METHOD'] === 'POST') {
694 header('Content-Type: application/json');
695 $input = json_decode(file_get_contents('php://input'), true);
696 $to = filter_var($input['to'] ?? '', FILTER_VALIDATE_EMAIL);
697 $mod = $input['mod'] ?? '';
698 $lang = $input['lang'] ?? '';
699 $data = $input['data'] ?? '';
700 if (!$to || !$data) {
701 http_response_code(400);
702 echo json_encode(['error' => 'Invalid email or data.']);
705 require_once dirname(__FILE__) . '/mainfile.php';
706 require_once XOOPS_ROOT_PATH . '/class/xoopsmailer.php';
707 $subject = 'XOOPSCube XCL - Recovery Language Audit Report';
708 if ($mod || $lang) $subject .= " [
" . ($mod ? "Module: $mod
" : "") . ($lang ? "Lang: $lang
" : "") . "]
";
709 $body = "Attached is the audit report as a JSON file.\n\n
";
710 $mailer = new xoopsmailer(); // Use the global $mailer instance
712 $mailer->setToEmails([$to]);
713 $mailer->setFromEmail($GLOBALS['xoopsConfig']['adminmail'] ?? 'webmaster@' . $_SERVER['SERVER_NAME']);
714 $mailer->setFromName($GLOBALS['xoopsConfig']['sitename'] ?? 'XOOPS Site');
715 $mailer->setSubject($subject);
716 $mailer->setBody($body);
717 // Attach the JSON report
718 $filename = 'lang_audit_report.json';
719 if (method_exists($mailer->multimailer, 'addStringAttachment')) {
720 $mailer->multimailer->addStringAttachment($data, $filename, 'base64', 'application/json');
722 $ok = $mailer->send();
724 echo json_encode(['success' => true]);
726 http_response_code(500);
727 $errors = method_exists($mailer, 'getErrors') ? $mailer->getErrors(false) : ['Mail send failed.'];
728 echo json_encode(['error' => 'Mail send failed.', 'details' => $errors]);
static & getDatabaseConnection()
This file was entirely rewritten by the XOOPSCube Legacy project for compatibility with XOOPS2.