fix: Add template loading for custom event edit form

- Added missing template_include filter to HVAC_Custom_Event_Edit class
- Added loadTemplate method to handle template loading for rewrite rules
- Added support for hierarchical URL /trainer/event/edit in load_custom_templates
- Created WordPress page at /trainer/event/edit/ for custom form
- Assigned page-edit-event-custom.php template to the page

This ensures the custom event edit form loads correctly without JavaScript dependencies
This commit is contained in:
Ben 2025-08-18 14:44:07 -03:00
parent 0f94a42f15
commit 0b854a8c5b
12 changed files with 1823 additions and 5 deletions

View file

@ -91,7 +91,12 @@
"Bash(scripts/deploy.sh:*)", "Bash(scripts/deploy.sh:*)",
"Bash(DISPLAY=:0 node test-tec-v5-validated.js)", "Bash(DISPLAY=:0 node test-tec-v5-validated.js)",
"Bash(DISPLAY=:0 node test-final-edit-workflow.js)", "Bash(DISPLAY=:0 node test-final-edit-workflow.js)",
"Bash(DISPLAY=:0 node test-simple-tec-access.js)" "Bash(DISPLAY=:0 node test-simple-tec-access.js)",
"Bash(DISPLAY=:0 node test-custom-event-edit.js)",
"mcp__zen-mcp__codereview",
"mcp__zen-mcp__consensus",
"Bash(DISPLAY=:0 node test-custom-edit-with-login.js)",
"Bash(DISPLAY=:0 XAUTHORITY=/run/user/1000/.mutter-Xwaylandauth.90WDB3 node test-custom-edit-with-login.js)"
], ],
"deny": [] "deny": []
}, },

View file

@ -255,4 +255,5 @@ The following systems are commented out in `/includes/class-hvac-plugin.php` lin
- **Trainer Dashboard Template Refactoring (2025-08-11)**: Fixed critical dashboard and navigation issues. Root cause: hardcoded template override in `class-hvac-community-events.php` lines 804-806 forcing old shortcode-based template. Solution: removed hardcoded override, updated to use refactored `page-trainer-dashboard.php` template with proper WordPress integration. Fixed navigation menu rendering by removing conflicting `HVAC_NAV_RENDERED` constant checks in `class-hvac-menu-system.php` and page templates. Added missing `hvac-menu-system.css` file via git pull. Dashboard now displays correctly with working navigation across all trainer pages. Deployment script updated to automatically assign correct page template during deployment. - **Trainer Dashboard Template Refactoring (2025-08-11)**: Fixed critical dashboard and navigation issues. Root cause: hardcoded template override in `class-hvac-community-events.php` lines 804-806 forcing old shortcode-based template. Solution: removed hardcoded override, updated to use refactored `page-trainer-dashboard.php` template with proper WordPress integration. Fixed navigation menu rendering by removing conflicting `HVAC_NAV_RENDERED` constant checks in `class-hvac-menu-system.php` and page templates. Added missing `hvac-menu-system.css` file via git pull. Dashboard now displays correctly with working navigation across all trainer pages. Deployment script updated to automatically assign correct page template during deployment.
- **Documentation Page Double Navigation Fix (2025-08-11)**: Resolved duplicate navigation bar issue on documentation page. Root cause: HVAC_Help_System class was rendering its own navigation (lines 223-231) via `[hvac_documentation]` shortcode while page template also rendered navigation. Solution: commented out duplicate navigation in `class-hvac-help-system.php`. Documentation page now uses comprehensive template (`page-trainer-documentation.php`) with table of contents sidebar, WordPress/Gutenberg integration, and single navigation instance. Help content provided via `HVAC_Documentation_Content` class with fallback to shortcode for empty pages. - **Documentation Page Double Navigation Fix (2025-08-11)**: Resolved duplicate navigation bar issue on documentation page. Root cause: HVAC_Help_System class was rendering its own navigation (lines 223-231) via `[hvac_documentation]` shortcode while page template also rendered navigation. Solution: commented out duplicate navigation in `class-hvac-help-system.php`. Documentation page now uses comprehensive template (`page-trainer-documentation.php`) with table of contents sidebar, WordPress/Gutenberg integration, and single navigation instance. Help content provided via `HVAC_Documentation_Content` class with fallback to shortcode for empty pages.
[... rest of the existing content remains unchanged ...] [... rest of the existing content remains unchanged ...]
- When testing any pages which require hvac trainer role permissions, always include login steps via the /training-login page.

View file

@ -862,6 +862,12 @@ class HVAC_Community_Events {
HVAC_Logger::info("Loading edit-event template", 'Template Loader'); HVAC_Logger::info("Loading edit-event template", 'Template Loader');
} }
// Check for new custom edit event page (hierarchical URL)
if (is_page() && strpos($_SERVER['REQUEST_URI'], '/trainer/event/edit') !== false) {
$custom_template = HVAC_PLUGIN_DIR . 'templates/page-edit-event-custom.php';
HVAC_Logger::info("Loading custom edit-event template", 'Template Loader');
}
// Check for event-summary page // Check for event-summary page
if (is_page('trainer/event/summary')) { if (is_page('trainer/event/summary')) {
$custom_template = HVAC_PLUGIN_DIR . 'templates/template-event-summary.php'; $custom_template = HVAC_PLUGIN_DIR . 'templates/template-event-summary.php';

View file

@ -0,0 +1,558 @@
<?php
/**
* HVAC Custom Event Edit Form Handler
*
* Memory-efficient, type-safe event editing using modern PHP patterns
* Replaces JavaScript-dependent TEC shortcode with server-side solution
*
* @package HVAC_Community_Events
* @since 2.1.0
*/
declare(strict_types=1);
if (!defined('ABSPATH')) {
exit;
}
/**
* Custom event edit form handler with generator-based data loading
*/
final class HVAC_Custom_Event_Edit {
use HVAC_Singleton_Trait;
private const NONCE_ACTION = 'hvac_edit_event';
private const NONCE_FIELD = 'hvac_event_nonce';
private const CACHE_GROUP = 'hvac_events';
private const CACHE_TTL = 300; // 5 minutes
/**
* @var SplObjectStorage<WP_Post, ArrayObject> Event data cache
*/
private SplObjectStorage $eventCache;
/**
* @var ?int Current event ID being edited
*/
private ?int $currentEventId = null;
/**
* Constructor with property promotion
*/
private function __construct() {
$this->eventCache = new SplObjectStorage();
$this->initHooks();
}
/**
* Initialize WordPress hooks
*/
private function initHooks(): void {
add_action('init', [$this, 'registerRewriteRules']);
add_action('template_redirect', [$this, 'handleFormSubmission']);
add_filter('query_vars', [$this, 'addQueryVars']);
add_filter('template_include', [$this, 'loadTemplate']);
add_action('wp_enqueue_scripts', [$this, 'enqueueAssets']);
}
/**
* Register rewrite rules for edit URL
*/
public function registerRewriteRules(): void {
add_rewrite_rule(
'^trainer/event/edit/?$',
'index.php?hvac_event_edit=1',
'top'
);
}
/**
* Add custom query vars
*/
public function addQueryVars(array $vars): array {
$vars[] = 'hvac_event_edit';
return $vars;
}
/**
* Load custom template for event edit page
*/
public function loadTemplate(string $template): string {
if (get_query_var('hvac_event_edit') === '1') {
$custom_template = HVAC_PLUGIN_DIR . 'templates/page-edit-event-custom.php';
if (file_exists($custom_template)) {
return $custom_template;
}
}
return $template;
}
/**
* Enqueue minimal required assets (no JavaScript!)
*/
public function enqueueAssets(): void {
if (!$this->isEditPage()) {
return;
}
wp_enqueue_style(
'hvac-event-edit-custom',
HVAC_PLUGIN_URL . 'assets/css/hvac-event-edit-custom.css',
[],
HVAC_PLUGIN_VERSION
);
}
/**
* Check if current page is event edit page
*/
private function isEditPage(): bool {
return get_query_var('hvac_event_edit') === '1'
|| (is_page() && get_page_template_slug() === 'templates/page-edit-event-custom.php');
}
/**
* Get event data using generator for memory efficiency
*
* @return Generator<string, mixed>
*/
public function getEventData(int $eventId): Generator {
// Check cache first
$cacheKey = "event_data_{$eventId}";
$cached = wp_cache_get($cacheKey, self::CACHE_GROUP);
if ($cached !== false) {
yield from $cached;
return;
}
$event = get_post($eventId);
if (!$event || $event->post_type !== 'tribe_events') {
throw new InvalidArgumentException("Invalid event ID: {$eventId}");
}
// Core data
yield 'id' => $eventId;
yield 'title' => $event->post_title;
yield 'content' => $event->post_content;
yield 'excerpt' => $event->post_excerpt;
yield 'status' => $event->post_status;
yield 'author' => $event->post_author;
// Meta data - lazy load as needed
$metaKeys = $this->getEventMetaKeys();
foreach ($metaKeys as $key) {
yield $key => get_post_meta($eventId, $key, true);
}
// Venue data
$venueId = get_post_meta($eventId, '_EventVenueID', true);
if ($venueId) {
yield 'venue' => $this->getVenueData((int) $venueId);
}
// Organizer data
$organizerId = get_post_meta($eventId, '_EventOrganizerID', true);
if ($organizerId) {
yield 'organizer' => $this->getOrganizerData((int) $organizerId);
}
// Taxonomies
yield 'categories' => wp_get_post_terms($eventId, 'tribe_events_cat', ['fields' => 'ids']);
yield 'tags' => wp_get_post_terms($eventId, 'post_tag', ['fields' => 'ids']);
}
/**
* Get TEC event meta keys
*/
private function getEventMetaKeys(): array {
return [
'_EventStartDate',
'_EventEndDate',
'_EventStartDateUTC',
'_EventEndDateUTC',
'_EventTimezone',
'_EventTimezoneAbbr',
'_EventAllDay',
'_EventDuration',
'_EventCost',
'_EventCurrencySymbol',
'_EventCurrencyCode',
'_EventURL',
'_EventShowMap',
'_EventShowMapLink',
];
}
/**
* Get venue data efficiently
*/
private function getVenueData(int $venueId): ArrayObject {
$venue = get_post($venueId);
if (!$venue || $venue->post_type !== 'tribe_venue') {
return new ArrayObject();
}
return new ArrayObject([
'id' => $venueId,
'title' => $venue->post_title,
'address' => get_post_meta($venueId, '_VenueAddress', true),
'city' => get_post_meta($venueId, '_VenueCity', true),
'state' => get_post_meta($venueId, '_VenueStateProvince', true),
'zip' => get_post_meta($venueId, '_VenueZip', true),
'country' => get_post_meta($venueId, '_VenueCountry', true),
'phone' => get_post_meta($venueId, '_VenuePhone', true),
'url' => get_post_meta($venueId, '_VenueURL', true),
], ArrayObject::ARRAY_AS_PROPS);
}
/**
* Get organizer data efficiently
*/
private function getOrganizerData(int $organizerId): ArrayObject {
$organizer = get_post($organizerId);
if (!$organizer || $organizer->post_type !== 'tribe_organizer') {
return new ArrayObject();
}
return new ArrayObject([
'id' => $organizerId,
'title' => $organizer->post_title,
'phone' => get_post_meta($organizerId, '_OrganizerPhone', true),
'website' => get_post_meta($organizerId, '_OrganizerWebsite', true),
'email' => get_post_meta($organizerId, '_OrganizerEmail', true),
], ArrayObject::ARRAY_AS_PROPS);
}
/**
* Handle form submission with comprehensive validation
*/
public function handleFormSubmission(): void {
if (!$this->isEditPage() || $_SERVER['REQUEST_METHOD'] !== 'POST') {
return;
}
// Verify nonce
if (!isset($_POST[self::NONCE_FIELD]) ||
!wp_verify_nonce($_POST[self::NONCE_FIELD], self::NONCE_ACTION)) {
wp_die('Security check failed');
}
// Check capabilities
$eventId = isset($_POST['event_id']) ? (int) $_POST['event_id'] : 0;
if (!$this->canUserEditEvent($eventId)) {
wp_die('Insufficient permissions');
}
try {
$eventId = $this->saveEvent($_POST);
// Clear cache
wp_cache_delete("event_data_{$eventId}", self::CACHE_GROUP);
// Redirect with success message
wp_safe_redirect(add_query_arg([
'event_id' => $eventId,
'updated' => 'true'
], home_url('/trainer/event/edit/')));
exit;
} catch (Exception $e) {
// Log error and show message
error_log('Event save error: ' . $e->getMessage());
wp_die('Error saving event: ' . esc_html($e->getMessage()));
}
}
/**
* Save event with validation and sanitization
*/
private function saveEvent(array $data): int {
$eventId = isset($data['event_id']) ? (int) $data['event_id'] : 0;
// Prepare post data
$postData = [
'ID' => $eventId,
'post_type' => 'tribe_events',
'post_title' => sanitize_text_field($data['post_title'] ?? ''),
'post_content' => wp_kses_post($data['post_content'] ?? ''),
'post_excerpt' => sanitize_textarea_field($data['post_excerpt'] ?? ''),
'post_status' => in_array($data['post_status'] ?? '', ['publish', 'draft'])
? $data['post_status']
: 'draft',
];
// Validate required fields
if (empty($postData['post_title'])) {
throw new InvalidArgumentException('Event title is required');
}
// Insert or update
if ($eventId > 0) {
$result = wp_update_post($postData, true);
} else {
$postData['post_author'] = get_current_user_id();
$result = wp_insert_post($postData, true);
}
if (is_wp_error($result)) {
throw new RuntimeException($result->get_error_message());
}
$eventId = (int) $result;
// Update meta fields
$this->updateEventMeta($eventId, $data);
// Update venue
$this->updateVenue($eventId, $data);
// Update organizer
$this->updateOrganizer($eventId, $data);
// Update taxonomies
if (isset($data['event_categories'])) {
wp_set_post_terms($eventId, array_map('intval', $data['event_categories']), 'tribe_events_cat');
}
return $eventId;
}
/**
* Update event meta fields with validation
*/
private function updateEventMeta(int $eventId, array $data): void {
// Date/time fields
$startDate = $this->parseDateTime($data['EventStartDate'] ?? '', $data['EventStartTime'] ?? '');
$endDate = $this->parseDateTime($data['EventEndDate'] ?? '', $data['EventEndTime'] ?? '');
if ($startDate && $endDate) {
if ($endDate < $startDate) {
throw new InvalidArgumentException('End date must be after start date');
}
update_post_meta($eventId, '_EventStartDate', $startDate->format('Y-m-d H:i:s'));
update_post_meta($eventId, '_EventEndDate', $endDate->format('Y-m-d H:i:s'));
update_post_meta($eventId, '_EventStartDateUTC', get_gmt_from_date($startDate->format('Y-m-d H:i:s')));
update_post_meta($eventId, '_EventEndDateUTC', get_gmt_from_date($endDate->format('Y-m-d H:i:s')));
update_post_meta($eventId, '_EventDuration', $endDate->getTimestamp() - $startDate->getTimestamp());
}
// Other meta fields
$metaFields = [
'_EventAllDay' => isset($data['EventAllDay']) ? '1' : '0',
'_EventCost' => sanitize_text_field($data['EventCost'] ?? ''),
'_EventCurrencySymbol' => sanitize_text_field($data['EventCurrencySymbol'] ?? '$'),
'_EventURL' => esc_url_raw($data['EventURL'] ?? ''),
'_EventShowMap' => isset($data['EventShowMap']) ? '1' : '0',
'_EventShowMapLink' => isset($data['EventShowMapLink']) ? '1' : '0',
'_EventTimezone' => sanitize_text_field($data['EventTimezone'] ?? get_option('timezone_string')),
];
foreach ($metaFields as $key => $value) {
update_post_meta($eventId, $key, $value);
}
}
/**
* Parse date and time into DateTime object
*/
private function parseDateTime(string $date, string $time): ?DateTime {
if (empty($date)) {
return null;
}
$dateTimeStr = $date;
if (!empty($time)) {
$dateTimeStr .= ' ' . $time;
} else {
$dateTimeStr .= ' 00:00:00';
}
try {
return new DateTime($dateTimeStr, wp_timezone());
} catch (Exception $e) {
return null;
}
}
/**
* Update or create venue
*/
private function updateVenue(int $eventId, array $data): void {
$venueId = isset($data['venue_id']) ? (int) $data['venue_id'] : 0;
// Use existing venue
if ($venueId > 0 && isset($data['use_existing_venue'])) {
update_post_meta($eventId, '_EventVenueID', $venueId);
return;
}
// Create new venue if data provided
if (!empty($data['venue_name'])) {
$venueData = [
'post_type' => 'tribe_venue',
'post_title' => sanitize_text_field($data['venue_name']),
'post_status' => 'publish',
];
if ($venueId > 0) {
$venueData['ID'] = $venueId;
$venueId = wp_update_post($venueData);
} else {
$venueId = wp_insert_post($venueData);
}
if ($venueId && !is_wp_error($venueId)) {
// Update venue meta
update_post_meta($venueId, '_VenueAddress', sanitize_text_field($data['venue_address'] ?? ''));
update_post_meta($venueId, '_VenueCity', sanitize_text_field($data['venue_city'] ?? ''));
update_post_meta($venueId, '_VenueStateProvince', sanitize_text_field($data['venue_state'] ?? ''));
update_post_meta($venueId, '_VenueZip', sanitize_text_field($data['venue_zip'] ?? ''));
update_post_meta($venueId, '_VenueCountry', sanitize_text_field($data['venue_country'] ?? ''));
update_post_meta($venueId, '_VenuePhone', sanitize_text_field($data['venue_phone'] ?? ''));
update_post_meta($venueId, '_VenueURL', esc_url_raw($data['venue_url'] ?? ''));
update_post_meta($eventId, '_EventVenueID', $venueId);
}
}
}
/**
* Update or create organizer
*/
private function updateOrganizer(int $eventId, array $data): void {
$organizerId = isset($data['organizer_id']) ? (int) $data['organizer_id'] : 0;
// Use existing organizer
if ($organizerId > 0 && isset($data['use_existing_organizer'])) {
update_post_meta($eventId, '_EventOrganizerID', $organizerId);
return;
}
// Create new organizer if data provided
if (!empty($data['organizer_name'])) {
$organizerData = [
'post_type' => 'tribe_organizer',
'post_title' => sanitize_text_field($data['organizer_name']),
'post_status' => 'publish',
];
if ($organizerId > 0) {
$organizerData['ID'] = $organizerId;
$organizerId = wp_update_post($organizerData);
} else {
$organizerId = wp_insert_post($organizerData);
}
if ($organizerId && !is_wp_error($organizerId)) {
// Update organizer meta
update_post_meta($organizerId, '_OrganizerPhone', sanitize_text_field($data['organizer_phone'] ?? ''));
update_post_meta($organizerId, '_OrganizerWebsite', esc_url_raw($data['organizer_website'] ?? ''));
update_post_meta($organizerId, '_OrganizerEmail', sanitize_email($data['organizer_email'] ?? ''));
update_post_meta($eventId, '_EventOrganizerID', $organizerId);
}
}
}
/**
* Check if user can edit event
*/
public function canUserEditEvent(int $eventId): bool {
if (!is_user_logged_in()) {
return false;
}
// New event
if ($eventId === 0) {
return current_user_can('edit_posts');
}
// Existing event
$event = get_post($eventId);
if (!$event || $event->post_type !== 'tribe_events') {
return false;
}
// Check ownership or admin capability
$userId = get_current_user_id();
return $event->post_author === $userId || current_user_can('edit_others_posts');
}
/**
* Get all venues for dropdown (using generator)
*
* @return Generator<int, string>
*/
public function getVenuesForDropdown(): Generator {
$venues = get_posts([
'post_type' => 'tribe_venue',
'posts_per_page' => -1,
'orderby' => 'title',
'order' => 'ASC',
'author' => get_current_user_id(),
]);
foreach ($venues as $venue) {
yield $venue->ID => $venue->post_title;
}
}
/**
* Get all organizers for dropdown (using generator)
*
* @return Generator<int, string>
*/
public function getOrganizersForDropdown(): Generator {
$organizers = get_posts([
'post_type' => 'tribe_organizer',
'posts_per_page' => -1,
'orderby' => 'title',
'order' => 'ASC',
'author' => get_current_user_id(),
]);
foreach ($organizers as $organizer) {
yield $organizer->ID => $organizer->post_title;
}
}
/**
* Get event categories for checkboxes
*
* @return Generator<int, string>
*/
public function getCategoriesForCheckboxes(): Generator {
$categories = get_terms([
'taxonomy' => 'tribe_events_cat',
'hide_empty' => false,
]);
if (!is_wp_error($categories)) {
foreach ($categories as $category) {
yield $category->term_id => $category->name;
}
}
}
}
/**
* Singleton trait for memory efficiency
*/
trait HVAC_Singleton_Trait {
private static ?self $instance = null;
public static function instance(): self {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function __clone() {}
public function __wakeup() {
throw new Exception("Cannot unserialize singleton");
}
}

View file

@ -151,6 +151,13 @@ class HVAC_Page_Manager {
'parent' => 'trainer/event', 'parent' => 'trainer/event',
'capability' => 'hvac_trainer' 'capability' => 'hvac_trainer'
], ],
'trainer/event/edit' => [
'title' => 'Edit Event',
'template' => 'page-edit-event-custom.php',
'public' => false,
'parent' => 'trainer/event',
'capability' => 'hvac_trainer'
],
'trainer/event/summary' => [ 'trainer/event/summary' => [
'title' => 'Event Summary', 'title' => 'Event Summary',
'template' => 'page-event-summary.php', 'template' => 'page-event-summary.php',

View file

@ -140,6 +140,11 @@ class HVAC_Plugin {
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-event-edit-comprehensive.php'; require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-event-edit-comprehensive.php';
} }
// Custom Event Edit Form (PHP-based, no JavaScript dependencies)
if (file_exists(HVAC_PLUGIN_DIR . 'includes/class-hvac-custom-event-edit.php')) {
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-custom-event-edit.php';
}
// Feature includes - check if files exist before including // Feature includes - check if files exist before including
$feature_includes = [ $feature_includes = [
'class-hvac-trainer-status.php', 'class-hvac-trainer-status.php',
@ -503,6 +508,11 @@ class HVAC_Plugin {
HVAC_Event_Edit_Comprehensive::instance(); HVAC_Event_Edit_Comprehensive::instance();
} }
// Initialize custom event edit form (PHP-based solution)
if (class_exists('HVAC_Custom_Event_Edit')) {
HVAC_Custom_Event_Edit::instance();
}
// Initialize trainer profile // Initialize trainer profile
if (class_exists('HVAC_Trainer_Profile')) { if (class_exists('HVAC_Trainer_Profile')) {
new HVAC_Trainer_Profile(); new HVAC_Trainer_Profile();

View file

@ -135,7 +135,7 @@ echo -e "${GREEN}Step 4: Clearing cache...${NC}"
sshpass -p "$SSH_PASS" ssh -o StrictHostKeyChecking=no "$SSH_USER@$SERVER_IP" "cd $SERVER_PATH && wp cache flush 2>/dev/null || echo 'WP-CLI cache flush not available' && wp breeze purge --cache=all 2>/dev/null || echo 'Breeze cache plugin not available' && wp eval 'if (function_exists(\"opcache_reset\")) { opcache_reset(); echo \"OPcache cleared\"; }' 2>/dev/null || echo 'OPcache reset not available'" sshpass -p "$SSH_PASS" ssh -o StrictHostKeyChecking=no "$SSH_USER@$SERVER_IP" "cd $SERVER_PATH && wp cache flush 2>/dev/null || echo 'WP-CLI cache flush not available' && wp breeze purge --cache=all 2>/dev/null || echo 'Breeze cache plugin not available' && wp eval 'if (function_exists(\"opcache_reset\")) { opcache_reset(); echo \"OPcache cleared\"; }' 2>/dev/null || echo 'OPcache reset not available'"
echo -e "${GREEN}Step 5: Activating plugin and creating pages...${NC}" echo -e "${GREEN}Step 5: Activating plugin and creating pages...${NC}"
sshpass -p "$SSH_PASS" ssh -o StrictHostKeyChecking=no "$SSH_USER@$SERVER_IP" "cd $SERVER_PATH && echo 'Deactivating plugin to ensure clean activation...' && wp plugin deactivate hvac-community-events --quiet && echo 'Activating plugin (this triggers page creation)...' && wp plugin activate hvac-community-events --quiet && echo 'Updating dashboard page template...' && PAGE_ID=\$(wp post list --post_type=page --name=dashboard --field=ID | head -1) && if [ ! -z \"\$PAGE_ID\" ]; then wp post meta update \$PAGE_ID _wp_page_template templates/page-trainer-dashboard.php --quiet && echo '✅ Dashboard template updated'; fi && echo 'Updating documentation page template...' && DOC_ID=\$(wp post list --post_type=page --name=documentation --field=ID | head -1) && if [ ! -z \"\$DOC_ID\" ]; then wp post meta update \$DOC_ID _wp_page_template templates/page-trainer-documentation.php --quiet && echo '✅ Documentation template updated'; fi && echo 'Flushing rewrite rules...' && wp rewrite flush --quiet && if wp plugin list --name=hvac-community-events --status=active --format=count | grep -q '1'; then echo '✅ Plugin activated successfully'; else echo '❌ Plugin activation failed!'; fi" sshpass -p "$SSH_PASS" ssh -o StrictHostKeyChecking=no "$SSH_USER@$SERVER_IP" "cd $SERVER_PATH && echo 'Deactivating plugin to ensure clean activation...' && wp plugin deactivate hvac-community-events --quiet && echo 'Activating plugin (this triggers page creation)...' && wp plugin activate hvac-community-events --quiet && echo 'Updating dashboard page template...' && PAGE_ID=\$(wp post list --post_type=page --name=dashboard --field=ID | head -1) && if [ ! -z \"\$PAGE_ID\" ]; then wp post meta update \$PAGE_ID _wp_page_template templates/page-trainer-dashboard.php --quiet && echo '✅ Dashboard template updated'; fi && echo 'Updating documentation page template...' && DOC_ID=\$(wp post list --post_type=page --name=documentation --field=ID | head -1) && if [ ! -z \"\$DOC_ID\" ]; then wp post meta update \$DOC_ID _wp_page_template templates/page-trainer-documentation.php --quiet && echo '✅ Documentation template updated'; fi && echo 'Updating edit event page template...' && EDIT_ID=\$(wp post list --post_type=page --pagename=edit --parent=\$(wp post list --post_type=page --pagename=event --field=ID | head -1) --field=ID | head -1) && if [ ! -z \"\$EDIT_ID\" ]; then wp post meta update \$EDIT_ID _wp_page_template templates/page-edit-event-custom.php --quiet && echo '✅ Edit event template updated'; else echo '⚠️ Edit page not found - may need manual creation'; fi && echo 'Flushing rewrite rules...' && wp rewrite flush --quiet && if wp plugin list --name=hvac-community-events --status=active --format=count | grep -q '1'; then echo '✅ Plugin activated successfully'; else echo '❌ Plugin activation failed!'; fi"
echo -e "${GREEN}Step 6: Verifying deployment...${NC}" echo -e "${GREEN}Step 6: Verifying deployment...${NC}"
sshpass -p "$SSH_PASS" ssh -o StrictHostKeyChecking=no "$SSH_USER@$SERVER_IP" "cd $SERVER_PATH && echo 'Checking if key pages exist...' && if wp post list --post_type=page --name=training-login --format=count | grep -q '1'; then echo '✅ Login page exists'; else echo '❌ Login page missing'; fi && if wp post list --post_type=page --name=certificate-reports --format=count | grep -q '1'; then echo '✅ Certificate reports page exists'; else echo '❌ Certificate reports page missing'; fi" sshpass -p "$SSH_PASS" ssh -o StrictHostKeyChecking=no "$SSH_USER@$SERVER_IP" "cd $SERVER_PATH && echo 'Checking if key pages exist...' && if wp post list --post_type=page --name=training-login --format=count | grep -q '1'; then echo '✅ Login page exists'; else echo '❌ Login page missing'; fi && if wp post list --post_type=page --name=certificate-reports --format=count | grep -q '1'; then echo '✅ Certificate reports page exists'; else echo '❌ Certificate reports page missing'; fi"

View file

@ -0,0 +1,573 @@
<?php
/**
* Template Name: Custom Event Edit
* Description: Server-side populated event edit form
*/
// Security check
if (!defined('ABSPATH')) {
exit;
}
// Define constant for page identification
define('HVAC_IN_PAGE_TEMPLATE', true);
// Get event ID from URL
$event_id = isset($_GET['event_id']) ? (int) $_GET['event_id'] : 0;
// Initialize form handler
$form_handler = HVAC_Custom_Event_Edit::instance();
// Check permissions
if (!$form_handler->canUserEditEvent($event_id)) {
wp_die('You do not have permission to edit this event.');
}
// Initialize event data
$event_data = [];
if ($event_id > 0) {
try {
// Use generator to efficiently load data
foreach ($form_handler->getEventData($event_id) as $key => $value) {
$event_data[$key] = $value;
}
} catch (Exception $e) {
wp_die('Error loading event: ' . esc_html($e->getMessage()));
}
}
// Set defaults for new events
$defaults = [
'title' => '',
'content' => '',
'excerpt' => '',
'status' => 'draft',
'_EventStartDate' => date('Y-m-d'),
'_EventEndDate' => date('Y-m-d'),
'_EventAllDay' => '0',
'_EventCost' => '',
'_EventCurrencySymbol' => '$',
'_EventURL' => '',
'_EventShowMap' => '1',
'_EventShowMapLink' => '1',
'_EventTimezone' => get_option('timezone_string'),
'venue' => new ArrayObject(),
'organizer' => new ArrayObject(),
'categories' => [],
];
// Merge with defaults
$event_data = array_merge($defaults, $event_data);
// Parse dates for form display
$start_date = '';
$start_time = '';
if (!empty($event_data['_EventStartDate'])) {
$start_dt = new DateTime($event_data['_EventStartDate']);
$start_date = $start_dt->format('Y-m-d');
$start_time = $start_dt->format('H:i');
}
$end_date = '';
$end_time = '';
if (!empty($event_data['_EventEndDate'])) {
$end_dt = new DateTime($event_data['_EventEndDate']);
$end_date = $end_dt->format('Y-m-d');
$end_time = $end_dt->format('H:i');
}
get_header();
?>
<div class="hvac-page-wrapper hvac-event-edit-page">
<?php
// Display navigation menu
if (class_exists('HVAC_Menu_System')) {
HVAC_Menu_System::instance()->render_trainer_menu();
}
// Display breadcrumbs
if (class_exists('HVAC_Breadcrumbs')) {
echo HVAC_Breadcrumbs::instance()->render_breadcrumbs();
}
?>
<div class="container">
<div class="hvac-event-edit-wrapper">
<h1 class="entry-title">
<?php echo $event_id > 0 ? 'Edit Event' : 'Create New Event'; ?>
</h1>
<?php if (isset($_GET['updated']) && $_GET['updated'] === 'true'): ?>
<div class="hvac-notice hvac-notice-success">
<p>Event saved successfully!</p>
</div>
<?php endif; ?>
<form method="post" action="" class="hvac-event-form" novalidate>
<?php wp_nonce_field('hvac_edit_event', 'hvac_event_nonce'); ?>
<input type="hidden" name="event_id" value="<?php echo esc_attr($event_id); ?>">
<!-- Basic Information -->
<div class="hvac-form-section">
<h2>Event Information</h2>
<div class="hvac-form-row">
<label for="post_title" class="required">Event Title</label>
<input type="text"
id="post_title"
name="post_title"
value="<?php echo esc_attr($event_data['title']); ?>"
required>
</div>
<div class="hvac-form-row">
<label for="post_content">Event Description</label>
<?php
wp_editor($event_data['content'], 'post_content', [
'textarea_name' => 'post_content',
'textarea_rows' => 10,
'media_buttons' => true,
'teeny' => false,
]);
?>
</div>
<div class="hvac-form-row">
<label for="post_excerpt">Event Summary</label>
<textarea id="post_excerpt"
name="post_excerpt"
rows="3"><?php echo esc_textarea($event_data['excerpt']); ?></textarea>
</div>
<div class="hvac-form-row">
<label for="post_status">Event Status</label>
<select id="post_status" name="post_status">
<option value="draft" <?php selected($event_data['status'], 'draft'); ?>>Draft</option>
<option value="publish" <?php selected($event_data['status'], 'publish'); ?>>Published</option>
</select>
</div>
</div>
<!-- Date & Time -->
<div class="hvac-form-section">
<h2>Date & Time</h2>
<div class="hvac-form-row hvac-row-half">
<div class="hvac-col">
<label for="EventStartDate" class="required">Start Date</label>
<input type="date"
id="EventStartDate"
name="EventStartDate"
value="<?php echo esc_attr($start_date); ?>"
required>
</div>
<div class="hvac-col">
<label for="EventStartTime">Start Time</label>
<input type="time"
id="EventStartTime"
name="EventStartTime"
value="<?php echo esc_attr($start_time); ?>">
</div>
</div>
<div class="hvac-form-row hvac-row-half">
<div class="hvac-col">
<label for="EventEndDate" class="required">End Date</label>
<input type="date"
id="EventEndDate"
name="EventEndDate"
value="<?php echo esc_attr($end_date); ?>"
required>
</div>
<div class="hvac-col">
<label for="EventEndTime">End Time</label>
<input type="time"
id="EventEndTime"
name="EventEndTime"
value="<?php echo esc_attr($end_time); ?>">
</div>
</div>
<div class="hvac-form-row">
<label class="hvac-checkbox-label">
<input type="checkbox"
id="EventAllDay"
name="EventAllDay"
value="1"
<?php checked($event_data['_EventAllDay'], '1'); ?>>
All Day Event
</label>
</div>
<div class="hvac-form-row">
<label for="EventTimezone">Timezone</label>
<select id="EventTimezone" name="EventTimezone">
<?php
$timezones = timezone_identifiers_list();
foreach ($timezones as $tz) {
echo '<option value="' . esc_attr($tz) . '" ' .
selected($event_data['_EventTimezone'], $tz, false) . '>' .
esc_html($tz) . '</option>';
}
?>
</select>
</div>
</div>
<!-- Event Details -->
<div class="hvac-form-section">
<h2>Event Details</h2>
<div class="hvac-form-row hvac-row-half">
<div class="hvac-col">
<label for="EventCost">Event Cost</label>
<div class="hvac-input-group">
<span class="hvac-input-prefix">$</span>
<input type="text"
id="EventCost"
name="EventCost"
value="<?php echo esc_attr($event_data['_EventCost']); ?>"
pattern="[0-9]*\.?[0-9]*">
</div>
</div>
<div class="hvac-col">
<label for="EventURL">Event Website</label>
<input type="url"
id="EventURL"
name="EventURL"
value="<?php echo esc_attr($event_data['_EventURL']); ?>">
</div>
</div>
<div class="hvac-form-row">
<label class="hvac-checkbox-label">
<input type="checkbox"
id="EventShowMap"
name="EventShowMap"
value="1"
<?php checked($event_data['_EventShowMap'], '1'); ?>>
Show Map
</label>
<label class="hvac-checkbox-label">
<input type="checkbox"
id="EventShowMapLink"
name="EventShowMapLink"
value="1"
<?php checked($event_data['_EventShowMapLink'], '1'); ?>>
Show Map Link
</label>
</div>
</div>
<!-- Venue -->
<div class="hvac-form-section">
<h2>Venue</h2>
<?php
$venues = iterator_to_array($form_handler->getVenuesForDropdown());
if (!empty($venues)): ?>
<div class="hvac-form-row">
<label for="venue_id">Select Existing Venue</label>
<select id="venue_id" name="venue_id">
<option value="">-- Create New Venue --</option>
<?php foreach ($venues as $id => $name): ?>
<option value="<?php echo esc_attr($id); ?>"
<?php selected($event_data['venue']->id ?? 0, $id); ?>>
<?php echo esc_html($name); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<div class="hvac-venue-fields">
<div class="hvac-form-row">
<label for="venue_name">Venue Name</label>
<input type="text"
id="venue_name"
name="venue_name"
value="<?php echo esc_attr($event_data['venue']->title ?? ''); ?>">
</div>
<div class="hvac-form-row">
<label for="venue_address">Address</label>
<input type="text"
id="venue_address"
name="venue_address"
value="<?php echo esc_attr($event_data['venue']->address ?? ''); ?>">
</div>
<div class="hvac-form-row hvac-row-half">
<div class="hvac-col">
<label for="venue_city">City</label>
<input type="text"
id="venue_city"
name="venue_city"
value="<?php echo esc_attr($event_data['venue']->city ?? ''); ?>">
</div>
<div class="hvac-col">
<label for="venue_state">State/Province</label>
<input type="text"
id="venue_state"
name="venue_state"
value="<?php echo esc_attr($event_data['venue']->state ?? ''); ?>">
</div>
</div>
<div class="hvac-form-row hvac-row-half">
<div class="hvac-col">
<label for="venue_zip">Zip/Postal Code</label>
<input type="text"
id="venue_zip"
name="venue_zip"
value="<?php echo esc_attr($event_data['venue']->zip ?? ''); ?>">
</div>
<div class="hvac-col">
<label for="venue_country">Country</label>
<input type="text"
id="venue_country"
name="venue_country"
value="<?php echo esc_attr($event_data['venue']->country ?? ''); ?>">
</div>
</div>
</div>
</div>
<!-- Organizer -->
<div class="hvac-form-section">
<h2>Organizer</h2>
<?php
$organizers = iterator_to_array($form_handler->getOrganizersForDropdown());
if (!empty($organizers)): ?>
<div class="hvac-form-row">
<label for="organizer_id">Select Existing Organizer</label>
<select id="organizer_id" name="organizer_id">
<option value="">-- Create New Organizer --</option>
<?php foreach ($organizers as $id => $name): ?>
<option value="<?php echo esc_attr($id); ?>"
<?php selected($event_data['organizer']->id ?? 0, $id); ?>>
<?php echo esc_html($name); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<div class="hvac-organizer-fields">
<div class="hvac-form-row">
<label for="organizer_name">Organizer Name</label>
<input type="text"
id="organizer_name"
name="organizer_name"
value="<?php echo esc_attr($event_data['organizer']->title ?? ''); ?>">
</div>
<div class="hvac-form-row hvac-row-half">
<div class="hvac-col">
<label for="organizer_phone">Phone</label>
<input type="tel"
id="organizer_phone"
name="organizer_phone"
value="<?php echo esc_attr($event_data['organizer']->phone ?? ''); ?>">
</div>
<div class="hvac-col">
<label for="organizer_email">Email</label>
<input type="email"
id="organizer_email"
name="organizer_email"
value="<?php echo esc_attr($event_data['organizer']->email ?? ''); ?>">
</div>
</div>
<div class="hvac-form-row">
<label for="organizer_website">Website</label>
<input type="url"
id="organizer_website"
name="organizer_website"
value="<?php echo esc_attr($event_data['organizer']->website ?? ''); ?>">
</div>
</div>
</div>
<!-- Categories -->
<div class="hvac-form-section">
<h2>Categories</h2>
<div class="hvac-form-row">
<div class="hvac-checkbox-group">
<?php
$categories = iterator_to_array($form_handler->getCategoriesForCheckboxes());
foreach ($categories as $id => $name): ?>
<label class="hvac-checkbox-label">
<input type="checkbox"
name="event_categories[]"
value="<?php echo esc_attr($id); ?>"
<?php checked(in_array($id, $event_data['categories'])); ?>>
<?php echo esc_html($name); ?>
</label>
<?php endforeach; ?>
</div>
</div>
</div>
<!-- Submit Buttons -->
<div class="hvac-form-actions">
<button type="submit" class="hvac-button hvac-button-primary">
<?php echo $event_id > 0 ? 'Update Event' : 'Create Event'; ?>
</button>
<a href="<?php echo esc_url(home_url('/trainer/dashboard/')); ?>"
class="hvac-button hvac-button-secondary">
Cancel
</a>
</div>
</form>
</div>
</div>
</div>
<style>
/* Custom Event Edit Form Styles */
.hvac-event-edit-wrapper {
max-width: 800px;
margin: 0 auto;
padding: 30px 0;
}
.hvac-form-section {
background: #fff;
border: 1px solid #e5e5e5;
border-radius: 4px;
padding: 20px;
margin-bottom: 20px;
}
.hvac-form-section h2 {
margin: 0 0 20px 0;
padding-bottom: 10px;
border-bottom: 1px solid #e5e5e5;
font-size: 18px;
color: #333;
}
.hvac-form-row {
margin-bottom: 15px;
}
.hvac-form-row label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #555;
}
.hvac-form-row label.required::after {
content: ' *';
color: #d63638;
}
.hvac-form-row input[type="text"],
.hvac-form-row input[type="email"],
.hvac-form-row input[type="url"],
.hvac-form-row input[type="tel"],
.hvac-form-row input[type="date"],
.hvac-form-row input[type="time"],
.hvac-form-row textarea,
.hvac-form-row select {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.hvac-row-half {
display: flex;
gap: 20px;
}
.hvac-row-half .hvac-col {
flex: 1;
}
.hvac-checkbox-label {
display: inline-block;
margin-right: 20px;
margin-bottom: 10px;
font-weight: normal;
cursor: pointer;
}
.hvac-checkbox-label input[type="checkbox"] {
margin-right: 5px;
}
.hvac-input-group {
display: flex;
align-items: center;
}
.hvac-input-prefix {
padding: 8px 12px;
background: #f5f5f5;
border: 1px solid #ddd;
border-right: none;
border-radius: 4px 0 0 4px;
}
.hvac-input-group input {
border-radius: 0 4px 4px 0 !important;
}
.hvac-form-actions {
display: flex;
gap: 10px;
padding-top: 20px;
}
.hvac-button {
padding: 10px 20px;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
text-decoration: none;
display: inline-block;
transition: background 0.2s;
}
.hvac-button-primary {
background: #0073aa;
color: white;
}
.hvac-button-primary:hover {
background: #005a87;
}
.hvac-button-secondary {
background: #f0f0f1;
color: #333;
}
.hvac-button-secondary:hover {
background: #e0e0e1;
}
.hvac-notice {
padding: 12px;
margin-bottom: 20px;
border-left: 4px solid;
background: #fff;
}
.hvac-notice-success {
border-color: #46b450;
background: #ecf7ed;
}
</style>
<?php
get_footer();

View file

@ -300,8 +300,8 @@ get_header();
<td class="column-revenue">$<?php echo esc_html( number_format( $event['revenue'], 2 ) ); ?></td> <td class="column-revenue">$<?php echo esc_html( number_format( $event['revenue'], 2 ) ); ?></td>
<td class="column-actions"> <td class="column-actions">
<?php <?php
// Link to the new page containing the TEC CE submission form shortcode // Link to the custom event edit form (no JavaScript dependencies!)
$edit_url = add_query_arg( 'event_id', $event['id'], home_url( '/trainer/event/manage/' ) ); $edit_url = add_query_arg( 'event_id', $event['id'], home_url( '/trainer/event/edit/' ) );
// Link to the custom event summary page // Link to the custom event summary page
$summary_url = add_query_arg( 'event_id', $event['id'], home_url( '/trainer/event/summary/' ) ); $summary_url = add_query_arg( 'event_id', $event['id'], home_url( '/trainer/event/summary/' ) );
// Link to the standard WP single event view // Link to the standard WP single event view

View file

@ -0,0 +1,220 @@
/**
* Test Custom Event Edit Form with Proper Login
* Uses /training-login page as required for HVAC trainer role permissions
*/
const { chromium } = require('playwright');
async function testCustomEditWithLogin() {
console.log('🔄 Testing Custom Event Edit Form with Proper Authentication...\n');
const browser = await chromium.launch({
headless: false,
args: ['--disable-dev-shm-usage', '--no-sandbox']
});
const context = await browser.newContext({
viewport: { width: 1280, height: 720 }
});
const page = await context.newPage();
const baseUrl = process.env.UPSKILL_STAGING_URL || 'https://upskill-staging.measurequick.com';
try {
// Step 1: Login via /training-login (required for HVAC trainer permissions)
console.log('1⃣ Logging in via /training-login...');
await page.goto(`${baseUrl}/training-login/`);
await page.waitForLoadState('networkidle');
// Fill login form - use seeded test account
await page.fill('input[name="log"]', 'JoeMedosch@gmail.com');
await page.fill('input[name="pwd"]', 'JoeTrainer2025@');
// Submit form - try multiple selectors
const submitButton = await page.$('input[type="submit"]') ||
await page.$('button[type="submit"]') ||
await page.$('#wp-submit');
if (submitButton) {
await submitButton.click();
} else {
// Log what's on the page for debugging
const formHtml = await page.$eval('form', el => el.outerHTML).catch(() => 'No form found');
console.log('Form HTML:', formHtml.substring(0, 500));
throw new Error('Login submit button not found');
}
// Wait for redirect after login
await page.waitForTimeout(3000);
console.log('✅ Login submitted\n');
// Step 2: Verify we're logged in by checking dashboard
console.log('2⃣ Verifying authentication...');
await page.goto(`${baseUrl}/trainer/dashboard/`);
await page.waitForLoadState('networkidle');
const pageTitle = await page.title();
console.log(` Dashboard title: ${pageTitle}`);
// Check for dashboard content
const dashboardContent = await page.content();
if (dashboardContent.includes('Trainer Dashboard') || dashboardContent.includes('Events Overview')) {
console.log(' ✅ Successfully authenticated and on dashboard\n');
} else if (dashboardContent.includes('Login') || dashboardContent.includes('log in')) {
throw new Error('Not authenticated - still seeing login page');
}
// Step 3: Navigate to custom edit form
console.log('3⃣ Navigating to custom event edit form...');
// First, let's check if we have any events
const eventLinks = await page.$$('a[href*="event"]');
console.log(` Found ${eventLinks.length} event-related links`);
// Try to navigate to edit page with a test event ID
const editUrl = `${baseUrl}/trainer/event/edit/?event_id=6161`;
console.log(` Navigating to: ${editUrl}`);
await page.goto(editUrl);
await page.waitForLoadState('networkidle');
// Step 4: Verify custom form loaded
console.log('\n4⃣ Checking custom form elements...');
const currentTitle = await page.title();
console.log(` Page title: ${currentTitle}`);
// Check for custom form wrapper
const formWrapper = await page.$('.hvac-event-edit-wrapper');
if (formWrapper) {
console.log(' ✅ Custom form wrapper found');
} else {
console.log(' ❌ Custom form wrapper NOT found');
// Check what's on the page
const content = await page.content();
if (content.includes('404') || content.includes('Page not found')) {
console.log(' ⚠️ 404 Error - Edit page not found');
console.log(' Creating edit page via WP-CLI...');
// The page needs to be created
// This would normally be done during plugin activation
} else if (content.includes('Access denied') || content.includes('not authorized')) {
console.log(' ⚠️ Access denied - permission issue');
} else if (content.includes('tribe-community-events')) {
console.log(' ⚠️ TEC form loaded instead of custom form');
}
}
// Check for form fields
const formFields = {
'Title': '#post_title',
'Start Date': '#EventStartDate',
'End Date': '#EventEndDate',
'Start Time': '#EventStartTime',
'End Time': '#EventEndTime',
'Event Cost': '#EventCost',
'Event URL': '#EventURL',
'Venue Name': '#venue_name',
'Organizer Name': '#organizer_name'
};
console.log('\n5⃣ Checking form fields...');
let fieldsFound = 0;
let fieldsMissing = 0;
for (const [name, selector] of Object.entries(formFields)) {
const field = await page.$(selector);
if (field) {
const value = await field.evaluate(el => el.value || '');
console.log(`${name}: Found (value: "${value.substring(0, 30)}...")`);
fieldsFound++;
} else {
console.log(`${name}: NOT FOUND`);
fieldsMissing++;
}
}
console.log(`\n Summary: ${fieldsFound} fields found, ${fieldsMissing} missing`);
// Step 6: If form is present, test editing
if (fieldsFound > 5) {
console.log('\n6⃣ Testing form editing...');
const titleField = await page.$('#post_title');
if (titleField) {
const originalTitle = await titleField.evaluate(el => el.value);
const newTitle = `Test Event - ${Date.now()}`;
await page.fill('#post_title', newTitle);
console.log(` Updated title to: ${newTitle}`);
// Update other fields
await page.fill('#EventCost', '299');
await page.fill('#EventURL', 'https://example.com/test');
// Try to save
const saveButton = await page.$('button[type="submit"]');
if (saveButton) {
console.log(' Clicking save button...');
await saveButton.click();
// Wait for response
await page.waitForTimeout(3000);
// Check for success message
const successMsg = await page.$('.hvac-notice-success');
if (successMsg) {
console.log(' ✅ Success message displayed');
}
// Verify changes persisted
const savedTitle = await page.$eval('#post_title', el => el.value).catch(() => '');
if (savedTitle === newTitle) {
console.log(' ✅ Changes persisted!');
}
} else {
console.log(' ❌ Save button not found');
}
}
}
console.log('\n✅ Test Complete!');
if (fieldsFound > 5) {
console.log('\n🎉 Custom Event Edit Form is working!');
console.log(' - Authentication via /training-login successful');
console.log(' - Custom form loads without JavaScript dependencies');
console.log(' - Fields populate from database');
console.log(' - No TEC shortcode dependencies');
} else {
console.log('\n⚠ Custom form not fully functional yet');
console.log(' - May need to create the edit page');
console.log(' - Check if rewrite rules need flushing');
}
} catch (error) {
console.error('\n❌ Test failed:', error.message);
// Take screenshot
await page.screenshot({
path: `custom-edit-error-${Date.now()}.png`,
fullPage: true
});
console.log('📸 Screenshot saved for debugging');
throw error;
} finally {
await browser.close();
}
}
// Run test
testCustomEditWithLogin()
.then(() => {
console.log('\n✨ Test completed successfully!');
process.exit(0);
})
.catch(error => {
console.error('\n💥 Test failed:', error);
process.exit(1);
});

266
test-custom-event-edit.js Normal file
View file

@ -0,0 +1,266 @@
/**
* Test Custom Event Edit Form
*
* Verifies that the new PHP-based event edit form:
* 1. Loads without JavaScript dependencies
* 2. Populates all fields from database
* 3. Saves changes properly
* 4. No longer relies on TEC shortcode
*/
const { chromium } = require('playwright');
async function testCustomEventEdit() {
console.log('🔄 Starting Custom Event Edit Form Test...\n');
// Configure browser
const browser = await chromium.launch({
headless: false,
args: ['--disable-dev-shm-usage', '--no-sandbox']
});
const context = await browser.newContext({
viewport: { width: 1280, height: 720 }
});
const page = await context.newPage();
const baseUrl = process.env.UPSKILL_STAGING_URL || 'https://upskill-staging.measurequick.com';
try {
// Step 1: Login
console.log('1⃣ Logging in as test_admin...');
await page.goto(`${baseUrl}/training-login/`);
await page.waitForLoadState('networkidle');
// Try both possible login forms
const loginForm = await page.$('#loginform') || await page.$('.hvac-login-form');
if (!loginForm) {
// Fallback to wp-login
await page.goto(`${baseUrl}/wp-login.php`);
}
await page.fill('input[name="log"], #user_login', 'test_admin');
await page.fill('input[name="pwd"], #user_pass', 'TestAdmin123!');
await page.click('input[type="submit"], #wp-submit');
// Wait for redirect
await page.waitForLoadState('networkidle');
console.log('✅ Logged in successfully\n');
// Step 2: Navigate to trainer dashboard
console.log('2⃣ Navigating to trainer dashboard...');
await page.goto(`${baseUrl}/trainer/dashboard/`);
await page.waitForLoadState('networkidle');
// Debug: Check what's on the page
const pageTitle = await page.title();
console.log(` Page title: ${pageTitle}`);
// Check if we need to wait for dynamic content
await page.waitForTimeout(2000);
// Verify events table is visible (try multiple selectors)
const eventsTable = await page.$('.hvac-events-table') ||
await page.$('table.events-table') ||
await page.$('[class*="events"]');
if (!eventsTable) {
// Take a screenshot to see what's happening
await page.screenshot({ path: 'dashboard-debug.png', fullPage: true });
console.log(' 📸 Dashboard screenshot saved as dashboard-debug.png');
// Get page content for debugging
const content = await page.content();
if (content.includes('No events found')) {
console.log(' ⚠️ No events found - need to seed test data first');
} else if (content.includes('Access Denied') || content.includes('not authorized')) {
console.log(' ⚠️ Access denied - user may not have proper role');
}
throw new Error('Events table not found on dashboard');
}
console.log('✅ Dashboard loaded with events table\n');
// Step 3: Click edit link for first event
console.log('3⃣ Looking for Edit link...');
// Try both old and new URL patterns
let editLink = await page.$('a[href*="/trainer/event/edit/"]') ||
await page.$('a[href*="/trainer/event/manage/"]');
if (!editLink) {
// Look for any Edit link text
editLink = await page.getByText('Edit').first();
}
if (!editLink) {
// Debug: list all links on page
const allLinks = await page.$$eval('a', links =>
links.map(l => ({ text: l.textContent, href: l.href }))
);
console.log(' Available links:', allLinks.filter(l => l.text && l.text.includes('Edit')));
throw new Error('No edit links found - verify events exist');
}
const editUrl = await editLink.getAttribute('href');
console.log(` Edit URL: ${editUrl}`);
await editLink.click();
await page.waitForLoadState('networkidle');
console.log('✅ Navigated to custom edit form\n');
// Step 4: Verify we're on the custom form (not TEC shortcode)
console.log('4⃣ Verifying custom form loaded...');
// Check for our custom form class
const customForm = await page.$('.hvac-event-edit-wrapper form.hvac-event-form');
if (!customForm) {
throw new Error('Custom form not found - may still be using TEC shortcode');
}
// Verify NO TEC community events elements
const tecForm = await page.$('.tribe-community-events');
if (tecForm) {
console.warn('⚠️ WARNING: TEC form elements still present');
}
console.log('✅ Custom form loaded (no TEC dependencies)\n');
// Step 5: Check field population
console.log('5⃣ Checking field population...');
const fields = [
{ name: 'Title', selector: '#post_title', type: 'input' },
{ name: 'Start Date', selector: '#EventStartDate', type: 'input' },
{ name: 'End Date', selector: '#EventEndDate', type: 'input' },
{ name: 'Start Time', selector: '#EventStartTime', type: 'input' },
{ name: 'End Time', selector: '#EventEndTime', type: 'input' },
{ name: 'Event Cost', selector: '#EventCost', type: 'input' },
{ name: 'Event URL', selector: '#EventURL', type: 'input' },
{ name: 'Venue Name', selector: '#venue_name', type: 'input' },
{ name: 'Venue Address', selector: '#venue_address', type: 'input' },
{ name: 'Venue City', selector: '#venue_city', type: 'input' },
{ name: 'Organizer Name', selector: '#organizer_name', type: 'input' },
{ name: 'Organizer Email', selector: '#organizer_email', type: 'input' },
{ name: 'Status', selector: '#post_status', type: 'select' }
];
let populatedCount = 0;
let emptyCount = 0;
for (const field of fields) {
const element = await page.$(field.selector);
if (!element) {
console.log(`${field.name}: Field not found`);
continue;
}
let value;
if (field.type === 'select') {
value = await page.$eval(field.selector, el => el.value);
} else {
value = await page.$eval(field.selector, el => el.value);
}
if (value && value.trim() !== '') {
console.log(`${field.name}: "${value}"`);
populatedCount++;
} else {
console.log(` ⚠️ ${field.name}: Empty`);
emptyCount++;
}
}
console.log(`\n Summary: ${populatedCount} populated, ${emptyCount} empty\n`);
// Step 6: Test field editing
console.log('6⃣ Testing field editing...');
// Update title
const originalTitle = await page.$eval('#post_title', el => el.value);
const testTitle = `${originalTitle} - Edited ${Date.now()}`;
await page.fill('#post_title', testTitle);
// Update cost
await page.fill('#EventCost', '599');
// Update URL
await page.fill('#EventURL', 'https://example.com/test-event');
console.log('✅ Fields updated\n');
// Step 7: Save changes
console.log('7⃣ Saving changes...');
const saveButton = await page.$('button[type="submit"]');
if (!saveButton) {
throw new Error('Save button not found');
}
await saveButton.click();
// Wait for redirect or success message
await Promise.race([
page.waitForURL(/updated=true/, { timeout: 10000 }),
page.waitForSelector('.hvac-notice-success', { timeout: 10000 })
]);
console.log('✅ Changes saved successfully\n');
// Step 8: Verify persistence
console.log('8⃣ Verifying changes persisted...');
// Check if we're back on the edit page with success message
const successMessage = await page.$('.hvac-notice-success');
if (successMessage) {
console.log(' ✅ Success message displayed');
}
// Verify title was saved
const savedTitle = await page.$eval('#post_title', el => el.value);
if (savedTitle === testTitle) {
console.log(` ✅ Title persisted: "${savedTitle}"`);
} else {
console.log(` ❌ Title not saved. Expected: "${testTitle}", Got: "${savedTitle}"`);
}
// Verify cost was saved
const savedCost = await page.$eval('#EventCost', el => el.value);
if (savedCost === '599') {
console.log(` ✅ Cost persisted: $${savedCost}`);
} else {
console.log(` ❌ Cost not saved. Expected: "599", Got: "${savedCost}"`);
}
console.log('\n✅ Custom Event Edit Form Test Complete!');
console.log(' - Form loads without JavaScript dependencies');
console.log(' - Fields populate from database');
console.log(' - Changes save and persist');
console.log(' - No longer relies on TEC shortcode\n');
} catch (error) {
console.error('❌ Test failed:', error.message);
// Take screenshot for debugging
await page.screenshot({
path: `test-error-${Date.now()}.png`,
fullPage: true
});
console.log('📸 Screenshot saved for debugging');
throw error;
} finally {
await browser.close();
}
}
// Run the test
testCustomEventEdit()
.then(() => {
console.log('🎉 All tests passed!');
process.exit(0);
})
.catch(error => {
console.error('💥 Test suite failed:', error);
process.exit(1);
});

172
test-custom-form-direct.js Normal file
View file

@ -0,0 +1,172 @@
/**
* Direct test of custom event edit form
* Tests by navigating directly to a known event edit URL
*/
const { chromium } = require('playwright');
async function testCustomFormDirect() {
console.log('🔄 Testing Custom Event Edit Form (Direct Access)...\n');
const browser = await chromium.launch({
headless: false,
args: ['--disable-dev-shm-usage', '--no-sandbox']
});
const context = await browser.newContext({
viewport: { width: 1280, height: 720 }
});
const page = await context.newPage();
const baseUrl = process.env.UPSKILL_STAGING_URL || 'https://upskill-staging.measurequick.com';
try {
// Step 1: Login
console.log('1⃣ Logging in...');
await page.goto(`${baseUrl}/wp-login.php`);
await page.fill('#user_login', 'test_admin');
await page.fill('#user_pass', 'TestAdmin123!');
await page.click('#wp-submit');
// Wait for login to complete
await page.waitForTimeout(3000);
console.log('✅ Login submitted\n');
// Step 2: Navigate directly to edit page for a known event
// Using event ID 6161 from the previous tests
console.log('2⃣ Navigating directly to edit form...');
const editUrl = `${baseUrl}/trainer/event/edit/?event_id=6161`;
console.log(` URL: ${editUrl}`);
await page.goto(editUrl);
await page.waitForLoadState('networkidle');
// Step 3: Check what loaded
console.log('3⃣ Checking page content...\n');
const pageTitle = await page.title();
console.log(` Page Title: ${pageTitle}`);
// Check for custom form
const customForm = await page.$('.hvac-event-edit-wrapper');
if (customForm) {
console.log(' ✅ Custom form wrapper found');
} else {
console.log(' ❌ Custom form wrapper NOT found');
}
// Check for form element
const formElement = await page.$('form.hvac-event-form');
if (formElement) {
console.log(' ✅ Form element found');
} else {
console.log(' ❌ Form element NOT found');
}
// Check for TEC elements (should NOT be present)
const tecForm = await page.$('.tribe-community-events');
if (tecForm) {
console.log(' ⚠️ TEC form detected (should not be present)');
} else {
console.log(' ✅ No TEC dependencies found');
}
// Step 4: Check critical fields
console.log('\n4⃣ Checking form fields...\n');
const fields = [
{ name: 'Title', selector: '#post_title' },
{ name: 'Start Date', selector: '#EventStartDate' },
{ name: 'End Date', selector: '#EventEndDate' },
{ name: 'Event Cost', selector: '#EventCost' },
{ name: 'Venue Dropdown', selector: '#venue_id' },
{ name: 'Organizer Dropdown', selector: '#organizer_id' }
];
for (const field of fields) {
const element = await page.$(field.selector);
if (element) {
const value = await element.evaluate(el => el.value || '');
console.log(`${field.name}: Found (value: "${value.substring(0, 30)}${value.length > 30 ? '...' : ''}")`);
} else {
console.log(`${field.name}: NOT FOUND`);
}
}
// Step 5: Test saving
console.log('\n5⃣ Testing form submission...\n');
const titleField = await page.$('#post_title');
if (titleField) {
const currentTitle = await titleField.evaluate(el => el.value);
const newTitle = `${currentTitle} - Test ${Date.now()}`;
await page.fill('#post_title', newTitle);
console.log(` Updated title to: ${newTitle}`);
// Find and click save button
const saveButton = await page.$('button[type="submit"]');
if (saveButton) {
console.log(' Clicking save button...');
await saveButton.click();
// Wait for save
await page.waitForTimeout(3000);
// Check for success
const successMsg = await page.$('.hvac-notice-success');
if (successMsg) {
console.log(' ✅ Success message displayed!');
} else {
console.log(' ⚠️ No success message found');
}
// Check if title persisted
const savedTitle = await page.$eval('#post_title', el => el.value).catch(() => '');
if (savedTitle === newTitle) {
console.log(' ✅ Title change persisted!');
} else {
console.log(' ❌ Title did not persist');
}
} else {
console.log(' ❌ Save button not found');
}
}
console.log('\n✅ Test Complete!');
} catch (error) {
console.error('\n❌ Test failed:', error.message);
// Take screenshot
await page.screenshot({
path: `custom-form-error-${Date.now()}.png`,
fullPage: true
});
console.log('📸 Screenshot saved');
// Get page content for debugging
const content = await page.content();
if (content.includes('404') || content.includes('Not Found')) {
console.log('⚠️ Page returned 404 - form page may not exist');
}
if (content.includes('Access Denied') || content.includes('not authorized')) {
console.log('⚠️ Access denied - check user permissions');
}
throw error;
} finally {
await browser.close();
}
}
// Run test
testCustomFormDirect()
.then(() => {
console.log('\n🎉 Direct test passed!');
process.exit(0);
})
.catch(error => {
console.error('\n💥 Direct test failed:', error);
process.exit(1);
});