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:
parent
0f94a42f15
commit
0b854a8c5b
12 changed files with 1823 additions and 5 deletions
|
|
@ -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": []
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -256,3 +256,4 @@ The following systems are commented out in `/includes/class-hvac-plugin.php` lin
|
||||||
- **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.
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
558
includes/class-hvac-custom-event-edit.php
Normal file
558
includes/class-hvac-custom-event-edit.php
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
573
templates/page-edit-event-custom.php
Normal file
573
templates/page-edit-event-custom.php
Normal 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();
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
220
test-custom-edit-with-login.js
Normal file
220
test-custom-edit-with-login.js
Normal 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
266
test-custom-event-edit.js
Normal 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
172
test-custom-form-direct.js
Normal 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);
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue