PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.3.7
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.3.7
4.4.7 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 All 138 releases
learnpress / inc / CourseBuilder / CourseBuilderAccessPolicy.php

CourseBuilderAccessPolicy.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.3.7, at inc/CourseBuilder/CourseBuilderAccessPolicy.php

306 lines 6.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace LearnPress\CourseBuilder;
4
5 use LearnPress\Models\CourseModel;
6
7 /**
8 * Centralized access policy for Course Builder object-level permissions.
9 *
10 * @since 4.3.0
11 */
12 class CourseBuilderAccessPolicy {
13 /**
14 * Check if current user can edit a specific course.
15 *
16 * @param int $course_id
17 *
18 * @return bool
19 */
20 public static function can_edit_course_by_id( int $course_id ): bool {
21 if ( $course_id <= 0 ) {
22 return false;
23 }
24
25 $course_model = CourseModel::find( $course_id, true );
26 if ( ! $course_model ) {
27 return false;
28 }
29
30 return self::can_edit_course( $course_model );
31 }
32
33 /**
34 * Check if current user can access the tab/post pair in Course Builder.
35 *
36 * @param string $tab
37 * @param int|string $post_id
38 *
39 * @return bool
40 */
41 public static function can_access_tab_post( string $tab, $post_id ): bool {
42 if ( ! is_user_logged_in() ) {
43 return false;
44 }
45
46 $item_type = self::get_item_type_by_tab( $tab );
47 if ( empty( $item_type ) ) {
48 return false;
49 }
50
51 if ( (string) $post_id === CourseBuilder::POST_NEW ) {
52 return self::can_create_item_type( $item_type );
53 }
54
55 $item_id = absint( $post_id );
56 if ( $item_id <= 0 ) {
57 return false;
58 }
59
60 if ( $item_type === LP_COURSE_CPT ) {
61 return self::can_edit_course_by_id( $item_id );
62 }
63
64 return self::can_edit_item( $item_type, $item_id );
65 }
66
67 /**
68 * Check if current user can create a specific item type in Course Builder.
69 *
70 * @param string $item_type
71 *
72 * @return bool
73 */
74 public static function can_create_item_type( string $item_type ): bool {
75 if ( ! is_user_logged_in() ) {
76 return false;
77 }
78
79 $capability = self::resolve_create_capability_by_item_type( $item_type );
80 if ( empty( $capability ) ) {
81 return false;
82 }
83
84 return current_user_can( $capability );
85 }
86
87 /**
88 * Check if current user can edit a specific lesson/quiz/question item.
89 *
90 * @param string $item_type
91 * @param int $item_id
92 *
93 * @return bool
94 */
95 public static function can_edit_item( string $item_type, int $item_id ): bool {
96 if ( ! is_user_logged_in() || $item_id <= 0 ) {
97 return false;
98 }
99
100 if ( self::is_admin_user() ) {
101 return true;
102 }
103
104 $post = get_post( $item_id );
105 if ( ! $post || $post->post_type !== $item_type ) {
106 return false;
107 }
108
109 $current_user_id = get_current_user_id();
110 if ( absint( $post->post_author ) === $current_user_id ) {
111 return true;
112 }
113
114 $course_id = self::get_course_id_by_item( $item_type, $item_id );
115 if ( $course_id <= 0 ) {
116 return false;
117 }
118
119 return self::can_edit_course_by_id( $course_id );
120 }
121
122 /**
123 * Check if current user can edit a course model by owner/co-instructor/admin.
124 *
125 * @param CourseModel $course_model
126 *
127 * @return bool
128 */
129 private static function can_edit_course( CourseModel $course_model ): bool {
130 if ( ! is_user_logged_in() ) {
131 return false;
132 }
133
134 if ( self::is_admin_user() ) {
135 return true;
136 }
137
138 $current_user_id = get_current_user_id();
139 if ( absint( $course_model->post_author ) === $current_user_id ) {
140 return true;
141 }
142
143 $co_instructor_ids = $course_model->get_meta_value_by_key( '_lp_co_teacher', [] );
144 if ( empty( $co_instructor_ids ) ) {
145 $co_instructor_ids = get_post_meta( $course_model->ID, '_lp_co_teacher', false );
146 }
147
148 $co_instructor_ids = self::normalize_user_ids( $co_instructor_ids );
149
150 return in_array( $current_user_id, $co_instructor_ids, true );
151 }
152
153 /**
154 * Find related course ID for item.
155 *
156 * @param string $item_type
157 * @param int $item_id
158 *
159 * @return int
160 */
161 private static function get_course_id_by_item( string $item_type, int $item_id ): int {
162 static $cache = [];
163
164 $cache_key = $item_type . ':' . $item_id;
165 if ( isset( $cache[ $cache_key ] ) ) {
166 return $cache[ $cache_key ];
167 }
168
169 global $wpdb;
170
171 $tb_sections = $wpdb->learnpress_sections ?? ( $wpdb->prefix . 'learnpress_sections' );
172 $tb_section_items = $wpdb->learnpress_section_items ?? ( $wpdb->prefix . 'learnpress_section_items' );
173
174 $course_id = (int) $wpdb->get_var(
175 $wpdb->prepare(
176 "SELECT s.section_course_id
177 FROM {$tb_sections} s
178 INNER JOIN {$tb_section_items} si ON si.section_id = s.section_id
179 WHERE si.item_id = %d
180 ORDER BY si.section_id DESC
181 LIMIT 1",
182 $item_id
183 )
184 );
185
186 // Questions belong to quizzes; resolve via quiz->course relation when needed.
187 if ( $course_id <= 0 && $item_type === LP_QUESTION_CPT ) {
188 $tb_quiz_questions = $wpdb->learnpress_quiz_questions ?? ( $wpdb->prefix . 'learnpress_quiz_questions' );
189
190 $course_id = (int) $wpdb->get_var(
191 $wpdb->prepare(
192 "SELECT s.section_course_id
193 FROM {$tb_quiz_questions} qq
194 INNER JOIN {$tb_section_items} si ON si.item_id = qq.quiz_id
195 INNER JOIN {$tb_sections} s ON s.section_id = si.section_id
196 WHERE qq.question_id = %d
197 ORDER BY si.section_id DESC
198 LIMIT 1",
199 $item_id
200 )
201 );
202 }
203
204 $cache[ $cache_key ] = absint( $course_id );
205
206 return $cache[ $cache_key ];
207 }
208
209 /**
210 * Map Course Builder tab to post type.
211 *
212 * @param string $tab
213 *
214 * @return string
215 */
216 private static function get_item_type_by_tab( string $tab ): string {
217 switch ( $tab ) {
218 case 'courses':
219 return LP_COURSE_CPT;
220 case 'lessons':
221 return LP_LESSON_CPT;
222 case 'quizzes':
223 return LP_QUIZ_CPT;
224 case 'questions':
225 return LP_QUESTION_CPT;
226 default:
227 return '';
228 }
229 }
230
231 /**
232 * Resolve create capability from registered post type capabilities.
233 * Falls back to legacy mapping when post type object is unavailable.
234 *
235 * @param string $item_type
236 *
237 * @return string
238 */
239 private static function resolve_create_capability_by_item_type( string $item_type ): string {
240 $post_type_object = get_post_type_object( $item_type );
241 if ( $post_type_object && isset( $post_type_object->cap ) ) {
242 $cap = $post_type_object->cap;
243 if ( ! empty( $cap->create_posts ) ) {
244 return $cap->create_posts;
245 }
246
247 if ( ! empty( $cap->edit_posts ) ) {
248 return $cap->edit_posts;
249 }
250 }
251
252 switch ( $item_type ) {
253 case LP_COURSE_CPT:
254 return 'edit_lp_courses';
255 case LP_LESSON_CPT:
256 return 'edit_lp_lessons';
257 case LP_QUIZ_CPT:
258 return 'edit_lp_quizzes';
259 case LP_QUESTION_CPT:
260 return 'edit_lp_questions';
261 default:
262 return '';
263 }
264 }
265
266 /**
267 * Check if current user is admin.
268 *
269 * @return bool
270 */
271 private static function is_admin_user(): bool {
272 if ( defined( 'ADMIN_ROLE' ) ) {
273 return current_user_can( ADMIN_ROLE );
274 }
275
276 return current_user_can( 'manage_options' );
277 }
278
279 /**
280 * Normalize values to unique integer user IDs.
281 *
282 * @param mixed $ids
283 *
284 * @return array
285 */
286 private static function normalize_user_ids( $ids ): array {
287 if ( empty( $ids ) ) {
288 return [];
289 }
290
291 if ( is_object( $ids ) ) {
292 $ids = (array) $ids;
293 }
294
295 if ( ! is_array( $ids ) ) {
296 $ids = [ $ids ];
297 }
298
299 $ids = array_map( 'absint', $ids );
300 $ids = array_filter( $ids );
301 $ids = array_values( array_unique( $ids ) );
302
303 return $ids;
304 }
305 }
306