Task #240454 chore : Bulk Certificates download at a once for the admins.#163
Task #240454 chore : Bulk Certificates download at a once for the admins.#163iamnileshmittha wants to merge 1 commit intotechjoomla:dev-joomla4from
Conversation
WalkthroughA 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
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
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
Documentation and Community
|
|
There was a problem hiding this comment.
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:
- Typo in line 161: "succesfully" should be "successfully"
- 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
checkImageExistsfunction 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
Uriclass 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:
- Remove excessive empty lines (lines 123-124, 133-134, 143-144)
- Add space after
ifkeyword on line 128- 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
@throwstag 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
⛔ Files ignored due to path filters (1)
src/components/com_tjcertificate/media/js/certificate.min.jsis 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.phpsrc/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'); |
There was a problem hiding this comment.
💡 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.
| 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.
| 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(); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Implement comprehensive error handling and security improvements.
The bulk download implementation has several areas for improvement:
- Missing error handling for configuration validation
- Security concerns with dynamic form creation
- 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.
| 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); | ||
| }); | ||
| }); | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| 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.
| $.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); | ||
| } | ||
| }); |
There was a problem hiding this comment.
💡 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.
| $zip = new ZipArchive(); | ||
|
|
||
| if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) |
There was a problem hiding this comment.
🛠️ 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.
| $certificateDatas = $app->input->get('certificates', [], 'array'); | ||
| $user_id = $app->input->getInt('user_id', 0); | ||
|
|
||
| $user= Factory::getUser($user_id); | ||
|
|
There was a problem hiding this comment.
🛠️ 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.
| $filePath = $folderPath . '/' . $certificateData['unique_certificate_id'] . '.png'; | ||
|
|
||
| if (file_exists($filePath)) | ||
| { | ||
| $zip->addFile($filePath, basename($filePath)); | ||
| } | ||
| else | ||
| { | ||
| error_log("Missing file: " . $filePath); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| $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.
| 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'); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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.




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
Bug Fixes
Documentation