get_wpdb_prefix(); $this->courses_table = $prefix.'courses'; $this->assignments_table = $prefix.'assignments'; $this->teachers_table = $prefix.'teachers'; $this->courses_teacher_rel_table = $prefix.'courses_teacher_rel'; $this->groups_table = $prefix.'groups'; $this->classmates_table = $prefix.'classmates'; $this->classmates_group_rel_table = $prefix.'classmates_group_rel'; $this->wpdb = $wpdb; } /** * Returns plain role number * @return int */ function getRole() { return 2; } /** * Finish subscribing sequence * * This method gets called only on successful subscription * @return local course id */ function finishSubscribing($course_data) { if($course_data) { $course = $course_data->courseMeta; $course_exist = $this->getActiveCourseByURL($course->url); if(!$course_exist) { $data = array('course_name' => (string) $course->name, 'course_url' => (string) $course->url, 'status' => (string) $course->extras->{'course-status'}, 'accept_key' => (string) $course->extras->{'accept-key'}, 'message' => (string) $course->extras->{'lepress-message'}); $result = $this->wpdb->insert($this->courses_table, $data); $local_course_id = $this->wpdb->insert_id; if($result) { //Add teachers foreach($course->teachers->teacher as $teacher) { $teacher_attr = $teacher->attributes(); $teacher_ext_id = (int) $teacher_attr['id']; $this->addUpdateTeacher($this->wpdb->insert_id, $teacher_ext_id, $teacher->firstname, $teacher->lastname, $teacher->email, $teacher->organization); } } return $local_course_id; } } } /** * Subscribe to course * * This method does all the subscribing part, if success "finishSubscribing" method is called * to handle XML returned by teacher blog. It also handles easy subscription method. * @return result code */ function subscribe($course_url, $course_message = '', $simple_subscribe = false) { global $LePress; //Now send actual request to Teacher blog $current_user = $LePress->getBlogOwnerUser(); $studentRequest = new LePressRequest($this->getRole(), 'subscribe'); $studentRequest->addParams(array('firstname' => !empty($current_user->user_firstname) ? $current_user->user_firstname : $current_user->user_login, 'lastname' => $current_user->user_lastname, 'email' => $current_user->user_email, 'lepress-message' => $course_message)); if($has_key = $this->getQueryParam($course_url, 'key')) { $studentRequest->addParam('invite-key', $has_key); } $studentRequest->doPost($course_url); //If we are dealing with easy subscription method - request from widget or blog url if($simple_subscribe) { if($studentRequest->getStatusCode() == 202) { $course_data = @simplexml_load_string($studentRequest->getBody()); $local_course_id = $this->finishSubscribing($course_data); $this->refreshAssignments($local_course_id); $this->refreshClassmates($local_course_id); return true; } elseif($studentRequest->getStatusCode() == 409) { return 2; } elseif($studentRequest->getStatusCode() == 405) { return 4; } else { return false; } } //Otherwise we are doing regular subscription switch($studentRequest->getStatusCode()) { case 202: //Everything went as it should, let's proceed $course_data = @simplexml_load_string($studentRequest->getBody()); $local_course_id = $this->finishSubscribing($course_data); $LePress->echoNoticeDiv(__('You have successfully subscribed to course', lepress_textdomain)); //Update assignments and classmates after successful subscription too, IF course->status == 1 //this means IF open access to course, no moderation on teacher side $this->refreshAssignments($local_course_id); $this->refreshClassmates($local_course_id); break; case 404: $LePress->echoNoticeDiv(__('This course does not exist', lepress_textdomain), true); break; case 401: $LePress->echoNoticeDiv(__('Invitation key is expired or sent for another email', lepress_textdomain), true); break; case 405: $LePress->echoNoticeDiv(__('This course does not accept any new subscriptions', lepress_textdomain), true); break; case 412: $LePress->echoNoticeDiv(__('Could not establish secure connection', lepress_textdomain), true); break; case 409: $LePress->echoNoticeDiv(__('You are already subscribed on this course', lepress_textdomain), true); break; case 500: $LePress->echoNoticeDiv(__('Subscriber with the same email address already subcribed', lepress_textdomain), true); break; case 400: $LePress->echoNoticeDiv(__('You cannot subcribe on your own course', lepress_textdomain), true); break; default: $LePress->echoNoticeDiv(__('Something went wrong... Try again', lepress_textdomain), true); } } /** * Get query parameter from the URL * @return string param value or boolean false */ function getQueryParam($url, $param) { $query_params = explode("&", parse_url($url, PHP_URL_QUERY)); if(count($query_params) > 0) { foreach($query_params as $param_pair) { $param_parts = split("=", $param_pair); if($param_parts[0] == $param) { if(isSet($param_parts[1])) { return $param_parts[1]; } } } } return false; } /** * Unsubscribe or cancel subscription */ function unsubscribe($local_course_id, $canceled = false, $uninstall = false) { global $LePress; if($local_course_id <= 0) { return false; } /* First request to teacher blog, if teacher blog returns 202/404 HTTP code, proceed on student side */ $course = $this->getCourse($local_course_id); if($course && $course->archived == 0) { $studentRequest = new LePressRequest($this->getRole(), 'unsubscribe'); $studentRequest->addParam('accept-key', $course->accept_key); if($uninstall) { $studentRequest->setBlocking('false'); } $studentRequest->doPost($course->course_url); switch($studentRequest->getStatusCode()) { case 202: if(!$canceled) { //archive course on local database $this->archiveCourse($local_course_id); } else { $this->removeCourse($local_course_id); } $LePress->echoNoticeDiv(__('You have successfully unsubscribed from the course.', lepress_textdomain)); break; case 500: $LePress->echoNoticeDiv(__('Subscription found, but could not remove: HTTP Status 500', lepress_textdomain)); break; case 404: //archive course on local database $this->archiveCourse($local_course_id); break; case 412: $LePress->echoNoticeDiv(__('Unsubscribing failed: HTTP Status 412', lepress_textdomain)); break; } } } /** * Set course archived flag */ function archiveCourse($local_course_id) { $data = array('archived' => 1); $where = array('ID' => $local_course_id); $this->wpdb->update($this->courses_table, $data, $where); } /** * Remove course from databse */ function removeCourse($local_course_id) { $this->wpdb->query('DELETE FROM '.$this->courses_table.' WHERE ID = "'.esc_sql($local_course_id).'"'); } /** * Update courses metadata, triggered by cron or finishSubcribing method */ function refreshCoursesMeta() { $course_teacher_ids = array(); foreach($this->getCourses() as $course) { if(!$course->archived && ($course->status > -1)) { $local_course_id = $course->ID; $studentRequest = new LePressRequest($this->getRole(), 'getCourseMeta'); $studentRequest->addParam('accept-key', $course->accept_key); $studentRequest->doPost($course->course_url); if($studentRequest->getStatusCode() == 200) { $course = @simplexml_load_string($studentRequest->getBody())->courseMeta; if($course) { foreach($course->teachers->teacher as $teacher) { $teacher_attr = $teacher->attributes(); $teacher_ext_id = $teacher_attr['id']; $course_teacher_ids[] = $teacher_ext_id; $this->addUpdateTeacher($local_course_id, $teacher_ext_id, $teacher->firstname, $teacher->lastname, $teacher->email, $teacher->organization); } $data = array('course_name' => $course->name, 'course_url' => $course->url); $where = array('ID' => $local_course_id); $this->wpdb->update($this->courses_table, $data, $where); //If course has been closed, archived it if($course->is_closed == 1) { $this->archiveCourse($local_course_id); } } } elseif($studentRequest->getStatusCode() == 404 || $studentRequest->getStatusCode() == 412) { //If course is not found anymore, set it as archived - teacher has disabled LePress teacher role or deleted category $this->archiveCourse($local_course_id); } } } //Clear broken relations $this->clearTeachers($course_teacher_ids); } /** * Clear declined courses from database */ function clear($action) { switch($action) { case 'declined': $this->wpdb->query('DELETE FROM '.$this->courses_table.' WHERE status = "-1"'); break; } } /** * Unsubscribe all active courses and Mark courses as archived */ function unsubscribeAllActive() { //iterate trough all the active courses foreach($this->getApproved() as $course) { $this->unsubscribe($course->ID, true); } //iterate trough all the pending courses foreach($this->getPending() as $course) { $this->cancelSubscription($course->ID, true); } } /** * Wrapper function for unscribe method call * * Cancel subscription */ function cancelSubscription($local_course_id, $uninstall = false) { $this->unsubscribe($local_course_id, true, $uninstall); } /** * Add OR update teacher data */ function addUpdateTeacher($local_course_id, $ext_teacher_id, $firstname, $lastname, $email, $organization) { $data = array('ext_user_id' => (string) $ext_teacher_id, 'firstname' => (string) $firstname, 'lastname' => (string) $lastname, 'email' => (string) $email, 'organization' => (string) $organization); $added = $this->wpdb->insert($this->teachers_table, $data); if(!$added) { $where = array('ext_user_id' => $ext_teacher_id); $this->wpdb->update($this->teachers_table, $data, $where); } //Add relation between course and teacher $data = array('rel_courses_id' => $local_course_id, 'rel_teacher_id' => $ext_teacher_id); $this->wpdb->insert($this->courses_teacher_rel_table, $data); } /** * Clear unrelated teachers from database */ function clearTeachers($teacher_ids) { if(count($teacher_ids) > 0) { $this->wpdb->query('DELETE FROM '.$this->teachers_table.' WHERE ext_user_id NOT IN ('.esc_sql(implode(',', $teacher_ids)).')'); } } /** * Update subscription status, usually triggered after teacher action accept/decline student * @return sql update status */ function updateStatus($course_url, $course_status, $accept_key, $message) { switch($course_status) { case 0: $data = array('status' => -1, 'message' => $message); break; case 2: $data = array('archived' => 1); break; default: $data = array('status' => $course_status); break; } $where = array('course_url' => $course_url, 'accept_key' => $accept_key); return $this->wpdb->update($this->courses_table, $data, $where); } /** * Verify teacher access to student's blog using course url and accept_key * @return local course id */ function verifyAccess($course_url, $accept_key) { return $this->wpdb->get_var('SELECT ID FROM '.$this->courses_table.' WHERE course_url = "'.esc_sql($course_url).'" AND accept_key = "'.esc_sql($accept_key).'" AND status > -1'); } /** * Verify teacher access to student's blog using md5 hash from course_url and accept_key * @return local course id */ function verifyAccessMD5($md5_key) { return $this->wpdb->get_var('SELECT ID FROM '.$this->courses_table.' WHERE md5(concat(course_url, accept_key)) = "'.esc_sql($md5_key).'" AND status > -1'); } /** * Send submission to teacher blog * * Send submission to teacher blog, post data includes - post url, post content, post_id, accept_key */ function sendAnswerURL($meta_data, $post) { $course = $this->getCourse($meta_data->course_id); if($course) { global $LePress; $studentRequest = new LePressRequest($this->getRole(), 'addAnswer'); $studentRequest->addParams(array('post_id' => $meta_data->post_id, 'answer-url' => get_permalink($post->ID), 'answer-content' => htmlspecialchars($LePress->mb_cutstr($post->post_content), ENT_COMPAT, $LePress->getBlogCharset(), false), 'answer-post-id' => $post->ID, 'accept-key' => $course->accept_key)); $studentRequest->doPost($course->course_url); } } /** * Add feedback to the submission * * Triggered by teacher blog request, if teacher gives feedback. Creates new comment on student blog and saves some metadata for that comment */ function addFeedback($local_post_id, $teacher_id, $accept_key, $feedback, $grade) { global $LePress; $this->setGrade($local_post_id, $grade); $teacher = $this->getTeacherByID($teacher_id, $accept_key); $comment = $LePress->fetchComment($local_post_id, $teacher->email, 'LePressTeacher'); $time = current_time('mysql'); $data = array( 'comment_post_ID' => $local_post_id, 'comment_author' => $teacher->lastname.' '.$teacher->firstname, 'comment_author_email' => $teacher->email, 'comment_author_url' => $teacher->course_url, 'comment_content' => $feedback, 'comment_agent' => 'LePressTeacher', 'comment_type' => '', 'comment_date' => $time, 'comment_parent' => 0, 'user_id' => 0, 'comment_approved' => 1); if(!$comment) { $comment_id = wp_insert_comment($data); update_comment_meta($comment_id, 'lepress-read', '1'); } else { $deleted = wp_delete_comment($comment->comment_ID, true); if($deleted) { $comment_id = wp_insert_comment($data); update_comment_meta($comment_id, 'lepress-read', '1'); } } } /** * Get is post assignment answer * @return metadata object */ function isAssignmentAnswer($post_id) { return is_object(get_post_meta($post_id, '_lepress-assignment-meta', true)); } /** * Get assignment status * * @return post id or boolean false */ function getAssignmentStatus($post_id, $draft_allowed = false) { $posts = get_posts(array('meta_query' => array(array('key' => '_lepress-assignment-meta')), 'post_status' => 'publish|private|password|draft', 'numberposts' => -1)); foreach($posts as $post) { $meta_data = get_post_meta($post->ID, '_lepress-assignment-meta', true); $post_status = $draft_allowed ? 'draft' : 'publish'; if($meta_data->post_id == $post_id && ($post->post_status == 'publish' || $post->post_status == 'private' || $post->post_status == 'password' || $post->post_status == $post_status)) { return $post->ID; } } return false; } /** * Get submission grade * @return string */ function getGrade($local_post_id) { if($local_post_id) { $meta_data = get_post_meta($local_post_id, '_lepress-assignment-meta', true); if(isSet($meta_data->grade)) { return $meta_data->grade; } } return "NA"; } /** * Set submission grade, called from addFeedback method */ function setGrade($local_post_id, $grade) { if($local_post_id) { $meta_data = get_post_meta($local_post_id, '_lepress-assignment-meta', true); if($meta_data) { $meta_data->grade = $grade; } else { $meta_data = (object) array('grade' => $grade); } update_post_meta($local_post_id, '_lepress-assignment-meta', $meta_data); } } /** * Get all the assignments for course * * Called by assignments.php page * @return SQL results (objects) */ function getAssignments($course) { if(is_int($course)) { $course = $this->getCourse($course); } return $this->wpdb->get_results('SELECT post_id, title, excerpt, start_date, end_date, url FROM '.$this->assignments_table.' WHERE course_id = '.esc_sql($course->ID)); } /** * Update assignments data * * This method is called by LePress cron periodically. Updates assignments data for all the courses */ function refreshAssignments($by_course_id = false) { $courses = array(); if($by_course_id) { $course = $this->getCourse($by_course_id); if($course->status == 1) { $courses[] = $course; } } else { $courses = $this->getApproved(); } //Iterate through courses foreach($courses as $course) { $studentRequest = new LePressRequest($this->getRole(), 'getAssignments'); $studentRequest->addParam('accept-key', $course->accept_key); $studentRequest->doPost($course->course_url); if($studentRequest->getStatusCode() == 200) { $xml = @simplexml_load_string($studentRequest->getBody()); if($xml) { $posts_found_ids = array(); //Iterate through found assignments and update database foreach($xml->assignment as $assignment) { $attrs = $assignment->attributes(); $post_id = (int) $attrs['post_id']; $title = (string) $assignment->title; $url = (string) $assignment->url; $excerpt = (string) $assignment->excerpt; $start_date = date('Y-m-d', strtotime((string) $assignment->start_date)); $end_date = date('Y-m-d', strtotime((string) $assignment->end_date)).' 23:59:59'; $data = array('post_id' => $post_id, 'title' => $title, 'excerpt' => $excerpt, 'url' => $url, 'start_date' => $start_date, 'end_date' => $end_date, 'course_id' => $course->ID); $result = $this->wpdb->insert($this->assignments_table, $data); if(!$result) { $where = array('post_id' => $post_id, 'course_id' => $course->ID); $this->wpdb->update($this->assignments_table, $data, $where); } $posts_found_ids[] = $post_id; } if(count($posts_found_ids) > 0) { //Delete assignments, which are not available anymore $this->wpdb->query('DELETE FROM '.$this->assignments_table.' WHERE post_id NOT IN ('.esc_sql(implode(",", $posts_found_ids)).') AND course_id = '.esc_sql($course->ID)); } } } elseif($studentRequest->getStatusCode() == 404 || $studentRequest->getStatusCode() == 412) { //If course is not found anymore, set it as archived - teacher has disabled LePress teacher role or deleted category $this->archiveCourse($course->ID); } } } /** * Update classmates data * * This method is called by LePress cron periodically. Updates classmates data for all the courses */ function refreshClassmates($by_course_id = false) { $courses = array(); if($by_course_id) { $course = $this->getCourse($by_course_id); if($course->status == 1) { $courses[] = $course; } } else { $courses = $this->getApproved(); } //Iterate through all the courses foreach($courses as $course) { $studentRequest = new LePressRequest($this->getRole(), 'getClassmates'); $studentRequest->addParam('accept-key', $course->accept_key); $studentRequest->doPost($course->course_url); if($studentRequest->getStatusCode() == 200) { $xml = @simplexml_load_string($studentRequest->getBody()); //Delete previous data, then add new $this->wpdb->query('DELETE FROM '.$this->classmates_table.' WHERE course_id = '.esc_sql($course->ID)); if($xml) { //Iterate through all the classmates and update database foreach($xml->classmate as $classmate) { $firstname = (string) $classmate->firstname; $lastname = (string) $classmate->lastname; $email = (string) $classmate->email; $blog_url = (string) $classmate->blog_url; $data = array('firstname' => $firstname, 'lastname' => $lastname, 'blog_url' => $blog_url, 'email' => $email, 'course_id' => $course->ID); $result = $this->wpdb->insert($this->classmates_table, $data); if(!$result) { //Probably never executed, due to DELETE query, was used in first prototype $where = array('blog_url' => $blog_url, 'course_id' => $course->ID); $this->wpdb->update($this->classmates_table, $data, $where); } } } } elseif($studentRequest->getStatusCode() == 404 || $studentRequest->getStatusCode() == 412) { //If course is not found anymore, set it as archived - teacher has disabled LePress teacher role or deleted category $this->archiveCourse($course->ID); } } } /** * Get classmates by group * @param $group_id Group id to look for */ function getClassmatesByGroup($group_id) { return $this->wpdb->get_results('SELECT '.$this->classmates_table.'.id as mate_id, firstname, lastname, email, blog_url, course_name, course_url FROM '.$this->classmates_table.','.$this->courses_table.','.$this->groups_table.' WHERE '.$this->classmates_table.'.course_id = '.$this->courses_table.'.id AND '.$this->groups_table.'.course_id = '.$this->courses_table.'.id AND status= 1 AND archived = 0 AND '.$this->groups_table.'.id = '.esc_sql($group_id)); } /** * Get classmate by ID * @param $mate_id Mates id to look for */ function getClassmateByID($mate_id) { return $this->wpdb->get_row('SELECT '.$this->classmates_table.'.id as mate_id, firstname, lastname, email, blog_url FROM '.$this->classmates_table.','.$this->courses_table.' WHERE course_id = '.$this->courses_table.'.id AND status= 1 AND archived = 0 AND '.$this->classmates_table.'.id = '.esc_sql($mate_id)); } /** * Get all the classmates on the course */ function getClassmates($local_course_id) { return $this->wpdb->get_results('SELECT '.$this->classmates_table.'.id as mate_id, firstname, lastname, email, blog_url, course_name, course_url FROM '.$this->classmates_table.','.$this->courses_table.' WHERE '.$this->classmates_table.'.course_id = '.$this->courses_table.'.id AND '.$this->courses_table.'.id = '.esc_sql($local_course_id).' AND status= 1 AND archived = 0 ORDER BY lastname ASC'); } /** * Get assignment by ID * * This method retrieves assignment from teacher blog, to be displayed new post page / when writing a submission * @return XML OR boolean false */ function getAssignmentByID($post_id, $local_course_id) { $course = $this->getCourse($local_course_id); if($course) { $studentRequest = new LePressRequest($this->getRole(), 'getAssignmentByID'); $studentRequest->addParam('accept-key', $course->accept_key); $studentRequest->addParam('post-id', $post_id); $studentRequest->doPost($course->course_url); $xml = @simplexml_load_string($studentRequest->getBody()); if($xml) { return $xml->assignment; } } return false; } /** * Get all the courses */ function getCourses() { return $this->wpdb->get_results('SELECT * FROM '.$this->courses_table); } /** * Get course by id */ function getCourse($local_course_id) { return $this->wpdb->get_row('SELECT * FROM '.$this->courses_table.' WHERE ID = "'.esc_sql($local_course_id).'"'); } /** * Get active course by URL */ function getActiveCourseByURL($course_url) { return $this->wpdb->get_row('SELECT * FROM '.$this->courses_table.' WHERE course_url = "'.esc_sql($course_url).'" AND status > -1 AND archived = 0'); } /** * Get all approved courses */ function getApproved() { return $this->wpdb->get_results('SELECT * FROM '.$this->courses_table.' WHERE status = 1 AND archived = 0'); } /** * Get all the teachers on the course */ function getTeachers($local_course_id) { return $this->wpdb->get_results('SELECT firstname, lastname, email, organization, course_url FROM '.$this->courses_table.', '.$this->teachers_table.', '.$this->courses_teacher_rel_table.' WHERE status = 1 AND '.$this->courses_table.'.ID = rel_courses_id AND rel_teacher_id = ext_user_id AND rel_courses_id = "'.esc_sql($local_course_id).'" ORDER BY lastname ASC'); } /** * Get teacher by ID */ function getTeacherByID($teacher_id, $accept_key) { return $this->wpdb->get_row('SELECT firstname, lastname, email, organization, course_url FROM '.$this->courses_table.', '.$this->teachers_table.', '.$this->courses_teacher_rel_table.' WHERE status = 1 AND '.$this->courses_table.'.ID = rel_courses_id AND rel_teacher_id = ext_user_id AND ext_user_id = "'.esc_sql($teacher_id).'" AND accept_key = "'.esc_sql($accept_key).'"'); } /** * Get all the pending courses */ function getPending() { return $this->wpdb->get_results('SELECT * FROM '.$this->courses_table.' WHERE status = 0 AND archived = 0'); } /** * Get pending courses count * @return int */ function getPendingCount() { return $this->wpdb->get_var('SELECT count(ID) FROM '.$this->courses_table.' WHERE status = 0 AND archived = 0'); } /** * Get pending assignments count * @return int */ function getPendingAssignmentsCount() { $count = 0; foreach($this->getApproved() as $course) { $assignments = $this->getAssignments($course); foreach($assignments as $assignment) { if(!$this->getAssignmentStatus($assignment->post_id)) { $count++; } } } return $count; } /** * Get all archived courses */ function getArchived() { return $this->wpdb->get_results('SELECT * FROM '.$this->courses_table.' WHERE status = 1 AND archived = 1'); } /** * Get all declined courses */ function getDeclined() { return $this->wpdb->get_results('SELECT * FROM '.$this->courses_table.' WHERE status = -1 AND (archived = 0 OR archived = 1)'); } /** * Get answer post by ID * @return XML or boolean false */ function getAnswerByPostID($post_id) { if(intval($post_id) > 0) { global $LePress; $post = get_post($post_id); if($post) { $xml = new SimpleXMLElement(xml_root); $answer = $xml->addChild('answer'); $answer->addAttribute('id', $post_id); $answer->addChild('title', 'safeStringInput($post->post_title).']]>'); $answer->addChild('url', 'safeStringInput(get_permalink($post_id)).']]>'); //$answer->addChild('content', 'safeStringInput(apply_filters('the_content', $post->post_content)).']]>'); //This is not used for now, with new iframe solution return htmlspecialchars_decode($xml->asXML()); } } return false; } } ?>