planningcenter / developers

Planning Center API docs and support
https://developer.planning.center/docs/
85 stars 8 forks source link

Events API Not Returning All Events #1204

Closed conrad-mscc closed 2 months ago

conrad-mscc commented 2 months ago

Affected Product Which product does this bug affect? Webhooks

Describe the bug I am using the API to pull events for display on a Wordpress page. It seems to be working okay, but some of the events won't show up. Our second and third services, our women's prayer events, etc. I can't see any reason why this is happening.

To Reproduce Run this code, see that not all events are displayed.

$start_date = date('Y-m-d'); // Get today's date
$end_date = date('Y-m-d', strtotime('+90 days')); // Get the date 90 days from today
// Get events JSON data
$events_json = get_pco_data("calendar/v2/event_instances?order=starts_at&per_page=100&where[starts_at][gte]=$start_date&where[starts_at][lte]=$end_date&include=event,tags");

Expected behavior I expect all events to display.

Screenshots This event does NOT display. Screenshot 2024-06-21 at 10 37 44 AM This event does display. Screenshot 2024-06-21 at 10 37 48 AM

Additional Context:

Additional context I sent this issue to customer support first. This is what I received back:

Hi Conrad,

Thanks for your patience while we looked into this. We are seeing the same behavior when we try pulling the same report. It seems like there is a bug within the API. It seems like some dates of that event aren’t “event instances”.

To submit a bug for the API, you would need to make a post in our Github: https://github.com/planningcenter/developers/issues, this will go directly to the developers who code the API and they can get this issue resolved.

I'm sorry that I can't log the bug for you. Here in support we are trained to assist with our own interfaces. Please let us know if you have any further concerns. Steve Workman Customer Support


And... here is my ENTIRE PHP block for pulling from the API. I sent the relevant portion, but just in case there is something else in here I messed up on.

/*******************************
 * Retrieves data from the Planning Center Online API.
 *
 * @param string $endpoint The endpoint of the PCO API to retrieve data from.
 * @return string|false The response from the API as a string, or false on failure.
 *******************************/
function get_pco_data($endpoint) {
    // Set PCO Application ID and Secret
    $app_id = '/* REDACTED */';
    $secret = '/* REDACTED */';

    // Initialize cURL session
    $ch = curl_init();

    // Set endpoint URL
    $full_endpoint = "https://api.planningcenteronline.com/$endpoint";

    // Set cURL options
    curl_setopt_array($ch, [
        CURLOPT_URL => $full_endpoint,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
            'Authorization: Basic ' . base64_encode("$app_id:$secret"),
            'Content-Type: application/json',
        ],
    ]);

    // Execute cURL request
    $response = curl_exec($ch);

    // Check for errors
    if ($response === false) {
        // Handle cURL error
        $error_message = curl_error($ch);
        error_log('PCO API Error: ' . $error_message);
        curl_close($ch);
        return false;
    }

    // Check HTTP status code
    $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if ($http_status !== 200) {
        // Handle HTTP error
        error_log('PCO API HTTP Error: Status Code ' . $http_status);
        curl_close($ch);
        return false;
    }

    // Close cURL session
    curl_close($ch);

    return $response;
}

/*******************************
 * Displays calendar events retrieved from the Planning Center Online API.
 *******************************/
function display_pco_calendar_events() {
    $start_date = date('Y-m-d'); // Get today's date
    $end_date = date('Y-m-d', strtotime('+90 days')); // Get the date 90 days from today

    // Get events JSON data
    $events_json = get_pco_data("calendar/v2/event_instances?order=starts_at&per_page=100&where[starts_at][gte]=$start_date&where[starts_at][lte]=$end_date&include=event,tags");

    // Process events data
    if ($events_json) {
        $data = json_decode($events_json, true);

        if (isset($data['data'], $data['included'])) {
            $mergedData = merge_event_data($data);

            foreach ($mergedData as $entry) {
                // Check if entry has attributes key
                if (isset($entry['attributes'])) {
                    // Ensure necessary attributes exist
                    if (isset($entry['attributes']['approval_status'], $entry['attributes']['visible_in_church_center'])) {
                        if (is_event_visible($entry)) {
                            output_event_html($entry);
                        }
                    } else {
                        // Log an error or handle the case where expected attributes are missing
                        error_log('Missing expected attributes for event: ' . print_r($entry, true));
                    }
                } else {
                    // Log an error or handle the case where 'attributes' key is missing
                    error_log('Missing attributes key for event: ' . print_r($entry, true));
                }
            }
        } else {
            echo "Failed to retrieve events data from PCO.";
        }
    } else {
        echo "Failed to retrieve events from PCO.";
    }
}

/*******************************
 * Merges event data with included data.
 *
 * @param array $data The API response data.
 * @return array The merged data.
 *******************************/
function merge_event_data($data) {
    $mergedData = [];

    foreach ($data['data'] as $item) {
        $eventId = $item['relationships']['event']['data']['id'];
        $eventTagData = $item['relationships']['tags']['data'];
        $eventTag = !empty($eventTagData) ? $eventTagData[0]['id'] : null;

        $includedItem = array_filter($data['included'], function ($included) use ($eventId, $eventTag) {
            return $included['id'] === $eventId || ($included['type'] === "Tag" && $included['id'] === $eventTag);
        });

        if (!empty($includedItem)) {
            $mergedData[] = array_merge_recursive($item, reset($includedItem));
        }
    }

    return $mergedData;
}

/*******************************
 * Checks if an event should be visible.
 *
 * @param array $entry The event entry.
 * @return bool True if the event should be visible, false otherwise.
 *******************************/
function is_event_visible($entry) {
    return isset($entry['attributes']['approval_status']) &&
           $entry['attributes']['approval_status'] === 'A' &&
           isset($entry['attributes']['visible_in_church_center']) &&
           ($entry['attributes']['visible_in_church_center'] === 1 || $entry['attributes']['visible_in_church_center'] === true);
}

/*******************************
 * Outputs the HTML for an event.
 *
 * @param array $entry The event entry.
 *******************************/
function output_event_html($entry) {
    $startDateTime = new DateTime($entry['attributes']['starts_at']);
    $endDateTime = new DateTime($entry['attributes']['ends_at']);
    $timezone = new DateTimeZone('America/Denver'); // Mountain Time (MT)
    $startDateTime->setTimezone($timezone);
    $endDateTime->setTimezone($timezone);
    $timezoneAbbreviation = $timezone->getName();
    $timezoneAbbreviation = date('I') ? 'MDT' : 'MST'; // 'I' returns 1 if Daylight Saving Time (DST) is active

    $startDateFormatted = get_formatted_date($startDateTime, $timezoneAbbreviation);
    $endDateFormatted = get_formatted_date($endDateTime, $timezoneAbbreviation);

    echo '<div class="event' . (isset($entry['attributes']['tag']) ? ' ' . esc_attr($entry['attributes']['tag']) : '') . '">';
    if (!empty($entry['attributes']['image_url'])) {
        echo '<img src="' . esc_url($entry['attributes']['image_url']) . '" alt="" class="event-image">';
    }
    echo '<h1 class="event-title">' . esc_html($entry['attributes']['name']) . '</h1>';
    echo '<a href="' . esc_url($entry['attributes']['church_center_url']) . '">View in Church Center</a>';
    echo '<h3 class="time start-time">' . $startDateFormatted . '</h3>';
    echo '<h3 class="time end-time">' . $endDateFormatted . '</h3>';
    echo '<p class="location">' . esc_html($entry['attributes']['location']) . '</p>';
    echo '<p class="summary">' . esc_html($entry['attributes']['summary']) . '</p>';
    if (!empty($entry['attributes']['registration_url'])) {
        echo '<a href="' . esc_url($entry['attributes']['registration_url']) . '" class="reg-link">Register Now</a>';
    }
    echo '</div>';
}

/*******************************
 * Gets the formatted date for display.
 *
 * @param DateTime $dateTime The date and time object.
 * @param string $timezoneAbbreviation The timezone abbreviation.
 * @return string The formatted date.
 *******************************/
function get_formatted_date($dateTime, $timezoneAbbreviation) {
    return '
        <span data-day="' . esc_attr($dateTime->format('l')) . '" ' .
        'data-date="' . esc_attr($dateTime->format('d')) . '" ' .
        'data-ordinal="' . esc_attr($dateTime->format('S')) . '" ' .
        'data-month="' . esc_attr($dateTime->format('F')) . '" ' .
        'data-year="' . esc_attr($dateTime->format('Y')) . '" ' .
        'data-hour="' . esc_attr($dateTime->format('h')) . '" ' .
        'data-minute="' . esc_attr($dateTime->format('i')) . '" ' .
        'data-meridian-designation="' . esc_attr($dateTime->format('a')) . '" ' .
        'data-timezone="' . esc_attr($timezoneAbbreviation) . '"></span> ';
}

/*******************************
 * Define shortcode for displaying PCO calendar events
 *******************************/
function pco_calendar_events_shortcode() {
    ob_start(); // Start output buffering
    display_pco_calendar_events(); // Call the display_pco_calendar_events() function
    return ob_get_clean(); // Return the buffered output
}
add_shortcode('pco_calendar_events', 'pco_calendar_events_shortcode'); // Register the shortcode

I have..

nicholaskillin commented 2 months ago

Hey @conrad-mscc, thanks for reaching out to bring this to our attention.

I did a little bit of digging in our API Explorer, and from what I can see the "Women's Prayer" event is showing up in that list.

That link should pretty closely emulate the query params that you have in your provided code.

Have you inspected the response you are getting from the endpoint when you run your code? That might be the next best step to see if something in your code is filtering those event instances out.

I'm going to close this issue for now, but please feel free to respond if still feel like you aren't getting the correct data back from out API.