PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / trunk
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses vtrunk
4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 4.2.1 4.2.1.1 All 137 releases
learnpress / inc / Statistics / HealthCheckProvider.php

HealthCheckProvider.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses trunk, at inc/Statistics/HealthCheckProvider.php

179 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class HealthCheckProvider
4 *
5 * @package LearnPress/Classes/Statistics
6 * @since 4.4.2
7 */
8
9 namespace LearnPress\Statistics;
10
11 use LP_Database;
12
13 defined( 'ABSPATH' ) || exit();
14
15 /**
16 * Content health checks for the statistics dashboard.
17 *
18 * Deliberately NOT time-filtered — a course with zero enrollments is a
19 * problem regardless of the selected period.
20 *
21 * @since 4.4.2
22 */
23 class HealthCheckProvider extends LP_Database {
24 /**
25 * @var HealthCheckProvider
26 */
27 private static $_instance;
28
29 protected function __construct() {
30 parent::__construct();
31 }
32
33 /**
34 * @return HealthCheckProvider
35 */
36 public static function getInstance(): HealthCheckProvider {
37 if ( is_null( self::$_instance ) ) {
38 self::$_instance = new self();
39 }
40
41 return self::$_instance;
42 }
43
44 /**
45 * @param StatisticsScope|null $scope
46 * @return array [ 'no_enrollment' => int, 'no_content' => int, 'pending_review' => int, 'quiz_low_pass' => int ]
47 */
48 public function get_checks( ?StatisticsScope $scope = null ): array {
49 return array(
50 'no_enrollment' => $this->count_courses_without_enrollment( $scope ),
51 'no_content' => $this->count_courses_without_content( $scope ),
52 'pending_review' => $this->count_pending_courses( $scope ),
53 'quiz_low_pass' => $this->count_low_pass_quizzes( $scope ),
54 );
55 }
56
57 /**
58 * @param StatisticsScope|null $scope
59 * @return string
60 */
61 private function scope_condition( ?StatisticsScope $scope, string $course_id_field ): string {
62 if ( ! $scope || $scope->is_empty() ) {
63 return '';
64 }
65
66 return $scope->sql_conditions( $course_id_field );
67 }
68
69 /**
70 * Published courses no user ever had a course row for.
71 *
72 * @param StatisticsScope|null $scope
73 * @return int
74 */
75 private function count_courses_without_enrollment( ?StatisticsScope $scope ): int {
76 $where = $this->scope_condition( $scope, 'p.ID' );
77
78 $sql = $this->wpdb->prepare(
79 "SELECT COUNT(*) FROM {$this->tb_posts} AS p
80 WHERE p.post_type = %s AND p.post_status = %s {$where}
81 AND NOT EXISTS (
82 SELECT 1 FROM {$this->tb_lp_user_items} AS ui
83 WHERE ui.item_id = p.ID AND ui.item_type = %s
84 )",
85 LP_COURSE_CPT,
86 'publish',
87 LP_COURSE_CPT
88 );
89
90 return (int) $this->wpdb->get_var( $sql );
91 }
92
93 /**
94 * Published courses with no section items (empty curriculum).
95 *
96 * @param StatisticsScope|null $scope
97 * @return int
98 */
99 private function count_courses_without_content( ?StatisticsScope $scope ): int {
100 $where = $this->scope_condition( $scope, 'p.ID' );
101
102 $sql = $this->wpdb->prepare(
103 "SELECT COUNT(*) FROM {$this->tb_posts} AS p
104 WHERE p.post_type = %s AND p.post_status = %s {$where}
105 AND NOT EXISTS (
106 SELECT 1 FROM {$this->tb_lp_sections} AS s
107 INNER JOIN {$this->tb_lp_section_items} AS si ON si.section_id = s.section_id
108 WHERE s.section_course_id = p.ID
109 )",
110 LP_COURSE_CPT,
111 'publish'
112 );
113
114 return (int) $this->wpdb->get_var( $sql );
115 }
116
117 /**
118 * Courses waiting for review.
119 *
120 * @param StatisticsScope|null $scope
121 * @return int
122 */
123 private function count_pending_courses( ?StatisticsScope $scope ): int {
124 $where = $this->scope_condition( $scope, 'p.ID' );
125
126 $sql = $this->wpdb->prepare(
127 "SELECT COUNT(*) FROM {$this->tb_posts} AS p
128 WHERE p.post_type = %s AND p.post_status = %s {$where}",
129 LP_COURSE_CPT,
130 'pending'
131 );
132
133 return (int) $this->wpdb->get_var( $sql );
134 }
135
136 /**
137 * Quizzes with a pass rate below the alert threshold, minimum attempts
138 * required so one failed try does not flag a quiz.
139 *
140 * Scope reaches the course through the section lineage
141 * ( quiz → section_items → sections → course ).
142 *
143 * @param StatisticsScope|null $scope
144 * @return int
145 */
146 private function count_low_pass_quizzes( ?StatisticsScope $scope ): int {
147 $threshold = (float) apply_filters( 'learn-press/statistics/quiz-pass-alert', 50 );
148 $min_attempts = (int) apply_filters( 'learn-press/statistics/quiz-pass-alert-min-attempts', 5 );
149
150 $where = '';
151 if ( $scope && ! $scope->is_empty() ) {
152 $course_conditions = $scope->sql_conditions( 's.section_course_id' );
153 $where = " AND EXISTS (
154 SELECT 1 FROM {$this->tb_lp_section_items} AS si
155 INNER JOIN {$this->tb_lp_sections} AS s ON s.section_id = si.section_id
156 WHERE si.item_id = ui.item_id {$course_conditions}
157 )";
158 }
159
160 $sql = $this->wpdb->prepare(
161 "SELECT COUNT(*) FROM (
162 SELECT ui.item_id
163 FROM {$this->tb_lp_user_items} AS ui
164 WHERE ui.item_type = %s AND ui.graduation IN ( %s, %s ) {$where}
165 GROUP BY ui.item_id
166 HAVING COUNT(*) >= %d AND ( SUM( ui.graduation = %s ) / COUNT(*) ) * 100 < %f
167 ) AS low_quizzes",
168 LP_QUIZ_CPT,
169 'passed',
170 'failed',
171 $min_attempts,
172 'passed',
173 $threshold
174 );
175
176 return (int) $this->wpdb->get_var( $sql );
177 }
178 }
179