diff --git a/assets/core/scss/components/_table.scss b/assets/core/scss/components/_table.scss index 9af4c04b11..82594148e3 100644 --- a/assets/core/scss/components/_table.scss +++ b/assets/core/scss/components/_table.scss @@ -7,8 +7,6 @@ display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; - border: 1px solid $tutor-border-idle; - border-radius: $tutor-radius-lg; table { min-width: 600px; } @@ -41,7 +39,6 @@ td { padding: $tutor-spacing-5; vertical-align: middle; - min-width: 180px; } } } diff --git a/assets/icons/commission.svg b/assets/icons/commission.svg new file mode 100644 index 0000000000..1b3fb72e59 --- /dev/null +++ b/assets/icons/commission.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/fees.svg b/assets/icons/fees.svg new file mode 100644 index 0000000000..2a329397dd --- /dev/null +++ b/assets/icons/fees.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/progress.svg b/assets/icons/progress.svg new file mode 100644 index 0000000000..eae3c8831e --- /dev/null +++ b/assets/icons/progress.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/reload-4.svg b/assets/icons/reload-4.svg new file mode 100644 index 0000000000..26120d4715 --- /dev/null +++ b/assets/icons/reload-4.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/sale.svg b/assets/icons/sale.svg new file mode 100644 index 0000000000..0d5c91cfec --- /dev/null +++ b/assets/icons/sale.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/wallet.svg b/assets/icons/wallet.svg new file mode 100644 index 0000000000..9d559af3f3 --- /dev/null +++ b/assets/icons/wallet.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/withdraw.svg b/assets/icons/withdraw.svg new file mode 100644 index 0000000000..d734eeee9d --- /dev/null +++ b/assets/icons/withdraw.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/src/js/frontend/dashboard/pages/instructor/home-charts.ts b/assets/src/js/frontend/dashboard/pages/instructor/home-charts.ts index 83c919eab8..4c5d1abb95 100644 --- a/assets/src/js/frontend/dashboard/pages/instructor/home-charts.ts +++ b/assets/src/js/frontend/dashboard/pages/instructor/home-charts.ts @@ -544,7 +544,7 @@ export const overviewChart = (data: OverviewChartProps) => ({ }, createChartConfig(data: OverviewChartProps, colors: OverviewChartColors): ChartConfiguration<'line'> { - const dataLength = data.earnings.length; + const dataLength = data?.earnings?.length || data?.enrolled?.length; return { type: 'line', diff --git a/assets/src/js/v3/shared/icons/types.ts b/assets/src/js/v3/shared/icons/types.ts index c17453fac8..4de4aa82c9 100644 --- a/assets/src/js/v3/shared/icons/types.ts +++ b/assets/src/js/v3/shared/icons/types.ts @@ -259,6 +259,7 @@ export const icons = [ 'profile', 'profileCircle', 'profileCircleFill', + 'progress', 'psd', 'publish', 'qa', @@ -285,6 +286,7 @@ export const icons = [ 'reload', 'reload2', 'reload3', + 'reload4', 'removeImage', 'report', 'resources', @@ -361,10 +363,12 @@ export const icons = [ 'videoQuality', 'vimeo', 'visited', + 'wallet', 'warning', 'weightBox', 'wifi', 'wishlist', + 'withdraw', 'x', 'xls', 'xml', diff --git a/assets/src/scss/frontend/dashboard/_profile.scss b/assets/src/scss/frontend/dashboard/_profile.scss index 0d49ff00ab..eb62fbf329 100644 --- a/assets/src/scss/frontend/dashboard/_profile.scss +++ b/assets/src/scss/frontend/dashboard/_profile.scss @@ -21,7 +21,7 @@ border: 1px solid $tutor-border-idle; border-radius: $tutor-radius-2xl; padding: $tutor-spacing-6; - margin-top: $tutor-spacing-9; + margin-top: $tutor-spacing-6; @include tutor-breakpoint-down(sm) { margin-top: $tutor-spacing-5; diff --git a/assets/src/scss/frontend/dashboard/_stat-card.scss b/assets/src/scss/frontend/dashboard/_stat-card.scss index e89317f32e..791a1985d9 100644 --- a/assets/src/scss/frontend/dashboard/_stat-card.scss +++ b/assets/src/scss/frontend/dashboard/_stat-card.scss @@ -92,6 +92,97 @@ } } + &-time-spent { + .tutor-stat-card-icon { + color: $tutor-text-exception4; + } + + .tutor-stat-card-value { + color: $tutor-text-exception4; + } + } + + &-courses { + .tutor-stat-card-icon { + color: $tutor-text-success; + } + + .tutor-stat-card-value { + color: $tutor-text-success; + } + } + + + &-students { + .tutor-stat-card-icon { + color: $tutor-text-brand; + } + + .tutor-stat-card-value { + color: $tutor-text-brand; + } + } + + &-reviews { + .tutor-stat-card-icon { + color: $tutor-warning-400; + } + + .tutor-stat-card-value { + color: $tutor-warning-400; + } + } + + &-passed { + .tutor-stat-card-icon { + color: $tutor-text-brand; + } + + .tutor-stat-card-value { + color: $tutor-text-brand; + } + } + + &-progress { + .tutor-stat-card-icon { + color: $tutor-exception-5; + } + + .tutor-stat-card-value { + color: $tutor-exception-5; + } + } + + &-completed { + .tutor-stat-card-icon { + color: $tutor-text-success; + } + + .tutor-stat-card-value { + color: $tutor-text-success; + } + } + + &-qa { + .tutor-stat-card-icon { + color: $tutor-exception-2; + } + + .tutor-stat-card-value { + color: $tutor-exception-2; + } + } + + &-book-2 { + .tutor-stat-card-icon { + color: $tutor-exception-1; + } + + .tutor-stat-card-value { + color: $tutor-exception-1; + } + } + &-exception1 { .tutor-stat-card-icon { color: $tutor-exception-1; @@ -132,4 +223,64 @@ color: $tutor-text-exception5; } } + + &-earning { + .tutor-stat-card-icon { + color: $tutor-text-success; + } + + .tutor-stat-card-value { + color: $tutor-text-success; + } + } + + &-wallet { + .tutor-stat-card-icon { + color: $tutor-text-brand; + } + + .tutor-stat-card-value { + color: $tutor-text-brand; + } + } + + &-withdraw { + .tutor-stat-card-icon { + color: $tutor-text-exception5; + } + + .tutor-stat-card-value { + color: $tutor-text-exception5; + } + } + + &-sale { + .tutor-stat-card-icon { + color: $tutor-text-exception1; + } + + .tutor-stat-card-value { + color: $tutor-text-exception1; + } + } + + &-commission { + .tutor-stat-card-icon { + color: $tutor-text-exception2; + } + + .tutor-stat-card-value { + color: $tutor-text-exception2; + } + } + + &-fees { + .tutor-stat-card-icon { + color: $tutor-text-exception4; + } + + .tutor-stat-card-value { + color: $tutor-text-exception4; + } + } } diff --git a/classes/Icon.php b/classes/Icon.php index 82b12f2f07..9941d3690f 100644 --- a/classes/Icon.php +++ b/classes/Icon.php @@ -91,6 +91,7 @@ final class Icon { const CODING = 'coding'; const COLLAPSED = 'collapsed'; const COLOR_OPTION = 'color-option'; + const COMMISSION = 'commission'; const COMMAND = 'command'; const COMMENTS = 'comments'; const COMPLETED = 'completed'; @@ -158,6 +159,7 @@ final class Icon { const EYE = 'eye'; const EYE_LINE = 'eye-line'; const FACEBOOK = 'facebook'; + const FEES = 'fees'; const FEATHER = 'feather'; const FILE = 'file'; const FILE_ATTACHEMENT = 'file-attachement'; @@ -275,6 +277,7 @@ final class Icon { const PROFILE = 'profile'; const PROFILE_CIRCLE = 'profile-circle'; const PROFILE_CIRCLE_FILL = 'profile-circle-fill'; + const PROGRESS = 'progress'; const PSD = 'psd'; const PUBLISH = 'publish'; const QA = 'qa'; @@ -301,11 +304,13 @@ final class Icon { const RELOAD = 'reload'; const RELOAD_2 = 'reload-2'; const RELOAD_3 = 'reload-3'; + const RELOAD_4 = 'reload-4'; const REMOVE_IMAGE = 'remove-image'; const REPORT = 'report'; const RESOURCES = 'resources'; const ROTATE = 'rotate'; const RTF = 'rtf'; + const SALE = 'sale'; const SALE_TYPE = 'sale-type'; const SAVE = 'save'; const SEARCH = 'search'; @@ -377,10 +382,12 @@ final class Icon { const VIDEO_QUALITY = 'video-quality'; const VIMEO = 'vimeo'; const VISITED = 'visited'; + const WALLET = 'wallet'; const WARNING = 'warning'; const WEIGHT_BOX = 'weight-box'; const WIFI = 'wifi'; const WISHLIST = 'wishlist'; + const WITHDRAW = 'withdraw'; const X = 'x'; const XLS = 'xls'; const XML = 'xml'; diff --git a/classes/Utils.php b/classes/Utils.php index 5a2f9a0697..990667c181 100644 --- a/classes/Utils.php +++ b/classes/Utils.php @@ -13,13 +13,13 @@ use Tutor\Ecommerce\Tax; use Tutor\Cache\TutorCache; use Tutor\Models\QuizModel; +use Tutor\Helpers\UrlHelper; use Tutor\Helpers\HttpHelper; use Tutor\Models\CourseModel; use Tutor\Ecommerce\Ecommerce; use Tutor\Helpers\QueryHelper; use Tutor\Traits\JsonResponse; use Tutor\Helpers\DateTimeHelper; -use Tutor\Helpers\UrlHelper; if ( ! defined( 'ABSPATH' ) ) { exit; @@ -3510,11 +3510,11 @@ public function get_students_by_instructor( int $instructor_id, int $offset, int $students = $wpdb->get_results( $wpdb->prepare( "SELECT COUNT(enrollment.post_author) AS course_taken, user.*, (SELECT post_date FROM {$wpdb->posts} WHERE post_author = user.ID LIMIT 1) AS enroll_date - FROM {$wpdb->posts} enrollment - INNER JOIN {$wpdb->posts} AS course - ON enrollment.post_parent=course.ID - INNER JOIN {$wpdb->users} AS user - ON user.ID = enrollment.post_author + FROM {$wpdb->posts} enrollment + INNER JOIN {$wpdb->posts} AS course + ON enrollment.post_parent=course.ID + INNER JOIN {$wpdb->users} AS user + ON user.ID = enrollment.post_author WHERE course.post_type = %s AND course.post_status IN ({$post_status}) AND enrollment.post_type = %s @@ -3523,11 +3523,9 @@ public function get_students_by_instructor( int $instructor_id, int $offset, int {$course_query} {$date_query} AND ( user.display_name LIKE %s OR user.user_nicename LIKE %s OR user.user_email = %s OR user.user_login LIKE %s ) - GROUP BY enrollment.post_author ORDER BY {$order_by} {$order} - LIMIT %d, %d - ", + LIMIT %d, %d", $course_post_type, 'tutor_enrolled', 'completed', @@ -3556,9 +3554,7 @@ public function get_students_by_instructor( int $instructor_id, int $offset, int {$course_query} {$date_query} GROUP BY enrollment.post_author - ORDER BY {$order_by} {$order} - - ", + ORDER BY {$order_by} {$order}", $course_post_type, 'tutor_enrolled', 'completed', @@ -9546,7 +9542,7 @@ public function should_show_dicussion_menu(): bool { */ public function default_menus(): array { $items = array( - 'index' => array( + 'index' => array( 'title' => __( 'Home', 'tutor' ), 'icon' => Icon::HOME, ), @@ -10742,28 +10738,31 @@ public function build_term_tree( $terms, $parent = 0 ) { * Render SVG icon * * @since 3.7.0 + * @since 4.0.0 added $return parameter. * * @param string $name Icon name. * @param integer $width Icon width. * @param integer $height Icon height. * @param array $attributes Custom attributes. + * @param bool $return Whether to return the SVG markup instead of echoing it. + * Default false (echo). * - * @return void + * @return string|null Returns the SVG markup when `$return` is true, otherwise null. */ - public function render_svg_icon( $name, $width = 16, $height = 16, $attributes = array() ) { + public function render_svg_icon( $name, $width = 16, $height = 16, $attributes = array(), $return = false ): ?string { $icon_path = tutor()->path . 'assets/icons/' . $name . '.svg'; if ( ! file_exists( $icon_path ) ) { - return; + return null; } $svg = file_get_contents( $icon_path ); if ( ! $svg ) { - return; + return null; } preg_match( '/]*viewBox="([^"]+)"[^>]*>(.*?)<\/svg>/is', $svg, $matches ); if ( ! $matches ) { - return; + return null; } list( $svg_tag, $view_box, $inner_svg ) = $matches; @@ -10779,7 +10778,12 @@ public function render_svg_icon( $name, $width = 16, $height = 16, $attributes = $attr_string .= ' ' . esc_attr( $key ) . '="' . esc_attr( $value ) . '"'; } - printf( '%s', $attr_string, $inner_svg ); + if ( ! $return ) { + printf( '%s', $attr_string, $inner_svg ); + return null; + } + + return '' . $inner_svg . ''; } /** @@ -10817,4 +10821,148 @@ public function get_script_locale_data( string $filename, string $locale = 'en_U return null; } + + /** + * Render a template and return its output as a string. + * + * @since 4.0.0 + * + * @param string $template Template file path or slug. + * @param array $data Data to be passed to the template. + * @param string $type Template type. Allowed values: + * - include + * - custom_template + * - template + * @param bool $tutor_pro Whether to load Tutor Pro template. + * + * @return string Rendered template output. + */ + public function render_template( $template, $data, $type, $tutor_pro = false ) { + + ob_start(); + + switch ( $type ) { + case 'include': + include $template; + break; + + case 'custom_template': + tutor_load_template_from_custom_path( $template, $data ); + break; + + case 'template': + tutor_load_template( $template, $data, $tutor_pro ); + break; + + default: + break; + } + + return (string) ob_get_clean(); + } + + /** + * Get topic-wise progress data for a course and a student. + * + * @since 4.0.0 + * + * @param int $course_id Course ID. + * @param int $student_id Student ID. + * + * @return array[] Topic progress data. + */ + public function get_topic_progress_by_course_id( $course_id, $student_id ) { + + $topics_query = $this->get_topics( $course_id ); + + $topic_list = array(); + + if ( empty( $topics_query ) || ! $topics_query->have_posts() ) { + return $topic_list; + } + + foreach ( $topics_query->posts as $topic_post ) { + $topic_id = (int) $topic_post->ID; + + $topic = array( + 'topic_id' => $topic_id, + 'topic_summary' => apply_filters( 'the_content', $topic_post->post_content ), + 'topic_title' => get_the_title( $topic_id ), + 'items' => array(), + 'topic_completed' => true, + 'topic_started' => false, + ); + + $contents_query = tutor_utils()->get_course_contents_by_topic( $topic_id, -1 ); + + if ( ! empty( $contents_query ) && $contents_query->have_posts() ) { + foreach ( $contents_query->posts as $content_post ) { + $post_id = (int) $content_post->ID; + $post_type = $content_post->post_type; + $is_completed = true; + + if ( 'tutor_quiz' === $post_type ) { + + $is_completed = (bool) tutor_utils()->has_attempted_quiz( $student_id, $post_id ); + + $topic['items'][] = array( + 'type' => 'quiz', + 'id' => $post_id, + 'link' => esc_url( get_permalink( $post_id ) ), + 'title' => $content_post->post_title, + 'is_completed' => $is_completed, + 'time_limit' => tutor_utils()->get_quiz_option( $post_id, 'time_limit.time_value' ), + 'time_type' => tutor_utils()->get_quiz_option( $post_id, 'time_limit.time_type' ), + ); + + } elseif ( 'tutor_assignments' === $post_type ) { + + $submitted_count = (int) tutor_utils()->get_submitted_assignment_count( $post_id, $student_id ); + $is_completed = $submitted_count > 0; + + $topic['items'][] = array( + 'type' => 'assignment', + 'id' => $post_id, + 'link' => esc_url( get_permalink( $post_id ) ), + 'title' => $content_post->post_title, + 'is_completed' => $is_completed, + ); + + } elseif ( 'tutor_zoom_meeting' === $post_type ) { //@todo Need to add more information. + $topic['items'][] = array( + 'type' => 'zoom_meeting', + 'id' => $post_id, + 'link' => esc_url( get_permalink( $post_id ) ), + ); + + } else { + $video = tutor_utils()->get_video_info( $post_id ); + $is_completed = (bool) tutor_utils()->is_completed_lesson( $post_id, $student_id ); + + $topic['items'][] = array( + 'type' => 'lesson', + 'id' => $post_id, + 'link' => esc_url( get_permalink( $post_id ) ), + 'title' => $content_post->post_title, + 'video' => $video, + 'video_play_time' => isset( $video->playtime ) ? $video->playtime : '', + 'is_completed' => $is_completed, + ); + } + + if ( ! $is_completed ) { + $topic['topic_completed'] = false; + } else { + $topic['topic_started'] = true; + } + } + } + + $topic_list[] = $topic; + } + + wp_reset_postdata(); + + return $topic_list; + } } diff --git a/components/BaseComponent.php b/components/BaseComponent.php index 5647fcd364..f40b3b569c 100644 --- a/components/BaseComponent.php +++ b/components/BaseComponent.php @@ -127,6 +127,11 @@ protected function render_attributes(): string { * @return string Escaped string. */ protected function esc( $value, $esc_fn = 'esc_html' ): string { + + if ( 'no_esc' === $esc_fn ) { + return $value; + } + return call_user_func( $esc_fn, $value ); } @@ -153,5 +158,4 @@ public function render(): void { // phpcs:ignore -- Sanitization is performed within each child class’s `get` method implementation. echo $this->get(); } - } diff --git a/components/Constants/InputType.php b/components/Constants/InputType.php index 573dd77f94..131f2aa1bf 100644 --- a/components/Constants/InputType.php +++ b/components/Constants/InputType.php @@ -36,6 +36,7 @@ abstract class InputType { public const PASSWORD = 'password'; public const NUMBER = 'number'; public const COLOR = 'color'; + public const SEARCH = 'search'; public const DROPDOWN = 'dropdown'; public const LINK = 'link'; public const DATE = 'date'; diff --git a/components/DateFilter.php b/components/DateFilter.php index e8f9f18985..6b9a8c5d44 100644 --- a/components/DateFilter.php +++ b/components/DateFilter.php @@ -71,6 +71,15 @@ class DateFilter extends BaseComponent { */ protected $placement = 'bottom-start'; + /** + * CSS class name used for the icon element. + * + * @since 4.0.0 + * + * @var string + */ + protected $icon_class; + /** * Set filter type. * @@ -107,6 +116,20 @@ public function placement( string $placement ): self { return $this; } + /** + * Set Icon Class. + * + * @since 4.0.0 + * + * @param string $icon_class CSS class name used for the icon element. + * + * @return self + */ + public function icon_class( string $icon_class ): self { + $this->icon_class = $icon_class; + return $this; + } + /** * Render the component. * @@ -128,9 +151,14 @@ public function get(): string { 'type' => 'multiple', 'selectionDatesMode' => 'multiple-ranged', ); + $button_classes .= ' tutor-gap-2'; $popover_classes .= ' tutor-range-calendar-popover'; + if ( ! empty( $this->icon_class ) ) { + $button_classes .= " {$this->icon_class}"; + } + // Default label for range if not set. if ( empty( $this->label ) ) { $this->label = $this->calculate_label(); diff --git a/components/InputField.php b/components/InputField.php index 4a58b0725c..e1aea28165 100644 --- a/components/InputField.php +++ b/components/InputField.php @@ -13,10 +13,10 @@ namespace Tutor\Components; +use TUTOR\Icon; use ReflectionClass; -use Tutor\Components\Constants\InputType; use Tutor\Components\Constants\Size; -use TUTOR\Icon; +use Tutor\Components\Constants\InputType; defined( 'ABSPATH' ) || exit; diff --git a/components/Table.php b/components/Table.php index 5c26847715..66c69453f2 100644 --- a/components/Table.php +++ b/components/Table.php @@ -177,7 +177,8 @@ protected function render_table_headings(): string { foreach ( $this->cell_headers as $heading ) { $headings .= sprintf( - '%1$s', + '%s', + $heading['class'], apply_filters( 'tutor_table_heading', $heading['content'] ) ); } diff --git a/components/Tabs.php b/components/Tabs.php index ff03f77596..43ea1f9c69 100644 --- a/components/Tabs.php +++ b/components/Tabs.php @@ -205,7 +205,9 @@ public function get(): string { :disabled="tab.disabled ? true : false" @click="selectTab(tab.id)" > - + diff --git a/models/OrderModel.php b/models/OrderModel.php index 345793b109..af45809d23 100644 --- a/models/OrderModel.php +++ b/models/OrderModel.php @@ -1093,7 +1093,7 @@ public function get_discounts_by_user( int $user_id, string $period = '', $start $date_range_clause = ''; $period_clause = ''; $course_clause = ''; - $group_clause = ' GROUP BY DATE(date_format) '; + $group_clause = ' GROUP BY MONTH(date_format) '; $discount_clause = 'o.coupon_amount as total'; if ( $start_date && $end_date ) { @@ -1138,7 +1138,8 @@ public function get_discounts_by_user( int $user_id, string $period = '', $start 0 ) ) AS total, - o.created_at_gmt AS date_format + o.created_at_gmt AS date_format, + DATE_FORMAT( o.created_at_gmt, '%b' ) AS month_name FROM {$this->table_name} o JOIN @@ -1180,7 +1181,8 @@ public function get_discounts_by_user( int $user_id, string $period = '', $start 0 ) ) AS total, - o.created_at_gmt AS date_format + o.created_at_gmt AS date_format, + DATE_FORMAT( o.created_at_gmt, '%b' ) AS month_name FROM {$this->table_name} AS o WHERE 1 = %d AND o.order_status = 'completed' @@ -1909,17 +1911,20 @@ private static function should_active_pay_button( $order, $show_pay_button ) { * Retrieves statements for a specific user. * * @since 3.5.0 + * @since 4.0.0 Added $order_by and $order option. * - * @param string $post_type_in_clause SQL clause to filter the course post types. - * @param string $course_query SQL query string to further filter the courses . - * @param string $date_query SQL query string to filter by date range. - * @param int $user_id The user ID for which the statements are being retrieved. - * @param int $offset The offset for pagination. - * @param int $limit The number of rows to return. + * @param string $post_type_in_clause Prepared SQL IN clause containing allowed course post types. + * @param string $course_query Optional SQL fragment to filter by course ID. + * @param string $date_query Optional SQL fragment to filter by statement date. + * @param int $user_id User (instructor) ID. + * @param int $offset Number of records to skip (pagination offset). + * @param int $limit Maximum number of records to return. + * @param string $order_by Column name to order results by. + * @param string $order Sort direction. Accepts 'ASC' or 'DESC'. * * @return array */ - public function get_statements( $post_type_in_clause, $course_query, $date_query, $user_id, $offset, $limit ): array { + public function get_statements( $post_type_in_clause, $course_query, $date_query, $user_id, $offset, $limit, $order_by, $order ): array { global $wpdb; //phpcs:disable @@ -1943,9 +1948,8 @@ public function get_statements( $post_type_in_clause, $course_query, $date_query WHERE statements.user_id = %d {$course_query} {$date_query} - ORDER BY statements.created_at DESC - LIMIT %d, %d - ", + ORDER BY {$order_by} {$order} + LIMIT %d, %d", $user_id, $offset, $limit @@ -1960,8 +1964,7 @@ public function get_statements( $post_type_in_clause, $course_query, $date_query AND course.post_type IN ({$post_type_in_clause}) WHERE statements.user_id = %d {$course_query} - {$date_query} - ", + {$date_query}", $user_id ) ); diff --git a/templates/dashboard/elements/pagination.php b/templates/dashboard/elements/pagination.php index 5dc27bf97d..bfc1270ec7 100644 --- a/templates/dashboard/elements/pagination.php +++ b/templates/dashboard/elements/pagination.php @@ -42,7 +42,7 @@ } if ( ( isset( $data['total_page'] ) && $data['total_page'] ) || ( isset( $data['total_items'] ) && $data['total_items'] ) ) : ?> -