From 0b854a8c5baaebe29f0885b22bb350eef96cb47f Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 18 Aug 2025 14:44:07 -0300 Subject: [PATCH] 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 --- .claude/settings.local.json | 7 +- CLAUDE.md | 3 +- includes/class-hvac-community-events.php | 6 + includes/class-hvac-custom-event-edit.php | 558 +++++++++++++++++++++ includes/class-hvac-page-manager.php | 7 + includes/class-hvac-plugin.php | 10 + scripts/deploy.sh | 2 +- templates/page-edit-event-custom.php | 573 ++++++++++++++++++++++ templates/page-trainer-dashboard.php | 4 +- test-custom-edit-with-login.js | 220 +++++++++ test-custom-event-edit.js | 266 ++++++++++ test-custom-form-direct.js | 172 +++++++ 12 files changed, 1823 insertions(+), 5 deletions(-) create mode 100644 includes/class-hvac-custom-event-edit.php create mode 100644 templates/page-edit-event-custom.php create mode 100644 test-custom-edit-with-login.js create mode 100644 test-custom-event-edit.js create mode 100644 test-custom-form-direct.js diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 09d3ea87..1cae27dc 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -91,7 +91,12 @@ "Bash(scripts/deploy.sh:*)", "Bash(DISPLAY=:0 node test-tec-v5-validated.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": [] }, diff --git a/CLAUDE.md b/CLAUDE.md index 94f2017f..608830f7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. - **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 ...] \ No newline at end of file +[... 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. \ No newline at end of file diff --git a/includes/class-hvac-community-events.php b/includes/class-hvac-community-events.php index 6c442c58..38e8cf37 100644 --- a/includes/class-hvac-community-events.php +++ b/includes/class-hvac-community-events.php @@ -862,6 +862,12 @@ class HVAC_Community_Events { 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 if (is_page('trainer/event/summary')) { $custom_template = HVAC_PLUGIN_DIR . 'templates/template-event-summary.php'; diff --git a/includes/class-hvac-custom-event-edit.php b/includes/class-hvac-custom-event-edit.php new file mode 100644 index 00000000..0846c54c --- /dev/null +++ b/includes/class-hvac-custom-event-edit.php @@ -0,0 +1,558 @@ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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"); + } +} \ No newline at end of file diff --git a/includes/class-hvac-page-manager.php b/includes/class-hvac-page-manager.php index e9cf91f7..8122b401 100644 --- a/includes/class-hvac-page-manager.php +++ b/includes/class-hvac-page-manager.php @@ -151,6 +151,13 @@ class HVAC_Page_Manager { 'parent' => 'trainer/event', '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' => [ 'title' => 'Event Summary', 'template' => 'page-event-summary.php', diff --git a/includes/class-hvac-plugin.php b/includes/class-hvac-plugin.php index 7b22619c..08fbe657 100644 --- a/includes/class-hvac-plugin.php +++ b/includes/class-hvac-plugin.php @@ -140,6 +140,11 @@ class HVAC_Plugin { 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 = [ 'class-hvac-trainer-status.php', @@ -503,6 +508,11 @@ class HVAC_Plugin { 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 if (class_exists('HVAC_Trainer_Profile')) { new HVAC_Trainer_Profile(); diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 52e49c74..84e87b9a 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -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'" 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}" 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" diff --git a/templates/page-edit-event-custom.php b/templates/page-edit-event-custom.php new file mode 100644 index 00000000..a28e47ea --- /dev/null +++ b/templates/page-edit-event-custom.php @@ -0,0 +1,573 @@ +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(); +?> + +
+ render_trainer_menu(); + } + + // Display breadcrumbs + if (class_exists('HVAC_Breadcrumbs')) { + echo HVAC_Breadcrumbs::instance()->render_breadcrumbs(); + } + ?> + +
+
+

+ 0 ? 'Edit Event' : 'Create New Event'; ?> +

+ + +
+

Event saved successfully!

+
+ + +
+ + + + +
+

Event Information

+ +
+ + +
+ +
+ + 'post_content', + 'textarea_rows' => 10, + 'media_buttons' => true, + 'teeny' => false, + ]); + ?> +
+ +
+ + +
+ +
+ + +
+
+ + +
+

Date & Time

+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ +
+ +
+ + +
+
+ + +
+

Event Details

+ +
+
+ +
+ $ + +
+
+
+ + +
+
+ +
+ + +
+
+ + +
+

Venue

+ + getVenuesForDropdown()); + if (!empty($venues)): ?> +
+ + +
+ + +
+
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+
+ + +
+

Organizer

+ + getOrganizersForDropdown()); + if (!empty($organizers)): ?> +
+ + +
+ + +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + +
+

Categories

+ +
+
+ getCategoriesForCheckboxes()); + foreach ($categories as $id => $name): ?> + + +
+
+
+ + +
+ + + Cancel + +
+
+
+
+
+ + + +$ 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); + }); \ No newline at end of file diff --git a/test-custom-event-edit.js b/test-custom-event-edit.js new file mode 100644 index 00000000..8144980b --- /dev/null +++ b/test-custom-event-edit.js @@ -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); + }); \ No newline at end of file diff --git a/test-custom-form-direct.js b/test-custom-form-direct.js new file mode 100644 index 00000000..8d91f518 --- /dev/null +++ b/test-custom-form-direct.js @@ -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); + }); \ No newline at end of file