PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 4.0.0
Tutor LMS – eLearning and online course solution v4.0.0
4.0.0 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / classes / User.php
tutor / classes Last commit date
Addons.php 1 day ago Admin.php 1 day ago Ajax.php 1 day ago Announcements.php 1 day ago Assets.php 1 day ago Backend_Page_Trait.php 1 year ago BaseController.php 1 year ago Config.php 1 day ago Container.php 11 months ago Course.php 1 day ago Course_Embed.php 3 years ago Course_Filter.php 1 day ago Course_List.php 1 day ago Course_Settings_Tabs.php 1 day ago Course_Widget.php 1 year ago Custom_Validation.php 1 day ago Dashboard.php 1 day ago Earnings.php 9 months ago FormHandler.php 1 day ago Frontend.php 1 day ago Gutenberg.php 1 year ago Icon.php 1 day ago Input.php 1 day ago Instructor.php 1 day ago Instructors_List.php 1 day ago Lesson.php 1 day ago Options_V2.php 1 day ago Permalink.php 1 day ago Post_types.php 2 days ago Private_Course_Access.php 1 day ago Q_And_A.php 1 day ago Question_Answers_List.php 11 months ago Quiz.php 1 day ago QuizBuilder.php 1 day ago Quiz_Attempts_List.php 1 day ago RestAPI.php 2 years ago Reviews.php 1 day ago Rewrite_Rules.php 2 years ago SampleCourse.php 1 day ago Shortcode.php 1 day ago Singleton.php 1 year ago Student.php 1 day ago Students_List.php 1 year ago Taxonomies.php 1 year ago Template.php 1 day ago Theme_Compatibility.php 3 years ago Tools.php 1 year ago Tools_V2.php 4 weeks ago Tutor.php 1 day ago TutorEDD.php 1 day ago Tutor_Base.php 2 years ago Tutor_Setup.php 1 day ago Upgrader.php 1 day ago User.php 1 day ago UserPreference.php 1 day ago Utils.php 1 day ago Video_Stream.php 3 years ago WhatsNew.php 10 months ago Withdraw.php 1 day ago Withdraw_Requests_List.php 11 months ago WooCommerce.php 1 day ago
User.php
896 lines
1 <?php
2 /**
3 * Manage user
4 *
5 * @package Tutor\User
6 * @author Themeum <support@themeum.com>
7 * @link https://themeum.com
8 * @since 1.0.0
9 */
10
11 namespace TUTOR;
12
13 defined( 'ABSPATH' ) || exit;
14
15 use Tutor\Helpers\HttpHelper;
16 use Tutor\Models\UserModel;
17 use Tutor\Traits\JsonResponse;
18 use TUTOR\InstructorList;
19
20 /**
21 * User class
22 *
23 * @since 1.0.0
24 */
25 class User {
26 use JsonResponse;
27
28 const STUDENT = 'subscriber';
29 const INSTRUCTOR = 'tutor_instructor';
30 const ADMIN = 'administrator';
31
32 /**
33 * User meta keys.
34 */
35 const REVIEW_POPUP_META = 'tutor_review_course_popup';
36 const LAST_LOGIN_META = 'tutor_last_login';
37 const TIMEZONE_META = '_tutor_timezone';
38 const PROFILE_PHOTO_META = '_tutor_profile_photo';
39 const PHONE_NUMBER_META = 'phone_number';
40 const COVER_PHOTO_META = '_tutor_cover_photo';
41 const PROFILE_BIO_META = '_tutor_profile_bio';
42 const PROFILE_JOB_TITLE_META = '_tutor_profile_job_title';
43 const TUTOR_STUDENT_META = '_is_tutor_student';
44 const TOUR_COMPLETED_META = '_tutor_tour_completed';
45 const APPLICATION_SOURCE_META = '_tutor_application_source';
46 const INSTRUCTOR_APPROVAL_NOTICE_META = 'tutor_instructor_show_approval_message';
47
48 const SOURCE_INSTRUCTOR_REGISTRATION = 'instructor_registration';
49 const SOURCE_STUDENT_DASHBOARD = 'student_dashboard';
50
51 /**
52 * View as constants
53 *
54 * @since 4.0.0
55 */
56 const VIEW_AS_INSTRUCTOR = 'instructor';
57 const VIEW_AS_STUDENT = 'student';
58
59 /**
60 * User meta key for storing view as mode
61 *
62 * @since 4.0.0
63 *
64 * @var string
65 */
66 const VIEW_MODE_USER_META = 'tutor_profile_view_mode';
67
68 /**
69 * User model
70 *
71 * @since 3.0.0
72 *
73 * @var UserModel
74 */
75 private $model;
76
77 /**
78 * Registration notice
79 *
80 * @since 1.0.0
81 *
82 * @var boolean
83 */
84 private static $hide_registration_notice = false;
85
86 /**
87 * Register hooks
88 *
89 * @since 1.0.0
90 * @since 2.2.0 $register_hooks param added to resuse the class without hooks register.
91 *
92 * @param bool $register_hooks register hooks.
93 *
94 * @return void
95 */
96 public function __construct( $register_hooks = true ) {
97 $this->model = new UserModel();
98 if ( ! $register_hooks ) {
99 return;
100 }
101
102 add_action( 'edit_user_profile', array( $this, 'edit_user_profile' ) );
103 add_action( 'show_user_profile', array( $this, 'edit_user_profile' ), 10, 1 );
104
105 add_action( 'profile_update', array( $this, 'profile_update' ) );
106 add_action( 'set_user_role', array( $this, 'set_user_role' ), 10, 3 );
107
108 add_action( 'wp_ajax_tutor_user_photo_remove', array( $this, 'tutor_user_photo_remove' ) );
109 add_action( 'wp_ajax_tutor_user_photo_upload', array( $this, 'update_user_photo' ) );
110
111 add_action( 'admin_notices', array( $this, 'show_registration_disabled' ) );
112 add_action( 'admin_init', array( $this, 'hide_notices' ) );
113 add_action( 'wp_login', array( $this, 'update_user_last_login' ), 10, 2 );
114 add_action( 'login_form', array( $this, 'add_timezone_input' ) );
115 add_action( 'wp_login', array( $this, 'set_timezone' ), 10, 2 );
116
117 add_action( 'wp_ajax_tutor_user_list', array( $this, 'ajax_user_list' ) );
118 add_action( 'wp_ajax_tutor_switch_profile', array( $this, 'ajax_switch_profile' ) );
119 add_action( 'wp_ajax_tutor_complete_tour', array( $this, 'ajax_complete_tour' ) );
120 }
121
122 /**
123 * Get meta key name for review popup.
124 *
125 * @since 2.4.0
126 *
127 * @param int $course_id course id.
128 *
129 * @return string user meta key name.
130 */
131 public static function get_review_popup_meta( $course_id ) {
132 return self::REVIEW_POPUP_META . '_' . $course_id;
133 }
134
135 /**
136 * Check user has provided role.
137 *
138 * @since 2.2.0
139 *
140 * @param string $role role.
141 *
142 * @return boolean
143 */
144 public static function is( string $role ) {
145 return current_user_can( $role );
146 }
147
148 /**
149 * Check current user has capability.
150 *
151 * Example usage:
152 *
153 * User::can( 'edit_posts' );
154 * User::can( 'edit_post', $post->ID );
155 * User::can( 'edit_post_meta', $post->ID, $meta_key );
156 *
157 * @since 4.0.0
158 *
159 * @param string $capability capability.
160 * @param mixed ...$args args.
161 *
162 * @return boolean
163 */
164 public static function can( string $capability = 'manage_options', ...$args ) {
165 return current_user_can( $capability, ...$args );
166 }
167
168 /**
169 * Check user has any role.
170 *
171 * @since 2.2.0
172 * @since 2.6.2 $user_id param added.
173 *
174 * @param array $roles roles.
175 * @param int $user_id user id.
176 *
177 * @return boolean
178 */
179 public static function has_any_role( array $roles, $user_id = 0 ) {
180 $user = get_userdata( tutor_utils()->get_user_id( $user_id ) );
181 if ( empty( $user->roles ) || empty( $roles ) ) {
182 return false;
183 }
184
185 foreach ( $roles as $role ) {
186 if ( in_array( $role, $user->roles, true ) ) {
187 return true;
188 }
189 }
190
191 return false;
192 }
193
194 /**
195 * Check user is student.
196 *
197 * @since 2.2.0
198 * @since 2.6.2 $user_id param added.
199 *
200 * @param int $user_id user id.
201 *
202 * @return boolean
203 */
204 public static function is_student( $user_id = 0 ) {
205 $is_tutor_student = get_user_meta( tutor_utils()->get_user_id( $user_id ), self::TUTOR_STUDENT_META, true );
206 return $is_tutor_student ? true : false;
207 }
208
209 /**
210 * Check user is admin.
211 *
212 * @since 2.2.0
213 * @since 3.2.0 user_id param added.
214 *
215 * @param int $user_id user id.
216 *
217 * @return boolean
218 */
219 public static function is_admin( $user_id = 0 ) {
220 return user_can( tutor_utils()->get_user_id( $user_id ), self::ADMIN );
221 }
222
223 /**
224 * Check current user is instructor.
225 *
226 * @since 2.2.0
227 * @since 3.2.0 user_id param added.
228 *
229 * @param int $user_id user id.
230 * @param bool $is_approved instructor is approved or not.
231 *
232 * @return boolean
233 */
234 public static function is_instructor( $user_id = 0, $is_approved = true ) {
235 return tutils()->is_instructor( $user_id, $is_approved );
236 }
237
238 /**
239 * Get Tutor application source for a user.
240 *
241 * @since 4.0.0
242 *
243 * @param int $user_id user id.
244 *
245 * @return string
246 */
247 public static function get_application_source( $user_id = 0 ): string {
248 return (string) get_user_meta(
249 tutor_utils()->get_user_id( $user_id ),
250 self::APPLICATION_SOURCE_META,
251 true
252 );
253 }
254
255 /**
256 * Check if the user came through instructor registration.
257 *
258 * @since 4.0.0
259 *
260 * @param int $user_id user id.
261 *
262 * @return boolean
263 */
264 public static function used_instructor_registration( $user_id = 0 ): bool {
265 return self::SOURCE_INSTRUCTOR_REGISTRATION === self::get_application_source( $user_id );
266 }
267
268 /**
269 * Check if a user can view instructor dashboard screens.
270 *
271 * @since 4.0.0
272 *
273 * @param int $user_id user id.
274 *
275 * @return boolean
276 */
277 public static function can_view_instructor_dashboard( $user_id = 0 ): bool {
278 $user_id = tutor_utils()->get_user_id( $user_id );
279
280 if ( self::is_admin( $user_id ) || self::is_instructor( $user_id ) ) {
281 return true;
282 }
283
284 if ( ! self::used_instructor_registration( $user_id ) ) {
285 return false;
286 }
287
288 return in_array(
289 tutor_utils()->instructor_status( $user_id, false ),
290 array( Instructors_List::STATUS_PENDING, Instructors_List::STATUS_APPROVED ),
291 true
292 );
293 }
294
295 /**
296 * Check if user has a pending instructor application.
297 *
298 * @since 4.0.0
299 *
300 * @param int $user_id user id.
301 *
302 * @return boolean
303 */
304 public static function has_pending_instructor_application( $user_id = 0 ): bool {
305 return Instructors_List::STATUS_PENDING === tutor_utils()->instructor_status( $user_id, false );
306 }
307
308 /**
309 * Check current user is only instructor as admin also has instructor role.
310 *
311 * @since 3.2.0
312 *
313 * @param int $user_id user id.
314 * @param bool $is_approved instructor is approved or not.
315 *
316 * @return boolean
317 */
318 public static function is_only_instructor( $user_id = 0, $is_approved = true ) {
319 return ! self::is_admin( $user_id ) && self::is_instructor( $user_id, $is_approved );
320 }
321
322 /**
323 * Profile layouts
324 *
325 * @since 1.0.0
326 *
327 * @var array
328 */
329 private $profile_layout = array(
330 'pp-circle',
331 'pp-rectangle',
332 'no-cp',
333 );
334
335 /**
336 * Include edit user template
337 *
338 * @since 1.0.0
339 *
340 * @param mixed $user user.
341 *
342 * @return void
343 */
344 public function edit_user_profile( $user ) {
345 include tutor()->path . 'views/metabox/user-profile-fields.php';
346 }
347
348 /**
349 * Delete existing user's photo
350 *
351 * @since 1.0.0
352 *
353 * @param int $user_id user id.
354 * @param string $type photo type.
355 *
356 * @return void
357 */
358 private function delete_existing_user_photo( $user_id, $type ) {
359 $meta_key = 'cover_photo' == $type ? '_tutor_cover_photo' : '_tutor_profile_photo';
360 $photo_id = get_user_meta( $user_id, $meta_key, true );
361 if ( is_numeric( $photo_id ) ) {
362 $attachment = get_post( $photo_id );
363 $post_author = (int) $attachment->post_author ?? 0;
364 if ( $user_id === $post_author ) {
365 wp_delete_attachment( $photo_id, true );
366 }
367 }
368 delete_user_meta( $user_id, $meta_key );
369 }
370
371 /**
372 * User photo remove
373 *
374 * @since 1.0.0
375 *
376 * @return void
377 */
378 public function tutor_user_photo_remove() {
379 tutor_utils()->checking_nonce();
380 $this->delete_existing_user_photo(
381 get_current_user_id(),
382 Input::post( 'photo_type', '' )
383 );
384 }
385
386 /**
387 * User photo update
388 *
389 * @since 1.0.0
390 *
391 * @return void
392 */
393 public function update_user_photo() {
394 tutor_utils()->checking_nonce();
395
396 $user_id = get_current_user_id();
397 $photo_type = Input::post( 'photo_type', '' );
398 $meta_key = 'cover_photo' === $photo_type ? '_tutor_cover_photo' : '_tutor_profile_photo';
399
400 /**
401 * Photo Update from profile
402 */
403 $photo = tutor_utils()->array_get( 'photo_file', $_FILES ); //phpcs:ignore -- already sanitized.
404 $photo_size = tutor_utils()->array_get( 'size', $photo );
405 $photo_type = tutor_utils()->array_get( 'type', $photo );
406
407 if ( $photo_size && strpos( $photo_type, 'image' ) !== false ) {
408 if ( ! function_exists( 'wp_handle_upload' ) ) {
409 require_once ABSPATH . 'wp-admin/includes/file.php';
410 }
411 $upload_overrides = array( 'test_form' => false );
412 $movefile = wp_handle_upload( $photo, $upload_overrides );
413
414 if ( $movefile && ! isset( $movefile['error'] ) ) {
415 $file_path = tutor_utils()->array_get( 'file', $movefile );
416 $file_url = tutor_utils()->array_get( 'url', $movefile );
417 $mime_type = '';
418 if ( file_exists( $file_path ) ) {
419 $image_info = getimagesize( $file_path );
420 $mime_type = is_array( $image_info ) && count( $image_info ) ? $image_info['mime'] : '';
421 }
422
423 $media_id = wp_insert_attachment(
424 array(
425 'guid' => $file_path,
426 'post_mime_type' => $mime_type,
427 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $file_url ) ),
428 'post_content' => '',
429 'post_status' => 'inherit',
430 ),
431 $file_path,
432 0
433 );
434
435 if ( $media_id ) {
436 // wp_generate_attachment_metadata() won't work if you do not include this file.
437 require_once ABSPATH . 'wp-admin/includes/image.php';
438
439 // Generate and save the attachment metas into the database.
440 wp_update_attachment_metadata( $media_id, wp_generate_attachment_metadata( $media_id, $file_path ) );
441
442 // Update it to user profile.
443 $this->delete_existing_user_photo( $user_id, Input::post( 'photo_type', '' ) );
444 update_user_meta( $user_id, $meta_key, $media_id );
445
446 exit( wp_json_encode( array( 'status' => 'success' ) ) );
447 }
448 }
449 }
450 }
451
452 /**
453 * Get user timezone string.
454 *
455 * @param int|object $user user id or user object. Default: current user.
456 *
457 * @return string user timezone string, fallback site timezone string.
458 */
459 public static function get_user_timezone_string( $user = 0 ) {
460 if ( is_numeric( $user ) ) {
461 $user = get_user_by( 'id', tutor_utils()->get_user_id( $user ) );
462 }
463
464 if ( ! is_a( $user, 'WP_User' ) ) {
465 return wp_timezone_string();
466 }
467
468 $timezone = get_user_meta( $user->ID, self::TIMEZONE_META, true );
469 if ( self::is_admin() || empty( $timezone ) ) {
470 $timezone = wp_timezone_string();
471 }
472
473 return $timezone;
474 }
475
476 /**
477 * Profile update
478 *
479 * @since 1.0.0
480 *
481 * @param int $user_id user id.
482 *
483 * @return void
484 */
485 public function profile_update( $user_id ) {
486 if ( 'tutor_profile_update_by_wp' !== Input::post( 'tutor_action' ) ) {
487 return;
488 }
489
490 $timezone = Input::post( 'timezone', '' );
491 $_tutor_profile_job_title = Input::post( self::PROFILE_JOB_TITLE_META, '' );
492 $_tutor_profile_bio = Input::post( self::PROFILE_BIO_META, '', Input::TYPE_KSES_POST );
493 $_tutor_profile_image = Input::post( self::PROFILE_PHOTO_META, '', Input::TYPE_KSES_POST );
494
495 update_user_meta( $user_id, self::PROFILE_JOB_TITLE_META, $_tutor_profile_job_title );
496 update_user_meta( $user_id, self::PROFILE_BIO_META, $_tutor_profile_bio );
497 update_user_meta( $user_id, self::PROFILE_PHOTO_META, $_tutor_profile_image );
498 update_user_meta( $user_id, self::TIMEZONE_META, $timezone );
499 }
500
501 /**
502 * Set user role
503 *
504 * @since 1.0.0
505 *
506 * @param int $user_id user id.
507 * @param string $role user role.
508 * @param array $old_roles old role.
509 *
510 * @return void
511 */
512 public function set_user_role( $user_id, $role, $old_roles ) {
513 $instructor_role = tutor()->instructor_role;
514
515 if ( $role === $instructor_role || in_array( $instructor_role, $old_roles ) ) {
516 tutor_utils()->add_instructor_role( $user_id );
517 }
518 }
519
520 /**
521 * Hide notices
522 *
523 * @since 1.0.0
524 *
525 * @return void
526 */
527 public function hide_notices() {
528 $hide_notice = Input::get( 'tutor-hide-notice', '' );
529 $is_register_enabled = Input::get( 'tutor-registration', '' );
530 $has_manage_cap = current_user_can( 'manage_options' );
531
532 if ( $has_manage_cap && is_admin() && 'registration' === $hide_notice ) {
533 tutor_utils()->checking_nonce( 'get' );
534
535 if ( 'enable' === $is_register_enabled ) {
536 update_option( 'users_can_register', 1 );
537 } else {
538 self::$hide_registration_notice = true;
539 setcookie( 'tutor_notice_hide_registration', 1, time() + MONTH_IN_SECONDS, tutor()->basepath );
540 }
541 }
542 }
543
544 /**
545 * Show registration disabled
546 *
547 * @since 1.0.0
548 *
549 * @return void
550 */
551 public function show_registration_disabled() {
552 if ( self::$hide_registration_notice ||
553 ( '0' !== get_option( 'users_can_register' ) ) ||
554 isset( $_COOKIE['tutor_notice_hide_registration'] ) ||
555 ! current_user_can( 'manage_options' )
556 ) {
557 return;
558 }
559
560 $hide_url = wp_nonce_url( add_query_arg( 'tutor-hide-notice', 'registration' ), tutor()->nonce_action, tutor()->nonce );
561 ?>
562 <div class="wrap tutor-user-registration-notice-wrapper">
563 <div class="tutor-user-registration-notice">
564 <div>
565 <img src="<?php echo esc_url( tutor()->url . 'assets/images/icon-info-round.svg' ); ?>"/>
566 </div>
567 <div>
568 <?php
569 echo wp_kses(
570 __( 'As membership is turned off, students and instructors will not be able to sign up. <strong>Press Enable</strong> or go to <strong>Settings > General > Membership</strong> and enable "Anyone can register".', 'tutor' ),
571 array( 'strong' => true )
572 );
573 ?>
574 </div>
575 <div>
576 <a href="<?php echo esc_url( add_query_arg( 'tutor-registration', 'enable', $hide_url ) ); ?>">
577 <?php esc_html_e( 'Enable', 'tutor' ); ?>
578 </a>
579 <hr/>
580 <a href="<?php echo esc_url( $hide_url ); ?>">
581 <?php esc_html_e( 'Dismiss', 'tutor' ); ?>
582 </a>
583 </div>
584 </div>
585 </div>
586 <?php
587 }
588
589 /**
590 * Set the user last active timestamp to now.
591 *
592 * @since 2.5.0
593 *
594 * @param string $user_login active user name.
595 * @param \WP_User $user User object data.
596 *
597 * @return void
598 */
599 public function update_user_last_login( $user_login, $user ) {
600 update_user_meta( $user->ID, self::LAST_LOGIN_META, time() );
601 }
602
603 /**
604 * Add timezone input field to login form.
605 *
606 * @since 3.0.0
607 */
608 public function add_timezone_input() {
609 ?>
610 <input type="hidden" name="timezone" value="<?php echo esc_attr( wp_timezone_string() ); ?>" />
611 <script>
612 document.addEventListener('DOMContentLoaded', function() {
613 const timezone = document.querySelector('input[name="timezone"]');
614 if ( timezone) {
615 const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
616 timezone.value = tz
617 }
618 });
619 </script>
620 <?php
621 }
622
623 /**
624 * Set user timezone if not it set.
625 *
626 * @since 3.0.0
627 *
628 * @param string $user_login active user name.
629 * @param \WP_User $user User object data.
630 *
631 * @return void
632 */
633 public function set_timezone( $user_login, $user ) {
634 if ( Input::has( 'timezone' ) ) {
635 $timezone = get_user_meta( $user->ID, self::TIMEZONE_META, true );
636 if ( empty( $timezone ) ) {
637 update_user_meta( $user->ID, self::TIMEZONE_META, Input::post( 'timezone', wp_timezone_string() ) );
638 }
639 }
640 }
641
642 /**
643 * Get profile settings data
644 *
645 * @since 4.0.0
646 *
647 * @param int $user_id user id.
648 *
649 * @return array
650 */
651 public static function get_profile_settings_data( $user_id = 0 ) {
652 $user_id = tutor_utils()->get_user_id( $user_id );
653 $user = get_userdata( $user_id );
654
655 // Prepare profile pic.
656 $profile_placeholder = apply_filters( 'tutor_login_default_avatar', tutor()->url . 'assets/images/profile-photo.png' );
657 $profile_photo_src = $profile_placeholder;
658 $profile_photo_id = get_user_meta( $user->ID, self::PROFILE_PHOTO_META, true );
659
660 if ( $profile_photo_id ) {
661 $url = wp_get_attachment_image_url( $profile_photo_id, 'full' );
662 if ( ! empty( $url ) ) {
663 $profile_photo_src = $url;
664 }
665 }
666
667 $timezone = self::get_user_timezone_string( $user );
668
669 // Prepare cover photo.
670 $cover_placeholder = tutor()->url . 'assets/images/cover-photo.webp';
671 $cover_photo_src = $cover_placeholder;
672 $cover_photo_id = get_user_meta( $user->ID, self::COVER_PHOTO_META, true );
673
674 if ( $cover_photo_id ) {
675 $url = wp_get_attachment_image_url( $cover_photo_id, 'full' );
676 if ( ! empty( $url ) ) {
677 $cover_photo_src = $url;
678 }
679 }
680
681 // Prepare display name.
682 $public_display = array();
683 $public_display['display_nickname'] = $user->nickname;
684 $public_display['display_username'] = $user->user_login;
685
686 if ( ! empty( $user->first_name ) ) {
687 $public_display['display_firstname'] = $user->first_name;
688 }
689
690 if ( ! empty( $user->last_name ) ) {
691 $public_display['display_lastname'] = $user->last_name;
692 }
693
694 if ( ! empty( $user->first_name ) && ! empty( $user->last_name ) ) {
695 $public_display['display_firstlast'] = $user->first_name . ' ' . $user->last_name;
696 $public_display['display_lastfirst'] = $user->last_name . ' ' . $user->first_name;
697 }
698
699 if ( ! in_array( $user->display_name, $public_display, true ) ) { // Only add this if it isn't duplicated elsewhere.
700 $public_display = array( 'display_displayname' => $user->display_name ) + $public_display;
701 }
702
703 $public_display = array_map( 'trim', $public_display );
704 $public_display = array_unique( $public_display );
705 $max_filesize = floatval( ini_get( 'upload_max_filesize' ) ) * ( 1024 * 1024 );
706
707 $profile_bio = wp_kses_post( get_user_meta( $user->ID, self::PROFILE_BIO_META, true ) );
708 $job_title = get_user_meta( $user->ID, self::PROFILE_JOB_TITLE_META, true );
709 $phone = get_user_meta( $user->ID, self::PHONE_NUMBER_META, true );
710
711 return array(
712 'user' => $user,
713 'profile_placeholder' => $profile_placeholder,
714 'profile_photo_src' => $profile_photo_src,
715 'profile_photo_id' => $profile_photo_id,
716 'cover_placeholder' => $cover_placeholder,
717 'cover_photo_src' => $cover_photo_src,
718 'cover_photo_id' => $cover_photo_id,
719 'timezone' => $timezone,
720 'public_display' => $public_display,
721 'max_filesize' => $max_filesize,
722 'profile_bio' => $profile_bio,
723 'job_title' => $job_title,
724 'phone_number' => $phone,
725 );
726 }
727
728 /**
729 * Get user list with pagination & support
730 * search term
731 *
732 * @since 3.0.0
733 *
734 * @return void
735 */
736 public function ajax_user_list() {
737 tutor_utils()->check_nonce();
738
739 $can_access = apply_filters( 'tutor_user_list_access', current_user_can( 'manage_options' ) );
740 if ( ! $can_access ) {
741 $this->json_response( tutor_utils()->error_message( 'forbidden' ), HttpHelper::STATUS_FORBIDDEN );
742 }
743
744 $response = array(
745 'results' => array(),
746 'total_items' => 0,
747 );
748
749 $limit = Input::post( 'limit', 10, Input::TYPE_INT );
750 $offset = Input::post( 'offset', 0, Input::TYPE_INT );
751
752 $args = array(
753 'limit' => $limit,
754 'offset' => $offset,
755 );
756
757 $filter = json_decode( wp_unslash( $_POST['filter'] ?? '{}' ) );//phpcs:ignore
758 if ( ! empty( $filter ) && property_exists( $filter, 'search' ) && ! empty( $filter->search ) ) {
759 $args['search'] = '*' . Input::sanitize( $filter->search ) . '*';
760 $args['search_columns'] = array( 'user_login', 'user_email', 'user_nicename', 'display_name', 'ID' );
761 }
762
763 $user_list = $this->model->get_users_list( $args );
764
765 if ( is_object( $user_list ) ) {
766 foreach ( $user_list->get_results() as $user ) {
767 // Set user avatar.
768 $user->avatar_url = get_avatar_url( $user->ID, array( 'size' => 32 ) );
769 $response['results'][] = $user;
770 }
771
772 $response['total_items'] = $user_list->get_total();
773 }
774
775 $this->json_response(
776 __( 'User list fetched successfully!', 'tutor' ),
777 $response
778 );
779 }
780
781 /**
782 * Switch user profile ajax-handler
783 *
784 * @since 4.0.0
785 *
786 * @return void send wp_json response
787 */
788 public function ajax_switch_profile() {
789 tutor_utils()->checking_nonce();
790
791 $user_id = get_current_user_id();
792 if ( ! self::can_switch_mode( $user_id ) ) {
793 $this->json_response(
794 tutor_utils()->error_message(),
795 null,
796 HttpHelper::STATUS_UNAUTHORIZED
797 );
798 }
799
800 $switch_mode = '';
801 $current_mode = Input::post( 'current_mode' );
802
803 if ( ! in_array( $current_mode, array( self::VIEW_AS_INSTRUCTOR, self::VIEW_AS_STUDENT ), true ) ) {
804 $this->response_bad_request( tutor_utils()->error_message( 'invalid_req' ) );
805 }
806
807 if ( self::VIEW_AS_INSTRUCTOR === $current_mode ) {
808 $switch_mode = self::VIEW_AS_STUDENT;
809 } elseif ( self::VIEW_AS_STUDENT === $current_mode ) {
810 $switch_mode = self::VIEW_AS_INSTRUCTOR;
811 }
812
813 update_user_meta( $user_id, self::VIEW_MODE_USER_META, $switch_mode );
814
815 // translators:%s for switching mode.
816 $this->response_success( sprintf( __( 'Profile switched to %s!', 'tutor' ), $switch_mode ) );
817 }
818
819 /**
820 * Get current view mode STUDENT/INSTRUCTOR
821 *
822 * @since 4.0.0
823 *
824 * @return string
825 */
826 public static function get_current_view_mode(): string {
827 $user_id = get_current_user_id();
828 $can_switch = self::can_switch_mode( $user_id );
829 $default_mode = $can_switch ? self::VIEW_AS_INSTRUCTOR : self::VIEW_AS_STUDENT;
830 $current_mode = get_user_meta( $user_id, self::VIEW_MODE_USER_META, true );
831
832 if ( $can_switch && in_array( $current_mode, array( self::VIEW_AS_INSTRUCTOR, self::VIEW_AS_STUDENT ), true ) ) {
833 return $current_mode;
834 }
835
836 if ( self::used_instructor_registration( $user_id ) ) {
837 $instructor_status = tutor_utils()->instructor_status( $user_id, false );
838 if ( in_array( $instructor_status, array( Instructors_List::STATUS_PENDING, Instructors_List::STATUS_APPROVED ), true ) ) {
839 return self::VIEW_AS_INSTRUCTOR;
840 }
841 }
842
843 return $default_mode;
844 }
845
846 /**
847 * Check if the user is in instructor view
848 *
849 * @since 4.0.0
850 *
851 * @return bool
852 */
853 public static function is_instructor_view(): bool {
854 return self::VIEW_AS_INSTRUCTOR === self::get_current_view_mode();
855 }
856
857 /**
858 * Check if the user is in student view
859 *
860 * @since 4.0.0
861 *
862 * @return bool
863 */
864 public static function is_student_view(): bool {
865 return self::VIEW_AS_STUDENT === self::get_current_view_mode();
866 }
867
868 /**
869 * Check if the user can switch between learner and instructor dashboard modes.
870 *
871 * @since 4.0.0
872 *
873 * @param int $user_id User ID.
874 *
875 * @return bool
876 */
877 public static function can_switch_mode( int $user_id = 0 ): bool {
878 return self::is_admin( $user_id ) || self::is_instructor( $user_id );
879 }
880
881 /**
882 * Mark dashboard tour as completed for the current user.
883 *
884 * @since 4.0.0
885 *
886 * @return void JSON response.
887 */
888 public function ajax_complete_tour() {
889 tutor_utils()->check_nonce();
890
891 update_user_meta( get_current_user_id(), self::TOUR_COMPLETED_META, true );
892
893 $this->json_response( __( 'Tour completed', 'tutor' ) );
894 }
895 }
896