Skip to content

Task #240454 chore : Bulk Certificates download at a once for the admins.#163

Open
iamnileshmittha wants to merge 1 commit intotechjoomla:dev-joomla4from
iamnileshmittha:nm-bulk
Open

Task #240454 chore : Bulk Certificates download at a once for the admins.#163
iamnileshmittha wants to merge 1 commit intotechjoomla:dev-joomla4from
iamnileshmittha:nm-bulk

Conversation

@iamnileshmittha
Copy link
Copy Markdown

@iamnileshmittha iamnileshmittha commented Jun 5, 2025

Implemented a feature in the Shika LMS product that allows administrators to download multiple course certificates at once, streamlining the certificate management process and saving time

Summary by CodeRabbit

  • New Features

    • Introduced bulk certificate download, allowing users to download multiple certificates as a ZIP archive.
    • Added a modal interface and loading spinner for improved user feedback during bulk operations.
    • Integrated client-side image generation for certificates not already available as images.
  • Bug Fixes

    • Resolved minor style inconsistencies in button borders.
  • Documentation

    • Added new language strings and user messages for bulk download actions, errors, and guidance.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 5, 2025

Walkthrough

A bulk certificate download feature was introduced, including backend controller methods for fetching and packaging certificates as a ZIP, new frontend JavaScript logic for handling the download process, modal and loader UI components styled via CSS, language strings for user messaging, and updates to the view template to integrate the new functionality.

Changes

File(s) Change Summary
src/components/com_tjcertificate/media/css/tjCertificate.css Added styles for modal, overlay, loader, and spinner components used in certificate bulk download UI.
src/components/com_tjcertificate/media/js/certificate.js Implemented frontend logic for bulk certificate download, image generation, modal handling, and AJAX interactions.
src/components/com_tjcertificate/site/controllers/certificates.php Added two public controller methods: one for fetching certificates (JSON), one for generating and serving ZIPs.
src/components/com_tjcertificate/site/languages/en-GB/en-GB.com_tjcertificate.ini Introduced new language strings for bulk download prompts, errors, and success messages.
src/components/com_tjcertificate/site/views/certificates/tmpl/my_bs5.php Updated view template: included scripts, modal/loader markup, and bulk download button integration.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant JS (certificate.js)
    participant Joomla Controller
    participant Model
    participant Server (ZIP)

    User->>Browser: Clicks "Bulk Certificate Download" button
    Browser->>JS (certificate.js): Triggers click handler
    JS (certificate.js)->>Joomla Controller: AJAX POST fetchCertificatesForBulkDownload
    Joomla Controller->>Model: Query certificates (with filters)
    Model-->>Joomla Controller: Return certificate data
    Joomla Controller-->>JS (certificate.js): Respond with certificate HTML/data (JSON)
    JS (certificate.js)->>Browser: Show modal with certificates, show loader
    loop For each certificate
        JS (certificate.js)->>Server (ZIP): HEAD request to check PNG existence
        alt PNG exists
            JS (certificate.js)-->>JS (certificate.js): Mark as ready
        else PNG missing
            JS (certificate.js)->>Browser: Use html2canvas to generate PNG
            JS (certificate.js)->>Server (ZIP): Upload PNG via AJAX
        end
    end
    JS (certificate.js)->>Server (ZIP): Submit form to bulkCertificateDownload (with cert list)
    Server (ZIP)->>Joomla Controller: Create ZIP, add PNGs
    Joomla Controller-->>Browser: Serve ZIP file for download
    Browser->>User: Prompts ZIP file download
Loading
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 5, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
B Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

}
else
{
error_log("Missing file: " . $filePath);

Check notice

Code scanning / SonarCloud

Logging should not be vulnerable to injection attacks Low

Change this code to not log user-controlled data. See more on SonarQube Cloud
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (9)
src/components/com_tjcertificate/site/views/certificates/tmpl/my_bs5.php (2)

61-71: Consider accessibility improvements for modal elements.

The modal structure could benefit from ARIA attributes for better accessibility:

-<div id="certificateModalOverlay">
+<div id="certificateModalOverlay" role="dialog" aria-labelledby="modalTitle" aria-hidden="true">
   <div id="certificateModalContainer">
-	<button id="closeModalBtn">×</button>
+	<button id="closeModalBtn" aria-label="Close modal">×</button>
     <div id="certificateModalContent" style="padding-top: 20px;">
     </div>
   </div>
 </div>

96-103: Consider adding loading state and error handling to the button.

The bulk download button could benefit from visual feedback and proper error states:

 <a href=""
 id="bulkCertBtn"
-class="btn btn-primary btn-sm pull-right mb-15 m-5">
+class="btn btn-primary btn-sm pull-right mb-15 m-5"
+data-loading-text="<?php echo Text::_('COM_TJCERTIFICATE_PROCESSING'); ?>">
 <i class="fa fa-download" aria-hidden="true"></i>
 <?php echo Text::_("COM_TJCERTIFICATE_BULK_CERTIFICATE_DOWNLOAD"); ?>
 </a>
src/components/com_tjcertificate/media/css/tjCertificate.css (1)

218-228: Consider adding responsive design improvements.

The modal container could benefit from better mobile responsiveness:

 #certificateModalContainer{
 	background: #fff;
-	width: 80%;              /* Increased width */
-	max-width: 1300px;        /* Max width limit */
+	width: 90%;              /* Better mobile width */
+	max-width: 1300px;
 	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 */
 }
+
+@media (max-width: 768px) {
+	#certificateModalContainer {
+		width: 95%;
+		margin: 20px auto;
+		padding: 15px;
+	}
+}
src/components/com_tjcertificate/site/languages/en-GB/en-GB.com_tjcertificate.ini (1)

154-162: Language strings are well-structured but have some issues.

The bulk download language strings follow good naming conventions, but there are some improvements needed:

  1. Typo in line 161: "succesfully" should be "successfully"
  2. Clarity improvement for line 155: The message could be clearer
-COM_TJCERTIFICATE_CERTIFICATE_FILTER_CERTIFICATE_CLIENT_OR_SCHOOL_SELECT="Please select user from filter."
+COM_TJCERTIFICATE_CERTIFICATE_FILTER_CERTIFICATE_CLIENT_OR_SCHOOL_SELECT="Please select a user from the filter."
-COM_TJCERTIFICATE_CERTIFICATE_ZIP_FILE_DOWNLOAD_SUCCESFULLY="Bulk Certificates downloaded succesfully."
+COM_TJCERTIFICATE_CERTIFICATE_ZIP_FILE_DOWNLOAD_SUCCESSFULLY="Bulk certificates downloaded successfully."
src/components/com_tjcertificate/media/js/certificate.js (1)

374-382: Add timeout and better error handling for image existence check.

The checkImageExists function should include timeout handling:

 function checkImageExists(imageUrl) {
     return new Promise((resolve) => {
         const xhr = new XMLHttpRequest();
         xhr.open('HEAD', imageUrl, true);
+        xhr.timeout = 5000; // 5 second timeout
         xhr.onload = () => resolve(xhr.status === 200);
         xhr.onerror = () => resolve(false);
+        xhr.ontimeout = () => resolve(false);
         xhr.send();
     });
 }
src/components/com_tjcertificate/site/controllers/certificates.php (4)

19-19: Remove trailing whitespace.

Line 19 contains a trailing tab/space character after the semicolon.

-use Joomla\CMS\Session\Session;	
+use Joomla\CMS\Session\Session;

21-21: Remove unused import.

The Uri class is imported but not used in the code.

-use Joomla\CMS\Uri\Uri;

121-184: Clean up formatting and use strict comparison.

The method has several formatting issues that should be addressed:

  1. Remove excessive empty lines (lines 123-124, 133-134, 143-144)
  2. Add space after if keyword on line 128
  3. Use strict comparison for state validation
 public function fetchCertificatesForBulkDownload()
 {
-
-
 		$app = Factory::getApplication();
 
 		// CSRF token check
-		if (!Session::checkToken('get')) {
+		if (!Session::checkToken('get'))
+		{
 			echo new JsonResponse(null, Text::_('JINVALID_TOKEN'), true);
 			$app->close();
 		}
-
-
 		// Authorization check
 		$user = Factory::getUser();
 		if (
 			!$user->authorise('certificate.external.manage', 'com_tjcertificate') &&
 			!$user->authorise('core.manageall', 'com_cluster')
-		) {
+		)
+		{
 			echo new JsonResponse(null, Text::_('JERROR_ALERTNOAUTHOR'), true);
 			$app->close();
 		}
-
-
 		// Input retrieval
 		$user_id = $app->input->getInt('user_id', 0);
 		$client   = $app->input->get('client', '', 'STRING');
-		if ($state != 1) {
+		if ($state !== '1')
+		{
 			echo new JsonResponse(null, Text::_('COM_TJCERTIFICATE_CERTIFICATE_FILTER_CERTIFICATE_CLIENT_IS_USED_STATE'), true);
 			$app->close();
 		}

187-194: Fix method documentation formatting.

The method documentation has unnecessary empty lines and is missing the @throws tag per Joomla standards.

 	/**
-	 * 
 	 * Function to download bulk certificate
-	 * 
 	 * @return  Null
+	 * @throws  Exception  When ZIP file creation fails
 	 *
 	 * @since  __DEPLOY_VERSION__
 	 */
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 0127f42 and 20a3cfc.

⛔ Files ignored due to path filters (1)
  • src/components/com_tjcertificate/media/js/certificate.min.js is excluded by !**/*.min.js, !**/*.min.js
📒 Files selected for processing (5)
  • src/components/com_tjcertificate/media/css/tjCertificate.css (1 hunks)
  • src/components/com_tjcertificate/media/js/certificate.js (1 hunks)
  • src/components/com_tjcertificate/site/controllers/certificates.php (2 hunks)
  • src/components/com_tjcertificate/site/languages/en-GB/en-GB.com_tjcertificate.ini (1 hunks)
  • src/components/com_tjcertificate/site/views/certificates/tmpl/my_bs5.php (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
`**/*.php`: "Review the PHP code for Joomla coding standards compliance, ensuring: - Code follows Joomla's coding standards, validated using PHPCS with the Joomla standard. - Adher...

**/*.php: "Review the PHP code for Joomla coding standards compliance, ensuring:

  • Code follows Joomla's coding standards, validated using PHPCS with the Joomla standard.
  • Adherence to Joomla’s MVC structure for extensions.
  • Proper use of Joomla’s built-in functions (e.g., Input, Factory).
  • SQL queries use Joomla’s database API to prevent SQL injection.
  • Code is well-documented, with clear function and class descriptions."
  • src/components/com_tjcertificate/site/controllers/certificates.php
  • src/components/com_tjcertificate/site/views/certificates/tmpl/my_bs5.php
`**/*.js`: "Review JavaScript code to ensure: - Adherence to Google JavaScript style guide. - Efficient DOM manipulation suitable for Joomla’s frontend. - Use of Joomla’s core Java...

**/*.js: "Review JavaScript code to ensure:

  • Adherence to Google JavaScript style guide.
  • Efficient DOM manipulation suitable for Joomla’s frontend.
  • Use of Joomla’s core JavaScript libraries where applicable (e.g., jQuery compatibility).
  • Best practices for asynchronous requests if AJAX is used."
  • src/components/com_tjcertificate/media/js/certificate.js
🧬 Code Graph Analysis (1)
src/components/com_tjcertificate/site/controllers/certificates.php (1)
src/components/com_tjcertificate/administrator/includes/tjcertificate.php (1)
  • model (78-83)
🪛 GitHub Check: SonarCloud
src/components/com_tjcertificate/site/controllers/certificates.php

[notice] 222-222: Logging should not be vulnerable to injection attacks

Change this code to not log user-controlled data.

See more on SonarQube Cloud

🔇 Additional comments (3)
src/components/com_tjcertificate/site/views/certificates/tmpl/my_bs5.php (2)

20-20: LGTM: Proper Joomla namespace usage.

Good use of Joomla's URI class for getting the site root URL.


47-58: Good security practice with proper token usage.

The implementation correctly:

  • Validates and casts user_id to integer
  • Includes CSRF token for security
  • Uses Joomla's addScriptOptions for safe data passing
src/components/com_tjcertificate/media/css/tjCertificate.css (1)

252-264: LGTM: Well-implemented loader with modern CSS.

The full-screen loader uses modern flexbox for centering and appropriate z-index stacking. The implementation is clean and effective.

HTMLHelper::_('script', 'com_tjcertificate/tjCertificateService.min.js', $options);
HTMLHelper::_('script', 'com_tjcertificate/certificate.min.js', $options);
HTMLHelper::StyleSheet('media/com_tjcertificate/css/tjCertificate.css');
HTMLHelper::script('media/com_tjcertificate/vendors/html2canvas/js/html2canvas.js');
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 html2canvas.js security and version.

The html2canvas library is included without version specification. Ensure you're using a secure, up-to-date version and consider the security implications of client-side HTML rendering.


🏁 Script executed:

#!/bin/bash
# Check if html2canvas.js exists and verify its version/integrity
echo "Checking html2canvas.js file..."
fd -t f "html2canvas.js" --exec head -20 {} \;

Length of output: 1206


Ensure html2canvas Is Up-to-Date and Loaded Securely

We found that you’re currently bundling html2canvas 1.0.0-rc.7 (from 2020) with no version pinning or integrity checks. To reduce security risks and benefit from bug/security fixes:

• Upgrade to the latest stable release (e.g. 1.4.1)
• Host via a trusted CDN (or keep a local copy named with its version)
• Add Subresource Integrity (SRI) and crossorigin attributes

For example, in src/components/com_tjcertificate/site/views/certificates/tmpl/my_bs5.php at line 45:

- HTMLHelper::script('media/com_tjcertificate/vendors/html2canvas/js/html2canvas.js');
+ HTMLHelper::script(
+   'https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js',
+   [
+     'integrity'  => 'sha384-<INSERT_CALCULATED_HASH>',
+     'crossorigin'=> 'anonymous'
+   ]
+);

Replace <INSERT_CALCULATED_HASH> with the SRI hash from the CDN provider or your own build.

📝 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
HTMLHelper::script('media/com_tjcertificate/vendors/html2canvas/js/html2canvas.js');
HTMLHelper::script(
'https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js',
[
'integrity' => 'sha384-<INSERT_CALCULATED_HASH>',
'crossorigin'=> 'anonymous'
]
);
🤖 Prompt for AI Agents
In src/components/com_tjcertificate/site/views/certificates/tmpl/my_bs5.php at
line 45, the html2canvas.js script is included without version pinning or
security attributes. Update the script to use the latest stable version (e.g.,
1.4.1) by either linking to a trusted CDN or renaming the local file to include
the version. Additionally, add Subresource Integrity (SRI) and crossorigin
attributes to the script tag to ensure secure loading and integrity
verification.

Comment on lines +244 to +333
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();
});
});
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.

Comment on lines +335 to +372
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);
});
});
}
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.

Comment on lines +252 to +327
$.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);
}
});
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.

Comment on lines +208 to +210
$zip = new ZipArchive();

if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true)
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

Check ZipArchive extension availability.

The code uses ZipArchive without checking if the PHP zip extension is installed.

+		if (!class_exists('ZipArchive'))
+		{
+			$app->enqueueMessage(Text::_('COM_TJCERTIFICATE_CERTIFICATE_ZIP_EXTENSION_NOT_AVAILABLE'), 'error');
+			$this->setRedirect('index.php?option=com_tjcertificate&view=certificates&layout=my');
+			return;
+		}
+
 		$zip = new ZipArchive();
🤖 Prompt for AI Agents
In src/components/com_tjcertificate/site/controllers/certificates.php around
lines 208 to 210, the code instantiates ZipArchive without verifying if the PHP
zip extension is enabled. Add a check using extension_loaded('zip') before
creating the ZipArchive object, and handle the case where the extension is not
available by logging an error or returning a meaningful message to prevent
runtime errors.

Comment on lines +199 to +203
$certificateDatas = $app->input->get('certificates', [], 'array');
$user_id = $app->input->getInt('user_id', 0);

$user= Factory::getUser($user_id);

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

Validate user exists before using user data.

The code doesn't check if the user exists before using $user->name for the ZIP filename.

 		$certificateDatas = $app->input->get('certificates', [], 'array');
 		$user_id         = $app->input->getInt('user_id', 0);
 	
 		$user= Factory::getUser($user_id);
+		
+		if (!$user->id)
+		{
+			$app->enqueueMessage(Text::_('COM_TJCERTIFICATE_CERTIFICATE_INVALID_USER'), 'error');
+			$this->setRedirect('index.php?option=com_tjcertificate&view=certificates&layout=my');
+			return;
+		}
🤖 Prompt for AI Agents
In src/components/com_tjcertificate/site/controllers/certificates.php around
lines 199 to 203, the code retrieves a user object but does not verify if the
user exists before accessing its properties like $user->name. Add a check to
confirm that $user is valid and exists before using its data, and handle the
case where the user is not found, such as returning an error or defaulting the
filename to avoid runtime errors.

Comment on lines +214 to +224
$filePath = $folderPath . '/' . $certificateData['unique_certificate_id'] . '.png';

if (file_exists($filePath))
{
$zip->addFile($filePath, basename($filePath));
}
else
{
error_log("Missing file: " . $filePath);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix security vulnerability in error logging and validate input.

The error_log call on line 222 logs user-controlled data which could lead to log injection attacks. Additionally, the unique_certificate_id should be validated to prevent directory traversal.

 			foreach ($certificateDatas as $certificateData)
 			{
+				// Validate certificate data structure
+				if (!isset($certificateData['unique_certificate_id']) || 
+					!preg_match('/^[a-zA-Z0-9_-]+$/', $certificateData['unique_certificate_id']))
+				{
+					continue;
+				}
+
 				$filePath = $folderPath . '/' . $certificateData['unique_certificate_id'] . '.png';
 
 				if (file_exists($filePath))
 				{
 					$zip->addFile($filePath, basename($filePath));
 				}
-				else
-				{
-					error_log("Missing file: " . $filePath);
-				}
 			}
📝 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
$filePath = $folderPath . '/' . $certificateData['unique_certificate_id'] . '.png';
if (file_exists($filePath))
{
$zip->addFile($filePath, basename($filePath));
}
else
{
error_log("Missing file: " . $filePath);
}
}
foreach ($certificateDatas as $certificateData)
{
// Validate certificate data structure
if (!isset($certificateData['unique_certificate_id'])
|| !preg_match('/^[a-zA-Z0-9_-]+$/', $certificateData['unique_certificate_id']))
{
continue;
}
$filePath = $folderPath . '/' . $certificateData['unique_certificate_id'] . '.png';
if (file_exists($filePath))
{
$zip->addFile($filePath, basename($filePath));
}
}
🧰 Tools
🪛 GitHub Check: SonarCloud

[notice] 222-222: Logging should not be vulnerable to injection attacks

Change this code to not log user-controlled data.

See more on SonarQube Cloud

🤖 Prompt for AI Agents
In src/components/com_tjcertificate/site/controllers/certificates.php around
lines 214 to 224, the error_log call logs the user-controlled
unique_certificate_id directly, risking log injection, and the
unique_certificate_id is used in file paths without validation, risking
directory traversal. To fix this, sanitize or escape the unique_certificate_id
before logging to prevent injection, and validate or whitelist the
unique_certificate_id format to ensure it does not contain directory traversal
characters before using it in file paths.

Comment on lines +195 to +256
public function bulkCertificateDownload()
{
$app = Factory::getApplication();

$certificateDatas = $app->input->get('certificates', [], 'array');
$user_id = $app->input->getInt('user_id', 0);

$user= Factory::getUser($user_id);

$folderPath = JPATH_SITE . '/media/com_tjcertificate/certificates';
$zipFileName = $user->name . '.zip';
$zipFilePath = $folderPath . '/' . $zipFileName;

$zip = new ZipArchive();

if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true)
{
foreach ($certificateDatas as $certificateData)
{
$filePath = $folderPath . '/' . $certificateData['unique_certificate_id'] . '.png';

if (file_exists($filePath))
{
$zip->addFile($filePath, basename($filePath));
}
else
{
error_log("Missing file: " . $filePath);
}
}

$zip->close();

if (file_exists($zipFilePath))
{
if (ob_get_level())
{
ob_end_clean();
}

header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . basename($zipFilePath) . '"');
header('Content-Length: ' . filesize($zipFilePath));
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Expires: 0');
header('Pragma: no-cache');

readfile($zipFilePath);
unlink($zipFilePath);
}
else
{
$app->enqueueMessage(Text::_('COM_TJCERTIFICATE_CERTIFICATE_ZIP_FILE_NOT_FOUND'), 'error');
$this->setRedirect('index.php?option=com_tjcertificate&view=certificates&layout=my');
}
}
else
{
$app->enqueueMessage(Text::_('COM_TJCERTIFICATE_CERTIFICATE_ZIP_FILE_CREATION_FAILED'), 'error');
$this->setRedirect('index.php?option=com_tjcertificate&view=certificates&layout=my');
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add CSRF token validation and authorization checks.

This method is missing critical security checks that must be implemented before processing the bulk download request.

Add the following security checks at the beginning of the method:

 public function bulkCertificateDownload()
 {
 		$app = Factory::getApplication();
+
+		// CSRF token check
+		if (!Session::checkToken())
+		{
+			$app->enqueueMessage(Text::_('JINVALID_TOKEN'), 'error');
+			$this->setRedirect('index.php?option=com_tjcertificate&view=certificates&layout=my');
+			return;
+		}
+
+		// Authorization check
+		$user = Factory::getUser();
+		if (!$user->authorise('certificate.external.manage', 'com_tjcertificate') &&
+			!$user->authorise('core.manageall', 'com_cluster'))
+		{
+			$app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error');
+			$this->setRedirect('index.php?option=com_tjcertificate&view=certificates&layout=my');
+			return;
+		}
 
 		$certificateDatas = $app->input->get('certificates', [], 'array');
📝 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
public function bulkCertificateDownload()
{
$app = Factory::getApplication();
$certificateDatas = $app->input->get('certificates', [], 'array');
$user_id = $app->input->getInt('user_id', 0);
$user= Factory::getUser($user_id);
$folderPath = JPATH_SITE . '/media/com_tjcertificate/certificates';
$zipFileName = $user->name . '.zip';
$zipFilePath = $folderPath . '/' . $zipFileName;
$zip = new ZipArchive();
if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true)
{
foreach ($certificateDatas as $certificateData)
{
$filePath = $folderPath . '/' . $certificateData['unique_certificate_id'] . '.png';
if (file_exists($filePath))
{
$zip->addFile($filePath, basename($filePath));
}
else
{
error_log("Missing file: " . $filePath);
}
}
$zip->close();
if (file_exists($zipFilePath))
{
if (ob_get_level())
{
ob_end_clean();
}
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . basename($zipFilePath) . '"');
header('Content-Length: ' . filesize($zipFilePath));
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Expires: 0');
header('Pragma: no-cache');
readfile($zipFilePath);
unlink($zipFilePath);
}
else
{
$app->enqueueMessage(Text::_('COM_TJCERTIFICATE_CERTIFICATE_ZIP_FILE_NOT_FOUND'), 'error');
$this->setRedirect('index.php?option=com_tjcertificate&view=certificates&layout=my');
}
}
else
{
$app->enqueueMessage(Text::_('COM_TJCERTIFICATE_CERTIFICATE_ZIP_FILE_CREATION_FAILED'), 'error');
$this->setRedirect('index.php?option=com_tjcertificate&view=certificates&layout=my');
}
}
public function bulkCertificateDownload()
{
$app = Factory::getApplication();
// CSRF token check
if (!Session::checkToken())
{
$app->enqueueMessage(Text::_('JINVALID_TOKEN'), 'error');
$this->setRedirect('index.php?option=com_tjcertificate&view=certificates&layout=my');
return;
}
// Authorization check
$user = Factory::getUser();
if (
!$user->authorise('certificate.external.manage', 'com_tjcertificate')
&& !$user->authorise('core.manageall', 'com_cluster')
) {
$app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error');
$this->setRedirect('index.php?option=com_tjcertificate&view=certificates&layout=my');
return;
}
$certificateDatas = $app->input->get('certificates', [], 'array');
$user_id = $app->input->getInt('user_id', 0);
$user = Factory::getUser($user_id);
$folderPath = JPATH_SITE . '/media/com_tjcertificate/certificates';
$zipFileName = $user->name . '.zip';
$zipFilePath = $folderPath . '/' . $zipFileName;
$zip = new ZipArchive();
if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true)
{
foreach ($certificateDatas as $certificateData)
{
$filePath = $folderPath . '/' . $certificateData['unique_certificate_id'] . '.png';
if (file_exists($filePath))
{
$zip->addFile($filePath, basename($filePath));
}
else
{
error_log("Missing file: " . $filePath);
}
}
$zip->close();
if (file_exists($zipFilePath))
{
if (ob_get_level())
{
ob_end_clean();
}
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . basename($zipFilePath) . '"');
header('Content-Length: ' . filesize($zipFilePath));
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Expires: 0');
header('Pragma: no-cache');
readfile($zipFilePath);
unlink($zipFilePath);
}
else
{
$app->enqueueMessage(Text::_('COM_TJCERTIFICATE_CERTIFICATE_ZIP_FILE_NOT_FOUND'), 'error');
$this->setRedirect('index.php?option=com_tjcertificate&view=certificates&layout=my');
}
}
else
{
$app->enqueueMessage(Text::_('COM_TJCERTIFICATE_CERTIFICATE_ZIP_FILE_CREATION_FAILED'), 'error');
$this->setRedirect('index.php?option=com_tjcertificate&view=certificates&layout=my');
}
}
🧰 Tools
🪛 GitHub Check: SonarCloud

[notice] 222-222: Logging should not be vulnerable to injection attacks

Change this code to not log user-controlled data.

See more on SonarQube Cloud

🤖 Prompt for AI Agents
In src/components/com_tjcertificate/site/controllers/certificates.php around
lines 195 to 256, the bulkCertificateDownload method lacks CSRF token validation
and user authorization checks. To fix this, add a CSRF token check using
Joomla's session token validation at the start of the method to prevent
cross-site request forgery. Also, verify that the current user is authorized to
download the requested certificates by checking their permissions or ownership
before proceeding. If either check fails, enqueue an error message and redirect
appropriately to stop unauthorized access.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants