File: /mnt/data/dreamsai-wp/wp-content/dreams-ai/templates/service-grid.php
<?php
/**
* Template Name: Service Grid Template
* Template for displaying service grid layout
*/
get_header();
?>
<div class="content">
<div class="container">
<!-- Loading Overlay -->
<div id="dt-loading-overlay"
style="position:fixed;inset:0;display:none;z-index:9999;background:rgba(255,255,255,0.8);align-items:center;justify-content:center;">
<div class="spinner-border text-primary" role="status" aria-label="Loading">
<span class="visually-hidden">
<?php esc_html_e('Loading...', 'dreams-ai'); ?>
</span>
</div>
</div>
<div class="row">
<?php
// Include the filter sidebar
include(plugin_dir_path(__FILE__) . '/filter-sidebar.php');
?>
<div class="col-xl-9 col-lg-8 theiaStickySidebar">
<!-- Start filter -->
<div class="sub-head-item d-flex align-items-center justify-content-between flex-wrap gap-2">
<?php
// Main query for services
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$args = array(
'post_type' => 'service',
'posts_per_page' => 9,
'paged' => $paged,
'post_status' => 'publish'
);
// Add search query if exists
if (isset($_GET['search']) && !empty($_GET['search'])) {
$args['s'] = sanitize_text_field($_GET['search']);
}
// Add destination filter
if (isset($_GET['destination']) && !empty($_GET['destination'])) {
$args['meta_query'][] = array(
'key' => 'service_destination',
'value' => sanitize_text_field($_GET['destination']),
'compare' => 'LIKE'
);
}
// Add check-in date filter
if (isset($_GET['check_in']) && !empty($_GET['check_in'])) {
$check_in_date = sanitize_text_field($_GET['check_in']);
$date_format_input = 'd-m-Y';
$date_format_output = 'Y-m-d';
$check_in_date_obj = DateTime::createFromFormat($date_format_input, $check_in_date);
if ($check_in_date_obj && $check_in_date_obj->format($date_format_input) === $check_in_date) {
$formatted_date = $check_in_date_obj->format($date_format_output);
$args['meta_query'][] = array(
'key' => 'service_start_date',
'value' => $formatted_date,
'compare' => '>=',
'type' => 'DATE',
);
}
}
// Add duration filter
if (isset($_GET['service_duration_days']) && !empty($_GET['service_duration_days'])) {
$args['tax_query'][] = array(
'taxonomy' => 'service_duration_days',
'field' => 'term_id',
'terms' => intval($_GET['service_duration_days']),
'operator' => 'IN'
);
}
// Add search query if exists
if (isset($_GET['search']) && !empty($_GET['search'])) {
$args['s'] = sanitize_text_field($_GET['search']);
}
// Add price range filter
if (isset($_GET['price_range']) && !empty($_GET['price_range'])) {
$price_range = explode(';', sanitize_text_field($_GET['price_range']));
if (count($price_range) === 2) {
$args['meta_query'][] = array(
'key' => 'service_price',
'value' => array_map('intval', $price_range),
'type' => 'NUMERIC',
'compare' => 'BETWEEN'
);
}
}
// Add category filter
if (isset($_GET['service_category']) && !empty($_GET['service_category'])) {
$args['tax_query'][] = array(
'taxonomy' => 'service_category',
'field' => 'term_id',
'terms' => array_map('intval', (array) $_GET['service_category']),
'operator' => 'IN'
);
}
// Add branches (locations) filter
if (isset($_GET['service_branch']) && !empty($_GET['service_branch'])) {
$args['tax_query'][] = array(
'taxonomy' => 'service_branch',
'field' => 'term_id',
'terms' => array_map('intval', (array) $_GET['service_branch']),
'operator' => 'IN',
);
}
// Add amenities filter
if (isset($_GET['service_amenities']) && !empty($_GET['service_amenities'])) {
$args['tax_query'][] = array(
'taxonomy' => 'service_amenities',
'field' => 'term_id',
'terms' => array_map('intval', (array) $_GET['service_amenities']),
'operator' => 'IN',
);
}
// Add rating filter based on comment reviews (average of comment meta 'rating')
if (isset($_GET['rating']) && !empty($_GET['rating'])) {
$selected_ratings = array_map('intval', (array) $_GET['rating']);
// Build a temp query using all current filters EXCEPT rating to get candidate IDs
$temp_args = $args;
unset($temp_args['paged']);
$temp_args['fields'] = 'ids';
$temp_args['posts_per_page'] = -1;
$candidate_ids = array();
$temp_query = new WP_Query($temp_args);
if ($temp_query->have_posts()) {
foreach ($temp_query->posts as $pid) {
$comments = get_comments(array(
'post_id' => $pid,
'status' => 'approve',
'orderby' => 'comment_date',
'order' => 'DESC',
));
$sum = 0;
$count = 0;
if ($comments) {
foreach ($comments as $c) {
$r = get_comment_meta($c->comment_ID, 'rating', true);
if ($r !== '' && $r !== null) {
$sum += intval($r);
$count++;
}
}
}
$avg = $count > 0 ? ($sum / $count) : 0;
$bucket = (int) floor($avg + 1e-6);
if (in_array($bucket, $selected_ratings, true)) {
$candidate_ids[] = $pid;
}
}
}
wp_reset_postdata();
// Constrain the final query to only matching IDs
$args['post__in'] = !empty($candidate_ids) ? $candidate_ids : array(0);
}
// Ensure tax_query and meta_query are properly structured
if (!empty($args['tax_query'])) {
$args['tax_query']['relation'] = 'AND';
}
if (!empty($args['meta_query'])) {
$args['meta_query']['relation'] = 'AND';
}
// Add sorting
if (isset($_GET['sort']) && !empty($_GET['sort'])) {
switch ($_GET['sort']) {
case 'price_low':
$args['meta_key'] = 'service_price';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'ASC';
break;
case 'price_high':
$args['meta_key'] = 'service_price';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
break;
case 'newest':
$args['orderby'] = 'date';
$args['order'] = 'DESC';
break;
case 'rating':
$args['meta_key'] = 'service_rating';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
break;
case 'reviews':
$args['meta_key'] = 'service_reviews';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
break;
default:
// Recommended - default ordering
break;
}
}
$services_query = new WP_Query($args);
?>
<h3 class="sub-head text-capitalize" id="service-count">
<?php echo esc_html($services_query->found_posts); ?>
<?php esc_html_e('Services Found', 'dreams-ai'); ?>
</h3>
<div class="filter-item d-flex align-items-center justify-content-between flex-wrap gap-2">
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
<?php
if (!function_exists('dreamsai_collect_current_query_args')) {
function dreamsai_collect_current_query_args() {
$args = array();
foreach ($_GET as $key => $value) {
if ($value === '' || $value === null) {
continue;
}
if (is_array($value)) {
$args[$key] = array_map('sanitize_text_field', wp_unslash($value));
} else {
$args[$key] = sanitize_text_field(wp_unslash($value));
}
}
return $args;
}
}
$current_query_args = dreamsai_collect_current_query_args();
$service_grid_page = dreamsai_fl_framework_getoptions('ai_grid_page');
$service_grid_page_url = get_permalink($service_grid_page) ?: '#';
$service_list_page = dreamsai_fl_framework_getoptions('ai_list_page');
$service_list_page_url = get_permalink($service_list_page) ?: '#';
$service_grid_page_url = ($service_grid_page_url && $service_grid_page_url !== '#')
? add_query_arg($current_query_args, $service_grid_page_url)
: $service_grid_page_url;
$service_list_page_url = ($service_list_page_url && $service_list_page_url !== '#')
? add_query_arg($current_query_args, $service_list_page_url)
: $service_list_page_url;
?>
<a href="<?php echo esc_url($service_grid_page_url); ?>"
class="btn btn-icon btn-primary list-icon" aria-label="Grid view">
<i class="ti ti-grid-dots" aria-hidden="true"></i>
</a>
<a href="<?php echo esc_url($service_list_page_url); ?>"
class="btn btn-icon border border-color list-icon" aria-label="List view">
<i class="ti ti-list" aria-hidden="true"></i>
</a>
</div>
<div class="dropdown">
<a href="#"
class="dropdown-toggle btn btn-md border border-color d-inline-flex align-items-center text-dark fw-normal"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="ti ti-sort-descending-2 me-1"></i><span
class="me-1"><?php esc_html_e('Sort By', 'dreams-ai'); ?> : </span>
<?php echo isset($_GET['sort']) && $_GET['sort'] === 'newest' ? esc_html__('Newest', 'dreams-ai') : (isset($_GET['sort']) && $_GET['sort'] === 'price_high' ? esc_html__('High to Low', 'dreams-ai') : (isset($_GET['sort']) && $_GET['sort'] === 'price_low' ? esc_html__('Low to High', 'dreams-ai') : esc_html__('Recommended', 'dreams-ai'))); ?>
</a>
<ul class="dropdown-menu dropdown-menu-end p-2">
<li><a href="#" class="dropdown-item rounded-1"
data-sort="recommended"><?php esc_html_e('Relevance', 'dreams-ai'); ?></a>
</li>
<li><a href="#" class="dropdown-item rounded-1"
data-sort="newest"><?php esc_html_e('Newest', 'dreams-ai'); ?></a></li>
<li><a href="#" class="dropdown-item rounded-1"
data-sort="price_high"><?php esc_html_e('High to Low', 'dreams-ai'); ?></a>
</li>
<li><a href="#" class="dropdown-item rounded-1"
data-sort="price_low"><?php esc_html_e('Low to High', 'dreams-ai'); ?></a>
</li>
</ul>
<form id="sort-form" class="d-none">
<input type="hidden" name="sort"
value="<?php echo isset($_GET['sort']) ? esc_attr($_GET['sort']) : 'recommended'; ?>">
<?php if (isset($_GET['search'])): ?><input type="hidden" name="search"
value="<?php echo esc_attr($_GET['search']); ?>"><?php endif; ?>
<?php if (isset($_GET['destination'])): ?><input type="hidden" name="destination"
value="<?php echo esc_attr($_GET['destination']); ?>"><?php endif; ?>
<?php if (isset($_GET['service_category'])):
foreach ((array) $_GET['service_category'] as $category): ?>
<input type="hidden" name="service_category[]"
value="<?php echo esc_attr($category); ?>">
<?php endforeach; endif; ?>
<?php if (isset($_GET['rating'])):
foreach ((array) $_GET['rating'] as $rating): ?>
<input type="hidden" name="rating[]" value="<?php echo esc_attr($rating); ?>">
<?php endforeach; endif; ?>
</form>
</div>
</div>
</div>
<!-- end filter -->
<div class="row listing-item-row justify-content-start" id="service-results">
<?php if ($services_query->have_posts()): ?>
<?php while ($services_query->have_posts()):
$services_query->the_post(); ?>
<?php
$service_price = get_post_meta(get_the_ID(), 'service_price', true);
$service_offer_price = get_post_meta(get_the_ID(), 'service_offer_price', true);
$service_duration = get_post_meta(get_the_ID(), 'service_duration', true);
$service_guests = get_post_meta(get_the_ID(), 'service_people_limit', true);
// Compute rating and review count from approved comments (like single-service)
$comments = get_comments(array(
'post_id' => get_the_ID(),
'status' => 'approve',
'orderby' => 'comment_date',
'order' => 'DESC'
));
$service_id = get_the_ID();
$service_reviews = is_array($comments) ? count($comments) : 0;
$sum_ratings = 0;
$rated_count = 0;
if ($comments) {
foreach ($comments as $c) {
$r = get_comment_meta($c->comment_ID, 'rating', true);
if ($r) {
$sum_ratings += intval($r);
$rated_count++;
}
}
}
$service_rating = $rated_count > 0 ? number_format($sum_ratings / $rated_count, 1) : '0.0';
$service_destination = get_post_meta(get_the_ID(), 'service_destination', true);
$service_types = get_the_terms(get_the_ID(), 'service_category');
$gallery = get_post_meta(get_the_ID(), 'service_gallery', true);
$author_id = get_the_author_meta('ID');
$author_avatar = get_avatar_url($author_id, array('size' => 96));
// Build Author Services page link using author_id query param
$author_services_page = function_exists('dreamsai_fl_framework_getoptions') ? dreamsai_fl_framework_getoptions('author_services_page') : 0;
$author_services_page_url = $author_services_page ? get_permalink($author_services_page) : '';
$author_link = $author_services_page_url ? add_query_arg(array(
'author_id' => (int) $author_id,
'paged' => 1,
), $author_services_page_url) : 'javascript:void(0);';
// Get first image from gallery or featured image
$service_image = '';
if (!empty($gallery) && is_array($gallery)) {
$service_image = wp_get_attachment_url($gallery[0]);
}
if (!$service_image && has_post_thumbnail()) {
$service_image = get_the_post_thumbnail_url(get_the_ID(), 'large');
}
if (!$service_image) {
$service_image = plugin_dir_url(__FILE__) . 'assets/img/services-07.jpg';
}
?>
<!-- Service Card -->
<div class="col-xl-4 col-lg-6 col-md-6 d-flex">
<div class="listing-item flex-fill mb-4 wow fadeInUp" data-wow-delay="0.2s">
<div class="listing-item-img mb-0 rounded-0 shiny position-relative overflow-hidden">
<div class="listing-slider mb-0">
<?php
$gallery = get_post_meta(get_the_ID(), 'service_gallery', true);
if ($gallery && is_array($gallery)):
foreach ($gallery as $img_id):
$img_url = wp_get_attachment_image_url($img_id, 'large');
if ($img_url):
?>
<div class="listing-slider-item">
<a href="<?php the_permalink(); ?>">
<img class="img-fluid" src="<?php echo esc_url($img_url); ?>"
alt="<?php the_title_attribute(); ?>">
</a>
</div>
<?php
endif;
endforeach;
else:
$fallback = '';
if (has_post_thumbnail()) {
$fallback = get_the_post_thumbnail_url(get_the_ID(), 'large');
} elseif (function_exists('wc_placeholder_img_src')) {
$fallback = wc_placeholder_img_src();
}
if ($fallback):
?>
<div class="listing-slider-item">
<a href="<?php the_permalink(); ?>">
<img class="img-fluid" src="<?php echo esc_url($fallback); ?>"
alt="<?php the_title_attribute(); ?>">
</a>
</div>
<?php
endif;
endif;
?>
</div>
<div
class="listing-badge-item d-flex align-items-center justify-content-end position-absolute top-0 start-0 end-0">
<?php $wishlist_active = is_user_logged_in() ? (function_exists('dreamsservice_is_in_wishlist') ? dreamsservice_is_in_wishlist(get_current_user_id(), $service_id) : false) : false; ?>
<button class="favourite <?php echo $wishlist_active ? 'selected' : ''; ?>"
id="dt-wishlist-btn" data-service-id="<?php echo (int) $service_id; ?>"
data-service-title="<?php echo esc_attr(get_the_title($service_id)); ?>"
aria-label="Add to favourites">
<i
class="ti ti-heart<?php echo $wishlist_active ? '-filled filled' : ''; ?>"></i>
</button>
</div>
</div>
<div class="listing-item-content">
<div class="d-flex align-items-center justify-content-between mb-3">
<div class="d-flex align-items-center justify-content-center gap-1">
<?php
$rating_value = floatval($service_rating);
for ($i = 1; $i <= 5; $i++):
$filled = $i <= round($rating_value);
?>
<i
class="ti ti-star-filled <?php echo $filled ? 'text-warning' : 'text-light'; ?>"></i>
<?php endfor; ?>
<p class="mb-0">
<span
class="text-dark"><?php echo $service_rating ? esc_html($service_rating) : '0.0'; ?></span>
(<?php echo $service_reviews ? esc_html($service_reviews) : '0'; ?>
<?php esc_html_e('Reviews', 'dreams-ai'); ?>)
</p>
</div>
</div>
<div class="d-flex align-items-center justify-content-between mb-3 pb-3 border-bottom">
<div>
<h3 class="title mb-1 text-capitalize">
<a href="<?php the_permalink(); ?>"
class="d-flex align-items-center gap-2"><?php the_title(); ?></a>
</h3>
<p class="d-flex align-items-center mb-0">
<i class="ti ti-map-pin-filled text-primary me-1"></i>
<?php
// Show service branch location if set, otherwise branch name; fallback to destination/excerpt
$branch_terms = get_the_terms(get_the_ID(), 'service_branch');
$branch_text = '';
if ($branch_terms && !is_wp_error($branch_terms)) {
$bt = $branch_terms[0];
$b_loc = (string) get_term_meta($bt->term_id, 'location', true);
$branch_text = $b_loc !== '' ? $b_loc : $bt->name;
}
if ($branch_text !== ''):
$display_text = function_exists('dreamsai_sentencecase') ? dreamsai_sentencecase($branch_text) : $branch_text;
echo esc_html($display_text);
elseif ($service_destination):
echo esc_html($service_destination);
else:
the_excerpt();
endif;
?>
</p>
</div>
</div>
<div
class="d-flex align-items-center justify-content-between flex-wrap flex-wrap gap-1">
<div>
<p class="fs-14 fw-normal mb-0">
<?php esc_html_e('Starts From', 'dreams-ai'); ?>
</p>
<p class="mb-0 price">
<strong>
<?php
$amount = ($service_offer_price !== '' && $service_offer_price !== null) ? (float) $service_offer_price : (float) ($service_price ?: 0);
if (function_exists('wc_price')) {
echo wp_kses_post(wc_price($amount));
} else {
$symbol = function_exists('get_woocommerce_currency_symbol') ? get_woocommerce_currency_symbol() : '$';
echo esc_html($symbol . number_format_i18n($amount, 2));
}
?>
</strong>
</p>
</div>
<div class="avatar user-avatar">
<?php
// Use service category image as avatar; fallback to author avatar
$cat_img_url = '';
if ($service_types && !is_wp_error($service_types)) {
$cat_img_id = (int) get_term_meta($service_types[0]->term_id, 'image_id', true);
if ($cat_img_id) {
$tmp = wp_get_attachment_image_url($cat_img_id, 'thumbnail');
if ($tmp) {
$cat_img_url = $tmp;
}
}
}
$avatar_src = $cat_img_url ? $cat_img_url : $author_avatar;
?>
<img src="<?php echo esc_url($avatar_src); ?>"
alt="<?php echo (isset($service_types[0]) && !is_wp_error($service_types)) ? esc_attr($service_types[0]->name) : esc_attr(get_the_author()); ?>"
class="rounded-circle border">
</div>
</div>
</div>
</div>
</div>
<!-- end card -->
<?php endwhile; ?>
<?php else: ?>
<div class="col-12">
<div class="alert alert-info">
<?php esc_html_e('No services found matching your criteria.', 'dreams-ai'); ?>
</div>
</div>
<?php endif;
wp_reset_postdata(); ?>
</div>
<!-- PAGINATION -->
<?php
$total_pages = $services_query->max_num_pages;
$current_page = max(1, $paged);
if ($total_pages > 1): ?>
<div class="col-md-12 mt-4">
<nav class="pagination-nav" aria-label="<?php esc_attr_e('Page navigation', 'dreams-ai'); ?>">
<ul class="pagination justify-content-center mt-md-4" id="service-pagination">
<?php if ($current_page > 1): ?>
<li class="page-item">
<a class="page-link" href="<?php echo get_pagenum_link($current_page - 1); ?>"
aria-label="<?php esc_attr_e('Previous', 'dreams-ai'); ?>">
<span aria-hidden="true"><i class="ti ti-chevron-left"></i></span>
</a>
</li>
<?php else: ?>
<li class="page-item disabled">
<a class="page-link" href="#"
aria-label="<?php esc_attr_e('Previous', 'dreams-ai'); ?>">
<span aria-hidden="true"><i class="ti ti-chevron-left"></i></span>
</a>
</li>
<?php endif; ?>
<?php
// Page numbers - show limited range around current page
$start_page = max(1, $current_page - 2);
$end_page = min($total_pages, $current_page + 2);
for ($i = $start_page; $i <= $end_page; $i++):
if ($i == $current_page): ?>
<li class="page-item active"><a class="page-link" href="#"><?php echo esc_html($i); ?></a></li>
<?php else: ?>
<li class="page-item"><a class="page-link"
href="<?php echo get_pagenum_link($i); ?>"><?php echo esc_html($i); ?></a></li>
<?php endif;
endfor;
?>
<?php if ($current_page < $total_pages): ?>
<li class="page-item">
<a class="page-link" href="<?php echo get_pagenum_link($current_page + 1); ?>"
aria-label="<?php esc_attr_e('Next', 'dreams-ai'); ?>">
<span aria-hidden="true"><i class="ti ti-chevron-right"></i></span>
</a>
</li>
<?php else: ?>
<li class="page-item disabled">
<a class="page-link" href="#"
aria-label="<?php esc_attr_e('Next', 'dreams-ai'); ?>">
<span aria-hidden="true"><i class="ti ti-chevron-right"></i></span>
</a>
</li>
<?php endif; ?>
</ul>
</nav>
</div>
<?php endif; ?>
<!-- END PAGINATION -->
</div>
</div>
</div>
</div>
<?php
// Inline script to handle loading overlay on navigation interactions
?>
<script>
(function ($) {
function toggleLoader(display) {
var $ov = $('#dt-loading-overlay');
if ($ov.length) {
$ov.css('display', display);
}
}
function showLoader() {
toggleLoader('flex');
}
function hideLoader() {
toggleLoader('none');
}
$(function () {
hideLoader();
});
$(window).on('load pageshow', function () {
hideLoader();
});
$(document).on('submit', '#service-search-form, #service-filter-form, #sort-form', function () {
showLoader();
});
$(document).on('click', '#service-pagination a', function (e) {
// Only show for real links (not disabled)
var href = $(this).attr('href');
var $li = $(this).closest('li');
if (href && href !== '#' && !$li.hasClass('disabled')) {
showLoader();
}
});
// Also show when user clicks grid/list toggle
$(document).on('click', '.list-icon', function () {
var href = $(this).attr('href');
if (href && href !== '#') { showLoader(); }
});
$(document).on('click', '#service-categories-filter .service-category-filter, #service-categories-filter .service-category-filter a', function () {
showLoader();
});
// General: any link inside results triggers loader
$(document).on('click', '#service-results a', function () {
var target = $(this).attr('target');
var href = $(this).attr('href');
if (href && href.indexOf('javascript:') !== 0 && target !== '_blank') {
showLoader();
}
});
$(window).on('pageshide', function () {
hideLoader();
});
// Sort dropdown items -> submit hidden sort form
$(document).on('click', '.dropdown-menu [data-sort]', function (e) {
e.preventDefault();
var sortVal = $(this).data('sort');
var $form = $('#sort-form');
if ($form.length) {
$form.find('input[name="sort"]').val(sortVal);
$form.trigger('submit');
}
});
})(jQuery);
</script>
<?php
get_footer();
?>