File: /mnt/data/dreamssalon-wp/wp-content/plugins/dreamsalon-widgets/widgets/class-popular-services.php
<?php
namespace dreamsalonelementor\Widgets;
use Elementor\Widget_Base;
use Elementor\Controls_Manager;
use Elementor\Group_Control_Typography;
if ( ! defined( 'ABSPATH' ) ) { exit; }
class DSPopularServices extends Widget_Base {
public function get_name() { return 'dreamsalon-popular-services'; }
public function get_title() { return __( 'DS Popular Services', 'dreamsalon_elementor' ); }
public function get_icon() { return 'eicon-post-list'; }
public function get_categories() { return [ 'dreamsalonelemetortheme' ]; }
protected function _register_controls() {
$this->start_controls_section('section_header', [ 'label' => __( 'Section Header', 'dreamsalon_elementor' ) ]);
$this->add_control('badge_text', [
'label' => __( 'Badge Text', 'dreamsalon_elementor' ),
'type' => Controls_Manager::TEXT,
'default' => 'Popular Services',
]);
$this->add_control('title_text', [
'label' => __( 'Title', 'dreamsalon_elementor' ),
'type' => Controls_Manager::TEXT,
'default' => 'Find the Right Service for You',
]);
$this->add_control('stitle_text', [
'label' => __( 'Title 2', 'dreamsalon_elementor' ),
'type' => Controls_Manager::TEXT,
'default' => 'Discover top wellness consultants, read real reviews',
]);
$this->end_controls_section();
$this->start_controls_section('section_query', [ 'label' => __( 'Query', 'dreamsalon_elementor' ) ]);
$this->add_control('service_categories', [
'label' => __( 'Service Categories', 'dreamsalon_elementor' ),
'type' => Controls_Manager::SELECT2,
'multiple' => true,
'options' => $this->get_service_categories_options(),
'description' => __( 'Select service_category terms to show as tabs. If empty, all categories will be used.', 'dreamsalon_elementor' ),
]);
$this->add_control('posts_per_tab', [
'label' => __( 'Posts Per Tab', 'dreamsalon_elementor' ),
'type' => Controls_Manager::NUMBER,
'default' => 4,
'min' => 1,
'max' => 20,
]);
$this->end_controls_section();
$this->start_controls_section(
'section_style',
[
'label' => __( 'Style', 'dreamsalon_elementor' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'title_typography',
'label' => __( 'Title Typography', 'dreamsalon_elementor' ),
'selector' => '{{WRAPPER}} .service-section-four .section-header h2',
]
);
$this->end_controls_section();
}
private function get_service_categories_options() {
$options = [];
if ( ! function_exists( 'get_terms' ) ) {
return $options;
}
$terms = get_terms([
'taxonomy' => 'service_category',
'hide_empty' => false,
]);
if ( is_array( $terms ) && ! is_wp_error( $terms ) ) {
foreach ( $terms as $term ) {
$options[ $term->term_id ] = $term->name;
}
}
return $options;
}
protected function render() {
$s = $this->get_settings_for_display();
$badge = isset( $s['badge_text'] ) ? $s['badge_text'] : '';
$title = isset( $s['title_text'] ) ? $s['title_text'] : '';
$stitle = isset( $s['stitle_text'] ) ? $s['stitle_text'] : '';
$posts_per_tab = ! empty( $s['posts_per_tab'] ) ? (int) $s['posts_per_tab'] : 4;
$selected_terms = isset( $s['service_categories'] ) && is_array( $s['service_categories'] ) ? array_filter( $s['service_categories'] ) : [];
$terms = [];
if ( ! empty( $selected_terms ) ) {
$terms = get_terms([
'taxonomy' => 'service_category',
'hide_empty' => true,
'include' => $selected_terms,
]);
} else {
$terms = get_terms([
'taxonomy' => 'service_category',
'hide_empty' => true,
]);
}
if ( is_wp_error( $terms ) ) {
$terms = [];
}
$widget_id = $this->get_id();
?>
<section class="service-section-four section">
<div class="container">
<div class="section-header d-flex align-items-center justify-content-between flex-wrap gap-3 wow fadeInUp" data-wow-duration="1.2s">
<div>
<?php if ( $badge ) : ?>
<span class="section-badge mb-3"><?php echo esc_html( $badge ); ?></span>
<?php endif; ?>
<?php if ( $title ) : ?>
<h2 class="mb-0 border_bottom"><?php echo esc_html( $title ); ?> <?php if ( $stitle ) : ?>
<span class="subtitle"><?php echo wp_kses_post( $stitle ); ?><span class="borderback"></span></span>
<?php endif; ?></h2>
<?php endif; ?>
</div>
<?php if ( ! empty( $terms ) ) : ?>
<ul class="nav nav-tabs listing-tab-item gap-3" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link btn active" href="#category-<?php echo esc_attr( $widget_id ); ?>" data-bs-toggle="tab" aria-selected="true" role="tab"><?php esc_html_e( 'All Category', 'dreamsalon_elementor' ); ?></a>
</li>
<?php foreach ( $terms as $index => $term ) : ?>
<li class="nav-item" role="presentation">
<a class="nav-link btn" href="#term-<?php echo esc_attr( $term->term_id . '-' . $widget_id ); ?>" data-bs-toggle="tab" aria-selected="false" role="tab"><?php echo esc_html( $term->name ); ?></a>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<div class="tab-content">
<div class="tab-pane show fade active" id="category-<?php echo esc_attr( $widget_id ); ?>" role="tabpanel">
<div class="row justify-content-center row-gap-4">
<?php
$all_query_args = [
'post_type' => 'service',
'posts_per_page' => $posts_per_tab,
];
if ( ! empty( $terms ) ) {
$all_query_args['tax_query'] = [
[
'taxonomy' => 'service_category',
'field' => 'term_id',
'terms' => wp_list_pluck( $terms, 'term_id' ),
],
];
}
$all_query = new \WP_Query( $all_query_args );
if ( $all_query->have_posts() ) :
while ( $all_query->have_posts() ) : $all_query->the_post();
$this->render_service_card();
endwhile;
wp_reset_postdata();
else :
?>
<div class="col-12">
<p><?php esc_html_e( 'No services found.', 'dreamsalon_elementor' ); ?></p>
</div>
<?php endif; ?>
</div>
</div>
<?php if ( ! empty( $terms ) ) : ?>
<?php foreach ( $terms as $term ) : ?>
<div class="tab-pane fade" id="term-<?php echo esc_attr( $term->term_id . '-' . $widget_id ); ?>" role="tabpanel">
<div class="row justify-content-center row-gap-4">
<?php
$term_query = new \WP_Query([
'post_type' => 'service',
'posts_per_page' => $posts_per_tab,
'tax_query' => [
[
'taxonomy' => 'service_category',
'field' => 'term_id',
'terms' => $term->term_id,
],
],
]);
if ( $term_query->have_posts() ) :
while ( $term_query->have_posts() ) : $term_query->the_post();
$this->render_service_card();
endwhile;
wp_reset_postdata();
else :
?>
<div class="col-12">
<p><?php esc_html_e( 'No services found in this category.', 'dreamsalon_elementor' ); ?></p>
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</section>
<?php
}
private function render_service_card() {
$post_id = get_the_ID();
$title = get_the_title();
$permalink = get_permalink();
// Prices
$service_price = get_post_meta( $post_id, 'service_price', true );
$service_offer_price = get_post_meta( $post_id, 'service_offer_price', true );
// Rating & reviews copied from service-grid.php logic
$comments = get_comments( [
'post_id' => $post_id,
'status' => 'approve',
'orderby' => 'comment_date',
'order' => 'DESC',
] );
$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 !== '' && $r !== null ) {
$sum_ratings += (int) $r;
$rated_count++;
}
}
}
$service_rating = $rated_count > 0 ? number_format( $sum_ratings / $rated_count, 1 ) : '0.0';
// Image: first from service_gallery, else featured image
$gallery_meta = get_post_meta( $post_id, 'service_gallery', true );
$service_image = '';
if ( ! empty( $gallery_meta ) && is_array( $gallery_meta ) ) {
$service_image = wp_get_attachment_url( $gallery_meta[0] );
}
if ( ! $service_image && has_post_thumbnail( $post_id ) ) {
$service_image = get_the_post_thumbnail_url( $post_id, 'large' );
}
// Fallback - let it be empty if nothing, to avoid wrong path between plugins
?>
<div class="col-xl-3 col-md-6 d-flex">
<div class="service-item-four flex-fill wow fadeInUp" data-wow-delay="0.2s">
<div class="listing-img">
<a href="<?php echo esc_url( $permalink ); ?>">
<?php if ( $service_image ) : ?>
<img class="img-fluid" src="<?php echo esc_url( $service_image ); ?>" alt="<?php echo esc_attr( $title ); ?>">
<?php endif; ?>
</a>
</div>
<div class="listing-content">
<h3 class="title text-truncate mb-2">
<a href="<?php echo esc_url( $permalink ); ?>" class="text-capitalize"><?php echo esc_html( $title ); ?></a>
</h3>
<div class="d-flex align-items-center justify-content-center gap-1 rating mb-3">
<?php
$rating_value = (float) $service_rating;
for ( $i = 1; $i <= 5; $i++ ) {
$filled = $i <= round( $rating_value );
echo '<i class="ti ti-star-filled ' . ( $filled ? 'text-warning' : 'text-light' ) . '"></i>';
}
?>
<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', 'dreamsalon_elementor' ); ?>)</p>
</div>
<div>
<?php
$amount = ( $service_offer_price !== '' && $service_offer_price !== null )
? (float) $service_offer_price
: (float) ( $service_price ?: 0 );
?>
<h6 class="fs-16 fw-semibold mb-0"><span class="fw-normal text-body"><?php esc_html_e( 'Starts From', 'dreamsalon_elementor' ); ?> : </span><?php echo get_woocommerce_currency_symbol(); ?><?php echo number_format_i18n( $amount, 2 ); ?></h6>
</div>
</div>
</div>
</div>
<?php
}
}