Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/tutor-pro/classes/LessonNotes.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + <?php
2 + /**
3 + * Handle Lesson Notes
4 + *
5 + * @package TutorPro\Classes
6 + * @author Themeum <support@themeum.com>
7 + * @link https://themeum.com
8 + * @since 3.9.0
9 + */
10 +
11 + namespace TUTOR_PRO;
12 +
13 + use Tutor\Helpers\HttpHelper;
14 + use TUTOR\Icon;
15 + use TUTOR\Input;
16 + use Tutor\Traits\JsonResponse;
17 +
18 + if ( ! defined( 'ABSPATH' ) ) {
19 + exit;
20 + }
21 +
22 + /**
23 + * Class Lesson Notes
24 + */
25 + class LessonNotes {
26 + use JsonResponse;
27 +
28 + /**
29 + * Lesson Notes Meta Key
30 + *
31 + * @since 3.9.0
32 + */
33 + const COMMENT_TYPE = 'lesson_note';
34 +
35 + const TYPE_REGULAR = 'regular';
36 + const TYPE_HIGHLIGHT = 'highlight';
37 + const TYPE_VIDEO = 'video';
38 +
39 + const NOTE_META_KEY = '_tutor_note_info';
40 +
41 + /**
42 + * Register hooks.
43 + */
44 + public function __construct() {
45 + /**
46 + * Lesson Notes feature
47 + *
48 + * @since 3.3.0
49 + */
50 + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_note_scripts' ) );
51 + add_filter( 'tutor_lesson_single_nav_items', array( $this, 'filter_lesson_single_nav_item' ) );
52 + add_filter( 'tutor_lesson_single_nav_contents', array( $this, 'filter_lesson_notes_tab_content' ) );
53 + add_action( 'tutor_lesson_single_after_nav_items', array( $this, 'load_lesson_notes_nav_button' ), 10, 2 );
54 + add_action( 'wp_ajax_tutor_pro_save_lesson_note', array( $this, 'ajax_save_lesson_note' ) );
55 + add_action( 'wp_ajax_tutor_pro_update_lesson_note', array( $this, 'ajax_update_lesson_note' ) );
56 + add_action( 'wp_ajax_tutor_pro_delete_lesson_note', array( $this, 'ajax_delete_lesson_note' ) );
57 + add_action( 'wp_ajax_tutor_pro_get_lesson_notes_html', array( $this, 'ajax_get_lesson_notes_html' ) );
58 + add_action( 'wp_ajax_tutor_pro_get_single_lesson_note_html', array( $this, 'ajax_get_single_lesson_note_html' ) );
59 + add_action( 'wp_ajax_tutor_pro_lesson_notes_load_more', array( $this, 'ajax_lesson_notes_load_more' ) );
60 + }
61 +
62 + /**
63 + * Enqueue lesson notes js and css
64 + *
65 + * @since 3.9.0
66 + */
67 + public function enqueue_note_scripts() {
68 + if ( is_single() && tutor()->lesson_post_type === get_post_type() ) {
69 + wp_enqueue_script( 'tutor-pro-lesson-notes', tutor_pro()->url . 'assets/js/lesson-notes.js', array( 'jquery', 'wp-i18n' ), TUTOR_PRO_VERSION, true );
70 +
71 + $lesson_notes = $this->get_lesson_notes( get_the_ID(), get_current_user_id(), 0, 1000 );
72 +
73 + $lesson_notes = $this->lesson_decode_unicode_sequences( $lesson_notes );
74 +
75 + wp_localize_script( 'tutor-pro-lesson-notes', 'lesson_notes', $lesson_notes );
76 +
77 + wp_enqueue_style( 'tutor-pro-lesson-notes', tutor_pro()->url . 'assets/css/lesson-notes.css', array(), TUTOR_PRO_VERSION );
78 + }
79 + }
80 +
81 + /**
82 + * Lesson decode unicode sequences.
83 + *
84 + * @param array $lesson_notes Lesson notes to decode.
85 + *
86 + * @return array Decoded lesson notes.
87 + */
88 + public function lesson_decode_unicode_sequences( $lesson_notes ) {
89 + foreach ( $lesson_notes as $note ) {
90 + $note->highlight_text = tutor_decode_unicode_sequences( $note->highlight_text );
91 + if ( 'highlight' === $note->type && ! empty( $note->highlight_serialized['text'] ) ) {
92 + $note->highlight_serialized['text'] = tutor_decode_unicode_sequences( $note->highlight_serialized['text'] );
93 + }
94 + }
95 + return $lesson_notes;
96 + }
97 +
98 + /**
99 + * Add Lesson Notes Nav Item
100 + *
101 + * @since 3.9.0
102 + *
103 + * @param array $nav_items Nav Items.
104 + *
105 + * @return array
106 + */
107 + public function filter_lesson_single_nav_item( $nav_items ) {
108 + if ( ! self::is_notes_tab_available() ) {
109 + return $nav_items;
110 + }
111 +
112 + $notes_tab = array(
113 + 'label' => esc_html__( 'Notes', 'tutor-pro' ),
114 + 'value' => 'notes',
115 + 'icon' => Icon::CALENDAR_LINES,
116 + 'icon_type' => 'svg',
117 + );
118 +
119 + $new_nav_items = array();
120 + $inserted = false;
121 +
122 + foreach ( $nav_items as $key => $item ) {
123 + // Insert before comments tab if it exists.
124 + if ( isset( $item['value'] ) && 'comments' === $item['value'] && ! $inserted ) {
125 + $new_nav_items['notes'] = $notes_tab;
126 + $inserted = true;
127 + }
128 +
129 + $new_nav_items[ $key ] = $item;
130 + }
131 +
132 + // If 'comments' wasn't found, append at the end.
133 + if ( ! $inserted ) {
134 + $new_nav_items['notes'] = $notes_tab;
135 + }
136 +
137 + return $new_nav_items;
138 + }
139 +
140 + /**
141 + * Add Lesson Notes Tab Content
142 + *
143 + * @since 3.9.0
144 + *
145 + * @param array $nav_contents Nav Contents.
146 + *
147 + * @return array
148 + */
149 + public function filter_lesson_notes_tab_content( $nav_contents ) {
150 + if ( ! self::is_notes_tab_available() ) {
151 + return $nav_contents;
152 + }
153 +
154 + $nav_contents['notes'] = array(
155 + 'label' => esc_html__( 'Notes', 'tutor-pro' ),
156 + 'value' => 'notes',
157 + 'template_path' => 'lesson-notes.tab-content',
158 + 'is_pro' => true,
159 + );
160 +
161 + return $nav_contents;
162 + }
163 +
164 + /**
165 + * Load Lesson Notes Nav Button
166 + *
167 + * @since 3.9.0
168 + *
169 + * @param int $lesson_id Lesson ID.
170 + * @param string $active_tab Active Page Tab.
171 + */
172 + public function load_lesson_notes_nav_button( $lesson_id, $active_tab ) {
173 + if ( ! self::is_notes_tab_available() ) {
174 + return;
175 + }
176 +
177 + ?>
178 + <button id="tutor-lesson-nav-take-note-btn" class="tutor-btn tutor-btn-sm <?php echo esc_attr( ( 'notes' === $active_tab ) ? 'tutor-d-none' : '' ); ?>">
179 + <?php tutor_utils()->render_svg_icon( Icon::FEATHER, 20, 20 ); ?>
180 + <?php esc_html_e( 'Take Note', 'tutor-pro' ); ?>
181 + </button>
182 + <?php
183 + }
184 +
185 + /**
186 + * Save Lesson Note
187 + *
188 + * @since 3.9.0
189 + */
190 + public function ajax_save_lesson_note() {
191 + tutor_utils()->check_nonce();
192 +
193 + $current_user = wp_get_current_user();
194 + $lesson_id = Input::post( 'lesson_id', 0, Input::TYPE_INT );
195 + $note_text = Input::post( 'note_text', '', Input::TYPE_TEXTAREA );
196 + $highlight_text = Input::post( 'highlight_text', '', Input::TYPE_KSES_POST );
197 + $highlight_serialized = Input::post( 'highlight_serialized', Input::TYPE_KSES_POST );
198 + $video_start = Input::post( 'video_start_time', '' );
199 + $video_end = Input::post( 'video_end_time', '' );
200 +
201 + if ( empty( $lesson_id ) || empty( $note_text ) ) {
202 + $this->response_bad_request( __( 'Invalid lesson or note text', 'tutor-pro' ) );
203 + }
204 +
205 + $lesson = get_post( $lesson_id );
206 + if ( ! $lesson || tutor()->lesson_post_type !== $lesson->post_type ) {
207 + $this->response_bad_request( __( 'Lesson not found', 'tutor-pro' ) );
208 + }
209 +
210 + if ( ! tutor_utils()->has_enrolled_content_access( 'lesson', $lesson_id ) ) {
211 + $this->json_response( __( 'You do not have access to this lesson', 'tutor-pro' ), null, HttpHelper::STATUS_FORBIDDEN );
212 + }
213 +
214 + $comment_data = array(
215 + 'comment_post_ID' => $lesson_id,
216 + 'comment_content' => $note_text,
217 + 'comment_type' => self::COMMENT_TYPE,
218 + 'comment_agent' => 'TutorLMSPlugin',
219 + 'comment_approved' => 1,
220 + 'user_id' => $current_user->ID,
221 + 'comment_author' => $current_user->user_login,
222 + 'comment_author_email' => $current_user->user_email,
223 + 'comment_author_url' => $current_user->user_url,
224 + );
225 +
226 + $comment_id = wp_insert_comment( $comment_data );
227 + if ( is_wp_error( $comment_id ) ) {
228 + $this->json_response( __( 'Failed to save note', 'tutor-pro' ), null, HttpHelper::STATUS_INTERNAL_SERVER_ERROR );
229 + }
230 +
231 + $note_type = self::TYPE_REGULAR;
232 + if ( $highlight_serialized ) {
233 + $note_type = self::TYPE_HIGHLIGHT;
234 + }
235 + if ( self::has_video_time( $video_start ) ) {
236 + $note_type = self::TYPE_VIDEO;
237 + }
238 +
239 + // Save meta data.
240 + $note_meta = array();
241 +
242 + if ( self::TYPE_HIGHLIGHT === $note_type ) {
243 + $note_meta = array(
244 + 'type' => $note_type,
245 + 'text' => $highlight_text,
246 + 'serialized' => json_decode( $highlight_serialized ),
247 + );
248 + }
249 +
250 + if ( self::TYPE_VIDEO === $note_type ) {
251 + $note_meta = array(
252 + 'type' => $note_type,
253 + 'video_start' => $video_start,
254 + 'video_end' => $video_end,
255 + );
256 + }
257 +
258 + if ( ! empty( $note_meta ) ) {
259 + update_comment_meta( $comment_id, self::NOTE_META_KEY, wp_json_encode( $note_meta ) );
260 + }
261 +
262 + $data = array(
263 + 'comment_ID' => $comment_id,
264 + 'comment_post_ID' => $lesson_id,
265 + 'comment_content' => $note_text,
266 + 'highlight_text' => $highlight_text,
267 + 'highlight_serialized' => $highlight_serialized,
268 + 'video_start_time' => $video_start,
269 + 'video_end_time' => $video_end,
270 + );
271 +
272 + $this->json_response( __( 'Note saved successfully', 'tutor-pro' ), $data );
273 + }
274 +
275 + /**
276 + * Update Lesson Note
277 + *
278 + * @since 3.9.0
279 + */
280 + public function ajax_update_lesson_note() {
281 + tutor_utils()->check_nonce();
282 +
283 + $note_id = Input::post( 'note_id', 0, Input::TYPE_INT );
284 + $note_text = Input::post( 'note_text', '', Input::TYPE_TEXTAREA );
285 +
286 + if ( empty( $note_id ) || empty( $note_text ) ) {
287 + $this->response_bad_request( __( 'Invalid comment or note text', 'tutor-pro' ) );
288 + }
289 +
290 + $comment = get_comment( $note_id );
291 + if ( ! $comment || get_current_user_id() !== (int) $comment->user_id ) {
292 + $this->json_response( __( 'You are not authorized to update this note', 'tutor-pro' ), null, HttpHelper::STATUS_FORBIDDEN );
293 + }
294 +
295 + $updated = wp_update_comment(
296 + array(
297 + 'comment_ID' => $note_id,
298 + 'comment_content' => $note_text,
299 + )
300 + );
301 +
302 + if ( is_wp_error( $updated ) ) {
303 + $this->json_response( __( 'Failed to update note', 'tutor-pro' ), null, HttpHelper::STATUS_INTERNAL_SERVER_ERROR );
304 + }
305 +
306 + $this->json_response( __( 'Note updated successfully', 'tutor-pro' ), array( 'note_id' => $note_id ) );
307 + }
308 +
309 + /**
310 + * Delete Lesson Note
311 + *
312 + * @since 3.9.0
313 + */
314 + public function ajax_delete_lesson_note() {
315 + tutor_utils()->check_nonce();
316 +
317 + $note_id = Input::post( 'note_id', 0, Input::TYPE_INT );
318 +
319 + if ( empty( $note_id ) ) {
320 + $this->response_bad_request( __( 'Invalid comment', 'tutor-pro' ) );
321 + }
322 +
323 + $comment = get_comment( $note_id );
324 + if ( ! $comment || self::COMMENT_TYPE !== $comment->comment_type ) {
325 + $this->json_response( __( 'Note not found', 'tutor-pro' ), null, HttpHelper::STATUS_NOT_FOUND );
326 + }
327 +
328 + if ( get_current_user_id() !== (int) $comment->user_id ) {
329 + $this->json_response( __( 'You are not authorized to delete this note', 'tutor-pro' ), null, HttpHelper::STATUS_FORBIDDEN );
330 + }
331 +
332 + $deleted = wp_delete_comment( $note_id, true );
333 +
334 + if ( ! $deleted ) {
335 + $this->json_response( __( 'Failed to delete note', 'tutor-pro' ), null, HttpHelper::STATUS_INTERNAL_SERVER_ERROR );
336 + }
337 +
338 + $this->json_response( __( 'Note deleted successfully', 'tutor-pro' ), array( 'note_id' => $note_id ) );
339 + }
340 +
341 + /**
342 + * Get Lesson Notes HTML
343 + *
344 + * @since 3.9.0
345 + */
346 + public function ajax_get_lesson_notes_html() {
347 + tutor_utils()->check_nonce();
348 +
349 + $lesson_id = Input::post( 'lesson_id', 0, Input::TYPE_INT );
350 +
351 + if ( empty( $lesson_id ) ) {
352 + $this->response_bad_request( __( 'Invalid lesson', 'tutor-pro' ) );
353 + }
354 +
355 + if ( ! tutor_utils()->has_enrolled_content_access( 'lesson', $lesson_id ) ) {
356 + $this->json_response( __( 'You do not have access to this lesson', 'tutor-pro' ), null, HttpHelper::STATUS_FORBIDDEN );
357 + }
358 +
359 + ob_start();
360 + tutor_load_template(
361 + 'lesson-notes/note-list',
362 + array(
363 + 'lesson_id' => $lesson_id,
364 + ),
365 + true
366 + );
367 + $html = ob_get_clean();
368 +
369 + $this->json_response( __( 'Notes fetched successfully', 'tutor-pro' ), array( 'html' => $html ) );
370 + }
371 +
372 + /**
373 + * Get Single Lesson Note HTML
374 + *
375 + * @since 3.9.0
376 + */
377 + public function ajax_get_single_lesson_note_html() {
378 + tutor_utils()->check_nonce();
379 +
380 + $lesson_id = Input::post( 'lesson_id', 0, Input::TYPE_INT );
381 + $note_id = Input::post( 'note_id', 0, Input::TYPE_INT );
382 + if ( empty( $lesson_id ) || empty( $note_id ) ) {
383 + $this->response_bad_request( __( 'Invalid lesson or comment', 'tutor-pro' ) );
384 + }
385 +
386 + if ( ! tutor_utils()->has_enrolled_content_access( 'lesson', $lesson_id ) ) {
387 + $this->json_response( __( 'You do not have access to this lesson', 'tutor-pro' ), null, HttpHelper::STATUS_FORBIDDEN );
388 + }
389 +
390 + $note = $this->get_single_lesson_note( $note_id );
391 + if ( ! $note ) {
392 + $this->json_response( __( 'Note not found', 'tutor-pro' ), null, HttpHelper::STATUS_NOT_FOUND );
393 + }
394 +
395 + ob_start();
396 + tutor_load_template(
397 + 'lesson-notes/note-item',
398 + array(
399 + 'lesson_id' => $lesson_id,
400 + 'note' => $note,
401 + ),
402 + true
403 + );
404 + $html = ob_get_clean();
405 + $this->json_response( __( 'Note fetched successfully', 'tutor-pro' ), array( 'html' => $html ) );
406 + }
407 +
408 + /**
409 + * Lesson Notes Load More
410 + *
411 + * @since 3.9.0
412 + */
413 + public function ajax_lesson_notes_load_more() {
414 + tutor_utils()->check_nonce();
415 +
416 + $lesson_id = Input::post( 'lesson_id', 0, Input::TYPE_INT );
417 + $offset = Input::post( 'offset', 0, Input::TYPE_INT );
418 +
419 + if ( empty( $lesson_id ) ) {
420 + $this->response_bad_request( __( 'Invalid lesson', 'tutor-pro' ) );
421 + }
422 +
423 + if ( ! tutor_utils()->has_enrolled_content_access( 'lesson', $lesson_id ) ) {
424 + $this->json_response( __( 'You do not have access to this lesson', 'tutor-pro' ), null, HttpHelper::STATUS_FORBIDDEN );
425 + }
426 +
427 + $items_per_page = tutor_utils()->get_option( 'pagination_per_page' );
428 +
429 + $note_list = $this->get_lesson_notes( $lesson_id, get_current_user_id(), $offset, $items_per_page );
430 + if ( empty( $note_list ) ) {
431 + $this->json_response( __( 'No more notes found', 'tutor-pro' ), null, HttpHelper::STATUS_NOT_FOUND );
432 + }
433 +
434 + ob_start();
435 + foreach ( $note_list as $note ) {
436 + tutor_load_template(
437 + 'lesson-notes/note-item',
438 + array(
439 + 'lesson_id' => $lesson_id,
440 + 'note' => $note,
441 + ),
442 + true
443 + );
444 + }
445 + $html = ob_get_clean();
446 + $this->json_response(
447 + __( 'Notes fetched successfully', 'tutor-pro' ),
448 + array(
449 + 'html' => $html,
450 + 'notes_count' => count( $note_list ),
451 + )
452 + );
453 + }
454 +
455 + /**
456 + * Get Lesson Notes
457 + *
458 + * @since 3.9.0
459 + *
460 + * @param int $lesson_id Lesson ID.
461 + * @param int $user_id User ID.
462 + * @param int $offset Offset.
463 + * @param int $item_per_page Items Per Page.
464 + */
465 + public function get_lesson_notes( $lesson_id, $user_id, $offset = 0, $item_per_page = 20 ) {
466 + if ( ! $lesson_id || ! $user_id ) {
467 + return array();
468 + }
469 +
470 + $paged = $offset > 0 ? (int) floor( $offset / $item_per_page ) + 1 : 1;
471 +
472 + $args = array(
473 + 'post_id' => $lesson_id,
474 + 'user_id' => $user_id,
475 + 'type' => self::COMMENT_TYPE,
476 + 'status' => 'approve',
477 + 'number' => $item_per_page,
478 + 'offset' => $offset,
479 + 'paged' => $paged,
480 + );
481 +
482 + $comments = get_comments( $args );
483 +
484 + return array_map( array( $this, 'add_note_meta_to_comment' ), $comments );
485 + }
486 +
487 + /**
488 + * Get Single Lesson Note
489 + *
490 + * @since 3.9.0
491 + *
492 + * @param int $note_id Note ID.
493 + *
494 + * @return object|null Comment object or null if not found.
495 + */
496 + public function get_single_lesson_note( $note_id ) {
497 + $comment = get_comment( $note_id );
498 + if ( ! $comment || self::COMMENT_TYPE !== $comment->comment_type ) {
499 + return null;
500 + }
501 + return $this->add_note_meta_to_comment( $comment );
502 + }
503 +
504 + /**
505 + * Add note meta to comment
506 + *
507 + * @since 3.9.0
508 + *
509 + * @param object $comment Note comment.
510 + *
511 + * @return object The note.
512 + */
513 + private function add_note_meta_to_comment( $comment ) {
514 + $highlight_data_json = get_comment_meta( $comment->comment_ID, self::NOTE_META_KEY, true );
515 +
516 + if ( ! empty( $highlight_data_json ) ) {
517 + $highlight_data = json_decode( $highlight_data_json, true );
518 +
519 + if ( is_array( $highlight_data ) ) {
520 + $comment->type = $highlight_data['type'] ?? '';
521 + $comment->highlight_text = $highlight_data['text'] ?? '';
522 + $comment->highlight_serialized = $highlight_data['serialized'] ?? '';
523 + $comment->video_start_time = $highlight_data['video_start'] ?? '';
524 + $comment->video_end_time = $highlight_data['video_end'] ?? '';
525 + }
526 + }
527 +
528 + return $comment;
529 + }
530 +
531 + /**
532 + * Get Lesson Notes Count
533 + *
534 + * @since 3.9.0
535 + *
536 + * @param int $lesson_id Lesson ID.
537 + * @param int $user_id User ID.
538 + *
539 + * @return int Number of notes taken for a lesson by a user.
540 + */
541 + public function get_lesson_notes_count( $lesson_id, $user_id ) {
542 + $args = array(
543 + 'post_id' => $lesson_id,
544 + 'user_id' => $user_id,
545 + 'type' => self::COMMENT_TYPE,
546 + 'status' => 'approve',
547 + 'count' => true,
548 + );
549 +
550 + return get_comments( $args );
551 + }
552 +
553 + /**
554 + * Check if video time is valid
555 + *
556 + * @since 3.9.0
557 + *
558 + * @param string|null $time Video time.
559 + *
560 + * @return bool True if time is valid, false otherwise.
561 + */
562 + public static function has_video_time( $time ) {
563 + return null !== $time && '' !== $time;
564 + }
565 +
566 + /**
567 + * Check if notes tab is available for current user
568 + *
569 + * @since 3.9.0
570 + *
571 + * @return bool True if notes tab is available, false otherwise.
572 + */
573 + public static function is_notes_tab_available() {
574 + $is_user_logged_in = is_user_logged_in();
575 +
576 + if ( ! $is_user_logged_in ) {
577 + return false;
578 + }
579 +
580 + $user_id = get_current_user_id();
581 + $lesson_id = get_the_ID();
582 + $course_id = tutor_utils()->get_course_id_by_lesson( $lesson_id );
583 + $is_public_course = 'yes' === get_post_meta( $course_id, '_tutor_is_public_course', true );
584 + $has_enrolled_content_access = tutor_utils()->has_enrolled_content_access( 'lesson', $lesson_id, $user_id );
585 +
586 + return $is_public_course || $has_enrolled_content_access;
587 + }
588 + }
589 +