Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/components/com_tjcertificate/media/css/tjCertificate.css
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,51 @@
}
.border-btn{
border:1px solid #b3b3b3;
}
#certificateModalContainer{
background: #fff;
width: 80%; /* Increased width */
max-width: 1300px; /* Max width limit */
margin: 50px auto;
padding: 20px;
border-radius: 10px;
position: relative;
max-height: 80vh; /* Max height of modal */
overflow-y: auto; /* Scroll content if it overflows */
}
#closeModalBtn{
position: absolute;
top: 10px;
right: 10px;
font-size: 18px;
background: none;
border: none;
outline: none;
cursor: pointer;
color: #000;
}
#certificateModalOverlay{

display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 1000;
overflow-y: auto;
}
#ziploader {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(255,255,255,0.8);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.zipspinner {
font-size: 18px;
font-weight: bold;
}
141 changes: 141 additions & 0 deletions src/components/com_tjcertificate/media/js/certificate.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,144 @@ var certificate = {
});
}
};


jQuery(document).ready(function ($) {
const config = Joomla.getOptions('tjcertificate.bulkDownload');

$('#bulkCertBtn').on('click', function (e) {
e.preventDefault();

$('#ziploader').fadeIn();

$.ajax({
url: Joomla.getOptions('system.paths').base + '/index.php?option=com_tjcertificate&task=certificates.fetchCertificatesForBulkDownload',
type: 'POST',
dataType: 'json',
data: {
user_id: config.user_id,
client: config.client,
state: config.state,
[config.token]: 1
},
success: function (response) {
if (response.success && response.data.length > 0) {
let html = '<h4>Downloaded Certificates</h4><ul>';
$('#certificateModalOverlay').fadeIn();

response.data.forEach(function (cert) {
html += '<div id="certificateContent' + cert.id + '" style="width: 1196px; height: auto;">' + cert.generated_body + '</div>';
});

$('#certificateModalContent').html(html);

const generatePromises = response.data.map(cert => {
const imageUrl = `${config.certRootUrl}media/com_tjcertificate/certificates/${cert.unique_certificate_id}.png`;
const certEl = document.getElementById('certificateContent' + cert.id);

return checkImageExists(imageUrl).then(exists => {
return exists ? Promise.resolve() : generateImage(certEl, cert.unique_certificate_id);
});
});

Promise.all(generatePromises).then(() => {
const form = $('<form>', {
method: 'POST',
action: Joomla.getOptions('system.paths').base + '/index.php?option=com_tjcertificate&task=certificates.bulkCertificateDownload',
style: 'display: none;'
});

form.append($('<input>', {
type: 'hidden',
name: 'user_id',
value: config.user_id
}));

response.data.forEach((cert, index) => {
form.append($('<input>', {
type: 'hidden',
name: 'certificates[' + index + '][unique_certificate_id]',
value: cert.unique_certificate_id
}));
});

form.append($('<input>', {
type: 'hidden',
name: config.token,
value: 1
}));

$('body').append(form);
$('#ziploader').fadeOut();
form.submit();
form.remove();

$('#certificateModalContent').html('<div class="alert alert-success">Certificates downloaded successfully.</div>');
setTimeout(() => $('#certificateModalOverlay').fadeOut(), 2000);
});

} else {
$('#ziploader').fadeOut();
alert(response.message || 'No certificates found.');
}
},
error: function (xhr) {
$('#ziploader').fadeOut();
console.error("AJAX Error", xhr.responseText);
}
});
Comment on lines +252 to +327
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify AJAX endpoint security and implement rate limiting.

The bulk download AJAX calls should be verified for proper authentication and rate limiting on the server side.


🏁 Script executed:

#!/bin/bash
# Check for the controller methods being called
echo "Checking for bulk download controller methods..."
ast-grep --pattern 'function fetchCertificatesForBulkDownload($_) {
  $$$
}'

ast-grep --pattern 'function bulkCertificateDownload($_) {
  $$$
}'

Length of output: 255


I’m digging into the PHP side to confirm that these endpoints enforce CSRF and authentication. Running:


🏁 Script executed:

#!/bin/bash
# Locate controller methods and inspect for CSRF/auth checks

echo "→ Finding fetchCertificatesForBulkDownload definition…"
rg -n "function fetchCertificatesForBulkDownload" -A5 .

echo "→ Finding bulkCertificateDownload definition…"
rg -n "function bulkCertificateDownload" -A5 .

echo "→ Scanning for CSRF token validation (JSession::checkToken)…"
rg -n "JSession::checkToken" -n .

echo "→ Scanning for JAccess authorisation…"
rg -n "authorise" -n .

Length of output: 7799


Enforce CSRF protection and throttle bulk‐download AJAX endpoints

The two controller actions lack explicit CSRF token validation and no rate limiting is in place. Please add server-side checks and throttling:

• In src/components/com_tjcertificate/site/controllers/certificates.php:
– At the start of fetchCertificatesForBulkDownload() (≈line 121), call Factory::getSession()->checkToken(), and abort on failure.
– At the start of bulkCertificateDownload() (≈line 195), do the same.

• Verify that the existing authorisation check at line 56 ($user->authorise('certificate.external.manage', …)) applies to these tasks; if not, add per-method checks.
• Introduce a simple rate limiter (e.g. track requests per user in session or database) to guard against abuse.

🤖 Prompt for AI Agents
In src/components/com_tjcertificate/site/controllers/certificates.php around
lines 121 and 195, add CSRF token validation by calling
Factory::getSession()->checkToken() at the start of
fetchCertificatesForBulkDownload() and bulkCertificateDownload() methods,
aborting the request if the token check fails. Also, verify if the existing
authorization check at line 56 covers these methods; if not, add explicit
authorization checks for each method. Finally, implement a simple rate limiter
per user (using session or database) to prevent abuse of these bulk download
AJAX endpoints.

});

$('#closeModalBtn').on('click', function () {
$('#certificateModalOverlay').fadeOut();
});
});
Comment on lines +244 to +333
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Implement comprehensive error handling and security improvements.

The bulk download implementation has several areas for improvement:

  1. Missing error handling for configuration validation
  2. Security concerns with dynamic form creation
  3. User experience improvements needed
 jQuery(document).ready(function ($) {
     const config = Joomla.getOptions('tjcertificate.bulkDownload');
+    
+    // Validate configuration
+    if (!config || !config.token) {
+        console.error('Bulk download configuration missing');
+        return;
+    }

     $('#bulkCertBtn').on('click', function (e) {
         e.preventDefault();
+        
+        // Disable button to prevent multiple clicks
+        $(this).prop('disabled', true).addClass('loading');

         $('#ziploader').fadeIn();
                         $('#ziploader').fadeOut();
+                        $('#bulkCertBtn').prop('disabled', false).removeClass('loading');
                         form.submit();
                         form.remove();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
jQuery(document).ready(function ($) {
const config = Joomla.getOptions('tjcertificate.bulkDownload');
$('#bulkCertBtn').on('click', function (e) {
e.preventDefault();
$('#ziploader').fadeIn();
$.ajax({
url: Joomla.getOptions('system.paths').base + '/index.php?option=com_tjcertificate&task=certificates.fetchCertificatesForBulkDownload',
type: 'POST',
dataType: 'json',
data: {
user_id: config.user_id,
client: config.client,
state: config.state,
[config.token]: 1
},
success: function (response) {
if (response.success && response.data.length > 0) {
let html = '<h4>Downloaded Certificates</h4><ul>';
$('#certificateModalOverlay').fadeIn();
response.data.forEach(function (cert) {
html += '<div id="certificateContent' + cert.id + '" style="width: 1196px; height: auto;">' + cert.generated_body + '</div>';
});
$('#certificateModalContent').html(html);
const generatePromises = response.data.map(cert => {
const imageUrl = `${config.certRootUrl}media/com_tjcertificate/certificates/${cert.unique_certificate_id}.png`;
const certEl = document.getElementById('certificateContent' + cert.id);
return checkImageExists(imageUrl).then(exists => {
return exists ? Promise.resolve() : generateImage(certEl, cert.unique_certificate_id);
});
});
Promise.all(generatePromises).then(() => {
const form = $('<form>', {
method: 'POST',
action: Joomla.getOptions('system.paths').base + '/index.php?option=com_tjcertificate&task=certificates.bulkCertificateDownload',
style: 'display: none;'
});
form.append($('<input>', {
type: 'hidden',
name: 'user_id',
value: config.user_id
}));
response.data.forEach((cert, index) => {
form.append($('<input>', {
type: 'hidden',
name: 'certificates[' + index + '][unique_certificate_id]',
value: cert.unique_certificate_id
}));
});
form.append($('<input>', {
type: 'hidden',
name: config.token,
value: 1
}));
$('body').append(form);
$('#ziploader').fadeOut();
form.submit();
form.remove();
$('#certificateModalContent').html('<div class="alert alert-success">Certificates downloaded successfully.</div>');
setTimeout(() => $('#certificateModalOverlay').fadeOut(), 2000);
});
} else {
$('#ziploader').fadeOut();
alert(response.message || 'No certificates found.');
}
},
error: function (xhr) {
$('#ziploader').fadeOut();
console.error("AJAX Error", xhr.responseText);
}
});
});
$('#closeModalBtn').on('click', function () {
$('#certificateModalOverlay').fadeOut();
});
});
jQuery(document).ready(function ($) {
const config = Joomla.getOptions('tjcertificate.bulkDownload');
// Validate configuration
if (!config || !config.token) {
console.error('Bulk download configuration missing');
return;
}
$('#bulkCertBtn').on('click', function (e) {
e.preventDefault();
// Disable button to prevent multiple clicks
$(this).prop('disabled', true).addClass('loading');
$('#ziploader').fadeIn();
$.ajax({
url: Joomla.getOptions('system.paths').base + '/index.php?option=com_tjcertificate&task=certificates.fetchCertificatesForBulkDownload',
type: 'POST',
dataType: 'json',
data: {
user_id: config.user_id,
client: config.client,
state: config.state,
[config.token]: 1
},
success: function (response) {
if (response.success && response.data.length > 0) {
let html = '<h4>Downloaded Certificates</h4><ul>';
$('#certificateModalOverlay').fadeIn();
response.data.forEach(function (cert) {
html += '<div id="certificateContent' + cert.id + '" style="width: 1196px; height: auto;">' + cert.generated_body + '</div>';
});
$('#certificateModalContent').html(html);
const generatePromises = response.data.map(cert => {
const imageUrl = `${config.certRootUrl}media/com_tjcertificate/certificates/${cert.unique_certificate_id}.png`;
const certEl = document.getElementById('certificateContent' + cert.id);
return checkImageExists(imageUrl).then(exists => {
return exists ? Promise.resolve() : generateImage(certEl, cert.unique_certificate_id);
});
});
Promise.all(generatePromises).then(() => {
const form = $('<form>', {
method: 'POST',
action: Joomla.getOptions('system.paths').base + '/index.php?option=com_tjcertificate&task=certificates.bulkCertificateDownload',
style: 'display: none;'
});
form.append($('<input>', {
type: 'hidden',
name: 'user_id',
value: config.user_id
}));
response.data.forEach((cert, index) => {
form.append($('<input>', {
type: 'hidden',
name: 'certificates[' + index + '][unique_certificate_id]',
value: cert.unique_certificate_id
}));
});
form.append($('<input>', {
type: 'hidden',
name: config.token,
value: 1
}));
$('body').append(form);
$('#ziploader').fadeOut();
$('#bulkCertBtn').prop('disabled', false).removeClass('loading');
form.submit();
form.remove();
$('#certificateModalContent').html('<div class="alert alert-success">Certificates downloaded successfully.</div>');
setTimeout(() => $('#certificateModalOverlay').fadeOut(), 2000);
});
} else {
$('#ziploader').fadeOut();
$('#bulkCertBtn').prop('disabled', false).removeClass('loading');
alert(response.message || 'No certificates found.');
}
},
error: function (xhr) {
$('#ziploader').fadeOut();
$('#bulkCertBtn').prop('disabled', false).removeClass('loading');
console.error("AJAX Error", xhr.responseText);
}
});
});
$('#closeModalBtn').on('click', function () {
$('#certificateModalOverlay').fadeOut();
});
});
🤖 Prompt for AI Agents
In src/components/com_tjcertificate/media/js/certificate.js between lines 244
and 333, add validation checks to ensure the config object and its required
properties (user_id, client, state, token) are defined before proceeding with
the AJAX call to prevent runtime errors. Enhance security by sanitizing any
dynamic content inserted into the DOM and avoid using inline styles or HTML
strings directly; instead, use safer DOM manipulation methods. Improve user
experience by adding error messages or notifications for configuration issues,
AJAX failures, and when no certificates are found, and ensure loading indicators
are properly managed in all code paths.


function generateImage($html, $item) {
return new Promise((resolve, reject) => {
if (typeof html2canvas === 'undefined') {
console.error('html2canvas is not available');
reject('Missing html2canvas');
return;
}

html2canvas($html, {
scrollX: 0,
scrollY: -window.scrollY,
allowTaint: true,
useCORS: true
}).then(function (canvas) {
const imageData = canvas.toDataURL('image/png');

jQuery.ajax({
url: Joomla.getOptions('system.paths').base + '/index.php?option=com_tjcertificate&task=certificate.uploadCertificate',
type: 'POST',
data: {
image: imageData,
certificateId: $item
},
success: function () {
resolve();
},
error: function (xhr) {
console.error("Upload failed for certificate " + $item, xhr.responseText);
reject(xhr.responseText);
}
});

}).catch(function (error) {
console.error("html2canvas failed:", error);
reject(error);
});
});
}
Comment on lines +335 to +372
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance image generation with better error handling.

The generateImage function needs improved error handling and security:

 function generateImage($html, $item) {
     return new Promise((resolve, reject) => {
         if (typeof html2canvas === 'undefined') {
             console.error('html2canvas is not available');
             reject('Missing html2canvas');
             return;
         }
+        
+        // Validate inputs
+        if (!$html || !$item) {
+            reject('Invalid parameters for image generation');
+            return;
+        }

         html2canvas($html, {
             scrollX: 0,
             scrollY: -window.scrollY,
             allowTaint: true,
-            useCORS: true
+            useCORS: true,
+            scale: 1,
+            logging: false  // Disable logging in production
         }).then(function (canvas) {
             const imageData = canvas.toDataURL('image/png');
+            
+            // Validate canvas size to prevent memory issues
+            if (canvas.width > 5000 || canvas.height > 5000) {
+                reject('Canvas size too large');
+                return;
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function generateImage($html, $item) {
return new Promise((resolve, reject) => {
if (typeof html2canvas === 'undefined') {
console.error('html2canvas is not available');
reject('Missing html2canvas');
return;
}
html2canvas($html, {
scrollX: 0,
scrollY: -window.scrollY,
allowTaint: true,
useCORS: true
}).then(function (canvas) {
const imageData = canvas.toDataURL('image/png');
jQuery.ajax({
url: Joomla.getOptions('system.paths').base + '/index.php?option=com_tjcertificate&task=certificate.uploadCertificate',
type: 'POST',
data: {
image: imageData,
certificateId: $item
},
success: function () {
resolve();
},
error: function (xhr) {
console.error("Upload failed for certificate " + $item, xhr.responseText);
reject(xhr.responseText);
}
});
}).catch(function (error) {
console.error("html2canvas failed:", error);
reject(error);
});
});
}
function generateImage($html, $item) {
return new Promise((resolve, reject) => {
if (typeof html2canvas === 'undefined') {
console.error('html2canvas is not available');
reject('Missing html2canvas');
return;
}
// Validate inputs
if (!$html || !$item) {
reject('Invalid parameters for image generation');
return;
}
html2canvas($html, {
scrollX: 0,
scrollY: -window.scrollY,
allowTaint: true,
useCORS: true,
scale: 1,
logging: false // Disable logging in production
}).then(function (canvas) {
// Validate canvas size to prevent memory issues
if (canvas.width > 5000 || canvas.height > 5000) {
reject('Canvas size too large');
return;
}
const imageData = canvas.toDataURL('image/png');
jQuery.ajax({
url: Joomla.getOptions('system.paths').base + '/index.php?option=com_tjcertificate&task=certificate.uploadCertificate',
type: 'POST',
data: {
image: imageData,
certificateId: $item
},
success: function () {
resolve();
},
error: function (xhr) {
console.error("Upload failed for certificate " + $item, xhr.responseText);
reject(xhr.responseText);
}
});
}).catch(function (error) {
console.error("html2canvas failed:", error);
reject(error);
});
});
}
🤖 Prompt for AI Agents
In src/components/com_tjcertificate/media/js/certificate.js between lines 335
and 372, the generateImage function lacks robust error handling and security
measures. Enhance it by adding more detailed error messages for all failure
points, including ajax errors and html2canvas failures. Also, ensure that any
user input or data sent in the ajax request is properly sanitized or validated
before sending. Wrap ajax calls with try-catch or equivalent error handling to
catch unexpected exceptions and reject the promise accordingly.


function checkImageExists(imageUrl) {
return new Promise((resolve) => {
const xhr = new XMLHttpRequest();
xhr.open('HEAD', imageUrl, true);
xhr.onload = () => resolve(xhr.status === 200);
xhr.onerror = () => resolve(false);
xhr.send();
});
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading