source/php/Helper/QueryEvents.php
public static function getEventsByInterval($params, $page = 1)
    {
        global $wpdb;

        $idString = null;
        // Get event near a location
        $location = !empty($params['location']) ? (array)$params['location'] : null;
        if (!empty($location['lat']) && !empty($location['lng'])) {
            $distance = (!empty($params['distance'])) ? $params['distance'] : 0;
            $locationIds = self::getNearbyLocations($location['lat'], $location['lng'], floatval($distance));
            $idString = ($locationIds) ? implode(',', array_column($locationIds, 'post_id')) : "0";
        }

        // Get events in given language
        $languageId = null;
        if (function_exists('pll_the_languages') && !empty($params['lang'])) {
            $languages = pll_the_languages(array('raw' => 1, 'hide_if_empty' => 0));
            $languageId = $languages[$params['lang']]['id'] ?? null;
        }

        // Build categories string
        $categories = (!empty($params['categories']) && is_array($params['categories'])) ? implode(
            ",",
            $params['categories']
        ) : null;

        // Build tags string
        $tags = (!empty($params['tags']) && is_array($params['tags'])) ? implode(
            ",",
            $params['tags']
        ) : null;

        // Build groups string
        $groups = (!empty($params['groups']) && is_array($params['groups'])) ? implode(
            ",",
            $params['groups']
        ) : null;

        // Search by text string
        $searchString = !empty($params['search_string']) ? $params['search_string'] : null;

        // Filter by age
        $ageGroup = (!empty($params['age_group']) && is_array($params['age_group'])) ? $params['age_group'] : null;

        // Calculate offset
        $page = (!is_numeric($page)) ? 1 : $page;
        $limit = intval($params['display_limit']);
        $offset = ($page - 1) * $limit;

        $db_table = $wpdb->prefix."integrate_occasions";
        $query = "
        SELECT      *, $wpdb->posts.ID AS ID
        FROM        $wpdb->posts
        LEFT JOIN   $db_table ON ($wpdb->posts.ID = $db_table.event_id) ";
        $query .= ($ageGroup) ? "LEFT JOIN $wpdb->postmeta age_from ON $wpdb->posts.ID = age_from.post_id " : '';
        $query .= ($ageGroup) ? "LEFT JOIN $wpdb->postmeta age_to ON $wpdb->posts.ID = age_to.post_id " : '';
        $query .= ($categories) ? "LEFT JOIN $wpdb->term_relationships term1 ON ($wpdb->posts.ID = term1.object_id) " : '';
        $query .= ($tags) ? "LEFT JOIN $wpdb->term_relationships term2 ON ($wpdb->posts.ID = term2.object_id) " : '';
        $query .= ($groups) ? "LEFT JOIN $wpdb->term_relationships term3 ON ($wpdb->posts.ID = term3.object_id) " : '';
        $query .= ($languageId) ? "LEFT JOIN $wpdb->term_relationships term4 ON ($wpdb->posts.ID = term4.object_id) " : '';
        $query .= "
        WHERE $wpdb->posts.post_type = %s 
        AND $wpdb->posts.post_status = %s 
        AND ($db_table.start_date BETWEEN %s AND %s OR $db_table.end_date BETWEEN %s AND %s) ";

        if ($ageGroup) {
            $query .= "AND (age_from.meta_key = 'age_group_from' AND age_to.meta_key = 'age_group_to' AND (";

            $numItems = count($ageGroup);
            $i = 0;
            foreach ($ageGroup as $key => $age) {
                if (++$i === $numItems) {
                    $query .= "{$age} BETWEEN age_from.meta_value AND age_to.meta_value";
                } else {
                    $query .= "{$age} BETWEEN age_from.meta_value AND age_to.meta_value OR ";
                }
            }
            $query .= ")) ";
        }

        $query .= ($searchString) ? "AND (($wpdb->posts.post_title LIKE %s) OR ($wpdb->posts.post_content LIKE %s))" : '';
        $query .= ($categories) ? "AND (term1.term_taxonomy_id IN ($categories)) " : '';
        $query .= ($tags) ? "AND (term2.term_taxonomy_id IN ($tags)) " : '';
        $query .= ($groups) ? "AND (term3.term_taxonomy_id IN ($groups)) " : '';
        $query .= ($languageId) ? "AND (term4.term_taxonomy_id IN ($languageId)) " : '';
        $query .= ($idString != null) ? "AND ($wpdb->posts.ID IN ($idString)) " : '';
        $query .= "GROUP BY $wpdb->posts.ID, $db_table.start_date, $db_table.end_date ";
        $query .= "ORDER BY $db_table.start_date ASC";
        $query .= ($limit == -1) ? '' : ' LIMIT'.' '.$offset.','.$limit;

        $placeholders = array(
            'event',
            'publish',
            $params['start_date'],
            $params['end_date'],
            $params['start_date'],
            $params['end_date'],
        );
        if ($searchString) {
            $placeholders[] = '%'.$wpdb->esc_like($searchString).'%';
            $placeholders[] = '%'.$wpdb->esc_like($searchString).'%';
        }

        $completeQuery = $wpdb->prepare(
            $query,
            $placeholders
        );
        $events = $wpdb->get_results($completeQuery);

        return $events;
    }
public static function getEventsByInterval($params, $page = 1)
    {
        global $wpdb;

        $idString = null;
        // Get event near a location
        $location = !empty($params['location']) ? (array)$params['location'] : null;
        if (!empty($location['lat']) && !empty($location['lng'])) {
            $distance = (!empty($params['distance'])) ? $params['distance'] : 0;
            $locationIds = self::getNearbyLocations($location['lat'], $location['lng'], floatval($distance));
            $idString = ($locationIds) ? implode(',', array_column($locationIds, 'post_id')) : "0";
        }

        // Get events in given language
        $languageId = null;
        if (function_exists('pll_the_languages') && !empty($params['lang'])) {
            $languages = pll_the_languages(array('raw' => 1, 'hide_if_empty' => 0));
            $languageId = $languages[$params['lang']]['id'] ?? null;
        }

        // Build categories string
        $categories = (!empty($params['categories']) && is_array($params['categories'])) ? implode(
            ",",
            $params['categories']
        ) : null;

        // Build tags string
        $tags = (!empty($params['tags']) && is_array($params['tags'])) ? implode(
            ",",
            $params['tags']
        ) : null;

        // Build groups string
        $groups = (!empty($params['groups']) && is_array($params['groups'])) ? implode(
            ",",
            $params['groups']
        ) : null;

        // Search by text string
        $searchString = !empty($params['search_string']) ? $params['search_string'] : null;

        // Filter by age
        $ageGroup = (!empty($params['age_group']) && is_array($params['age_group'])) ? $params['age_group'] : null;

        // Calculate offset
        $page = (!is_numeric($page)) ? 1 : $page;
        $limit = intval($params['display_limit']);
        $offset = ($page - 1) * $limit;

        $db_table = $wpdb->prefix."integrate_occasions";
        $query = "
        SELECT      *, $wpdb->posts.ID AS ID
        FROM        $wpdb->posts
        LEFT JOIN   $db_table ON ($wpdb->posts.ID = $db_table.event_id) ";
        $query .= ($ageGroup) ? "LEFT JOIN $wpdb->postmeta age_from ON $wpdb->posts.ID = age_from.post_id " : '';
        $query .= ($ageGroup) ? "LEFT JOIN $wpdb->postmeta age_to ON $wpdb->posts.ID = age_to.post_id " : '';
        $query .= ($categories) ? "LEFT JOIN $wpdb->term_relationships term1 ON ($wpdb->posts.ID = term1.object_id) " : '';
        $query .= ($tags) ? "LEFT JOIN $wpdb->term_relationships term2 ON ($wpdb->posts.ID = term2.object_id) " : '';
        $query .= ($groups) ? "LEFT JOIN $wpdb->term_relationships term3 ON ($wpdb->posts.ID = term3.object_id) " : '';
        $query .= ($languageId) ? "LEFT JOIN $wpdb->term_relationships term4 ON ($wpdb->posts.ID = term4.object_id) " : '';
        $query .= "
        WHERE $wpdb->posts.post_type = %s 
        AND $wpdb->posts.post_status = %s 
        AND ($db_table.start_date BETWEEN %s AND %s OR $db_table.end_date BETWEEN %s AND %s) ";

        if ($ageGroup) {
            $query .= "AND (age_from.meta_key = 'age_group_from' AND age_to.meta_key = 'age_group_to' AND (";

            $numItems = count($ageGroup);
            $i = 0;
            foreach ($ageGroup as $key => $age) {
                if (++$i === $numItems) {
                    $query .= "{$age} BETWEEN age_from.meta_value AND age_to.meta_value";
                } else {
                    $query .= "{$age} BETWEEN age_from.meta_value AND age_to.meta_value OR ";
                }
            }
            $query .= ")) ";
        }

        $query .= ($searchString) ? "AND (($wpdb->posts.post_title LIKE %s) OR ($wpdb->posts.post_content LIKE %s))" : '';
        $query .= ($categories) ? "AND (term1.term_taxonomy_id IN ($categories)) " : '';
        $query .= ($tags) ? "AND (term2.term_taxonomy_id IN ($tags)) " : '';
        $query .= ($groups) ? "AND (term3.term_taxonomy_id IN ($groups)) " : '';
        $query .= ($languageId) ? "AND (term4.term_taxonomy_id IN ($languageId)) " : '';
        $query .= ($idString != null) ? "AND ($wpdb->posts.ID IN ($idString)) " : '';
        $query .= "GROUP BY $wpdb->posts.ID, $db_table.start_date, $db_table.end_date ";
        $query .= "ORDER BY $db_table.start_date ASC";
        $query .= ($limit == -1) ? '' : ' LIMIT'.' '.$offset.','.$limit;

        $placeholders = array(
            'event',
            'publish',
            $params['start_date'],
            $params['end_date'],
            $params['start_date'],
            $params['end_date'],
        );
        if ($searchString) {
            $placeholders[] = '%'.$wpdb->esc_like($searchString).'%';
            $placeholders[] = '%'.$wpdb->esc_like($searchString).'%';
        }

        $completeQuery = $wpdb->prepare(
            $query,
            $placeholders
        );
        $events = $wpdb->get_results($completeQuery);

        return $events;
    }
Get events with occurance date within date range and other parameters
@param  array       $params      array of query arguments:
string      'start_date'    start date range
string      'end_date'      end date range
int         'limit'         maximum events to get
array       'taxonomies'    array of taxonomies IDs
object      'location'      object with location data
string      'distance'      max distance from location
@param  int          $page       selected pagination page number
@return array                    Events list