feat: Add Email Attendees functionality (Phase 2)
Implements the Email Attendees feature which allows trainers to: - Email event attendees directly from the Event Summary page - Filter attendees by ticket type - Use a rich text editor to compose messages - Include CC recipients - Send personalized emails to selected attendees Includes unit tests, integration tests, and E2E tests to verify functionality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
11bad93a65
commit
e6bdce4301
14 changed files with 1871 additions and 6 deletions
125
wordpress-dev/bin/run-email-attendees-tests.sh
Executable file
125
wordpress-dev/bin/run-email-attendees-tests.sh
Executable file
|
|
@ -0,0 +1,125 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Email Attendees Feature Tests Runner
|
||||
# This script:
|
||||
# 1. Transfers test files to the staging server
|
||||
# 2. Runs the tests on the staging server
|
||||
# 3. Returns the results
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
STAGING_HOST="146.190.76.204"
|
||||
STAGING_USER="roodev"
|
||||
REMOTE_PLUGIN_PATH="/home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/plugins/hvac-community-events"
|
||||
LOCAL_PATH="$(pwd)"
|
||||
|
||||
# Print header
|
||||
echo -e "${GREEN}=== Running Email Attendees Tests on Staging ===${NC}"
|
||||
echo -e "Remote host: ${STAGING_HOST}"
|
||||
echo -e "Remote user: ${STAGING_USER}"
|
||||
echo -e "Plugin path: ${REMOTE_PLUGIN_PATH}"
|
||||
echo -e "======================================="
|
||||
|
||||
# Check if test directory exists on staging
|
||||
ssh ${STAGING_USER}@${STAGING_HOST} "if [ ! -d ${REMOTE_PLUGIN_PATH}/tests ]; then mkdir -p ${REMOTE_PLUGIN_PATH}/tests; fi"
|
||||
ssh ${STAGING_USER}@${STAGING_HOST} "if [ ! -d ${REMOTE_PLUGIN_PATH}/tests/unit ]; then mkdir -p ${REMOTE_PLUGIN_PATH}/tests/unit; fi"
|
||||
ssh ${STAGING_USER}@${STAGING_HOST} "if [ ! -d ${REMOTE_PLUGIN_PATH}/tests/integration ]; then mkdir -p ${REMOTE_PLUGIN_PATH}/tests/integration; fi"
|
||||
|
||||
# Transfer test files
|
||||
echo -e "Transferring test files to staging server..."
|
||||
scp "${LOCAL_PATH}/wordpress/wp-content/plugins/hvac-community-events/tests/unit/test-email-attendees-data.php" ${STAGING_USER}@${STAGING_HOST}:${REMOTE_PLUGIN_PATH}/tests/unit/
|
||||
scp "${LOCAL_PATH}/wordpress/wp-content/plugins/hvac-community-events/tests/integration/test-email-attendees-integration.php" ${STAGING_USER}@${STAGING_HOST}:${REMOTE_PLUGIN_PATH}/tests/integration/
|
||||
|
||||
# Create bootstrap file if it doesn't exist
|
||||
cat > /tmp/bootstrap-staging.php << 'EOF'
|
||||
<?php
|
||||
/**
|
||||
* PHPUnit bootstrap file for staging tests
|
||||
*/
|
||||
|
||||
// Load WordPress test environment
|
||||
$_tests_dir = getenv( 'WP_TESTS_DIR' );
|
||||
if ( ! $_tests_dir ) {
|
||||
$_tests_dir = '/tmp/wordpress-tests-lib';
|
||||
}
|
||||
|
||||
// Give access to tests_add_filter() function
|
||||
require_once $_tests_dir . '/includes/functions.php';
|
||||
|
||||
/**
|
||||
* Manually load the plugin being tested
|
||||
*/
|
||||
function _manually_load_plugin() {
|
||||
// First load The Events Calendar and other dependencies
|
||||
require '/home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/plugins/the-events-calendar/the-events-calendar.php';
|
||||
require '/home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/plugins/event-tickets/event-tickets.php';
|
||||
|
||||
// Then load our plugin
|
||||
require '/home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/plugins/hvac-community-events/hvac-community-events.php';
|
||||
}
|
||||
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
|
||||
|
||||
// Start up the WP testing environment
|
||||
require $_tests_dir . '/includes/bootstrap.php';
|
||||
EOF
|
||||
|
||||
# Transfer bootstrap file
|
||||
scp /tmp/bootstrap-staging.php ${STAGING_USER}@${STAGING_HOST}:${REMOTE_PLUGIN_PATH}/tests/
|
||||
|
||||
# Create test config file
|
||||
cat > /tmp/wp-tests-config-staging.php << 'EOF'
|
||||
<?php
|
||||
/**
|
||||
* WordPress Test Suite configuration for staging.
|
||||
*/
|
||||
|
||||
// Test with WordPress debug mode on
|
||||
define( 'WP_DEBUG', true );
|
||||
|
||||
// Path to WordPress core
|
||||
define( 'ABSPATH', '/home/974670.cloudwaysapps.com/uberrxmprk/public_html/' );
|
||||
|
||||
// Test database config
|
||||
define( 'DB_NAME', 'uberrxmprk' );
|
||||
define( 'DB_USER', 'uberrxmprk' );
|
||||
define( 'DB_PASSWORD', '89FqzFg9vG' );
|
||||
define( 'DB_HOST', 'localhost' );
|
||||
define( 'DB_CHARSET', 'utf8' );
|
||||
define( 'DB_COLLATE', '' );
|
||||
|
||||
// WordPress DB table prefix
|
||||
$table_prefix = 'wp_';
|
||||
|
||||
// Test with multisite disabled
|
||||
define( 'WP_TESTS_MULTISITE', false );
|
||||
|
||||
// Disable automatic updates
|
||||
define( 'AUTOMATIC_UPDATER_DISABLED', true );
|
||||
|
||||
define( 'WP_DEBUG_LOG', false );
|
||||
define( 'WP_DEBUG_DISPLAY', false );
|
||||
define( 'SCRIPT_DEBUG', false );
|
||||
define( 'SAVEQUERIES', false );
|
||||
EOF
|
||||
|
||||
# Transfer config file
|
||||
scp /tmp/wp-tests-config-staging.php ${STAGING_USER}@${STAGING_HOST}:${REMOTE_PLUGIN_PATH}/tests/
|
||||
|
||||
# Run the tests on staging
|
||||
echo -e "Running tests on staging server..."
|
||||
ssh ${STAGING_USER}@${STAGING_HOST} "cd ${REMOTE_PLUGIN_PATH} && php vendor/bin/phpunit --bootstrap tests/bootstrap-staging.php tests/unit/test-email-attendees-data.php"
|
||||
echo -e "---------------------------------------"
|
||||
ssh ${STAGING_USER}@${STAGING_HOST} "cd ${REMOTE_PLUGIN_PATH} && php vendor/bin/phpunit --bootstrap tests/bootstrap-staging.php tests/integration/test-email-attendees-integration.php"
|
||||
|
||||
# Run E2E tests if available
|
||||
echo -e "---------------------------------------"
|
||||
echo -e "${YELLOW}To run E2E tests:${NC}"
|
||||
echo -e "cd ${LOCAL_PATH} && npx playwright test email-attendees.test.ts --config=playwright.config.ts --reporter=list"
|
||||
|
||||
echo -e "${GREEN}===================================================${NC}"
|
||||
echo -e "${GREEN}Email Attendees Test Suite Completed${NC}"
|
||||
159
wordpress-dev/tests/e2e/email-attendees.test.ts
Normal file
159
wordpress-dev/tests/e2e/email-attendees.test.ts
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import { loginAsTrainer } from './utils/login-helpers';
|
||||
import { createTestEvent } from './utils/event-helpers';
|
||||
|
||||
/**
|
||||
* Email Attendees feature tests
|
||||
* @group @email-attendees
|
||||
*/
|
||||
test.describe('Email Attendees Functionality', () => {
|
||||
let eventId: string;
|
||||
let eventTitle: string = 'Test Event for Email Attendees';
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
// Create a test event with attendees
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
await loginAsTrainer(page);
|
||||
|
||||
// Create a test event
|
||||
eventId = await createTestEvent(page, {
|
||||
title: eventTitle,
|
||||
description: 'This is a test event for the email attendees functionality',
|
||||
ticketType: 'General Admission',
|
||||
price: '50.00'
|
||||
});
|
||||
|
||||
// Add test attendees - In a real environment, you'd use the Tribe Tickets API
|
||||
// For testing, we'd either mock this or use a separate helper to purchase tickets
|
||||
|
||||
await context.close();
|
||||
});
|
||||
|
||||
test('can access Email Attendees page from Event Summary', async ({ page }) => {
|
||||
// Login as trainer
|
||||
await loginAsTrainer(page);
|
||||
|
||||
// Navigate to event summary
|
||||
await page.goto(`/event-summary/?event_id=${eventId}`);
|
||||
await expect(page).toHaveTitle(new RegExp(eventTitle));
|
||||
|
||||
// Check that the Email Attendees button exists
|
||||
const emailAttendeesButton = page.locator('a:text("Email Attendees")');
|
||||
await expect(emailAttendeesButton).toBeVisible();
|
||||
|
||||
// Click Email Attendees button
|
||||
await emailAttendeesButton.click();
|
||||
|
||||
// Verify we're on the Email Attendees page
|
||||
await expect(page).toHaveURL(new RegExp(`/email-attendees/\\?event_id=${eventId}`));
|
||||
await expect(page.locator('h1:text("Email Attendees")')).toBeVisible();
|
||||
await expect(page.locator(`h2:text("${eventTitle}")`)).toBeVisible();
|
||||
});
|
||||
|
||||
test('email form has all required elements', async ({ page }) => {
|
||||
// Login and go to Email Attendees page
|
||||
await loginAsTrainer(page);
|
||||
await page.goto(`/email-attendees/?event_id=${eventId}`);
|
||||
|
||||
// Check for required form elements
|
||||
await expect(page.locator('#email_subject')).toBeVisible();
|
||||
await expect(page.locator('#email_cc')).toBeVisible();
|
||||
|
||||
// The rich text editor might be in an iframe, so check for either
|
||||
const hasEditor = await page.locator('.wp-editor-area, #email_message').count() > 0;
|
||||
expect(hasEditor).toBeTruthy();
|
||||
|
||||
// Check for recipients section
|
||||
await expect(page.locator('h3:text("Recipients")')).toBeVisible();
|
||||
|
||||
// Check for Send Email button
|
||||
await expect(page.locator('button[name="hvac_send_email"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('can filter attendees by ticket type', async ({ page }) => {
|
||||
// Login and go to Email Attendees page
|
||||
await loginAsTrainer(page);
|
||||
await page.goto(`/email-attendees/?event_id=${eventId}`);
|
||||
|
||||
// Check if there's a ticket type filter (may not be visible if only one ticket type)
|
||||
const hasTicketTypeFilter = await page.locator('#ticket_type_filter').count() > 0;
|
||||
|
||||
if (hasTicketTypeFilter) {
|
||||
// Select a ticket type
|
||||
await page.selectOption('#ticket_type_filter', 'General Admission');
|
||||
|
||||
// Wait for page to refresh
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Verify URL contains the ticket type parameter
|
||||
await expect(page).toHaveURL(new RegExp('ticket_type=General\\+Admission'));
|
||||
|
||||
// Verify attendees are filtered
|
||||
const attendeeItems = page.locator('.hvac-attendee-item');
|
||||
await expect(attendeeItems).toContainText(['General Admission']);
|
||||
}
|
||||
});
|
||||
|
||||
test('can select all attendees', async ({ page }) => {
|
||||
// Login and go to Email Attendees page
|
||||
await loginAsTrainer(page);
|
||||
await page.goto(`/email-attendees/?event_id=${eventId}`);
|
||||
|
||||
// Get initial count of checked boxes
|
||||
const initialCheckedCount = await page.locator('.hvac-attendee-checkbox:checked').count();
|
||||
expect(initialCheckedCount).toBe(0);
|
||||
|
||||
// Click "Select All" checkbox
|
||||
await page.locator('#select_all_attendees').click();
|
||||
|
||||
// Verify all checkboxes are now checked
|
||||
const attendeeCheckboxes = page.locator('.hvac-attendee-checkbox');
|
||||
const attendeeCount = await attendeeCheckboxes.count();
|
||||
const checkedCount = await page.locator('.hvac-attendee-checkbox:checked').count();
|
||||
|
||||
expect(checkedCount).toBe(attendeeCount);
|
||||
});
|
||||
|
||||
test('shows validation error when form is incomplete', async ({ page }) => {
|
||||
// Login and go to Email Attendees page
|
||||
await loginAsTrainer(page);
|
||||
await page.goto(`/email-attendees/?event_id=${eventId}`);
|
||||
|
||||
// Submit form without filling required fields
|
||||
await page.locator('button[name="hvac_send_email"]').click();
|
||||
|
||||
// Verify error message is shown
|
||||
await expect(page.locator('.hvac-email-error')).toBeVisible();
|
||||
await expect(page.locator('.hvac-email-error')).toContainText('fill in all required fields');
|
||||
});
|
||||
|
||||
test('can send email to attendees', async ({ page }) => {
|
||||
// Login and go to Email Attendees page
|
||||
await loginAsTrainer(page);
|
||||
await page.goto(`/email-attendees/?event_id=${eventId}`);
|
||||
|
||||
// Fill out the form
|
||||
await page.fill('#email_subject', 'Test Email Subject');
|
||||
|
||||
// Fill the message (handling both regular textarea and TinyMCE)
|
||||
if (await page.locator('#email_message').count() > 0) {
|
||||
await page.fill('#email_message', 'This is a test email message.');
|
||||
} else {
|
||||
// For TinyMCE, we need to use the iframe
|
||||
const frame = page.frameLocator('.wp-editor-container iframe');
|
||||
await frame.locator('body').fill('This is a test email message.');
|
||||
}
|
||||
|
||||
// Select recipients (first attendee)
|
||||
await page.locator('.hvac-attendee-checkbox').first().check();
|
||||
|
||||
// Submit form
|
||||
await page.locator('button[name="hvac_send_email"]').click();
|
||||
|
||||
// Verify success message
|
||||
await expect(page.locator('.hvac-email-sent')).toBeVisible();
|
||||
await expect(page.locator('.hvac-email-sent')).toContainText('Email successfully sent');
|
||||
});
|
||||
});
|
||||
89
wordpress-dev/tests/e2e/utils/event-helpers.ts
Normal file
89
wordpress-dev/tests/e2e/utils/event-helpers.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import { Page } from '@playwright/test';
|
||||
import { DashboardPage } from '../pages/DashboardPage';
|
||||
import { CreateEventPage } from '../pages/CreateEventPage';
|
||||
|
||||
interface EventData {
|
||||
title: string;
|
||||
description?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
ticketType?: string;
|
||||
price?: string;
|
||||
venue?: string;
|
||||
organizer?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create a test event
|
||||
* @param page Playwright Page object
|
||||
* @param eventData Event data to create
|
||||
* @returns Event ID of the created event
|
||||
*/
|
||||
export async function createTestEvent(page: Page, eventData: EventData): Promise<string> {
|
||||
// Navigate to dashboard
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.navigate();
|
||||
|
||||
// Click create event button
|
||||
await dashboardPage.clickCreateEvent();
|
||||
|
||||
// Fill event form
|
||||
const createEventPage = new CreateEventPage(page);
|
||||
await createEventPage.fillEventTitle(eventData.title);
|
||||
|
||||
if (eventData.description) {
|
||||
await createEventPage.fillEventDescription(eventData.description);
|
||||
}
|
||||
|
||||
// Set dates and times if provided
|
||||
if (eventData.startDate) {
|
||||
await createEventPage.setStartDate(eventData.startDate);
|
||||
}
|
||||
|
||||
if (eventData.endDate) {
|
||||
await createEventPage.setEndDate(eventData.endDate);
|
||||
}
|
||||
|
||||
if (eventData.startTime) {
|
||||
await createEventPage.setStartTime(eventData.startTime);
|
||||
}
|
||||
|
||||
if (eventData.endTime) {
|
||||
await createEventPage.setEndTime(eventData.endTime);
|
||||
}
|
||||
|
||||
// Add ticket if price is provided
|
||||
if (eventData.ticketType && eventData.price) {
|
||||
await createEventPage.addTicket(eventData.ticketType, eventData.price);
|
||||
}
|
||||
|
||||
// Set venue if provided
|
||||
if (eventData.venue) {
|
||||
await createEventPage.setVenue(eventData.venue);
|
||||
}
|
||||
|
||||
// Set organizer if provided
|
||||
if (eventData.organizer) {
|
||||
await createEventPage.setOrganizer(eventData.organizer);
|
||||
}
|
||||
|
||||
// Submit the form
|
||||
const eventId = await createEventPage.submitForm();
|
||||
|
||||
return eventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get event ID from URL
|
||||
* @param url Event URL
|
||||
* @returns Event ID extracted from URL
|
||||
*/
|
||||
export function extractEventId(url: string): string {
|
||||
const match = url.match(/event_id=(\d+)/);
|
||||
if (match && match[1]) {
|
||||
return match[1];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
34
wordpress-dev/tests/e2e/utils/login-helpers.ts
Normal file
34
wordpress-dev/tests/e2e/utils/login-helpers.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { Page } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/LoginPage';
|
||||
|
||||
/**
|
||||
* Helper function to login as a trainer
|
||||
* @param page Playwright Page object
|
||||
*/
|
||||
export async function loginAsTrainer(page: Page): Promise<void> {
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.navigate();
|
||||
await loginPage.login('trainer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to login as an admin trainer
|
||||
* @param page Playwright Page object
|
||||
*/
|
||||
export async function loginAsAdminTrainer(page: Page): Promise<void> {
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.navigate();
|
||||
await loginPage.login('adminTrainer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to login with custom credentials
|
||||
* @param page Playwright Page object
|
||||
* @param username Username to login with
|
||||
* @param password Password to login with
|
||||
*/
|
||||
export async function loginWithCredentials(page: Page, username: string, password: string): Promise<void> {
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.navigate();
|
||||
await loginPage.loginWithCredentials(username, password);
|
||||
}
|
||||
88
wordpress-dev/tests/manual/email-attendees-test-plan.md
Normal file
88
wordpress-dev/tests/manual/email-attendees-test-plan.md
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Email Attendees Feature - Manual Test Plan
|
||||
|
||||
This document outlines a manual testing plan for the Email Attendees functionality in the HVAC Community Events plugin.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A WordPress site with The Events Calendar and Event Tickets plugins installed and activated
|
||||
- The HVAC Community Events plugin installed and activated
|
||||
- A user with the `hvac_trainer` role
|
||||
- At least one event created by the trainer
|
||||
- At least one attendee registered for the event
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### 1. Navigation and Access Control
|
||||
|
||||
| # | Test Case | Expected Result | Pass/Fail |
|
||||
|---|-----------|-----------------|-----------|
|
||||
| 1.1 | As a logged-in trainer, navigate to Event Summary page of your event | Event Summary page loads with event details and Email Attendees button is visible | |
|
||||
| 1.2 | Click on Email Attendees button | Redirects to Email Attendees page with the same event ID | |
|
||||
| 1.3 | Try to access Email Attendees page directly when logged out | Redirects to login page | |
|
||||
| 1.4 | Try to access Email Attendees page for an event you don't own | Access denied message appears | |
|
||||
|
||||
### 2. UI Elements
|
||||
|
||||
| # | Test Case | Expected Result | Pass/Fail |
|
||||
|---|-----------|-----------------|-----------|
|
||||
| 2.1 | Load Email Attendees page for an event with attendees | Page shows event title, navigation buttons, email form, and list of attendees | |
|
||||
| 2.2 | Verify form elements | Subject field, CC field, rich text editor, and recipient list are present | |
|
||||
| 2.3 | Check navigation buttons | View Event Summary and Return to Dashboard buttons are functional | |
|
||||
| 2.4 | Verify attendee list | List shows attendee names, emails, and ticket types | |
|
||||
| 2.5 | Check for Select All functionality | Select All checkbox selects/deselects all attendees | |
|
||||
|
||||
### 3. Filtering
|
||||
|
||||
| # | Test Case | Expected Result | Pass/Fail |
|
||||
|---|-----------|-----------------|-----------|
|
||||
| 3.1 | With multiple ticket types, check if filter exists | Ticket type filter dropdown is visible with all ticket types | |
|
||||
| 3.2 | Select a specific ticket type | Page reloads with only attendees of that ticket type | |
|
||||
| 3.3 | Select "All Tickets" option | Page shows all attendees regardless of ticket type | |
|
||||
|
||||
### 4. Form Validation
|
||||
|
||||
| # | Test Case | Expected Result | Pass/Fail |
|
||||
|---|-----------|-----------------|-----------|
|
||||
| 4.1 | Submit form without subject | Error message indicates subject is required | |
|
||||
| 4.2 | Submit form without message | Error message indicates message is required | |
|
||||
| 4.3 | Submit form without selecting recipients | Error message indicates at least one recipient is required | |
|
||||
| 4.4 | Enter invalid email in CC field | Error message indicates invalid email format | |
|
||||
|
||||
### 5. Email Sending
|
||||
|
||||
| # | Test Case | Expected Result | Pass/Fail |
|
||||
|---|-----------|-----------------|-----------|
|
||||
| 5.1 | Fill all required fields and submit | Success message indicates email was sent | |
|
||||
| 5.2 | Check recipient receives email | Email is received with correct subject and content | |
|
||||
| 5.3 | Send email with CC | CC recipient also receives the email | |
|
||||
| 5.4 | Verify personalization | Email begins with attendee's name if available | |
|
||||
| 5.5 | Check email subject | Subject contains event title and custom subject text | |
|
||||
|
||||
### 6. Edge Cases
|
||||
|
||||
| # | Test Case | Expected Result | Pass/Fail |
|
||||
|---|-----------|-----------------|-----------|
|
||||
| 6.1 | Access page for an event with no attendees | Message indicates no attendees available | |
|
||||
| 6.2 | Try to email non-existent event | Redirects to dashboard with error message | |
|
||||
| 6.3 | Test with a large number of attendees | List correctly paginates or scrolls, Select All works correctly | |
|
||||
| 6.4 | Test with special characters in email fields | Special characters are properly escaped | |
|
||||
|
||||
## Test Results
|
||||
|
||||
**Tester:** ______________________
|
||||
|
||||
**Date:** ________________________
|
||||
|
||||
**Environment:** _________________
|
||||
|
||||
**Overall Result:** _______________
|
||||
|
||||
## Notes
|
||||
|
||||
- Record any bugs, issues, or unexpected behavior in this section
|
||||
- Include browser and device information for any UI or display issues
|
||||
- Document any performance concerns or usability issues
|
||||
|
||||
## Recommendations
|
||||
|
||||
- Add any recommendations for future improvements or feature enhancements here
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* Styles for the Email Attendees page
|
||||
*/
|
||||
|
||||
.hvac-email-attendees-wrapper {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.hvac-email-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hvac-email-title h1 {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.hvac-email-navigation {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hvac-email-form {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.hvac-email-info {
|
||||
background-color: #f9f9f9;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hvac-email-form-row {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.hvac-email-form-row label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hvac-email-form-row input[type="text"],
|
||||
.hvac-email-form-row textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.hvac-email-recipients {
|
||||
margin-top: 20px;
|
||||
border: 1px solid #ddd;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.hvac-email-filter {
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hvac-attendee-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #eee;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.hvac-attendee-item {
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.hvac-attendee-checkbox {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.hvac-email-sent {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hvac-email-error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hvac-count-badge {
|
||||
background-color: #007cba;
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 0.8em;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.hvac-select-all-container {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Responsive styles */
|
||||
@media (max-width: 768px) {
|
||||
.hvac-email-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.hvac-email-navigation {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.hvac-email-filter {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
|
@ -63,6 +63,10 @@ function hvac_ce_create_required_pages() {
|
|||
'title' => 'Event Summary',
|
||||
'content' => '<!-- wp:shortcode -->[hvac_event_summary]<!-- /wp:shortcode -->',
|
||||
],
|
||||
'email-attendees' => [ // Add email attendees page
|
||||
'title' => 'Email Attendees',
|
||||
'content' => '<!-- wp:shortcode -->[hvac_email_attendees]<!-- /wp:shortcode -->',
|
||||
],
|
||||
// REMOVED: 'submit-event' page creation. Will link to default TEC CE page.
|
||||
// 'submit-event' => [
|
||||
// 'title' => 'Submit Event',
|
||||
|
|
@ -231,6 +235,22 @@ function hvac_ce_enqueue_event_summary_styles() {
|
|||
}
|
||||
add_action( 'wp_enqueue_scripts', 'hvac_ce_enqueue_event_summary_styles' );
|
||||
|
||||
/**
|
||||
* Enqueue styles specifically for the HVAC Email Attendees page.
|
||||
*/
|
||||
function hvac_ce_enqueue_email_attendees_styles() {
|
||||
// Check if we are on the email attendees page
|
||||
if ( is_page( 'email-attendees' ) ) {
|
||||
wp_enqueue_style(
|
||||
'hvac-email-attendees-style',
|
||||
HVAC_CE_PLUGIN_URL . 'assets/css/hvac-email-attendees.css',
|
||||
[], // No dependencies for now
|
||||
HVAC_CE_VERSION
|
||||
);
|
||||
}
|
||||
}
|
||||
add_action( 'wp_enqueue_scripts', 'hvac_ce_enqueue_email_attendees_styles' );
|
||||
|
||||
|
||||
|
||||
// Include the main plugin class
|
||||
|
|
|
|||
|
|
@ -110,6 +110,9 @@ class HVAC_Community_Events {
|
|||
|
||||
// Add authentication check for event summary page
|
||||
add_action('template_redirect', array($this, 'check_event_summary_auth'));
|
||||
|
||||
// Add authentication check for email attendees page
|
||||
add_action('template_redirect', array($this, 'check_email_attendees_auth'));
|
||||
} // End init_hooks
|
||||
|
||||
/**
|
||||
|
|
@ -124,6 +127,18 @@ class HVAC_Community_Events {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check authentication for email attendees page
|
||||
*/
|
||||
public function check_email_attendees_auth() {
|
||||
// Check if we're on the email-attendees page
|
||||
if (is_page('email-attendees') && !is_user_logged_in()) {
|
||||
// Redirect to login page
|
||||
wp_redirect(home_url('/community-login/?redirect_to=' . urlencode($_SERVER['REQUEST_URI'])));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin activation (Should be called statically or from the main plugin file context)
|
||||
*/
|
||||
|
|
@ -235,6 +250,9 @@ class HVAC_Community_Events {
|
|||
// Add edit profile shortcode
|
||||
add_shortcode('hvac_edit_profile', array('HVAC_Registration', 'render_edit_profile_form'));
|
||||
|
||||
// Add email attendees shortcode
|
||||
add_shortcode('hvac_email_attendees', array($this, 'render_email_attendees'));
|
||||
|
||||
// Remove the event form shortcode as we're using TEC's shortcode instead
|
||||
// add_shortcode('hvac_event_form', array('HVAC_Community_Event_Handler', 'render_event_form'));
|
||||
|
||||
|
|
@ -303,6 +321,40 @@ class HVAC_Community_Events {
|
|||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render email attendees content
|
||||
*/
|
||||
public function render_email_attendees() {
|
||||
// Check if user is logged in
|
||||
if (!is_user_logged_in()) {
|
||||
return '<p>Please log in to email event attendees.</p>';
|
||||
}
|
||||
|
||||
// Get event ID from URL parameter
|
||||
$event_id = isset($_GET['event_id']) ? absint($_GET['event_id']) : 0;
|
||||
|
||||
if ($event_id <= 0) {
|
||||
return '<div class="hvac-error">No event ID provided. Please access this page from your dashboard or event summary page.</div>';
|
||||
}
|
||||
|
||||
// Check if the event exists and user has permission to view it
|
||||
$event = get_post($event_id);
|
||||
if (!$event || get_post_type($event) !== Tribe__Events__Main::POSTTYPE) {
|
||||
return '<div class="hvac-error">Event not found or invalid.</div>';
|
||||
}
|
||||
|
||||
// Check if the current user has permission to view this event
|
||||
// For now, we'll check if they're the post author or have edit_posts capability
|
||||
if ($event->post_author != get_current_user_id() && !current_user_can('edit_posts')) {
|
||||
return '<div class="hvac-error">You do not have permission to email attendees for this event.</div>';
|
||||
}
|
||||
|
||||
// Include the email attendees template
|
||||
ob_start();
|
||||
include HVAC_CE_PLUGIN_DIR . 'templates/email-attendees/template-email-attendees.php';
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Include custom templates for plugin pages
|
||||
*/
|
||||
|
|
@ -347,6 +399,14 @@ class HVAC_Community_Events {
|
|||
}
|
||||
}
|
||||
|
||||
// Check for email-attendees page
|
||||
if (is_page('email-attendees')) {
|
||||
$custom_template = HVAC_CE_PLUGIN_DIR . 'templates/email-attendees/template-email-attendees.php';
|
||||
if (file_exists($custom_template)) {
|
||||
return $custom_template;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for edit-profile page
|
||||
if (is_page('edit-profile')) {
|
||||
$custom_template = HVAC_CE_PLUGIN_DIR . 'templates/template-edit-profile.php';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,277 @@
|
|||
<?php
|
||||
/**
|
||||
* HVAC Community Events - Email Attendees Data Class
|
||||
*
|
||||
* Handles retrieving attendee data and sending emails for the Email Attendees functionality.
|
||||
*
|
||||
* @package HVAC_Community_Events
|
||||
* @subpackage Community
|
||||
*/
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class HVAC_Email_Attendees_Data
|
||||
*
|
||||
* Handles data operations for the Email Attendees functionality.
|
||||
*/
|
||||
class HVAC_Email_Attendees_Data {
|
||||
|
||||
/**
|
||||
* The event ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $event_id;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $event_id The event ID.
|
||||
*/
|
||||
public function __construct( $event_id = 0 ) {
|
||||
$this->event_id = intval( $event_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the event is valid.
|
||||
*
|
||||
* @return bool Whether the event exists and is valid.
|
||||
*/
|
||||
public function is_valid_event() {
|
||||
if ( empty( $this->event_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$event = get_post( $this->event_id );
|
||||
return ( $event && $event->post_type === 'tribe_events' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user can view and email attendees for this event.
|
||||
*
|
||||
* @return bool Whether the user can view and email attendees.
|
||||
*/
|
||||
public function user_can_email_attendees() {
|
||||
if ( ! is_user_logged_in() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$event = get_post( $this->event_id );
|
||||
if ( ! $event ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow event author or admins with edit_posts capability
|
||||
return ( get_current_user_id() === (int) $event->post_author || current_user_can( 'edit_posts' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all attendees for the event.
|
||||
*
|
||||
* @return array Array of attendee data.
|
||||
*/
|
||||
public function get_attendees() {
|
||||
if ( ! $this->is_valid_event() ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Use The Events Calendar's function to get attendees
|
||||
$attendees = tribe_tickets_get_attendees( $this->event_id );
|
||||
$processed_attendees = array();
|
||||
|
||||
if ( ! empty( $attendees ) ) {
|
||||
foreach ( $attendees as $attendee ) {
|
||||
$email = isset( $attendee['holder_email'] ) ? $attendee['holder_email'] : '';
|
||||
$name = isset( $attendee['holder_name'] ) ? $attendee['holder_name'] : '';
|
||||
$ticket_name = isset( $attendee['ticket_name'] ) ? $attendee['ticket_name'] : '';
|
||||
|
||||
// Only include attendees with valid emails
|
||||
if ( ! empty( $email ) && is_email( $email ) ) {
|
||||
$processed_attendees[] = array(
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'ticket_name' => $ticket_name,
|
||||
'attendee_id' => isset( $attendee['attendee_id'] ) ? $attendee['attendee_id'] : 0,
|
||||
'order_id' => isset( $attendee['order_id'] ) ? $attendee['order_id'] : 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $processed_attendees;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attendees filtered by ticket type.
|
||||
*
|
||||
* @param string $ticket_type The ticket type to filter by.
|
||||
* @return array Filtered attendees.
|
||||
*/
|
||||
public function get_attendees_by_ticket_type( $ticket_type ) {
|
||||
$attendees = $this->get_attendees();
|
||||
|
||||
if ( empty( $ticket_type ) ) {
|
||||
return $attendees;
|
||||
}
|
||||
|
||||
return array_filter( $attendees, function( $attendee ) use ( $ticket_type ) {
|
||||
return $attendee['ticket_name'] === $ticket_type;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all ticket types for the event.
|
||||
*
|
||||
* @return array Array of ticket types.
|
||||
*/
|
||||
public function get_ticket_types() {
|
||||
$attendees = $this->get_attendees();
|
||||
$ticket_types = array();
|
||||
|
||||
foreach ( $attendees as $attendee ) {
|
||||
if ( ! empty( $attendee['ticket_name'] ) && ! in_array( $attendee['ticket_name'], $ticket_types ) ) {
|
||||
$ticket_types[] = $attendee['ticket_name'];
|
||||
}
|
||||
}
|
||||
|
||||
return $ticket_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event details.
|
||||
*
|
||||
* @return array Event details.
|
||||
*/
|
||||
public function get_event_details() {
|
||||
if ( ! $this->is_valid_event() ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$event = get_post( $this->event_id );
|
||||
|
||||
return array(
|
||||
'id' => $this->event_id,
|
||||
'title' => get_the_title( $event ),
|
||||
'start_date' => tribe_get_start_date( $event, false, 'F j, Y' ),
|
||||
'start_time' => tribe_get_start_date( $event, false, 'g:i a' ),
|
||||
'end_date' => tribe_get_end_date( $event, false, 'F j, Y' ),
|
||||
'end_time' => tribe_get_end_date( $event, false, 'g:i a' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send email to attendees.
|
||||
*
|
||||
* @param array $recipients Array of recipient emails or attendee IDs.
|
||||
* @param string $subject The email subject.
|
||||
* @param string $message The email message.
|
||||
* @param string $cc Optional CC email addresses.
|
||||
* @return array Result with status and message.
|
||||
*/
|
||||
public function send_email( $recipients, $subject, $message, $cc = '' ) {
|
||||
if ( empty( $recipients ) || empty( $subject ) || empty( $message ) ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => 'Missing required fields (recipients, subject, or message).',
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $this->is_valid_event() || ! $this->user_can_email_attendees() ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => 'You do not have permission to email attendees for this event.',
|
||||
);
|
||||
}
|
||||
|
||||
$headers = array('Content-Type: text/html; charset=UTF-8');
|
||||
$event_details = $this->get_event_details();
|
||||
$event_title = $event_details['title'];
|
||||
|
||||
// Add CC if provided
|
||||
if ( ! empty( $cc ) ) {
|
||||
$cc_emails = explode( ',', $cc );
|
||||
foreach ( $cc_emails as $cc_email ) {
|
||||
$cc_email = trim( $cc_email );
|
||||
if ( is_email( $cc_email ) ) {
|
||||
$headers[] = 'Cc: ' . $cc_email;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add sender information
|
||||
$current_user = wp_get_current_user();
|
||||
$from_name = $current_user->display_name;
|
||||
$from_email = $current_user->user_email;
|
||||
$headers[] = 'From: ' . $from_name . ' <' . $from_email . '>';
|
||||
|
||||
// Process recipients
|
||||
$all_attendees = $this->get_attendees();
|
||||
$attendee_emails = array();
|
||||
$sent_count = 0;
|
||||
$error_count = 0;
|
||||
|
||||
// Handle numeric IDs or email addresses
|
||||
foreach ( $recipients as $recipient ) {
|
||||
if ( is_numeric( $recipient ) ) {
|
||||
// Find attendee by ID
|
||||
foreach ( $all_attendees as $attendee ) {
|
||||
if ( $attendee['attendee_id'] == $recipient ) {
|
||||
$attendee_emails[$attendee['email']] = $attendee['name'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} elseif ( is_email( $recipient ) ) {
|
||||
// Add directly if it's an email
|
||||
$attendee_name = '';
|
||||
foreach ( $all_attendees as $attendee ) {
|
||||
if ( $attendee['email'] === $recipient ) {
|
||||
$attendee_name = $attendee['name'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
$attendee_emails[$recipient] = $attendee_name;
|
||||
}
|
||||
}
|
||||
|
||||
// Subject with event title
|
||||
$email_subject = sprintf( '[%s] %s', $event_title, $subject );
|
||||
|
||||
// Send to each recipient individually for personalization
|
||||
foreach ( $attendee_emails as $email => $name ) {
|
||||
// Personalize message with attendee name if available
|
||||
$personalized_message = $message;
|
||||
if ( ! empty( $name ) ) {
|
||||
$personalized_message = "Hello " . $name . ",\n\n" . $message;
|
||||
}
|
||||
|
||||
$mail_sent = wp_mail( $email, $email_subject, wpautop( $personalized_message ), $headers );
|
||||
|
||||
if ( $mail_sent ) {
|
||||
$sent_count++;
|
||||
} else {
|
||||
$error_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Return results
|
||||
if ( $error_count > 0 ) {
|
||||
return array(
|
||||
'success' => $sent_count > 0,
|
||||
'message' => sprintf(
|
||||
'Email sent to %d recipients. Failed to send to %d recipients.',
|
||||
$sent_count,
|
||||
$error_count
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'message' => sprintf( 'Email successfully sent to %d recipients.', $sent_count ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,336 @@
|
|||
<?php
|
||||
/**
|
||||
* HVAC Community Events - Email Attendees Template
|
||||
*
|
||||
* Template for the Email Attendees page.
|
||||
*
|
||||
* @package HVAC_Community_Events
|
||||
* @subpackage Templates
|
||||
*/
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check if user is logged in
|
||||
if ( ! is_user_logged_in() ) {
|
||||
wp_redirect( site_url( '/community-login/' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get the event ID from the URL
|
||||
$event_id = isset( $_GET['event_id'] ) ? intval( $_GET['event_id'] ) : 0;
|
||||
|
||||
// Load the email attendees data class
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/community/class-email-attendees-data.php';
|
||||
$email_data = new HVAC_Email_Attendees_Data( $event_id );
|
||||
|
||||
// Check if event is valid and user has permission
|
||||
if ( ! $email_data->is_valid_event() ) {
|
||||
wp_redirect( site_url( '/hvac-dashboard/' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! $email_data->user_can_email_attendees() ) {
|
||||
wp_die( __( 'You do not have permission to email attendees for this event.', 'hvac-community-events' ) );
|
||||
}
|
||||
|
||||
// Get event details and attendees
|
||||
$event_details = $email_data->get_event_details();
|
||||
$attendees = $email_data->get_attendees();
|
||||
$ticket_types = $email_data->get_ticket_types();
|
||||
|
||||
// Handle form submission
|
||||
$email_sent = false;
|
||||
$email_error = '';
|
||||
$email_success = '';
|
||||
|
||||
if ( isset( $_POST['hvac_send_email'] ) && isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'hvac_email_attendees_' . $event_id ) ) {
|
||||
|
||||
$subject = isset( $_POST['email_subject'] ) ? sanitize_text_field( $_POST['email_subject'] ) : '';
|
||||
$message = isset( $_POST['email_message'] ) ? wp_kses_post( $_POST['email_message'] ) : '';
|
||||
$cc = isset( $_POST['email_cc'] ) ? sanitize_text_field( $_POST['email_cc'] ) : '';
|
||||
|
||||
// Get selected recipients
|
||||
$recipients = array();
|
||||
if ( isset( $_POST['email_attendees'] ) && is_array( $_POST['email_attendees'] ) ) {
|
||||
$recipients = array_map( 'sanitize_text_field', $_POST['email_attendees'] );
|
||||
}
|
||||
|
||||
// Validate and send email
|
||||
if ( empty( $subject ) || empty( $message ) || empty( $recipients ) ) {
|
||||
$email_error = __( 'Please fill in all required fields (subject, message, and select at least one recipient).', 'hvac-community-events' );
|
||||
} else {
|
||||
$result = $email_data->send_email( $recipients, $subject, $message, $cc );
|
||||
|
||||
if ( $result['success'] ) {
|
||||
$email_sent = true;
|
||||
$email_success = $result['message'];
|
||||
} else {
|
||||
$email_error = $result['message'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get filtered attendees if a ticket type is selected
|
||||
$selected_ticket_type = isset( $_GET['ticket_type'] ) ? sanitize_text_field( $_GET['ticket_type'] ) : '';
|
||||
if ( ! empty( $selected_ticket_type ) ) {
|
||||
$attendees = $email_data->get_attendees_by_ticket_type( $selected_ticket_type );
|
||||
}
|
||||
|
||||
// Get the site title for the page title
|
||||
$site_title = get_bloginfo( 'name' );
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html <?php language_attributes(); ?>>
|
||||
<head>
|
||||
<meta charset="<?php bloginfo( 'charset' ); ?>">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?php echo esc_html( $site_title ); ?> - <?php _e( 'Email Attendees', 'hvac-community-events' ); ?></title>
|
||||
<?php wp_head(); ?>
|
||||
<style>
|
||||
.hvac-email-attendees-wrapper {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.hvac-email-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.hvac-email-title h1 {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
.hvac-email-navigation {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.hvac-email-form {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.hvac-email-info {
|
||||
background-color: #f9f9f9;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.hvac-email-form-row {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.hvac-email-form-row label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.hvac-email-form-row input[type="text"],
|
||||
.hvac-email-form-row textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
.hvac-email-recipients {
|
||||
margin-top: 20px;
|
||||
border: 1px solid #ddd;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.hvac-email-filter {
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.hvac-attendee-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #eee;
|
||||
padding: 10px;
|
||||
}
|
||||
.hvac-attendee-item {
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.hvac-attendee-checkbox {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.hvac-email-sent {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.hvac-email-error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.hvac-count-badge {
|
||||
background-color: #007cba;
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 0.8em;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.hvac-select-all-container {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body <?php body_class(); ?>>
|
||||
<?php wp_body_open(); ?>
|
||||
|
||||
<div class="hvac-email-attendees-wrapper">
|
||||
<div class="hvac-email-header">
|
||||
<div class="hvac-email-title">
|
||||
<h1><?php _e( 'Email Attendees', 'hvac-community-events' ); ?></h1>
|
||||
<h2><?php echo esc_html( $event_details['title'] ); ?></h2>
|
||||
<p>
|
||||
<?php echo esc_html( $event_details['start_date'] ); ?>
|
||||
<?php echo esc_html( $event_details['start_time'] ); ?> -
|
||||
<?php
|
||||
if ( $event_details['start_date'] !== $event_details['end_date'] ) {
|
||||
echo esc_html( $event_details['end_date'] ) . ' ';
|
||||
}
|
||||
echo esc_html( $event_details['end_time'] );
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="hvac-email-navigation">
|
||||
<a href="<?php echo esc_url( site_url( '/event-summary/?event_id=' . $event_id ) ); ?>" class="ast-button ast-button-secondary">
|
||||
<?php _e( 'View Event Summary', 'hvac-community-events' ); ?>
|
||||
</a>
|
||||
<a href="<?php echo esc_url( site_url( '/hvac-dashboard/' ) ); ?>" class="ast-button ast-button-secondary">
|
||||
<?php _e( 'Return to Dashboard', 'hvac-community-events' ); ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ( $email_sent ) : ?>
|
||||
<div class="hvac-email-sent">
|
||||
<?php echo esc_html( $email_success ); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( $email_error ) : ?>
|
||||
<div class="hvac-email-error">
|
||||
<?php echo esc_html( $email_error ); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( empty( $attendees ) ) : ?>
|
||||
<div class="hvac-email-info">
|
||||
<?php _e( 'This event has no attendees registered yet.', 'hvac-community-events' ); ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<form method="post" class="hvac-email-form">
|
||||
<?php wp_nonce_field( 'hvac_email_attendees_' . $event_id ); ?>
|
||||
|
||||
<div class="hvac-email-form-row">
|
||||
<label for="email_subject"><?php _e( 'Subject:', 'hvac-community-events' ); ?> <span class="required">*</span></label>
|
||||
<input type="text" name="email_subject" id="email_subject" required value="<?php echo isset( $_POST['email_subject'] ) ? esc_attr( $_POST['email_subject'] ) : ''; ?>">
|
||||
</div>
|
||||
|
||||
<div class="hvac-email-form-row">
|
||||
<label for="email_cc"><?php _e( 'CC:', 'hvac-community-events' ); ?></label>
|
||||
<input type="text" name="email_cc" id="email_cc" value="<?php echo isset( $_POST['email_cc'] ) ? esc_attr( $_POST['email_cc'] ) : ''; ?>" placeholder="<?php _e( 'Separate multiple emails with commas', 'hvac-community-events' ); ?>">
|
||||
</div>
|
||||
|
||||
<div class="hvac-email-form-row">
|
||||
<label for="email_message"><?php _e( 'Message:', 'hvac-community-events' ); ?> <span class="required">*</span></label>
|
||||
<?php
|
||||
// Use WordPress editor if available
|
||||
if ( function_exists( 'wp_editor' ) ) {
|
||||
$content = isset( $_POST['email_message'] ) ? wp_kses_post( $_POST['email_message'] ) : '';
|
||||
$editor_settings = array(
|
||||
'textarea_name' => 'email_message',
|
||||
'textarea_rows' => 10,
|
||||
'media_buttons' => false,
|
||||
'teeny' => true,
|
||||
);
|
||||
wp_editor( $content, 'email_message', $editor_settings );
|
||||
} else {
|
||||
// Fallback to textarea
|
||||
echo '<textarea name="email_message" id="email_message" rows="10" required>' .
|
||||
( isset( $_POST['email_message'] ) ? esc_textarea( $_POST['email_message'] ) : '' ) .
|
||||
'</textarea>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="hvac-email-recipients">
|
||||
<h3><?php _e( 'Recipients', 'hvac-community-events' ); ?> <span class="hvac-count-badge"><?php echo count( $attendees ); ?></span></h3>
|
||||
|
||||
<?php if ( ! empty( $ticket_types ) && count( $ticket_types ) > 1 ) : ?>
|
||||
<div class="hvac-email-filter">
|
||||
<label for="ticket_type_filter"><?php _e( 'Filter by ticket type:', 'hvac-community-events' ); ?></label>
|
||||
<select id="ticket_type_filter" onchange="window.location.href='<?php echo esc_url( add_query_arg( array( 'event_id' => $event_id ), site_url( '/email-attendees/' ) ) ); ?>&ticket_type=' + this.value">
|
||||
<option value=""><?php _e( 'All Tickets', 'hvac-community-events' ); ?></option>
|
||||
<?php foreach ( $ticket_types as $ticket_type ) : ?>
|
||||
<option value="<?php echo esc_attr( $ticket_type ); ?>" <?php selected( $selected_ticket_type, $ticket_type ); ?>>
|
||||
<?php echo esc_html( $ticket_type ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="hvac-select-all-container">
|
||||
<label>
|
||||
<input type="checkbox" id="select_all_attendees" onclick="toggleAllAttendees(this)">
|
||||
<?php _e( 'Select All', 'hvac-community-events' ); ?>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="hvac-attendee-list">
|
||||
<?php foreach ( $attendees as $attendee ) : ?>
|
||||
<div class="hvac-attendee-item">
|
||||
<label>
|
||||
<input type="checkbox" class="hvac-attendee-checkbox" name="email_attendees[]" value="<?php echo esc_attr( $attendee['email'] ); ?>">
|
||||
<strong><?php echo esc_html( $attendee['name'] ); ?></strong>
|
||||
(<?php echo esc_html( $attendee['email'] ); ?>)
|
||||
<?php if ( ! empty( $attendee['ticket_name'] ) ) : ?>
|
||||
- <?php echo esc_html( $attendee['ticket_name'] ); ?>
|
||||
<?php endif; ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hvac-email-form-row" style="margin-top: 20px;">
|
||||
<button type="submit" name="hvac_send_email" class="ast-button ast-button-primary">
|
||||
<?php _e( 'Send Email', 'hvac-community-events' ); ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function toggleAllAttendees(checkbox) {
|
||||
var attendeeCheckboxes = document.querySelectorAll('.hvac-attendee-checkbox');
|
||||
for (var i = 0; i < attendeeCheckboxes.length; i++) {
|
||||
attendeeCheckboxes[i].checked = checkbox.checked;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php wp_footer(); ?>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -131,10 +131,9 @@ get_header();
|
|||
// View public event page
|
||||
echo '<a href="' . esc_url( $event_details['permalink'] ) . '" class="ast-button ast-button-secondary" target="_blank">View Public Page</a>';
|
||||
|
||||
// Email attendees link (future feature)
|
||||
// Email attendees link
|
||||
if ( current_user_can( 'edit_post', $event_id ) ) {
|
||||
// TODO: Link to actual Email Attendees page when implemented (Phase 2)
|
||||
$email_url = '#'; // Placeholder for now
|
||||
$email_url = add_query_arg( 'event_id', $event_id, home_url( '/email-attendees/' ) );
|
||||
echo '<a href="' . esc_url( $email_url ) . '" class="ast-button ast-button-secondary">Email Attendees</a>';
|
||||
}
|
||||
?>
|
||||
|
|
|
|||
|
|
@ -131,10 +131,9 @@ get_header();
|
|||
// View public event page
|
||||
echo '<a href="' . esc_url( $event_details['permalink'] ) . '" class="ast-button ast-button-secondary" target="_blank">View Public Page</a>';
|
||||
|
||||
// Email attendees link (future feature)
|
||||
// Email attendees link
|
||||
if ( current_user_can( 'edit_post', $event_id ) ) {
|
||||
// TODO: Link to actual Email Attendees page when implemented (Phase 2)
|
||||
$email_url = '#'; // Placeholder for now
|
||||
$email_url = add_query_arg( 'event_id', $event_id, home_url( '/email-attendees/' ) );
|
||||
echo '<a href="' . esc_url( $email_url ) . '" class="ast-button ast-button-secondary">Email Attendees</a>';
|
||||
}
|
||||
?>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
/**
|
||||
* Integration tests for the Email Attendees functionality
|
||||
*/
|
||||
|
||||
class Test_Email_Attendees_Integration extends WP_UnitTestCase {
|
||||
/**
|
||||
* @var int Test event ID
|
||||
*/
|
||||
private $event_id;
|
||||
|
||||
/**
|
||||
* @var int Test user ID
|
||||
*/
|
||||
private $user_id;
|
||||
|
||||
/**
|
||||
* @var WP_REST_Server
|
||||
*/
|
||||
private $server;
|
||||
|
||||
/**
|
||||
* Set up test data before each test
|
||||
*/
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Initialize REST API server
|
||||
global $wp_rest_server;
|
||||
$this->server = $wp_rest_server = new WP_REST_Server;
|
||||
do_action('rest_api_init');
|
||||
|
||||
// Create a test user with trainer role
|
||||
$this->user_id = $this->factory->user->create(array(
|
||||
'role' => 'hvac_trainer',
|
||||
'user_login' => 'test_trainer',
|
||||
'user_pass' => 'password',
|
||||
'user_email' => 'trainer@example.com',
|
||||
'display_name' => 'Test Trainer',
|
||||
));
|
||||
|
||||
// Set current user for permission testing
|
||||
wp_set_current_user($this->user_id);
|
||||
|
||||
// Make sure the classes exist
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/community/class-email-attendees-data.php';
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/community/class-event-summary-data.php';
|
||||
|
||||
// Create a test event
|
||||
$event_data = array(
|
||||
'post_title' => 'Test Event for Email Attendees Integration',
|
||||
'post_content' => 'Test event integration test content',
|
||||
'post_status' => 'publish',
|
||||
'post_type' => Tribe__Events__Main::POSTTYPE,
|
||||
'post_author' => $this->user_id,
|
||||
);
|
||||
$this->event_id = wp_insert_post($event_data);
|
||||
|
||||
// Set up mock attendees if needed using Event Tickets functions
|
||||
// This is more complex and would depend on the actual implementation
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up test data after each test
|
||||
*/
|
||||
public function tearDown(): void {
|
||||
// Delete test event
|
||||
wp_delete_post($this->event_id, true);
|
||||
|
||||
// Delete test user
|
||||
wp_delete_user($this->user_id);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the email attendees shortcode exists and renders
|
||||
*/
|
||||
public function test_email_attendees_shortcode_exists() {
|
||||
// Verify the shortcode is registered
|
||||
global $shortcode_tags;
|
||||
$this->assertArrayHasKey('hvac_email_attendees', $shortcode_tags);
|
||||
|
||||
// Test shortcode output with no parameters
|
||||
$output = do_shortcode('[hvac_email_attendees]');
|
||||
$this->assertStringContainsString('Please log in to email event attendees', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the integration between Event Summary and Email Attendees
|
||||
*/
|
||||
public function test_event_summary_links_to_email_attendees() {
|
||||
// Get event summary output
|
||||
ob_start();
|
||||
include HVAC_CE_PLUGIN_DIR . 'templates/event-summary/template-event-summary.php';
|
||||
$output = ob_get_clean();
|
||||
|
||||
// Check that the email attendees link is included
|
||||
$expected_url = add_query_arg('event_id', $this->event_id, home_url('/email-attendees/'));
|
||||
$this->assertStringContainsString($expected_url, $output);
|
||||
$this->assertStringContainsString('Email Attendees', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the email attendees template loads correctly
|
||||
*/
|
||||
public function test_email_attendees_template_loads() {
|
||||
// Set up $_GET parameter
|
||||
$_GET['event_id'] = $this->event_id;
|
||||
|
||||
// Capture template output
|
||||
ob_start();
|
||||
include HVAC_CE_PLUGIN_DIR . 'templates/email-attendees/template-email-attendees.php';
|
||||
$output = ob_get_clean();
|
||||
|
||||
// Verify template elements
|
||||
$this->assertStringContainsString('Email Attendees', $output);
|
||||
$this->assertStringContainsString('Subject:', $output);
|
||||
$this->assertStringContainsString('CC:', $output);
|
||||
$this->assertStringContainsString('Message:', $output);
|
||||
$this->assertStringContainsString('Recipients', $output);
|
||||
$this->assertStringContainsString('Send Email', $output);
|
||||
|
||||
// Unset $_GET parameter
|
||||
unset($_GET['event_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the authentication check for email attendees page
|
||||
*/
|
||||
public function test_email_attendees_auth_check() {
|
||||
// Create an instance of the main plugin class
|
||||
$plugin = new HVAC_Community_Events();
|
||||
|
||||
// Log out to test the redirect
|
||||
wp_set_current_user(0);
|
||||
|
||||
// Mock WordPress is_page function
|
||||
global $wp_query;
|
||||
$wp_query->is_page = true;
|
||||
$wp_query->queried_object = (object) array('post_name' => 'email-attendees');
|
||||
|
||||
// Capture redirect
|
||||
ob_start();
|
||||
$plugin->check_email_attendees_auth();
|
||||
$output = ob_get_clean();
|
||||
|
||||
// Since we're not in a real page request, the function will try to redirect
|
||||
// but won't exit, so we don't expect any output
|
||||
$this->assertEmpty($output);
|
||||
|
||||
// Reset current user
|
||||
wp_set_current_user($this->user_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test form submission handling
|
||||
*/
|
||||
public function test_form_submission_handling() {
|
||||
// This test is more complex as it involves form submission
|
||||
// We would need to mock the $_POST data and possibly override wp_mail
|
||||
|
||||
// Set up $_GET and $_POST data
|
||||
$_GET['event_id'] = $this->event_id;
|
||||
$_POST['hvac_send_email'] = 1;
|
||||
$_POST['email_subject'] = 'Integration Test Subject';
|
||||
$_POST['email_message'] = 'Integration test message content';
|
||||
$_POST['email_attendees'] = array('test@example.com');
|
||||
|
||||
// Create nonce
|
||||
$_POST['_wpnonce'] = wp_create_nonce('hvac_email_attendees_' . $this->event_id);
|
||||
|
||||
// Override wp_mail for testing
|
||||
add_filter('wp_mail', function($args) {
|
||||
// Capture email arguments for validation
|
||||
$this->assertEquals('Integration Test Subject', $args['subject']);
|
||||
$this->assertEquals('test@example.com', $args['to']);
|
||||
return false; // Prevent actual email sending
|
||||
});
|
||||
|
||||
// Capture template output
|
||||
ob_start();
|
||||
include HVAC_CE_PLUGIN_DIR . 'templates/email-attendees/template-email-attendees.php';
|
||||
$output = ob_get_clean();
|
||||
|
||||
// Verify success message appears in output
|
||||
$this->assertStringContainsString('Email successfully sent', $output);
|
||||
|
||||
// Clean up
|
||||
unset($_GET['event_id']);
|
||||
unset($_POST['hvac_send_email']);
|
||||
unset($_POST['email_subject']);
|
||||
unset($_POST['email_message']);
|
||||
unset($_POST['email_attendees']);
|
||||
unset($_POST['_wpnonce']);
|
||||
remove_all_filters('wp_mail');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,345 @@
|
|||
<?php
|
||||
/**
|
||||
* Unit tests for the Email Attendees Data class
|
||||
*/
|
||||
|
||||
class Test_HVAC_Email_Attendees_Data extends WP_UnitTestCase {
|
||||
/**
|
||||
* @var int Test event ID
|
||||
*/
|
||||
private $event_id;
|
||||
|
||||
/**
|
||||
* @var int Test user ID
|
||||
*/
|
||||
private $user_id;
|
||||
|
||||
/**
|
||||
* Set up test data before each test
|
||||
*/
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create a test user
|
||||
$this->user_id = $this->factory->user->create(array(
|
||||
'role' => 'hvac_trainer',
|
||||
));
|
||||
|
||||
// Set current user for permission testing
|
||||
wp_set_current_user($this->user_id);
|
||||
|
||||
// Make sure the class exists
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/community/class-email-attendees-data.php';
|
||||
|
||||
// Create a test event
|
||||
$event_data = array(
|
||||
'post_title' => 'Test Event for Email Attendees',
|
||||
'post_content' => 'Test event content',
|
||||
'post_status' => 'publish',
|
||||
'post_type' => Tribe__Events__Main::POSTTYPE,
|
||||
'post_author' => $this->user_id,
|
||||
);
|
||||
$this->event_id = wp_insert_post($event_data);
|
||||
|
||||
// Mock tribe_tickets_get_attendees functionality if needed
|
||||
// This would require more setup depending on how deeply you want to test
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up test data after each test
|
||||
*/
|
||||
public function tearDown(): void {
|
||||
// Delete test event
|
||||
wp_delete_post($this->event_id, true);
|
||||
|
||||
// Delete test user
|
||||
wp_delete_user($this->user_id);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test constructor
|
||||
*/
|
||||
public function test_constructor() {
|
||||
$email_data = new HVAC_Email_Attendees_Data($this->event_id);
|
||||
$this->assertInstanceOf('HVAC_Email_Attendees_Data', $email_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is_valid_event method
|
||||
*/
|
||||
public function test_is_valid_event() {
|
||||
$email_data = new HVAC_Email_Attendees_Data($this->event_id);
|
||||
$this->assertTrue($email_data->is_valid_event());
|
||||
|
||||
// Test with invalid event ID
|
||||
$email_data_invalid = new HVAC_Email_Attendees_Data(999999);
|
||||
$this->assertFalse($email_data_invalid->is_valid_event());
|
||||
|
||||
// Test with empty event ID
|
||||
$email_data_empty = new HVAC_Email_Attendees_Data();
|
||||
$this->assertFalse($email_data_empty->is_valid_event());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user_can_email_attendees method
|
||||
*/
|
||||
public function test_user_can_email_attendees() {
|
||||
$email_data = new HVAC_Email_Attendees_Data($this->event_id);
|
||||
|
||||
// Current user is event author, should return true
|
||||
$this->assertTrue($email_data->user_can_email_attendees());
|
||||
|
||||
// Create another user who is not the event author
|
||||
$another_user_id = $this->factory->user->create(array(
|
||||
'role' => 'hvac_trainer',
|
||||
));
|
||||
wp_set_current_user($another_user_id);
|
||||
|
||||
// Another user without edit_posts capability should return false
|
||||
$this->assertFalse($email_data->user_can_email_attendees());
|
||||
|
||||
// Create admin user
|
||||
$admin_user_id = $this->factory->user->create(array(
|
||||
'role' => 'administrator',
|
||||
));
|
||||
wp_set_current_user($admin_user_id);
|
||||
|
||||
// Admin with edit_posts capability should return true
|
||||
$this->assertTrue($email_data->user_can_email_attendees());
|
||||
|
||||
// Reset to original user
|
||||
wp_set_current_user($this->user_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_event_details method
|
||||
*/
|
||||
public function test_get_event_details() {
|
||||
$email_data = new HVAC_Email_Attendees_Data($this->event_id);
|
||||
$event_details = $email_data->get_event_details();
|
||||
|
||||
// Verify event details contain expected data
|
||||
$this->assertIsArray($event_details);
|
||||
$this->assertEquals($this->event_id, $event_details['id']);
|
||||
$this->assertEquals('Test Event for Email Attendees', $event_details['title']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_attendees and get_attendees_by_ticket_type methods with mocked data
|
||||
*/
|
||||
public function test_get_attendees_with_mock_data() {
|
||||
// Create a test instance
|
||||
$email_data = $this->getMockBuilder('HVAC_Email_Attendees_Data')
|
||||
->setConstructorArgs(array($this->event_id))
|
||||
->setMethods(array('get_attendees'))
|
||||
->getMock();
|
||||
|
||||
// Mock attendee data
|
||||
$mock_attendees = array(
|
||||
array(
|
||||
'name' => 'Test Attendee 1',
|
||||
'email' => 'attendee1@example.com',
|
||||
'ticket_name' => 'General Admission',
|
||||
'attendee_id' => 101,
|
||||
'order_id' => 1001,
|
||||
),
|
||||
array(
|
||||
'name' => 'Test Attendee 2',
|
||||
'email' => 'attendee2@example.com',
|
||||
'ticket_name' => 'VIP Pass',
|
||||
'attendee_id' => 102,
|
||||
'order_id' => 1002,
|
||||
),
|
||||
array(
|
||||
'name' => 'Test Attendee 3',
|
||||
'email' => 'attendee3@example.com',
|
||||
'ticket_name' => 'General Admission',
|
||||
'attendee_id' => 103,
|
||||
'order_id' => 1003,
|
||||
),
|
||||
);
|
||||
|
||||
// Configure mock to return test data
|
||||
$email_data->expects($this->any())
|
||||
->method('get_attendees')
|
||||
->will($this->returnValue($mock_attendees));
|
||||
|
||||
// Use reflection to access non-public methods for testing
|
||||
$reflection = new ReflectionClass('HVAC_Email_Attendees_Data');
|
||||
$method = $reflection->getMethod('get_attendees_by_ticket_type');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Test filtering by ticket type
|
||||
$general_admission = $method->invokeArgs($email_data, array('General Admission'));
|
||||
$this->assertCount(2, $general_admission);
|
||||
$this->assertEquals('Test Attendee 1', $general_admission[0]['name']);
|
||||
$this->assertEquals('Test Attendee 3', $general_admission[2]['name']);
|
||||
|
||||
// Test filtering by another ticket type
|
||||
$vip_pass = $method->invokeArgs($email_data, array('VIP Pass'));
|
||||
$this->assertCount(1, $vip_pass);
|
||||
$this->assertEquals('Test Attendee 2', $vip_pass[0]['name']);
|
||||
|
||||
// Test getting all attendees (no filter)
|
||||
$all_attendees = $method->invokeArgs($email_data, array(''));
|
||||
$this->assertCount(3, $all_attendees);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_ticket_types method with mocked data
|
||||
*/
|
||||
public function test_get_ticket_types_with_mock_data() {
|
||||
// Create a test instance
|
||||
$email_data = $this->getMockBuilder('HVAC_Email_Attendees_Data')
|
||||
->setConstructorArgs(array($this->event_id))
|
||||
->setMethods(array('get_attendees'))
|
||||
->getMock();
|
||||
|
||||
// Mock attendee data
|
||||
$mock_attendees = array(
|
||||
array(
|
||||
'name' => 'Test Attendee 1',
|
||||
'email' => 'attendee1@example.com',
|
||||
'ticket_name' => 'General Admission',
|
||||
'attendee_id' => 101,
|
||||
'order_id' => 1001,
|
||||
),
|
||||
array(
|
||||
'name' => 'Test Attendee 2',
|
||||
'email' => 'attendee2@example.com',
|
||||
'ticket_name' => 'VIP Pass',
|
||||
'attendee_id' => 102,
|
||||
'order_id' => 1002,
|
||||
),
|
||||
array(
|
||||
'name' => 'Test Attendee 3',
|
||||
'email' => 'attendee3@example.com',
|
||||
'ticket_name' => 'General Admission',
|
||||
'attendee_id' => 103,
|
||||
'order_id' => 1003,
|
||||
),
|
||||
);
|
||||
|
||||
// Configure mock to return test data
|
||||
$email_data->expects($this->any())
|
||||
->method('get_attendees')
|
||||
->will($this->returnValue($mock_attendees));
|
||||
|
||||
// Use reflection to access the get_ticket_types method
|
||||
$reflection = new ReflectionClass('HVAC_Email_Attendees_Data');
|
||||
$method = $reflection->getMethod('get_ticket_types');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Test getting ticket types
|
||||
$ticket_types = $method->invoke($email_data);
|
||||
$this->assertIsArray($ticket_types);
|
||||
$this->assertCount(2, $ticket_types);
|
||||
$this->assertContains('General Admission', $ticket_types);
|
||||
$this->assertContains('VIP Pass', $ticket_types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test send_email method with mocked wp_mail
|
||||
*/
|
||||
public function test_send_email() {
|
||||
global $wp_mail_called, $wp_mail_args;
|
||||
|
||||
// Override wp_mail for testing
|
||||
$wp_mail_called = 0;
|
||||
$wp_mail_args = array();
|
||||
|
||||
function test_wp_mail($to, $subject, $message, $headers = '', $attachments = array()) {
|
||||
global $wp_mail_called, $wp_mail_args;
|
||||
$wp_mail_called++;
|
||||
$wp_mail_args[] = array(
|
||||
'to' => $to,
|
||||
'subject' => $subject,
|
||||
'message' => $message,
|
||||
'headers' => $headers,
|
||||
'attachments' => $attachments,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Replace WordPress's wp_mail with our test version
|
||||
add_filter('wp_mail', 'test_wp_mail', 10, 5);
|
||||
|
||||
// Create a test instance
|
||||
$email_data = $this->getMockBuilder('HVAC_Email_Attendees_Data')
|
||||
->setConstructorArgs(array($this->event_id))
|
||||
->setMethods(array('is_valid_event', 'user_can_email_attendees', 'get_attendees', 'get_event_details'))
|
||||
->getMock();
|
||||
|
||||
// Mock necessary methods
|
||||
$email_data->expects($this->any())
|
||||
->method('is_valid_event')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$email_data->expects($this->any())
|
||||
->method('user_can_email_attendees')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$email_data->expects($this->any())
|
||||
->method('get_event_details')
|
||||
->will($this->returnValue(array(
|
||||
'id' => $this->event_id,
|
||||
'title' => 'Test Event for Email Attendees',
|
||||
'start_date' => date('F j, Y'),
|
||||
'start_time' => '10:00 am',
|
||||
'end_date' => date('F j, Y'),
|
||||
'end_time' => '4:00 pm',
|
||||
)));
|
||||
|
||||
$mock_attendees = array(
|
||||
array(
|
||||
'name' => 'Test Attendee 1',
|
||||
'email' => 'attendee1@example.com',
|
||||
'ticket_name' => 'General Admission',
|
||||
'attendee_id' => 101,
|
||||
'order_id' => 1001,
|
||||
),
|
||||
array(
|
||||
'name' => 'Test Attendee 2',
|
||||
'email' => 'attendee2@example.com',
|
||||
'ticket_name' => 'VIP Pass',
|
||||
'attendee_id' => 102,
|
||||
'order_id' => 1002,
|
||||
),
|
||||
);
|
||||
|
||||
$email_data->expects($this->any())
|
||||
->method('get_attendees')
|
||||
->will($this->returnValue($mock_attendees));
|
||||
|
||||
// Test sending email
|
||||
$recipients = array('attendee1@example.com', 'attendee2@example.com');
|
||||
$subject = 'Test Email Subject';
|
||||
$message = 'Test email message body';
|
||||
$cc = 'cc@example.com';
|
||||
|
||||
$result = $email_data->send_email($recipients, $subject, $message, $cc);
|
||||
|
||||
// Verify email was sent successfully
|
||||
$this->assertTrue($result['success']);
|
||||
$this->assertEquals(2, $wp_mail_called); // One email per recipient
|
||||
|
||||
// Verify email content
|
||||
$this->assertEquals('attendee1@example.com', $wp_mail_args[0]['to']);
|
||||
$this->assertStringContains('[Test Event for Email Attendees]', $wp_mail_args[0]['subject']);
|
||||
$this->assertStringContains('Test email message body', $wp_mail_args[0]['message']);
|
||||
|
||||
// Verify CC header
|
||||
foreach ($wp_mail_args as $args) {
|
||||
$this->assertStringContains('Cc: cc@example.com', implode("\n", $args['headers']));
|
||||
}
|
||||
|
||||
// Test with invalid parameters
|
||||
$result = $email_data->send_email(array(), $subject, $message);
|
||||
$this->assertFalse($result['success']);
|
||||
|
||||
// Clean up
|
||||
remove_filter('wp_mail', 'test_wp_mail');
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue