upskill-event-manager/docs/TEC-V5-BEST-PRACTICES.md
Ben 25d5c9ac7d feat: implement TEC v5.0.8 field mapping and best practices
- Created comprehensive field mapping documentation for TEC v5.0.8
- Documented all meta keys, input selectors, and field types
- Built validation tests using correct TEC v5.0.8 selectors
- Verified working selectors through staging environment testing
- Added best practices guide with implementation patterns
- Included JavaScript and PHP code examples
- Documented common issues and solutions
- Added debugging tips and performance optimizations

Test results show successful field discovery and persistence:
- Title, Start Date, End Date, and URL fields verified working
- Cost field may be hidden when Events Tickets is active
- All date/time fields use expected selectors
- Venue and organizer fields use array notation in names

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-18 13:29:20 -03:00

16 KiB

The Events Calendar v5.0.8 Best Practices Guide

Overview

This guide documents the best practices for working with The Events Calendar (TEC) v5.0.8 and Community Events 5.0.8, based on comprehensive testing and field validation.

Verified Working Configuration

Plugin Versions (Staging Environment)

  • The Events Calendar: 6.14.2
  • The Events Calendar Community Events: 5.0.8
  • Events Calendar Pro: 7.6.3
  • WordPress: Latest compatible version

Field Selector Best Practices

1. Core Fields (Always Available)

These fields are standard WordPress fields and always work:

const coreFields = {
    title: '#title',                    // Event title
    content: '#content',                // Event description
    excerpt: '#excerpt',                // Event excerpt
    status: '#post_status'              // Post status
};

2. Date/Time Fields (Verified Working)

const dateTimeFields = {
    startDate: '#EventStartDate',       // Format: MM/DD/YYYY
    endDate: '#EventEndDate',           // Format: MM/DD/YYYY
    startTime: '#EventStartTime',       // Format: HH:MM am/pm
    endTime: '#EventEndTime',           // Format: HH:MM am/pm
    allDay: '#EventAllDay',             // Checkbox
    timezone: 'select[name="EventTimezone"]'  // Dropdown
};

3. Event Details (Verified Working)

const eventDetails = {
    eventURL: '#EventURL',              // External event website
    showMap: '#EventShowMap',           // Checkbox
    showMapLink: '#EventShowMapLink'    // Checkbox
};

4. Cost Fields (May Be Hidden)

Note: Cost fields may be hidden if Events Tickets plugin is active.

const costFields = {
    cost: '#EventCost',                 // May not be visible
    currencySymbol: '#EventCurrencySymbol',
    currencyCode: '#EventCurrencyCode'
};

// Check visibility before interacting
async function checkCostField(page) {
    const costField = await page.$('#EventCost');
    if (costField) {
        const isVisible = await costField.isVisible();
        if (!isVisible) {
            console.log('Cost field is hidden - Events Tickets may be active');
        }
    }
}

5. Venue Fields (Complex Selectors)

Venue fields use array notation in their name attributes:

const venueFields = {
    venueDropdown: 'select[name="venue[VenueID]"]',
    venueName: 'input[name="venue[Venue]"]',
    venueAddress: 'input[name="venue[Address]"]',
    venueCity: 'input[name="venue[City]"]',
    venueState: 'input[name="venue[State]"]',
    venueZip: 'input[name="venue[Zip]"]',
    venueCountry: 'select[name="venue[Country]"]'
};

6. Organizer Fields (Complex Selectors)

const organizerFields = {
    organizerDropdown: 'select[name="organizer[OrganizerID]"]',
    organizerName: 'input[name="organizer[Organizer]"]',
    organizerEmail: 'input[name="organizer[Email]"]',
    organizerPhone: 'input[name="organizer[Phone]"]',
    organizerWebsite: 'input[name="organizer[Website]"]'
};

JavaScript Implementation Patterns

Pattern 1: Flexible Field Access

Always check multiple possible selectors for maximum compatibility:

async function getTECField(page, selectors) {
    for (const selector of selectors) {
        const element = await page.$(selector);
        if (element && await element.isVisible()) {
            return element;
        }
    }
    return null;
}

// Usage
const startDateField = await getTECField(page, [
    '#EventStartDate',
    'input[name="EventStartDate"]',
    '.tribe-datetime-block input[name*="StartDate"]'
]);

Pattern 2: Safe Field Updates

Always verify field exists and is visible before updating:

async function updateTECField(page, selector, value) {
    try {
        const element = await page.$(selector);
        if (!element) {
            console.log(`Field not found: ${selector}`);
            return false;
        }
        
        const isVisible = await element.isVisible();
        if (!isVisible) {
            console.log(`Field not visible: ${selector}`);
            return false;
        }
        
        await element.fill(value);
        return true;
    } catch (error) {
        console.log(`Error updating field: ${error.message}`);
        return false;
    }
}

Pattern 3: Date Format Handling

TEC accepts multiple date formats. Be flexible:

function formatDateForTEC(date) {
    // TEC typically expects MM/DD/YYYY in the UI
    const d = new Date(date);
    const month = String(d.getMonth() + 1).padStart(2, '0');
    const day = String(d.getDate()).padStart(2, '0');
    const year = d.getFullYear();
    return `${month}/${day}/${year}`;
}

// For meta updates (database), use YYYY-MM-DD HH:MM:SS
function formatDateForMeta(date) {
    const d = new Date(date);
    return d.toISOString().slice(0, 19).replace('T', ' ');
}

PHP Implementation Patterns

Pattern 1: Direct Meta Updates

When updating events programmatically:

function update_tec_event($event_id, $data) {
    // Update core post data
    wp_update_post([
        'ID' => $event_id,
        'post_title' => $data['title'],
        'post_content' => $data['description']
    ]);
    
    // Update TEC meta fields
    update_post_meta($event_id, '_EventStartDate', $data['start_date']);
    update_post_meta($event_id, '_EventEndDate', $data['end_date']);
    update_post_meta($event_id, '_EventStartDateUTC', get_gmt_from_date($data['start_date']));
    update_post_meta($event_id, '_EventEndDateUTC', get_gmt_from_date($data['end_date']));
    
    // Calculate duration in seconds
    $start = strtotime($data['start_date']);
    $end = strtotime($data['end_date']);
    $duration = $end - $start;
    update_post_meta($event_id, '_EventDuration', $duration);
    
    // Update venue if provided
    if (!empty($data['venue_id'])) {
        update_post_meta($event_id, '_EventVenueID', $data['venue_id']);
    }
    
    // Update organizer if provided
    if (!empty($data['organizer_id'])) {
        update_post_meta($event_id, '_EventOrganizerID', $data['organizer_id']);
    }
}

Pattern 2: Creating Events with Full Metadata

function create_tec_event($data) {
    // Create the event post
    $event_id = wp_insert_post([
        'post_type' => 'tribe_events',
        'post_title' => $data['title'],
        'post_content' => $data['description'],
        'post_status' => 'publish',
        'post_author' => get_current_user_id()
    ]);
    
    if (!is_wp_error($event_id)) {
        // Add all TEC meta fields
        $meta_fields = [
            '_EventStartDate' => $data['start_date'],
            '_EventEndDate' => $data['end_date'],
            '_EventStartDateUTC' => get_gmt_from_date($data['start_date']),
            '_EventEndDateUTC' => get_gmt_from_date($data['end_date']),
            '_EventCost' => $data['cost'] ?? '',
            '_EventCurrencySymbol' => '$',
            '_EventURL' => $data['url'] ?? '',
            '_EventShowMap' => '1',
            '_EventShowMapLink' => '1',
            '_EventTimezone' => get_option('timezone_string'),
            '_EventTimezoneAbbr' => date('T'),
            '_EventOrigin' => 'events-calendar'
        ];
        
        foreach ($meta_fields as $key => $value) {
            update_post_meta($event_id, $key, $value);
        }
    }
    
    return $event_id;
}

Testing Best Practices

1. Always Test Field Visibility

// Before running tests, check which fields are actually available
async function auditTECFields(page) {
    const audit = {
        visible: [],
        hidden: [],
        missing: []
    };
    
    const allSelectors = {
        ...coreFields,
        ...dateTimeFields,
        ...eventDetails,
        ...costFields,
        ...venueFields,
        ...organizerFields
    };
    
    for (const [name, selector] of Object.entries(allSelectors)) {
        const element = await page.$(selector);
        if (!element) {
            audit.missing.push(name);
        } else if (await element.isVisible()) {
            audit.visible.push(name);
        } else {
            audit.hidden.push(name);
        }
    }
    
    return audit;
}

2. Test Data Persistence

Always verify that changes persist after save:

async function testFieldPersistence(page, selector, testValue) {
    // Set value
    await page.fill(selector, testValue);
    
    // Save
    await page.click('#publish');
    await page.waitForSelector('.notice-success');
    
    // Reload page
    await page.reload();
    await page.waitForLoadState('networkidle');
    
    // Check if value persisted
    const currentValue = await page.$eval(selector, el => el.value);
    return currentValue === testValue;
}

3. Handle AJAX-Loaded Elements

Some TEC elements load via AJAX:

async function waitForTECElements(page) {
    // Wait for TEC admin scripts to load
    await page.waitForFunction(() => {
        return window.tribe && window.tribe.events;
    }, { timeout: 10000 });
    
    // Additional wait for dynamic elements
    await page.waitForTimeout(1000);
}

Common Issues and Solutions

Issue 1: Date Format Inconsistencies

Problem: Different installations may use different date formats. Solution: Check WordPress date format setting and adapt:

async function getDateFormat(page) {
    return await page.evaluate(() => {
        // Check if TEC provides format
        if (window.tribe_events_calendar && window.tribe_events_calendar.date_format) {
            return window.tribe_events_calendar.date_format;
        }
        // Default to US format
        return 'MM/DD/YYYY';
    });
}

Issue 2: Venue/Organizer Dropdowns Not Populating

Problem: Select2/Chosen dropdowns may not populate immediately. Solution: Wait for initialization and trigger manually if needed:

async function selectVenue(page, venueId) {
    // Wait for Select2 initialization
    await page.waitForFunction(() => {
        return jQuery && jQuery('select[name="venue[VenueID]"]').data('select2');
    }, { timeout: 5000 }).catch(() => {});
    
    // Try Select2 method first
    await page.evaluate((id) => {
        jQuery('select[name="venue[VenueID]"]').val(id).trigger('change');
    }, venueId);
    
    // Fallback to standard select
    await page.selectOption('select[name="venue[VenueID]"]', venueId).catch(() => {});
}

Issue 3: Cost Field Hidden by Events Tickets

Problem: When Events Tickets is active, cost field is managed differently. Solution: Check for alternative cost input methods:

async function setCost(page, amount) {
    // Try standard cost field
    const standardField = await page.$('#EventCost');
    if (standardField && await standardField.isVisible()) {
        await standardField.fill(amount);
        return true;
    }
    
    // Check for Events Tickets price field
    const ticketField = await page.$('input[name="_tribe_ticket_price"]');
    if (ticketField && await ticketField.isVisible()) {
        await ticketField.fill(amount);
        return true;
    }
    
    console.log('Cost field not available - may need to create ticket');
    return false;
}

Migration from Older Versions

Key Changes from TEC v4.x to v5.x

  1. Block Editor Support: v5.x includes Gutenberg block support
  2. Updated Meta Keys: Some meta keys have changed format
  3. New REST API: Enhanced REST API endpoints
  4. Modified Admin UI: Updated admin interface selectors

Backward Compatibility

Most v4.x selectors still work in v5.x, but always test:

const legacySelectors = {
    // v4.x selectors that may still work
    'event-cost': '.tribe-event-cost input',
    'event-website': '.tribe-event-website input',
    'venue-select': '#saved_venue'
};

Performance Optimization

1. Batch Meta Updates

When updating multiple fields, batch the updates:

function batch_update_event_meta($event_id, $meta_data) {
    global $wpdb;
    
    // Begin transaction
    $wpdb->query('START TRANSACTION');
    
    try {
        foreach ($meta_data as $key => $value) {
            update_post_meta($event_id, $key, $value);
        }
        
        // Commit all changes
        $wpdb->query('COMMIT');
        
        // Clear TEC cache
        tribe_cache()->delete('', 'save_post');
        
        return true;
    } catch (Exception $e) {
        $wpdb->query('ROLLBACK');
        return false;
    }
}

2. Cache Field Selectors

Cache discovered selectors for better performance:

class TECFieldCache {
    constructor() {
        this.cache = new Map();
    }
    
    async getField(page, fieldName, selectors) {
        // Check cache first
        if (this.cache.has(fieldName)) {
            const cached = this.cache.get(fieldName);
            const element = await page.$(cached);
            if (element && await element.isVisible()) {
                return element;
            }
        }
        
        // Find and cache working selector
        for (const selector of selectors) {
            const element = await page.$(selector);
            if (element && await element.isVisible()) {
                this.cache.set(fieldName, selector);
                return element;
            }
        }
        
        return null;
    }
}

Security Considerations

1. Always Sanitize Input

// Sanitize event data before saving
$clean_data = [
    'title' => sanitize_text_field($data['title']),
    'description' => wp_kses_post($data['description']),
    'start_date' => sanitize_text_field($data['start_date']),
    'cost' => floatval($data['cost']),
    'url' => esc_url_raw($data['url'])
];

2. Verify Capabilities

// Check user can edit events
if (!current_user_can('edit_tribe_events')) {
    wp_die('Insufficient permissions');
}

3. Validate Nonces

// Always verify nonces in AJAX handlers
if (!wp_verify_nonce($_POST['nonce'], 'tec_event_edit')) {
    wp_die('Security check failed');
}

Debugging Tips

1. Enable TEC Debug Mode

Add to wp-config.php:

define('TRIBE_EVENTS_DEBUG', true);

2. Log Field Discovery

async function debugFieldDiscovery(page) {
    const results = await page.evaluate(() => {
        const fields = {};
        
        // Find all inputs with Event in name/id
        document.querySelectorAll('input[id*="Event"], input[name*="Event"]').forEach(el => {
            fields[el.id || el.name] = {
                type: el.type,
                value: el.value,
                visible: el.offsetParent !== null
            };
        });
        
        return fields;
    });
    
    console.log('Discovered fields:', JSON.stringify(results, null, 2));
    return results;
}

3. Monitor Meta Updates

// Hook to log meta updates
add_action('updated_post_meta', function($meta_id, $object_id, $meta_key, $meta_value) {
    if (get_post_type($object_id) === 'tribe_events') {
        error_log("TEC Meta Update: Post $object_id, Key: $meta_key, Value: " . print_r($meta_value, true));
    }
}, 10, 4);

Conclusion

Working with TEC v5.0.8 requires understanding:

  1. The correct field selectors for each component
  2. How to handle dynamic/AJAX-loaded elements
  3. The proper meta key structure for database operations
  4. Date format handling across different contexts
  5. Compatibility considerations with other Events Calendar plugins

By following these best practices, you can reliably interact with TEC events both through the UI and programmatically.

References