From 145daaf0ba6229aba392845633bd4b6dafa25dac Mon Sep 17 00:00:00 2001 From: Christian Beeznest Date: Tue, 15 Apr 2025 13:47:23 -0500 Subject: [PATCH 1/2] Gradebook: Fix session handling for surveys and student publications - refs BT#22564 --- .../lib/be/studentpublicationlink.class.php | 93 +------ .../gradebook/lib/be/surveylink.class.php | 233 ++++++------------ public/main/inc/ajax/model.ajax.php | 3 +- public/main/work/work.lib.php | 2 +- .../CSurveyInvitationRepository.php | 54 ++++ 5 files changed, 148 insertions(+), 237 deletions(-) diff --git a/public/main/gradebook/lib/be/studentpublicationlink.class.php b/public/main/gradebook/lib/be/studentpublicationlink.class.php index 6fd40e73a05..ccbb55d8bc9 100644 --- a/public/main/gradebook/lib/be/studentpublicationlink.class.php +++ b/public/main/gradebook/lib/be/studentpublicationlink.class.php @@ -46,28 +46,9 @@ public function get_all_links() $sessionId = $this->get_session_id(); $session = api_get_session_entity($sessionId); - /* - if (empty($session_id)) { - $session_condition = api_get_session_condition(0, true); - } else { - $session_condition = api_get_session_condition($session_id, true, true); - } - $sql = "SELECT id, url, title FROM $tbl_grade_links - WHERE c_id = {$this->course_id} AND filetype='folder' AND active = 1 $session_condition ";*/ - - //Only show works from the session - //AND has_properties != '' $repo = Container::getStudentPublicationRepository(); $qb = $repo->findAllByCourse(api_get_course_entity($this->course_id), $session, null, 1, 'folder'); $links = $qb->getQuery()->getResult(); - - /*$links = Container::getStudentPublicationRepository() - ->findBy([ - 'cId' => $this->course_id, - 'active' => true, - 'filetype' => 'folder', - 'session' => $session, - ]);*/ $cats = []; foreach ($links as $data) { $work_name = $data->getTitle(); @@ -93,10 +74,8 @@ public function has_results() $id = $studentPublication->getIid(); $session = api_get_session_entity($this->get_session_id()); $results = Container::getStudentPublicationRepository() - ->findBy([ - 'parentId' => $id, - 'session' => $session, - ]); + ->getStudentAssignments($studentPublication, api_get_course_entity($this->course_id), $session); + return 0 !== count($results); } @@ -109,76 +88,30 @@ public function has_results() public function calc_score($studentId = null, $type = null) { $studentId = (int) $studentId; - $em = Database::getManager(); $assignment = $this->getStudentPublication(); if (empty($assignment)) { return []; } $session = api_get_session_entity($this->get_session_id()); + $course = api_get_course_entity($this->course_id); - // @todo check session id / course id access - /*$id = $studentPublication->getIid(); - $assignment = Container::getStudentPublicationRepository() - ->findOneBy([ - 'cId' => $this->course_id, - 'iid' => $id, - 'session' => $session, - ]) - ; - $parentId = !$assignment ? 0 : $assignment->getId(); - */ - - $parentId = $assignment->getIid(); - - if (empty($session)) { - $dql = 'SELECT a FROM ChamiloCourseBundle:CStudentPublication a - WHERE - a.active = :active AND - a.publicationParent = :parent AND - a.session is null AND - a.qualificatorId <> 0 - '; - $params = [ - 'parent' => $parentId, - 'active' => true, - ]; - } else { - $dql = 'SELECT a FROM ChamiloCourseBundle:CStudentPublication a - WHERE - a.active = :active AND - a.publicationParent = :parent AND - a.session = :session AND - a.qualificatorId <> 0 - '; - - $params = [ - 'parent' => $parentId, - 'session' => $session, - 'active' => true, - ]; - } - - if (!empty($studentId)) { - $dql .= ' AND a.userId = :student '; - $params['student'] = $studentId; - } + $qb = Container::getStudentPublicationRepository() + ->getStudentAssignments($assignment, $course, $session, null, $studentId ? api_get_user_entity($studentId) : null); $order = api_get_setting('student_publication_to_take_in_gradebook'); switch ($order) { case 'last': - // latest attempt - $dql .= ' ORDER BY a.sentDate DESC'; + $qb->orderBy('resource.sentDate', 'DESC'); break; case 'first': default: - // first attempt - $dql .= ' ORDER BY a.iid'; + $qb->orderBy('resource.iid', 'ASC'); break; } - $scores = $em->createQuery($dql)->execute($params); + $scores = $qb->getQuery()->getResult(); // for 1 student if (!empty($studentId)) { @@ -196,8 +129,8 @@ public function calc_score($studentId = null, $type = null) ]; } - $students = []; // user list, needed to make sure we only - // take first attempts into account + // multiple students + $students = []; $rescount = 0; $sum = 0; $bestResult = 0; @@ -205,7 +138,7 @@ public function calc_score($studentId = null, $type = null) $sumResult = 0; foreach ($scores as $data) { - if (!(array_key_exists($data->getUserId(), $students))) { + if (!array_key_exists($data->getUserId(), $students)) { if (0 != $assignment->getQualification()) { $students[$data->getUserId()] = $data->getQualification(); $rescount++; @@ -227,16 +160,12 @@ public function calc_score($studentId = null, $type = null) switch ($type) { case 'best': return [$bestResult, $weight]; - break; case 'average': return [$sumResult / $rescount, $weight]; - break; case 'ranking': return AbstractLink::getCurrentUserRanking($studentId, $students); - break; default: return [$sum, $rescount]; - break; } } diff --git a/public/main/gradebook/lib/be/surveylink.class.php b/public/main/gradebook/lib/be/surveylink.class.php index a4ef50f276c..e2ae1ed0964 100644 --- a/public/main/gradebook/lib/be/surveylink.class.php +++ b/public/main/gradebook/lib/be/surveylink.class.php @@ -3,6 +3,7 @@ /* For licensing terms, see /license.txt */ use Chamilo\CoreBundle\Framework\Container; +use Chamilo\CourseBundle\Entity\CSurvey; /** * Gradebook link to a survey item. @@ -11,8 +12,7 @@ */ class SurveyLink extends AbstractLink { - private $survey_table; - /** @var \Chamilo\CourseBundle\Entity\CSurvey */ + /** @var CSurvey */ private $survey_data; /** @@ -24,50 +24,49 @@ public function __construct() $this->set_type(LINK_SURVEY); } - /** - * @return string - */ - public function get_name() + public function get_name(): string { - $this->get_survey_data(); + $survey = $this->get_survey_data(); - return $this->survey_data->getCode().': '.self::html_to_text($this->survey_data->getTitle()); + if (!$survey instanceof CSurvey) { + return get_lang('Untitled Survey'); + } + + return $survey->getCode() . ': ' . self::html_to_text($survey->getTitle()); } - /** - * @return string - */ - public function get_description() + public function get_description(): string { - $this->get_survey_data(); + $survey = $this->get_survey_data(); + + if (!$survey instanceof CSurvey) { + return ''; + } - return $this->survey_data->getSubtitle(); + return $survey->getSubtitle(); } - /** - * @return string - */ - public function get_type_name() + public function get_type_name(): string { return get_lang('Survey'); } - public function is_allowed_to_change_name() + public function is_allowed_to_change_name(): bool { return false; } - public function needs_name_and_description() + public function needs_name_and_description(): bool { return false; } - public function needs_max() + public function needs_max(): bool { return false; } - public function needs_results() + public function needs_results(): bool { return false; } @@ -77,27 +76,25 @@ public function needs_results() * * @return array 2-dimensional array - every element contains 2 subelements (id, name) */ - public function get_all_links() + public function get_all_links(): array { if (empty($this->course_id)) { - exit('Error in get_all_links() : course ID not set'); + return []; } - $sessionId = $this->get_session_id(); - $course_id = $this->getCourseId(); + $session = api_get_session_entity($this->get_session_id()); + $course = api_get_course_entity($this->getCourseId()); $repo = Container::getSurveyRepository(); - $course = api_get_course_entity($course_id); - $session = !empty($sessionId) ? api_get_session_entity($sessionId) : null; $qb = $repo->getResourcesByCourse($course, $session); $surveys = $qb->getQuery()->getResult(); $links = []; - /** @var \Chamilo\CourseBundle\Entity\CSurvey $survey */ + /** @var CSurvey $survey */ foreach ($surveys as $survey) { $links[] = [ $survey->getIid(), api_trunc_str( - $survey->getCode().': '.self::html_to_text($survey->getTitle()), + $survey->getCode() . ': ' . self::html_to_text($survey->getTitle()), 80 ), ]; @@ -110,28 +107,20 @@ public function get_all_links() * Has anyone done this survey yet? * Implementation of the AbstractLink class, mainly used dynamically in gradebook/lib/fe. */ - public function has_results() + public function has_results(): bool { - $ref_id = $this->get_ref_id(); - $sessionId = $this->get_session_id(); - $courseId = $this->getCourseId(); - - $tbl_survey = Database::get_course_table(TABLE_SURVEY); - $table = Database::get_course_table(TABLE_SURVEY_INVITATION); - $sql = "SELECT - COUNT(i.answered) - FROM $tbl_survey AS s - INNER JOIN $table AS i - ON s.code = i.survey_code - WHERE - i.c_id = $courseId AND - s.iid = $ref_id AND - i.session_id = $sessionId"; - - $sql_result = Database::query($sql); - $data = Database::fetch_array($sql_result); - - return 0 != $data[0]; + $survey = $this->get_survey_data(); + if (!$survey) { + return false; + } + + $repo = Container::getSurveyInvitationRepository(); + $course = api_get_course_entity($this->course_id); + $session = api_get_session_entity($this->get_session_id()); + + $results = $repo->getAnsweredInvitations($survey, $course, $session); + + return count($results) > 0; } /** @@ -142,111 +131,62 @@ public function has_results() * * @return array|null */ - public function calc_score($studentId = null, $type = null) + public function calc_score($studentId = null, $type = null): ?array { - // Note: Max score is assumed to be always 1 for surveys, - // only student's participation is to be taken into account. - $max_score = 1; - $ref_id = $this->get_ref_id(); - $sessionId = $this->get_session_id(); - $courseId = $this->getCourseId(); - $tbl_survey = Database::get_course_table(TABLE_SURVEY); - $tbl_survey_invitation = Database::get_course_table(TABLE_SURVEY_INVITATION); - $get_individual_score = !is_null($studentId); - - $sql = "SELECT i.answered - FROM $tbl_survey AS s - JOIN $tbl_survey_invitation AS i - ON s.code = i.survey_code - WHERE - i.c_id = $courseId AND - s.iid = $ref_id AND - i.session_id = $sessionId - "; - - if ($get_individual_score) { - $sql .= ' AND i.user = '.intval($studentId); + $survey = $this->get_survey_data(); + if (!$survey) { + return [null, null]; } - $sql_result = Database::query($sql); + $course = api_get_course_entity($this->course_id); + $session = api_get_session_entity($this->get_session_id()); + $repo = Container::getSurveyInvitationRepository(); + $max_score = 1; - if ($get_individual_score) { - // for 1 student - if ($data = Database::fetch_array($sql_result)) { - return [$data['answered'] ? $max_score : 0, $max_score]; - } + if ($studentId) { + $user = api_get_user_entity($studentId); + $answered = $repo->hasUserAnswered($survey, $course, $session, $user); - return [0, $max_score]; - } else { - // for all the students -> get average - $rescount = 0; - $sum = 0; - $bestResult = 0; - while ($data = Database::fetch_array($sql_result)) { - $sum += $data['answered'] ? $max_score : 0; - $rescount++; - if ($data['answered'] > $bestResult) { - $bestResult = $data['answered']; - } - } - $sum = $sum / $max_score; + return [$answered ? $max_score : 0, $max_score]; + } - if (0 == $rescount) { - return [null, null]; - } + $results = $repo->getAnsweredInvitations($survey, $course, $session); + $rescount = count($results); - switch ($type) { - case 'best': - return [$bestResult, $rescount]; - break; - case 'average': - return [$sum, $rescount]; - break; - case 'ranking': - return null; - break; - default: - return [$sum, $rescount]; - break; - } + if ($rescount === 0) { + return [null, null]; + } + + switch ($type) { + case 'best': + case 'average': + default: + return [$rescount, $rescount]; } } /** * Check if this still links to a survey. */ - public function is_valid_link() + public function is_valid_link(): bool { - $sessionId = $this->get_session_id(); - $courseId = $this->getCourseId(); - - $sql = 'SELECT count(iid) FROM '.$this->get_survey_table().' - WHERE - c_id = '.$courseId.' AND - iid = '.$this->get_ref_id().' AND - session_id = '.$sessionId; - $result = Database::query($sql); - $number = Database::fetch_row($result); - - return 0 != $number[0]; + return null !== $this->get_survey_data(); } - public function get_link() + public function get_link(): ?string { if ('true' === api_get_setting('survey.hide_survey_reporting_button')) { return null; } if (api_is_allowed_to_edit()) { - // Let students make access only through "Surveys" tool. - $sessionId = $this->get_session_id(); - $courseId = $this->getCourseId(); $survey = $this->get_survey_data(); - if ($survey) { - $survey_id = $survey->getIid(); + $sessionId = $this->get_session_id(); - return api_get_path(WEB_CODE_PATH).'survey/reporting.php?'. - api_get_cidreq_params($this->getCourseId(), $sessionId).'&survey_id='.$survey_id; + if ($survey) { + return api_get_path(WEB_CODE_PATH) . 'survey/reporting.php?' . + api_get_cidreq_params($this->getCourseId(), $sessionId) . + '&survey_id=' . $survey->getIid(); } } @@ -255,36 +195,25 @@ public function get_link() /** * Get the name of the icon for this tool. - * - * @return string */ - public function get_icon_name() + public function get_icon_name(): string { return 'survey'; } - /** - * Lazy load function to get the database table of the surveys. - */ - private function get_survey_table() - { - $this->survey_table = Database::get_course_table(TABLE_SURVEY); - - return $this->survey_table; - } - /** * Get the survey data from the c_survey table with the current object id. - * - * @return \Chamilo\CourseBundle\Entity\CSurvey */ - private function get_survey_data() + private function get_survey_data(): ?CSurvey { if (empty($this->survey_data)) { - $courseId = $this->getCourseId(); - $sessionId = $this->get_session_id(); $repo = Container::getSurveyRepository(); $survey = $repo->find($this->get_ref_id()); + + if (!$survey instanceof CSurvey) { + return null; + } + $this->survey_data = $survey; } @@ -293,10 +222,8 @@ private function get_survey_data() /** * @param string $string - * - * @return string */ - private static function html_to_text($string) + private static function html_to_text($string): string { return strip_tags($string); } diff --git a/public/main/inc/ajax/model.ajax.php b/public/main/inc/ajax/model.ajax.php index 22770ceae7a..c23de99b9d2 100644 --- a/public/main/inc/ajax/model.ajax.php +++ b/public/main/inc/ajax/model.ajax.php @@ -556,7 +556,8 @@ function getWhereClause($col, $oper, $val) $count = AnnouncementManager::getNumberAnnouncements($cid, $sid); break; case 'get_work_teacher': - $count = getWorkListTeacher(0, $limit, null, null, $whereCondition, true); + $countResult = getWorkListTeacher(0, $limit, null, null, $whereCondition, true); + $count = is_array($countResult) ? count($countResult) : (int) $countResult; break; case 'get_work_student': $count = getWorkListStudent(0, $limit, null, null, $whereCondition, true); diff --git a/public/main/work/work.lib.php b/public/main/work/work.lib.php index dc131f7a678..3423c55a31a 100644 --- a/public/main/work/work.lib.php +++ b/public/main/work/work.lib.php @@ -1546,7 +1546,7 @@ function getWorkListTeacher( ?string $direction, ?string $where_condition, ?bool $getCount = false -): array { +): int|array { $course_id = api_get_course_int_id(); $session_id = api_get_session_id(); $group_id = api_get_group_id(); diff --git a/src/CourseBundle/Repository/CSurveyInvitationRepository.php b/src/CourseBundle/Repository/CSurveyInvitationRepository.php index 3f14317f236..91209282feb 100644 --- a/src/CourseBundle/Repository/CSurveyInvitationRepository.php +++ b/src/CourseBundle/Repository/CSurveyInvitationRepository.php @@ -6,8 +6,11 @@ namespace Chamilo\CourseBundle\Repository; +use Chamilo\CoreBundle\Entity\Course; +use Chamilo\CoreBundle\Entity\Session; use Chamilo\CoreBundle\Entity\User; use Chamilo\CoreBundle\Repository\ResourceRepository; +use Chamilo\CourseBundle\Entity\CSurvey; use Chamilo\CourseBundle\Entity\CSurveyInvitation; use Datetime; use Doctrine\Common\Collections\Criteria; @@ -42,4 +45,55 @@ public function getUserPendingInvitations(User $user) return $qb->getQuery()->getResult(); } + + public function getAnsweredInvitations(CSurvey $survey, Course $course, ?Session $session = null): array + { + $qb = $this->createQueryBuilder('i') + ->select('i') + ->innerJoin('i.user', 'u') + ->innerJoin('i.survey', 's') + ->where('s = :survey') + ->andWhere('i.course = :course') + ->andWhere('i.answered = 1') + ->setParameter('survey', $survey) + ->setParameter('course', $course); + + if ($session) { + $qb->andWhere('i.session = :session') + ->setParameter('session', $session); + } else { + $qb->andWhere('i.session IS NULL'); + } + + return $qb->getQuery()->getResult(); + } + + public function hasUserAnswered( + CSurvey $survey, + Course $course, + User $user, + ?Session $session = null + ): bool { + $qb = $this->createQueryBuilder('i') + ->select('i') + ->innerJoin('i.survey', 's') + ->where('s = :survey') + ->andWhere('i.user = :user') + ->andWhere('i.course = :course') + ->andWhere('i.answered = 1') + ->setParameters([ + 'survey' => $survey, + 'user' => $user, + 'course' => $course, + ]); + + if ($session) { + $qb->andWhere('i.session = :session') + ->setParameter('session', $session); + } else { + $qb->andWhere('i.session IS NULL'); + } + + return (bool) $qb->getQuery()->getOneOrNullResult(); + } } From f9dd38e45e339cd39f9db70d68cd8201519695f8 Mon Sep 17 00:00:00 2001 From: Christian Beeznest Date: Tue, 15 Apr 2025 13:55:01 -0500 Subject: [PATCH 2/2] Gradebook: Fix order parameter in hasUserAnswered - refs BT#22564 --- public/main/gradebook/lib/be/surveylink.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/main/gradebook/lib/be/surveylink.class.php b/public/main/gradebook/lib/be/surveylink.class.php index e2ae1ed0964..2fb4a9672dd 100644 --- a/public/main/gradebook/lib/be/surveylink.class.php +++ b/public/main/gradebook/lib/be/surveylink.class.php @@ -145,7 +145,7 @@ public function calc_score($studentId = null, $type = null): ?array if ($studentId) { $user = api_get_user_entity($studentId); - $answered = $repo->hasUserAnswered($survey, $course, $session, $user); + $answered = $repo->hasUserAnswered($survey, $course, $user, $session); return [$answered ? $max_score : 0, $max_score]; }