diff --git a/wordpress-dev/bin/deploy-config.sh b/wordpress-dev/bin/deploy-config.sh new file mode 100755 index 00000000..77152659 --- /dev/null +++ b/wordpress-dev/bin/deploy-config.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Deployment Configuration +PROJECT_ROOT="/Users/ben/dev/upskill-event-manager" +REMOTE_HOST="146.190.76.204" +REMOTE_USER="roodev" +REMOTE_PATH_BASE="/home/974670.cloudwaysapps.com/uberrxmprk/public_html" +PLUGIN_SLUG="hvac-community-events" +REMOTE_PLUGIN_PATH="/home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/plugins/hvac-community-events" +LOCAL_PLUGIN_PATH="/Users/ben/dev/upskill-event-manager/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events" \ No newline at end of file diff --git a/wordpress-dev/bin/test-certificate-email.sh b/wordpress-dev/bin/test-certificate-email.sh new file mode 100755 index 00000000..a739067a --- /dev/null +++ b/wordpress-dev/bin/test-certificate-email.sh @@ -0,0 +1,149 @@ +#!/bin/bash + +# Script to test certificate email functionality on staging server + +# Load configuration +source bin/deploy-config.sh + +# Set email address to test with +TEST_EMAIL="ben@tealmaker.com" + +echo "=== Testing Certificate Email Functionality ===" +echo "Remote host: $REMOTE_HOST" +echo "Remote user: $REMOTE_USER" +echo "WordPress path: $REMOTE_PATH_BASE" +echo "Test email: $TEST_EMAIL" +echo "===============================" + +# Connect to the remote server and run a WP-CLI command to create a test event, attendee, and certificate +ssh $REMOTE_USER@$REMOTE_HOST "cd $REMOTE_PATH_BASE && \ + wp eval ' + // Create a test event if needed + \$event_exists = get_posts(array( + \"post_type\" => \"tribe_events\", + \"meta_key\" => \"_test_certificate_event\", + \"meta_value\" => \"yes\", + \"posts_per_page\" => 1 + )); + + if (empty(\$event_exists)) { + \$event_id = wp_insert_post(array( + \"post_title\" => \"Test Certificate Event\", + \"post_content\" => \"This is a test event for certificate generation\", + \"post_status\" => \"publish\", + \"post_type\" => \"tribe_events\" + )); + update_post_meta(\$event_id, \"_test_certificate_event\", \"yes\"); + echo \"Created test event with ID: {\$event_id}\\n\"; + } else { + \$event_id = \$event_exists[0]->ID; + echo \"Using existing test event with ID: {\$event_id}\\n\"; + } + + // Create a test attendee + \$attendee_exists = get_posts(array( + \"post_type\" => \"tribe_rsvp_attendees\", + \"meta_key\" => \"_tribe_tickets_email\", + \"meta_value\" => \"$TEST_EMAIL\", + \"posts_per_page\" => 1 + )); + + if (empty(\$attendee_exists)) { + \$attendee_id = wp_insert_post(array( + \"post_title\" => \"Test Attendee\", + \"post_status\" => \"publish\", + \"post_type\" => \"tribe_rsvp_attendees\" + )); + + update_post_meta(\$attendee_id, \"_tribe_tickets_email\", \"$TEST_EMAIL\"); + update_post_meta(\$attendee_id, \"_tribe_tickets_full_name\", \"Test Attendee\"); + update_post_meta(\$attendee_id, \"_tribe_rsvp_event\", \$event_id); + update_post_meta(\$attendee_id, \"_tribe_rsvp_status\", \"yes\"); + update_post_meta(\$attendee_id, \"_tribe_tickets_attendee_status\", \"yes\"); + update_post_meta(\$attendee_id, \"check_in\", \"1\"); + + echo \"Created test attendee with ID: {\$attendee_id}\\n\"; + } else { + \$attendee_id = \$attendee_exists[0]->ID; + echo \"Using existing attendee with ID: {\$attendee_id}\\n\"; + } + + // Load certificate manager + require_once WP_PLUGIN_DIR . \"/hvac-community-events/includes/certificates/class-certificate-manager.php\"; + require_once WP_PLUGIN_DIR . \"/hvac-community-events/includes/certificates/class-certificate-generator.php\"; + + \$certificate_manager = HVAC_Certificate_Manager::instance(); + \$certificate_generator = HVAC_Certificate_Generator::instance(); + + // Generate certificate + \$certificate_exists = \$certificate_manager->get_certificate_by_attendee(\$event_id, \$attendee_id); + + if (!\$certificate_exists) { + \$result = \$certificate_generator->generate_certificate(\$event_id, \$attendee_id); + if (\$result) { + \$certificate = \$certificate_manager->get_certificate_by_attendee(\$event_id, \$attendee_id); + echo \"Generated certificate with ID: {\$certificate->certificate_id}\\n\"; + } else { + echo \"Failed to generate certificate\\n\"; + } + } else { + echo \"Using existing certificate with ID: {\$certificate_exists->certificate_id}\\n\"; + } + + // Get existing certificate + \$certificate = \$certificate_manager->get_certificate_by_attendee(\$event_id, \$attendee_id); + if (!\$certificate) { + echo \"No certificate found\\n\"; + exit; + } + + // Email certificate + require_once WP_PLUGIN_DIR . \"/hvac-community-events/includes/certificates/class-certificate-security.php\"; + \$certificate_security = HVAC_Certificate_Security::instance(); + + // Generate secure download URL + \$event = get_post(\$certificate->event_id); + \$attendee_name = get_post_meta(\$certificate->attendee_id, \"_tribe_tickets_full_name\", true); + + \$certificate_data = array( + \"file_path\" => \$certificate->file_path, + \"event_name\" => \$event->post_title, + \"attendee_name\" => \$attendee_name + ); + + \$download_url = \$certificate_security->generate_download_token(\$certificate->certificate_id, \$certificate_data, 7 * DAY_IN_SECONDS); + + if (!\$download_url) { + echo \"Failed to generate download URL\\n\"; + exit; + } + + // Email subject + \$subject = sprintf( + \"Your Certificate for %s\", + \$event->post_title + ); + + // Email body + \$message = sprintf( + \"Hello %s,\\n\\nThank you for attending %s.\\n\\nYour certificate of completion is now available. Please click the link below to download your certificate:\\n\\n%s\\n\\nThis link will expire in 7 days.\\n\\nRegards,\\nTest System\", + \$attendee_name, + \$event->post_title, + \$download_url + ); + + // Send email + \$headers = array(\"Content-Type: text/plain; charset=UTF-8\"); + \$sent = wp_mail(\"$TEST_EMAIL\", \$subject, \$message, \$headers); + + if (\$sent) { + // Record email sent + \$certificate_manager->mark_certificate_emailed(\$certificate->certificate_id); + echo \"Certificate email sent successfully to $TEST_EMAIL\\n\"; + } else { + echo \"Failed to send certificate email\\n\"; + } + ' +" + +echo "Email verification complete. Please check $TEST_EMAIL for the certificate email." \ No newline at end of file diff --git a/wordpress-dev/tests/e2e/certificate-generation.test.ts b/wordpress-dev/tests/e2e/certificate-generation.test.ts new file mode 100644 index 00000000..81391bad --- /dev/null +++ b/wordpress-dev/tests/e2e/certificate-generation.test.ts @@ -0,0 +1,127 @@ +import { test, expect } from '@playwright/test'; + +/** + * Certificate Generation End-to-End Test + * + * This test verifies the certificate generation functionality: + * - Login as a trainer + * - Navigate to an event summary page + * - Generate certificates for attendees + * - View certificate + * - Email certificate to a test email + * - Revoke certificate + */ + +test.describe('Certificate Generation Tests', () => { + const stagingUrl = 'https://wordpress-974670-5399585.cloudwaysapps.com/'; + const loginUrl = `${stagingUrl}community-login/`; + const dashboardUrl = `${stagingUrl}hvac-dashboard/`; + const testEmail = 'ben@tealmaker.com'; + + test.beforeEach(async ({ page }) => { + // Login as trainer + await page.goto(loginUrl); + await page.fill('input[name="log"]', 'test_trainer'); + await page.fill('input[name="pwd"]', 'Test123!'); + await page.click('input[type="submit"]'); + + // Verify login was successful by checking for dashboard + await expect(page).toHaveURL(dashboardUrl); + }); + + test('Generate and manage certificates for an event', async ({ page }) => { + // Navigate to the dashboard to find an event + await page.goto(dashboardUrl); + + // Click on the first event summary link + await page.click('.hvac-event-title a'); + + // Wait for the event summary page to load + await expect(page.locator('h1')).toContainText('Summary'); + + // Check if we have attendees + const hasAttendees = await page.locator('.hvac-transactions-table').isVisible(); + + if (hasAttendees) { + // Check if any attendee doesn't have a certificate yet + const generateButtonExists = await page.locator('.hvac-cert-action:text("Generate")').isVisible(); + + if (generateButtonExists) { + // Generate a certificate for an attendee + await page.click('.hvac-cert-action:text("Generate")'); + + // Navigate to the generate certificates page + await expect(page.locator('h1')).toContainText('Generate Certificates'); + + // Check the 'checked_in_only' checkbox + await page.check('#checked-in-only-checkbox'); + + // Generate certificates + await page.click('button[type="submit"]'); + + // Wait for success message + await expect(page.locator('.hvac-success-message')).toBeVisible(); + + // Go back to the event summary + await page.click('a:text("Back to Event Summary")'); + } + + // Check if there are any generated certificates + const viewButtonExists = await page.locator('.hvac-cert-action:text("View")').isVisible(); + + if (viewButtonExists) { + // View a certificate + await page.click('.hvac-cert-action:text("View")', { force: true }); + + // Wait for the certificate modal to appear + await expect(page.locator('#hvac-certificate-modal')).toBeVisible(); + + // Wait for the certificate iframe to load + await page.waitForSelector('#hvac-certificate-preview[src^="http"]'); + + // Close the modal + await page.click('.hvac-modal-close'); + + // Email a certificate + await page.click('.hvac-cert-action:text("Email")', { force: true }); + + // Confirm the email dialog + await page.once('dialog', dialog => dialog.accept()); + + // Wait for success message + await page.waitForTimeout(2000); // Wait for alert to appear and dismiss + + // Revoke a certificate + await page.click('.hvac-cert-action:text("Revoke")', { force: true }); + + // Enter reason in the prompt + await page.once('dialog', dialog => dialog.accept('Revocation test')); + + // Wait for status to update to Revoked + await expect(page.locator('td:has-text("Revoked")')).toBeVisible(); + } else { + console.log('No generated certificates available to test with'); + } + } else { + console.log('No attendees found for the event'); + } + }); + + test('Navigate to Certificate Reports page', async ({ page }) => { + // Navigate to certificate reports page + await page.goto(`${stagingUrl}certificate-reports/`); + + // Verify the page loaded successfully + await expect(page.locator('h1')).toContainText('Certificate Reports'); + + // Try filtering certificates + await page.selectOption('select[name="filter_status"]', 'all'); + await page.click('button[type="submit"]'); + + // Check for certificate data or empty message + const hasReports = await page.locator('.hvac-certificate-table').isVisible(); + if (!hasReports) { + await expect(page.locator('.hvac-no-certificates')).toBeVisible(); + } + }); +}); \ No newline at end of file diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/composer.lock b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/composer.lock index 95997504..458680de 100644 --- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/composer.lock +++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d03d5891db11eabba74e5b9558c9c7b9", + "content-hash": "fabbbe60cdbad1e1d198c566db326a4c", "packages": [ { "name": "composer/installers", @@ -156,6 +156,77 @@ } ], "time": "2021-09-13T08:19:44+00:00" + }, + { + "name": "tecnickcom/tcpdf", + "version": "6.9.4", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/TCPDF.git", + "reference": "c838d7f7babb0d35763acfb9ecf78c3f45966f83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/c838d7f7babb0d35763acfb9ecf78c3f45966f83", + "reference": "c838d7f7babb0d35763acfb9ecf78c3f45966f83", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=7.1.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "config", + "include", + "tcpdf.php", + "tcpdf_barcodes_1d.php", + "tcpdf_barcodes_2d.php", + "include/tcpdf_colors.php", + "include/tcpdf_filters.php", + "include/tcpdf_font_data.php", + "include/tcpdf_fonts.php", + "include/tcpdf_images.php", + "include/tcpdf_static.php", + "include/barcodes/datamatrix.php", + "include/barcodes/pdf417.php", + "include/barcodes/qrcode.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "TCPDF is a PHP class for generating PDF documents and barcodes.", + "homepage": "http://www.tcpdf.org/", + "keywords": [ + "PDFD32000-2008", + "TCPDF", + "barcodes", + "datamatrix", + "pdf", + "pdf417", + "qrcode" + ], + "support": { + "issues": "https://github.com/tecnickcom/TCPDF/issues", + "source": "https://github.com/tecnickcom/TCPDF/tree/6.9.4" + }, + "funding": [ + { + "url": "https://www.paypal.com/donate/?hosted_button_id=NZUEC5XS8MFBJ", + "type": "custom" + } + ], + "time": "2025-05-13T11:34:35+00:00" } ], "packages-dev": [ @@ -349,20 +420,20 @@ }, { "name": "hamcrest/hamcrest-php", - "version": "v2.0.1", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", "shasum": "" }, "require": { - "php": "^5.3|^7.0|^8.0" + "php": "^7.4|^8.0" }, "replace": { "cordoval/hamcrest-php": "*", @@ -370,8 +441,8 @@ "kodova/hamcrest-php": "*" }, "require-dev": { - "phpunit/php-file-iterator": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { @@ -394,9 +465,9 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" }, - "time": "2020-07-09T08:09:16+00:00" + "time": "2025-04-30T06:54:44+00:00" }, { "name": "mockery/mockery", @@ -483,16 +554,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.0", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414" + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", "shasum": "" }, "require": { @@ -531,7 +602,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" }, "funding": [ { @@ -539,7 +610,7 @@ "type": "tidelift" } ], - "time": "2025-02-12T12:17:51+00:00" + "time": "2025-04-29T12:36:36+00:00" }, { "name": "nikic/php-parser", @@ -1038,16 +1109,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.22", + "version": "9.6.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", "shasum": "" }, "require": { @@ -1058,7 +1129,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", @@ -1121,7 +1192,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23" }, "funding": [ { @@ -1132,12 +1203,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2024-12-05T13:48:26+00:00" + "time": "2025-05-02T06:40:34+00:00" }, { "name": "sebastian/cli-parser", @@ -2154,16 +2233,16 @@ }, { "name": "wp-phpunit/wp-phpunit", - "version": "6.7.2", + "version": "6.8.1", "source": { "type": "git", "url": "https://github.com/wp-phpunit/wp-phpunit.git", - "reference": "e2bb06bacc92a8e9e405e83f56989e8ed9359db1" + "reference": "a33d328dab5a4a9ddf0c560bcadbabb58b5ee67f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-phpunit/wp-phpunit/zipball/e2bb06bacc92a8e9e405e83f56989e8ed9359db1", - "reference": "e2bb06bacc92a8e9e405e83f56989e8ed9359db1", + "url": "https://api.github.com/repos/wp-phpunit/wp-phpunit/zipball/a33d328dab5a4a9ddf0c560bcadbabb58b5ee67f", + "reference": "a33d328dab5a4a9ddf0c560bcadbabb58b5ee67f", "shasum": "" }, "type": "library", @@ -2198,7 +2277,7 @@ "issues": "https://github.com/wp-phpunit/issues", "source": "https://github.com/wp-phpunit/wp-phpunit" }, - "time": "2025-02-12T01:22:52+00:00" + "time": "2025-04-16T01:40:54+00:00" }, { "name": "yoast/phpunit-polyfills",