Actualización

This commit is contained in:
Xes
2025-04-10 12:49:05 +02:00
parent 4aff98e77b
commit 1cdd00920f
9151 changed files with 1800913 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class PortfolioLink.
*/
class PortfolioLink extends EvalLink
{
public function __construct()
{
parent::__construct();
$this->set_type(LINK_PORTFOLIO);
}
public function get_type_name()
{
return get_lang('Portfolio');
}
public function is_allowed_to_change_name()
{
return false;
}
public function get_icon_name()
{
return 'portfolio';
}
protected function get_evaluation()
{
$this->evaluation = parent::get_evaluation();
$this->evaluation->set_type('portfolio');
return $this->evaluation;
}
}

View File

@@ -0,0 +1,803 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CoreBundle\Entity\GradebookLink;
/**
* Class AbstractLink
* Defines a gradebook AbstractLink object.
* To implement specific links,
* extend this class and define a type in LinkFactory.
* Use the methods in LinkFactory to create link objects.
*
* @author Bert Steppé
* @author Julio Montoya <gugli100@gmail.com> security improvements
*/
abstract class AbstractLink implements GradebookItem
{
public $course_id;
public $studentList;
/** @var GradebookLink */
public $entity;
protected $id;
protected $type;
protected $ref_id;
protected $user_id;
protected $course_code;
/** @var Category */
protected $category;
protected $created_at;
protected $weight;
protected $visible;
protected $session_id;
/**
* Constructor.
*/
public function __construct()
{
$this->course_id = api_get_course_int_id();
}
/**
* @return bool
*/
abstract public function has_results();
/**
* @return string
*/
abstract public function get_link();
/**
* @return bool
*/
abstract public function is_valid_link();
/**
* @return string
*/
abstract public function get_type_name();
/**
* @return bool
*/
abstract public function needs_name_and_description();
/**
* @return bool
*/
abstract public function needs_max();
/**
* @return bool
*/
abstract public function needs_results();
/**
* @return bool
*/
abstract public function is_allowed_to_change_name();
/**
* @return int
*/
public function get_id()
{
return $this->id;
}
/**
* @return string
*/
public function get_type()
{
return $this->type;
}
/**
* @return int
*/
public function get_ref_id()
{
return (int) $this->ref_id;
}
/**
* @return int
*/
public function get_session_id()
{
return (int) $this->session_id;
}
/**
* @return int
*/
public function get_user_id()
{
return $this->user_id;
}
/**
* @return string
*/
public function get_course_code()
{
return $this->course_code;
}
/**
* @return Category
*/
public function getCategory()
{
return $this->category;
}
/**
* @param Category $category
*/
public function setCategory($category)
{
$this->category = $category;
}
/**
* @return int
*/
public function get_category_id()
{
return $this->category->get_id();
}
/**
* @param int $category_id
*/
public function set_category_id($category_id)
{
$categories = Category::load($category_id);
if (isset($categories[0])) {
$this->setCategory($categories[0]);
}
}
public function get_date()
{
return $this->created_at;
}
public function get_weight()
{
return $this->weight;
}
public function is_locked()
{
return isset($this->locked) && 1 == $this->locked ? true : false;
}
public function is_visible()
{
return $this->visible;
}
public function set_id($id)
{
$this->id = $id;
}
public function set_type($type)
{
$this->type = $type;
}
public function set_ref_id($ref_id)
{
$this->ref_id = $ref_id;
}
public function set_user_id($user_id)
{
$this->user_id = $user_id;
}
/**
* @param string $course_code
*/
public function set_course_code($course_code)
{
$courseInfo = api_get_course_info($course_code);
if ($courseInfo) {
$this->course_code = $course_code;
$this->course_id = $courseInfo['real_id'];
}
}
/**
* @return array
*/
public function getStudentList()
{
if (empty($this->studentList)) {
return [];
}
return $this->studentList;
}
/**
* @param array $list
*/
public function setStudentList($list)
{
$this->studentList = $list;
}
public function set_date($date)
{
$this->created_at = $date;
}
public function set_weight($weight)
{
$this->weight = $weight;
}
public function set_visible($visible)
{
$this->visible = $visible;
}
/**
* @param int $id
*/
public function set_session_id($id)
{
$this->session_id = $id;
}
/**
* @param $locked
*/
public function set_locked($locked)
{
$this->locked = $locked;
}
/**
* @return int
*/
public function getCourseId()
{
return (int) $this->course_id;
}
/**
* Retrieve links and return them as an array of extensions of AbstractLink.
* To keep consistency, do not call this method but LinkFactory::load instead.
*
* @param int $id
* @param int $type
* @param int $ref_id
* @param int $user_id
* @param string $course_code
* @param int $category_id
* @param int $visible
*
* @return array
*/
public static function load(
$id = null,
$type = null,
$ref_id = null,
$user_id = null,
$course_code = null,
$category_id = null,
$visible = null
) {
$tbl_grade_links = Database::get_main_table(TABLE_MAIN_GRADEBOOK_LINK);
$sql = 'SELECT * FROM '.$tbl_grade_links;
$paramcount = 0;
if (isset($id)) {
$sql .= ' WHERE id = '.intval($id);
$paramcount++;
}
if (isset($type)) {
if (0 != $paramcount) {
$sql .= ' AND';
} else {
$sql .= ' WHERE';
}
$sql .= ' type = '.intval($type);
$paramcount++;
}
if (isset($ref_id)) {
if (0 != $paramcount) {
$sql .= ' AND';
} else {
$sql .= ' WHERE';
}
$sql .= ' ref_id = '.intval($ref_id);
$paramcount++;
}
if (isset($user_id)) {
if (0 != $paramcount) {
$sql .= ' AND';
} else {
$sql .= ' WHERE';
}
$sql .= ' user_id = '.intval($user_id);
$paramcount++;
}
if (isset($course_code)) {
if (0 != $paramcount) {
$sql .= ' AND';
} else {
$sql .= ' WHERE';
}
$sql .= " course_code = '".Database::escape_string($course_code)."'";
$paramcount++;
}
if (isset($category_id)) {
if (0 != $paramcount) {
$sql .= ' AND';
} else {
$sql .= ' WHERE';
}
$sql .= ' category_id = '.intval($category_id);
$paramcount++;
}
if (isset($visible)) {
if (0 != $paramcount) {
$sql .= ' AND';
} else {
$sql .= ' WHERE';
}
$sql .= ' visible = '.intval($visible);
}
$result = Database::query($sql);
$links = self::create_objects_from_sql_result($result);
return $links;
}
/**
* Insert this link into the database.
*/
public function add()
{
$this->add_linked_data();
if (isset($this->type) &&
isset($this->ref_id) &&
isset($this->user_id) &&
isset($this->course_code) &&
isset($this->category) &&
isset($this->weight) &&
isset($this->visible)
) {
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_LINK);
$sql = "SELECT count(*) count FROM $table
WHERE
ref_id = ".$this->get_ref_id()." AND
category_id = ".$this->category->get_id()." AND
course_code = '".$this->course_code."' AND
type = ".$this->type;
$result = Database::query($sql);
$row = Database::fetch_array($result, 'ASSOC');
if ($row['count'] == 0) {
$params = [
'type' => $this->get_type(),
'ref_id' => $this->get_ref_id(),
'user_id' => $this->get_user_id(),
'course_code' => $this->get_course_code(),
'category_id' => $this->get_category_id(),
'weight' => $this->get_weight(),
'visible' => $this->is_visible(),
'created_at' => api_get_utc_datetime(),
'locked' => 0,
];
$id = Database::insert($table, $params);
$this->set_id($id);
return $id;
}
}
return false;
}
/**
* Update the properties of this link in the database.
*/
public function save()
{
$em = Database::getManager();
$link = $em->find('ChamiloCoreBundle:GradebookLink', $this->id);
if (!$link) {
return;
}
self::add_link_log($this->id);
$this->save_linked_data();
$link
->setType($this->get_type())
->setRefId($this->get_ref_id())
->setUserId($this->get_user_id())
->setCourseCode($this->get_course_code())
->setCategoryId($this->get_category_id())
->setWeight($this->get_weight())
->setVisible($this->is_visible());
$em->merge($link);
$em->flush();
}
/**
* @param int $evaluationId
*/
public static function add_link_log($evaluationId, $nameLog = null)
{
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_LINKEVAL_LOG);
$dateobject = self::load($evaluationId, null, null, null, null);
$now = api_get_utc_datetime();
$arreval = get_object_vars($dateobject[0]);
$description_log = isset($arreval['description']) ? $arreval['description'] : '';
if (empty($nameLog)) {
if (isset($_POST['name_link'])) {
$name_log = isset($_POST['name_link']) ? $_POST['name_link'] : $arreval['course_code'];
} elseif (isset($_POST['link_'.$evaluationId]) && $_POST['link_'.$evaluationId]) {
$name_log = $_POST['link_'.$evaluationId];
} else {
$name_log = $arreval['course_code'];
}
} else {
$name_log = $nameLog;
}
$params = [
'id_linkeval_log' => $arreval['id'],
'name' => $name_log,
'description' => $description_log,
'created_at' => $now,
'weight' => $arreval['weight'],
'visible' => $arreval['visible'],
'type' => 'Link',
'user_id_log' => api_get_user_id(),
];
Database::insert($table, $params);
}
/**
* Delete this link from the database.
*/
public function delete()
{
$this->delete_linked_data();
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_LINK);
$sql = 'DELETE FROM '.$table.'
WHERE id = '.intval($this->id);
Database::query($sql);
}
/**
* Generate an array of possible categories where this link can be moved to.
* Notice: its own parent will be included in the list: it's up to the frontend
* to disable this element.
*
* @return array 2-dimensional array - every element contains 3 subelements (id, name, level)
*/
public function get_target_categories()
{
// links can only be moved to categories inside this course
$targets = [];
$level = 0;
$categories = Category::load(null, null, $this->get_course_code(), 0);
foreach ($categories as $cat) {
$targets[] = [$cat->get_id(), $cat->get_name(), $level + 1];
$targets = $this->addTargetSubcategories(
$targets,
$level + 1,
$cat->get_id()
);
}
return $targets;
}
/**
* Move this link to the given category.
* If this link moves to outside a course, delete it.
*/
public function move_to_cat($cat)
{
if ($this->get_course_code() != $cat->get_course_code()) {
$this->delete();
} else {
$this->set_category_id($cat->get_id());
$this->save();
}
}
/**
* Find links by name
* To keep consistency, do not call this method but LinkFactory::find_links instead.
*
* @todo can be written more efficiently using a new (but very complex) sql query
*
* @param string $name_mask
*
* @return array
*/
public function find_links($name_mask, $selectcat)
{
$rootcat = Category::load($selectcat);
$links = $rootcat[0]->get_links((api_is_allowed_to_edit() ? null : api_get_user_id()), true);
$foundlinks = [];
foreach ($links as $link) {
if (!(api_strpos(api_strtolower($link->get_name()), api_strtolower($name_mask)) === false)) {
$foundlinks[] = $link;
}
}
return $foundlinks;
}
/**
* @return string
*/
public function get_item_type()
{
return 'L';
}
/**
* @return string
*/
public function get_icon_name()
{
return 'link';
}
public function get_all_links()
{
return [];
}
public function add_linked_data()
{
}
public function save_linked_data()
{
}
public function delete_linked_data()
{
}
/**
* @param string $name
*/
public function set_name($name)
{
}
/**
* @param string $description
*/
public function set_description($description)
{
}
/**
* @param int $max
*/
public function set_max($max)
{
}
public function get_view_url($stud_id)
{
return null;
}
/**
* Locks a link.
*
* @param int $locked 1 or unlocked 0
*
* */
public function lock($locked)
{
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_LINK);
$sql = "UPDATE $table SET locked = '".intval($locked)."'
WHERE id='".$this->id."'";
Database::query($sql);
}
/**
* Get current user ranking.
*
* @param int $userId
* @param array $studentList Array with user id and scores
* Example: [1 => 5.00, 2 => 8.00]
*
* @return array
*/
public static function getCurrentUserRanking($userId, $studentList)
{
$ranking = null;
$currentUserId = $userId;
if (!empty($studentList) && !empty($currentUserId)) {
$studentList = array_map('floatval', $studentList);
asort($studentList);
$ranking = $count = count($studentList);
foreach ($studentList as $userId => $position) {
if ($currentUserId == $userId) {
break;
}
$ranking--;
}
// If no ranking was detected.
if ($ranking == 0) {
return [];
}
return [$ranking, $count];
}
return [];
}
/**
* @return string
*/
public function getSkillsFromItem()
{
$toolType = '';
switch ($this->type) {
case LINK_ATTENDANCE:
$toolType = ITEM_TYPE_ATTENDANCE;
break;
case LINK_EXERCISE:
$toolType = ITEM_TYPE_EXERCISE;
break;
case LINK_FORUM_THREAD:
$toolType = ITEM_TYPE_FORUM_THREAD;
break;
case LINK_LEARNPATH:
$toolType = ITEM_TYPE_LEARNPATH;
break;
case LINK_HOTPOTATOES:
$toolType = ITEM_TYPE_HOTPOTATOES;
break;
case LINK_STUDENTPUBLICATION:
$toolType = ITEM_TYPE_STUDENT_PUBLICATION;
break;
case LINK_SURVEY:
$toolType = ITEM_TYPE_SURVEY;
break;
case LINK_PORTFOLIO:
$toolType = ITEM_TYPE_PORTFOLIO;
break;
}
$skillToString = Skill::getSkillRelItemsToString($toolType, $this->get_ref_id());
return $skillToString;
}
/**
* @param int $itemId
* @param int $linkType
* @param string $courseCode
* @param int $sessionId
*
* @return array|bool|\Doctrine\DBAL\Driver\Statement
*/
public static function getGradebookLinksFromItem($itemId, $linkType, $courseCode, $sessionId = 0)
{
if (empty($courseCode) || empty($itemId) || empty($linkType)) {
return false;
}
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_LINK);
$tableCategory = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
$itemId = (int) $itemId;
$linkType = (int) $linkType;
$sessionId = (int) $sessionId;
$sessionCondition = api_get_session_condition($sessionId, true, false, 'c.session_id');
$courseCode = Database::escape_string($courseCode);
$sql = "SELECT DISTINCT l.*
FROM $table l INNER JOIN $tableCategory c
ON (c.course_code = l.course_code AND c.id = l.category_id)
WHERE
ref_id = $itemId AND
type = $linkType AND
l.course_code = '$courseCode'
$sessionCondition ";
$result = Database::query($sql);
if (Database::num_rows($result)) {
$result = Database::store_result($result);
return $result;
}
return false;
}
/**
* @param Doctrine\DBAL\Driver\Statement|null $result
*
* @return array
*/
private static function create_objects_from_sql_result($result)
{
$links = [];
$allow = api_get_configuration_value('allow_gradebook_stats');
if ($allow) {
$em = Database::getManager();
$repo = $em->getRepository('ChamiloCoreBundle:GradebookLink');
}
while ($data = Database::fetch_array($result)) {
$link = LinkFactory::create($data['type']);
$link->set_id($data['id']);
$link->set_type($data['type']);
$link->set_ref_id($data['ref_id']);
$link->set_user_id($data['user_id']);
$link->set_course_code($data['course_code']);
$link->set_category_id($data['category_id']);
$link->set_date($data['created_at']);
$link->set_weight($data['weight']);
$link->set_visible($data['visible']);
$link->set_locked($data['locked']);
//session id should depend of the category --> $data['category_id']
$session_id = api_get_session_id();
$link->set_session_id($session_id);
if ($allow) {
$link->entity = $repo->find($data['id']);
}
$links[] = $link;
}
return $links;
}
/**
* Internal function used by get_target_categories().
*
* @param array $targets
* @param int $level
* @param int $catid
*
* @return array
*/
private function addTargetSubcategories($targets, $level, $catid)
{
$subcats = Category::load(null, null, null, $catid);
foreach ($subcats as $cat) {
$targets[] = [$cat->get_id(), $cat->get_name(), $level + 1];
$targets = $this->addTargetSubcategories(
$targets,
$level + 1,
$cat->get_id()
);
}
return $targets;
}
}

View File

@@ -0,0 +1,276 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Gradebook link to attendance item.
*
* @author Christian Fasanando (christian1827@gmail.com)
*/
class AttendanceLink extends AbstractLink
{
private $attendance_table = null;
private $itemprop_table = null;
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
$this->set_type(LINK_ATTENDANCE);
}
/**
* @return string
*/
public function get_type_name()
{
return get_lang('Attendance');
}
/**
* @return bool
*/
public function is_allowed_to_change_name()
{
return false;
}
/**
* Generate an array of all attendances available.
*
* @return array 2-dimensional array - every element contains 2 subelements (id, name)
*/
public function get_all_links()
{
if (empty($this->course_code)) {
return [];
}
$tbl_attendance = $this->get_attendance_table();
$sessionId = $this->get_session_id();
$sql = 'SELECT att.id, att.name, att.attendance_qualify_title
FROM '.$tbl_attendance.' att
WHERE
att.c_id = '.$this->course_id.' AND
att.active = 1 AND
att.session_id = '.$sessionId;
$result = Database::query($sql);
while ($data = Database::fetch_array($result)) {
if (isset($data['attendance_qualify_title']) && '' != $data['attendance_qualify_title']) {
$cats[] = [$data['id'], $data['attendance_qualify_title']];
} else {
$cats[] = [$data['id'], $data['name']];
}
}
$my_cats = isset($cats) ? $cats : [];
return $my_cats;
}
/**
* Has anyone done this exercise yet ?
*/
public function has_results()
{
$tbl_attendance_result = Database::get_course_table(TABLE_ATTENDANCE_RESULT);
$sessionId = $this->get_session_id();
$sql = 'SELECT count(*) AS number FROM '.$tbl_attendance_result."
WHERE
session_id = $sessionId AND
c_id = '.$this->course_id.' AND
attendance_id = '".$this->get_ref_id()."'";
$result = Database::query($sql);
$number = Database::fetch_row($result);
return 0 != $number[0];
}
/**
* @param int $stud_id
*
* @return array|null
*/
public function calc_score($stud_id = null, $type = null)
{
$tbl_attendance_result = Database::get_course_table(TABLE_ATTENDANCE_RESULT);
$sessionId = $this->get_session_id();
// get attendance qualify max
$sql = 'SELECT att.attendance_qualify_max
FROM '.$this->get_attendance_table().' att
WHERE
att.c_id = '.$this->course_id.' AND
att.id = '.$this->get_ref_id().' AND
att.session_id = '.$sessionId;
$query = Database::query($sql);
$attendance = Database::fetch_array($query, 'ASSOC');
// Get results
$sql = 'SELECT *
FROM '.$tbl_attendance_result.'
WHERE c_id = '.$this->course_id.' AND attendance_id = '.$this->get_ref_id();
if (isset($stud_id)) {
$sql .= ' AND user_id = '.intval($stud_id);
}
$scores = Database::query($sql);
// for 1 student
if (isset($stud_id)) {
if ($data = Database::fetch_array($scores, 'ASSOC')) {
return [
$data['score'],
$attendance['attendance_qualify_max'],
];
} else {
//We sent the 0/attendance_qualify_max instead of null for correct calculations
return [0, $attendance['attendance_qualify_max']];
}
} else {
// all students -> get average
$students = []; // user list, needed to make sure we only
// take first attempts into account
$rescount = 0;
$sum = 0;
$sumResult = 0;
$bestResult = 0;
while ($data = Database::fetch_array($scores)) {
if (!(array_key_exists($data['user_id'], $students))) {
if (0 != $attendance['attendance_qualify_max']) {
$students[$data['user_id']] = $data['score'];
$rescount++;
$sum += $data['score'] / $attendance['attendance_qualify_max'];
$sumResult += $data['score'];
if ($data['score'] > $bestResult) {
$bestResult = $data['score'];
}
$weight = $attendance['attendance_qualify_max'];
}
}
}
if (0 == $rescount) {
return [null, null];
} else {
switch ($type) {
case 'best':
return [$bestResult, $weight];
break;
case 'average':
return [$sumResult / $rescount, $weight];
break;
case 'ranking':
return AbstractLink::getCurrentUserRanking($stud_id, $students);
break;
default:
return [$sum, $rescount];
break;
}
}
}
}
public function needs_name_and_description()
{
return false;
}
public function needs_max()
{
return false;
}
public function needs_results()
{
return false;
}
/**
* @return string
*/
public function get_name()
{
$this->get_attendance_data();
$attendance_title = isset($this->attendance_data['name']) ? $this->attendance_data['name'] : '';
$attendance_qualify_title = isset($this->attendance_data['attendance_qualify_title']) ? $this->attendance_data['attendance_qualify_title'] : '';
if (isset($attendance_qualify_title) && '' != $attendance_qualify_title) {
return $this->attendance_data['attendance_qualify_title'];
} else {
return $attendance_title;
}
}
/**
* @return string
*/
public function get_description()
{
return '';
}
/**
* Check if this still links to an exercise.
*/
public function is_valid_link()
{
$sql = 'SELECT count(att.id) FROM '.$this->get_attendance_table().' att
WHERE att.c_id = '.$this->course_id.' AND att.id = '.$this->get_ref_id();
$result = Database::query($sql);
$number = Database::fetch_row($result);
return 0 != $number[0];
}
public function get_link()
{
// it was extracts the attendance id
$sessionId = $this->get_session_id();
$sql = 'SELECT * FROM '.$this->get_attendance_table().' att
WHERE att.c_id = '.$this->course_id.' AND att.id = '.$this->get_ref_id();
$result = Database::query($sql);
$row = Database::fetch_array($result, 'ASSOC');
$attendance_id = $row['id'];
$url = api_get_path(WEB_PATH).'main/attendance/index.php?action=attendance_sheet_list&gradebook=view&attendance_id='.$attendance_id.'&'.api_get_cidreq_params($this->get_course_code(), $sessionId);
return $url;
}
/**
* @return string
*/
public function get_icon_name()
{
return 'attendance';
}
/**
* Lazy load function to get the database table of the student publications.
*/
private function get_attendance_table()
{
$this->attendance_table = Database::get_course_table(TABLE_ATTENDANCE);
return $this->attendance_table;
}
/**
* @return array|bool
*/
private function get_attendance_data()
{
$tbl_name = $this->get_attendance_table();
if ('' == $tbl_name) {
return false;
} elseif (!isset($this->attendance_data)) {
$sql = 'SELECT * FROM '.$this->get_attendance_table().' att
WHERE att.c_id = '.$this->course_id.' AND att.id = '.$this->get_ref_id();
$query = Database::query($sql);
$this->attendance_data = Database::fetch_array($query);
}
return $this->attendance_data;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Gradebook link to dropbox item.
*
* @author Bert Steppé
*/
class DropboxLink extends EvalLink
{
private $dropbox_table = null;
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
$this->set_type(LINK_DROPBOX);
}
/**
* Returns the URL of a document
* This function is loaded when using a gradebook as a tab (gradebook = -1) see issue #2705.
*/
public function get_view_url($stud_id)
{
// find a file uploaded by the given student,
// with the same title as the evaluation name
$eval = $this->get_evaluation();
$sql = 'SELECT filename FROM '.$this->get_dropbox_table().'
WHERE
c_id = '.$this->course_id.' AND
uploader_id = '.intval($stud_id)." AND
title = '".Database::escape_string($eval->get_name())."'";
$result = Database::query($sql);
if ($fileurl = Database::fetch_row($result)) {
return null;
} else {
return null;
}
}
public function get_type_name()
{
return get_lang('LMSDropbox');
}
public function is_allowed_to_change_name()
{
return false;
}
public function get_icon_name()
{
return 'dropbox';
}
/**
* Lazy load function to get the dropbox database table.
*/
private function get_dropbox_table()
{
$this->dropbox_table = Database::get_course_table(TABLE_DROPBOX_FILE);
return $this->dropbox_table;
}
}

View File

@@ -0,0 +1,187 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class to be used as basis for links referring to Evaluation objects.
*
* @author Bert Steppé
*/
abstract class EvalLink extends AbstractLink
{
protected $evaluation;
/**
* @return bool
*/
public function has_results()
{
$eval = $this->get_evaluation();
return $eval->has_results();
}
/**
* @param int $userId
* @param string $type
*
* @return array
*/
public function calc_score($userId = null, $type = null)
{
$eval = $this->get_evaluation();
return $eval->calc_score($userId, $type);
}
public function get_link()
{
$eval = $this->get_evaluation();
// course/platform admin can go to the view_results page
if (api_is_allowed_to_edit()) {
return 'gradebook_view_result.php?'.api_get_cidreq().'&selecteval='.$eval->get_id();
} elseif (ScoreDisplay::instance()->is_custom()) {
// students can go to the statistics page (if custom display enabled)
return 'gradebook_statistics.php?'.api_get_cidreq().'&selecteval='.$eval->get_id();
}
return null;
}
public function get_name()
{
$eval = $this->get_evaluation();
return $eval->get_name();
}
public function get_description()
{
$eval = $this->get_evaluation();
return $eval->get_description();
}
public function get_max()
{
$eval = $this->get_evaluation();
return $eval->get_max();
}
public function is_valid_link()
{
$eval = $this->get_evaluation();
return isset($eval);
}
public function needs_name_and_description()
{
return true;
}
public function needs_max()
{
return true;
}
public function needs_results()
{
return true;
}
public function add_linked_data()
{
if ($this->is_valid_link()) {
$this->evaluation->add();
$this->set_ref_id($this->evaluation->get_id());
}
}
public function save_linked_data()
{
if ($this->is_valid_link()) {
$this->evaluation->save();
}
}
public function delete_linked_data()
{
if ($this->is_valid_link()) {
$this->evaluation->delete_with_results();
}
}
public function set_name($name)
{
if ($this->is_valid_link()) {
$this->evaluation->set_name($name);
}
}
public function set_description($description)
{
if ($this->is_valid_link()) {
$this->evaluation->set_description($description);
}
}
public function set_max($max)
{
if ($this->is_valid_link()) {
$this->evaluation->set_max($max);
}
}
// Functions overriding non-trivial implementations from AbstractLink
public function set_date($date)
{
$this->created_at = $date;
if ($this->is_valid_link()) {
$this->evaluation->set_date($date);
}
}
public function set_weight($weight)
{
$this->weight = $weight;
if ($this->is_valid_link()) {
$this->evaluation->set_weight($weight);
}
}
public function set_visible($visible)
{
$this->visible = $visible;
if ($this->is_valid_link()) {
$this->evaluation->set_visible($visible);
}
}
/**
* Lazy load function to get the linked evaluation.
*/
protected function get_evaluation()
{
if (!isset($this->evaluation)) {
if (isset($this->ref_id)) {
$evalarray = Evaluation::load($this->get_ref_id());
$this->evaluation = $evalarray[0];
} else {
$eval = new Evaluation();
$eval->set_category_id(-1);
$eval->set_date(api_get_utc_datetime()); // these values will be changed
$eval->set_weight(0); // when the link setter
$eval->set_visible(0); // is called
$eval->set_id(-1); // a 'real' id will be set when eval is added to db
$eval->set_user_id($this->get_user_id());
$eval->set_course_code($this->get_course_code());
$this->evaluation = $eval;
$this->set_ref_id($eval->get_id());
}
}
return $this->evaluation;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,676 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class ExerciseLink
* Defines a gradebook ExerciseLink object.
*
* @author Bert Steppé
*/
class ExerciseLink extends AbstractLink
{
// This variable is used in the WSGetGradebookUserItemScore service, to check base course tests.
public $checkBaseExercises = false;
private $course_info;
private $exercise_table;
private $exercise_data = [];
private $is_hp;
/**
* @param int $hp
*/
public function __construct($hp = 0)
{
parent::__construct();
$this->set_type(LINK_EXERCISE);
$this->is_hp = $hp;
if (1 == $this->is_hp) {
$this->set_type(LINK_HOTPOTATOES);
}
}
/**
* Generate an array of all exercises available.
*
* @param bool $getOnlyHotPotatoes
*
* @return array 2-dimensional array - every element contains 2 subelements (id, name)
*/
public function get_all_links($getOnlyHotPotatoes = false)
{
$TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
$tableItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
$exerciseTable = $this->get_exercise_table();
$lpItemTable = Database::get_course_table(TABLE_LP_ITEM);
$lpTable = Database::get_course_table(TABLE_LP_MAIN);
$documentPath = api_get_path(SYS_COURSE_PATH).$this->course_code.'/document';
if (empty($this->course_code)) {
return [];
}
$sessionId = $this->get_session_id();
if (empty($sessionId)) {
$session_condition = api_get_session_condition(0, true, false, 'e.session_id');
} else {
$session_condition = api_get_session_condition($sessionId, true, true, 'e.session_id');
}
// @todo
$uploadPath = null;
$courseId = $this->course_id;
$sql = "SELECT iid, title FROM $exerciseTable e
WHERE
c_id = $courseId AND
active = 1
$session_condition ";
$sqlLp = "SELECT e.iid, e.title, lp.name lp_name
FROM $exerciseTable e
INNER JOIN $lpItemTable i
ON (e.c_id = i.c_id AND e.iid = i.path)
INNER JOIN $lpTable lp
ON (lp.c_id = e.c_id AND lp.id = i.lp_id)
WHERE
e.c_id = $courseId AND
active = 0 AND
item_type = 'quiz'
$session_condition";
$sql2 = "SELECT d.path as path, d.comment as comment, ip.visibility as visibility, d.id
FROM $TBL_DOCUMENT d
INNER JOIN $tableItemProperty ip
ON (d.id = ip.ref AND d.c_id = ip.c_id)
WHERE
d.c_id = $courseId AND
ip.c_id = $courseId AND
ip.tool = '".TOOL_DOCUMENT."' AND
(d.path LIKE '%htm%') AND
(d.path LIKE '%HotPotatoes_files%') AND
d.path LIKE '".Database::escape_string($uploadPath.'/%/%')."' AND
ip.visibility = '1'
";
require_once api_get_path(SYS_CODE_PATH).'exercise/hotpotatoes.lib.php';
$exerciseInLP = [];
if (!$this->is_hp) {
$result = Database::query($sql);
$resultLp = Database::query($sqlLp);
$exerciseInLP = Database::store_result($resultLp);
} else {
$result2 = Database::query($sql2);
}
$cats = [];
$exerciseList = [];
if (isset($result)) {
if (Database::num_rows($result) > 0) {
while ($data = Database::fetch_array($result)) {
$cats[] = [$data['iid'], $data['title']];
$exerciseList[] = $data;
}
}
}
$hotPotatoes = [];
if (isset($result2)) {
if (Database::num_rows($result2) > 0) {
while ($row = Database::fetch_array($result2)) {
$attribute['path'][] = $row['path'];
$attribute['visibility'][] = $row['visibility'];
$attribute['comment'][] = $row['comment'];
$attribute['id'] = $row['id'];
if (isset($attribute['path']) && is_array($attribute['path'])) {
foreach ($attribute['path'] as $path) {
$title = GetQuizName($path, $documentPath);
if ($title == '') {
$title = basename($path);
}
$element = [$attribute['id'], $title.'(HP)'];
$cats[] = $element;
$hotPotatoes[] = $element;
}
}
}
}
}
if ($getOnlyHotPotatoes) {
return $hotPotatoes;
}
if (!empty($exerciseInLP)) {
$allExercises = array_column($exerciseList, 'iid');
foreach ($exerciseInLP as $exercise) {
if (in_array($exercise['iid'], $allExercises)) {
continue;
}
$allExercises[] = $exercise['iid'];
//$lpName = strip_tags($exercise['lp_name']);
/*$cats[] = [
$exercise['iid'],
strip_tags(Exercise::get_formated_title_variable($exercise['title'])).
' ('.get_lang('ToolLearnpath').' - '.$lpName.')',
];*/
$cats[] = [
$exercise['iid'],
strip_tags(Exercise::get_formated_title_variable($exercise['title'])),
];
}
}
return $cats;
}
/**
* Has anyone done this exercise yet ?
*/
public function has_results()
{
$tbl_stats = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$sessionId = $this->get_session_id();
$course_id = api_get_course_int_id($this->get_course_code());
$sql = "SELECT count(exe_id) AS number
FROM $tbl_stats
WHERE
session_id = $sessionId AND
c_id = $course_id AND
exe_exo_id = ".$this->get_ref_id();
$result = Database::query($sql);
$number = Database::fetch_row($result);
return $number[0] != 0;
}
/**
* Get the score of this exercise. Only the first attempts are taken into account.
*
* @param int $stud_id student id (default: all students who have results -
* then the average is returned)
* @param string $type
*
* @return array (score, max) if student is given
* array (sum of scores, number of scores) otherwise
* or null if no scores available
*/
public function calc_score($stud_id = null, $type = null)
{
$allowStats = api_get_configuration_value('allow_gradebook_stats');
if ($allowStats) {
$link = $this->entity;
if (!empty($link)) {
$weight = $link->getScoreWeight();
switch ($type) {
case 'best':
$bestResult = $link->getBestScore();
return [$bestResult, $weight];
break;
case 'average':
$count = count($link->getUserScoreList());
//$count = count($this->getStudentList());
if (empty($count)) {
return [0, $weight];
}
$sumResult = array_sum($link->getUserScoreList());
return [$sumResult / $count, $weight];
break;
case 'ranking':
return [null, null];
break;
default:
if (!empty($stud_id)) {
$scoreList = $link->getUserScoreList();
$result = [0, $weight];
if (isset($scoreList[$stud_id])) {
$result = [$scoreList[$stud_id], $weight];
}
return $result;
} else {
$studentCount = count($this->getStudentList());
$sumResult = array_sum($link->getUserScoreList());
$result = [$sumResult, $studentCount];
}
return $result;
break;
}
}
}
$tblStats = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$tblHp = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
$tblDoc = Database::get_course_table(TABLE_DOCUMENT);
/* the following query should be similar (in conditions) to the one used
in exercise/exercise.php, look for note-query-exe-results marker*/
$sessionId = $this->get_session_id();
$courseId = $this->getCourseId();
$exerciseData = $this->get_exercise_data();
$exerciseId = isset($exerciseData['iid']) ? (int) $exerciseData['iid'] : 0;
$stud_id = (int) $stud_id;
if (empty($exerciseId)) {
return null;
}
$key = 'exercise_link_id:'.
$this->get_id().
'exerciseId:'.$exerciseId.'student:'.$stud_id.'session:'.$sessionId.'courseId:'.$courseId.'type:'.$type;
$useCache = api_get_configuration_value('gradebook_use_apcu_cache');
$cacheAvailable = api_get_configuration_value('apc') && $useCache;
$cacheDriver = null;
if ($cacheAvailable) {
$cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
if ($cacheDriver->contains($key)) {
return $cacheDriver->fetch($key);
}
}
$exercise = new Exercise($courseId);
$exercise->read($exerciseId);
if (!$this->is_hp) {
if (false == $exercise->exercise_was_added_in_lp) {
$sql = "SELECT * FROM $tblStats
WHERE
exe_exo_id = $exerciseId AND
orig_lp_id = 0 AND
orig_lp_item_id = 0 AND
status <> 'incomplete' AND
session_id = $sessionId AND
c_id = $courseId
";
} else {
$lpCondition = null;
if (!empty($exercise->lpList)) {
//$lpId = $exercise->getLpBySession($sessionId);
$lpList = [];
foreach ($exercise->lpList as $lpData) {
if ($this->checkBaseExercises) {
if ((int) $lpData['session_id'] == 0) {
$lpList[] = $lpData['lp_id'];
}
} else {
if ((int) $lpData['session_id'] == $sessionId) {
$lpList[] = $lpData['lp_id'];
}
}
}
if (empty($lpList) && !empty($sessionId)) {
// Check also if an LP was added in the base course.
foreach ($exercise->lpList as $lpData) {
if ((int) $lpData['session_id'] == 0) {
$lpList[] = $lpData['lp_id'];
}
}
}
$lpCondition = ' (orig_lp_id = 0 OR (orig_lp_id IN ("'.implode('", "', $lpList).'"))) AND ';
}
$sql = "SELECT *
FROM $tblStats
WHERE
exe_exo_id = $exerciseId AND
$lpCondition
status <> 'incomplete' AND
session_id = $sessionId AND
c_id = $courseId ";
}
if (!empty($stud_id) && 'ranking' !== $type) {
$sql .= " AND exe_user_id = $stud_id ";
}
$sql .= ' ORDER BY exe_id DESC ';
} else {
$sql = "SELECT * FROM $tblHp hp
INNER JOIN $tblDoc doc
ON (hp.exe_name = doc.path AND doc.c_id = hp.c_id)
WHERE
hp.c_id = $courseId AND
doc.id = $exerciseId";
if (!empty($stud_id)) {
$sql .= " AND hp.exe_user_id = $stud_id ";
}
}
$scores = Database::query($sql);
if (isset($stud_id) && empty($type)) {
// for 1 student
if ($data = Database::fetch_array($scores, 'ASSOC')) {
$attempts = Database::query($sql);
$counter = 0;
while ($attempt = Database::fetch_array($attempts)) {
$counter++;
}
$result = [$data['exe_result'], $data['exe_weighting'], $data['exe_date'], $counter];
if ($cacheAvailable) {
$cacheDriver->save($key, $result);
}
return $result;
} else {
if ($cacheAvailable) {
$cacheDriver->save($key, null);
}
return null;
}
} else {
// all students -> get average
// normal way of getting the info
$students = []; // user list, needed to make sure we only
// take first attempts into account
$student_count = 0;
$sum = 0;
$bestResult = 0;
$weight = 0;
$sumResult = 0;
$studentList = $this->getStudentList();
$studentIdList = [];
if (!empty($studentList)) {
$studentIdList = array_column($studentList, 'user_id');
}
while ($data = Database::fetch_array($scores, 'ASSOC')) {
// Only take into account users in the current student list.
if (!empty($studentIdList)) {
if (!in_array($data['exe_user_id'], $studentIdList)) {
continue;
}
}
if (!isset($students[$data['exe_user_id']])) {
if ($data['exe_weighting'] != 0) {
$students[$data['exe_user_id']] = $data['exe_result'];
$student_count++;
if ($data['exe_result'] > $bestResult) {
$bestResult = $data['exe_result'];
}
$sum += $data['exe_result'] / $data['exe_weighting'];
$sumResult += $data['exe_result'];
$weight = $data['exe_weighting'];
}
}
}
if ($student_count == 0) {
if ($cacheAvailable) {
$cacheDriver->save($key, null);
}
return null;
} else {
switch ($type) {
case 'best':
$result = [$bestResult, $weight];
if ($cacheAvailable) {
$cacheDriver->save($key, $result);
}
return $result;
break;
case 'average':
$count = count($this->getStudentList());
if (empty($count)) {
$result = [0, $weight];
if ($cacheAvailable) {
$cacheDriver->save($key, $result);
}
return $result;
}
$result = [$sumResult / $count, $weight];
if ($cacheAvailable) {
$cacheDriver->save($key, $result);
}
return $result;
break;
case 'ranking':
$ranking = AbstractLink::getCurrentUserRanking($stud_id, $students);
if ($cacheAvailable) {
$cacheDriver->save($key, $ranking);
}
return $ranking;
break;
default:
$result = [$sum, $student_count];
if ($cacheAvailable) {
$cacheDriver->save($key, $result);
}
return $result;
break;
}
}
}
}
/**
* Get URL where to go to if the user clicks on the link.
* First we go to exercise_jump.php and then to the result page.
* Check this php file for more info.
*/
public function get_link()
{
$sessionId = $this->get_session_id();
$data = $this->get_exercise_data();
$exerciseId = $data['iid'];
$path = isset($data['path']) ? $data['path'] : '';
return api_get_path(WEB_CODE_PATH).'gradebook/exercise_jump.php?'
.http_build_query(
[
'path' => $path,
'session_id' => $sessionId,
'cidReq' => $this->get_course_code(),
'gradebook' => 'view',
'exerciseId' => $exerciseId,
'type' => $this->get_type(),
]
);
}
/**
* Get name to display: same as exercise title.
*/
public function get_name()
{
$documentPath = api_get_path(SYS_COURSE_PATH).$this->course_code.'/document';
require_once api_get_path(SYS_CODE_PATH).'exercise/hotpotatoes.lib.php';
$data = $this->get_exercise_data();
if ($this->is_hp == 1) {
if (isset($data['path'])) {
$title = GetQuizName($data['path'], $documentPath);
if ($title == '') {
$title = basename($data['path']);
}
return $title;
}
}
return strip_tags(Exercise::get_formated_title_variable($data['title']));
}
public function getLpListToString()
{
$data = $this->get_exercise_data();
$lpList = Exercise::getLpListFromExercise($data['iid'], $this->getCourseId());
$lpListToString = '';
if (!empty($lpList)) {
foreach ($lpList as &$list) {
$list['name'] = Display::label($list['name'], 'warning');
}
$lpListToString = implode('&nbsp;', array_column($lpList, 'name'));
}
return $lpListToString;
}
/**
* Get description to display: same as exercise description.
*/
public function get_description()
{
$data = $this->get_exercise_data();
return isset($data['description']) ? $data['description'] : null;
}
/**
* Check if this still links to an exercise.
*/
public function is_valid_link()
{
$exerciseData = $this->get_exercise_data();
return !empty($exerciseData);
}
/**
* @return string
*/
public function get_type_name()
{
if ($this->is_hp == 1) {
return 'HotPotatoes';
}
return get_lang('Quiz');
}
public function needs_name_and_description()
{
return false;
}
public function needs_max()
{
return false;
}
public function needs_results()
{
return false;
}
public function is_allowed_to_change_name()
{
return false;
}
/**
* @return string
*/
public function get_icon_name()
{
return 'exercise';
}
/**
* @param bool $hp
*/
public function setHp($hp)
{
$this->hp = $hp;
}
public function getBestScore()
{
return $this->getStats('best');
}
public function getStats($type)
{
switch ($type) {
case 'best':
break;
}
}
/**
* Lazy load function to get the database contents of this exercise.
*/
public function get_exercise_data()
{
$tableItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
if ($this->is_hp == 1) {
$table = Database::get_course_table(TABLE_DOCUMENT);
} else {
$table = Database::get_course_table(TABLE_QUIZ_TEST);
}
$exerciseId = $this->get_ref_id();
if (empty($this->exercise_data)) {
if ($this->is_hp == 1) {
$sql = "SELECT * FROM $table ex
INNER JOIN $tableItemProperty ip
ON (ip.ref = ex.id AND ip.c_id = ex.c_id)
WHERE
ip.c_id = $this->course_id AND
ex.c_id = $this->course_id AND
ip.ref = $exerciseId AND
ip.tool = '".TOOL_DOCUMENT."' AND
ex.path LIKE '%htm%' AND
ex.path LIKE '%HotPotatoes_files%' AND
ip.visibility = 1";
$result = Database::query($sql);
$this->exercise_data = Database::fetch_array($result);
} else {
// Try with iid
$sql = "SELECT * FROM $table
WHERE
iid = $exerciseId";
$result = Database::query($sql);
$rows = Database::num_rows($result);
if (!empty($rows)) {
$this->exercise_data = Database::fetch_array($result);
} else {
// Try wit id
$sql = "SELECT * FROM $table
WHERE
iid = $exerciseId";
$result = Database::query($sql);
$this->exercise_data = Database::fetch_array($result);
}
}
}
if (empty($this->exercise_data)) {
return false;
}
return $this->exercise_data;
}
/**
* Lazy load function to get the database table of the exercise.
*/
private function get_exercise_table()
{
$this->exercise_table = Database::get_course_table(TABLE_QUIZ_TEST);
return $this->exercise_table;
}
}

View File

@@ -0,0 +1,357 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class ForumThreadLink.
*
* @author Bert Steppé
*/
class ForumThreadLink extends AbstractLink
{
private $forum_thread_table;
private $itemprop_table;
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
$this->set_type(LINK_FORUM_THREAD);
}
/**
* @return string
*/
public function get_type_name()
{
return get_lang('ForumThreads');
}
/**
* @return bool
*/
public function is_allowed_to_change_name()
{
return false;
}
/**
* Generate an array of all exercises available.
*
* @return array 2-dimensional array - every element contains 2 subelements (id, name)
*/
public function get_all_links()
{
if (empty($this->course_code)) {
return [];
}
$tbl_grade_links = Database::get_course_table(TABLE_FORUM_THREAD);
$tbl_item_property = Database::get_course_table(TABLE_ITEM_PROPERTY);
$sessionId = $this->get_session_id();
if ($sessionId) {
$session_condition = 'tl.session_id='.$sessionId;
} else {
$session_condition = '(tl.session_id = 0 OR tl.session_id IS NULL)';
}
$sql = 'SELECT tl.thread_id, tl.thread_title, tl.thread_title_qualify
FROM '.$tbl_grade_links.' tl INNER JOIN '.$tbl_item_property.' ip
ON (tl.thread_id = ip.ref AND tl.c_id = ip.c_id)
WHERE
tl.c_id = '.$this->course_id.' AND
ip.c_id = '.$this->course_id.' AND
ip.tool = "forum_thread" AND
ip.visibility <> 2 AND
'.$session_condition.'
';
$result = Database::query($sql);
while ($data = Database::fetch_array($result)) {
if (isset($data['thread_title_qualify']) && '' != $data['thread_title_qualify']) {
$cats[] = [$data['thread_id'], $data['thread_title_qualify']];
} else {
$cats[] = [$data['thread_id'], $data['thread_title']];
}
}
$my_cats = isset($cats) ? $cats : [];
return $my_cats;
}
/**
* Has anyone done this exercise yet ?
*
* @return bool
*/
public function has_results()
{
$table = Database::get_course_table(TABLE_FORUM_POST);
$sql = "SELECT count(*) AS number FROM $table
WHERE
c_id = ".$this->course_id." AND
thread_id = '".$this->get_ref_id()."'
";
$result = Database::query($sql);
$number = Database::fetch_row($result);
return 0 != $number[0];
}
/**
* @param int $stud_id
* @param string $type
*
* @return array|null
*/
public function calc_score($stud_id = null, $type = null)
{
require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
$threadInfo = get_thread_information('', $this->get_ref_id());
$thread_qualify = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY);
$sessionId = $this->get_session_id();
$sessionCondition = api_get_session_condition(
$sessionId,
true,
false,
'session_id'
);
$sql = 'SELECT thread_qualify_max
FROM '.Database::get_course_table(TABLE_FORUM_THREAD)."
WHERE
c_id = ".$this->course_id." AND
thread_id = '".$this->get_ref_id()."'
$sessionCondition
";
$query = Database::query($sql);
$assignment = Database::fetch_array($query);
$sql = "SELECT * FROM $thread_qualify
WHERE
c_id = ".$this->course_id." AND
thread_id = ".$this->get_ref_id()."
$sessionCondition
";
if (isset($stud_id)) {
$sql .= ' AND user_id = '.intval($stud_id);
}
// order by id, that way the student's first attempt is accessed first
$sql .= ' ORDER BY qualify_time DESC';
$scores = Database::query($sql);
// for 1 student
if (isset($stud_id)) {
if (0 == $threadInfo['thread_peer_qualify']) {
// Classic way of calculate score
if ($data = Database::fetch_array($scores)) {
return [
$data['qualify'],
$assignment['thread_qualify_max'],
];
} else {
// We sent the 0/thread_qualify_max instead of null for correct calculations
return [0, $assignment['thread_qualify_max']];
}
} else {
// Take average
$score = 0;
$counter = 0;
if (Database::num_rows($scores)) {
while ($data = Database::fetch_array($scores, 'ASSOC')) {
$score += $data['qualify'];
$counter++;
}
}
// If no result
if (empty($counter) || $counter <= 2) {
return [0, $assignment['thread_qualify_max']];
}
return [$score / $counter, $assignment['thread_qualify_max']];
}
} else {
// All students -> get average
$students = []; // user list, needed to make sure we only
// take first attempts into account
$counter = 0;
$sum = 0;
$bestResult = 0;
$weight = 0;
$sumResult = 0;
while ($data = Database::fetch_array($scores)) {
if (!(array_key_exists($data['user_id'], $students))) {
if (0 != $assignment['thread_qualify_max']) {
$students[$data['user_id']] = $data['qualify'];
$counter++;
$sum += $data['qualify'] / $assignment['thread_qualify_max'];
$sumResult += $data['qualify'];
if ($data['qualify'] > $bestResult) {
$bestResult = $data['qualify'];
}
$weight = $assignment['thread_qualify_max'];
}
}
}
if (0 == $counter) {
return [null, null];
} else {
switch ($type) {
case 'best':
return [$bestResult, $weight];
break;
case 'average':
return [$sumResult / $counter, $weight];
break;
case 'ranking':
return AbstractLink::getCurrentUserRanking($stud_id, $students);
break;
default:
return [$sum, $counter];
break;
}
}
}
}
public function needs_name_and_description()
{
return false;
}
public function needs_max()
{
return false;
}
public function needs_results()
{
return false;
}
/**
* @return string
*/
public function get_name()
{
$this->get_exercise_data();
$thread_title = isset($this->exercise_data['thread_title']) ? $this->exercise_data['thread_title'] : '';
$thread_title_qualify = isset($this->exercise_data['thread_title_qualify']) ? $this->exercise_data['thread_title_qualify'] : '';
if (isset($thread_title_qualify) && '' != $thread_title_qualify) {
return $this->exercise_data['thread_title_qualify'];
}
return $thread_title;
}
/**
* @return string
*/
public function get_description()
{
return ''; //$this->exercise_data['description'];
}
/**
* Check if this still links to an exercise.
*/
public function is_valid_link()
{
$sessionId = $this->get_session_id();
$sql = 'SELECT count(id) from '.$this->get_forum_thread_table().'
WHERE
c_id = '.$this->course_id.' AND
thread_id = '.$this->get_ref_id().' AND
session_id='.$sessionId;
$result = Database::query($sql);
$number = Database::fetch_row($result);
return 0 != $number[0];
}
public function get_link()
{
$sessionId = $this->get_session_id();
//it was extracts the forum id
$sql = 'SELECT * FROM '.$this->get_forum_thread_table()."
WHERE
c_id = '.$this->course_id.' AND
thread_id = '".$this->get_ref_id()."' AND
session_id = $sessionId ";
$result = Database::query($sql);
$row = Database::fetch_array($result, 'ASSOC');
$forum_id = $row['forum_id'];
$url = api_get_path(WEB_PATH).'main/forum/viewthread.php?'.api_get_cidreq_params($this->get_course_code(), $sessionId).'&thread='.$this->get_ref_id().'&gradebook=view&forum='.$forum_id;
return $url;
}
public function get_icon_name()
{
return 'forum';
}
public function save_linked_data()
{
$weight = $this->get_weight();
$ref_id = $this->get_ref_id();
if (!empty($ref_id)) {
$sql = 'UPDATE '.$this->get_forum_thread_table().' SET
thread_weight='.api_float_val($weight).'
WHERE c_id = '.$this->course_id.' AND thread_id= '.$ref_id;
Database::query($sql);
}
}
public function delete_linked_data()
{
$ref_id = $this->get_ref_id();
if (!empty($ref_id)) {
// Cleans forum
$sql = 'UPDATE '.$this->get_forum_thread_table().' SET
thread_qualify_max = 0,
thread_weight = 0,
thread_title_qualify = ""
WHERE c_id = '.$this->course_id.' AND thread_id= '.$ref_id;
Database::query($sql);
}
}
/**
* Lazy load function to get the database table of the student publications.
*/
private function get_forum_thread_table()
{
return $this->forum_thread_table = Database::get_course_table(TABLE_FORUM_THREAD);
}
private function get_exercise_data()
{
$sessionId = $this->get_session_id();
if ($sessionId) {
$session_condition = 'session_id = '.$sessionId;
} else {
$session_condition = '(session_id = 0 OR session_id IS NULL)';
}
if (!isset($this->exercise_data)) {
$sql = 'SELECT * FROM '.$this->get_forum_thread_table().'
WHERE
c_id = '.$this->course_id.' AND
thread_id = '.$this->get_ref_id().' AND
'.$session_condition;
$query = Database::query($sql);
$this->exercise_data = Database::fetch_array($query);
}
return $this->exercise_data;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Interface for all displayable items in the gradebook.
*
* @author Bert Steppé
*/
interface GradebookItem
{
public function get_item_type();
public function get_id();
public function get_name();
public function get_description();
public function get_course_code();
public function get_weight();
public function get_date();
public function is_visible();
public function get_icon_name();
public function getStudentList();
public function setStudentList($list);
public function calc_score($stud_id = null, $type = null);
}

View File

@@ -0,0 +1,7 @@
<html>
<head>
<meta http-equiv="refresh" content="0; url=../../gradebook.php">
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,257 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Defines a gradebook LearnpathLink object.
*
* @author Yannick Warnier <yannick.warnier@beeznest.com>
* @author Bert Steppé
*/
class LearnpathLink extends AbstractLink
{
private $course_info;
private $learnpath_table;
private $learnpath_data;
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
$this->set_type(LINK_LEARNPATH);
}
/**
* Generate an array of all learnpaths available.
*
* @return array 2-dimensional array - every element contains 2 subelements (id, name)
*/
public function get_all_links()
{
if (empty($this->course_code)) {
return [];
}
$session_id = $this->get_session_id();
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, name FROM '.$this->get_learnpath_table().'
WHERE c_id = '.$this->course_id.' '.$session_condition.' ';
$result = Database::query($sql);
$cats = [];
while ($data = Database::fetch_array($result)) {
$cats[] = [$data['id'], $data['name']];
}
return $cats;
}
/**
* Has anyone used this learnpath yet ?
*/
public function has_results()
{
$tbl_stats = Database::get_course_table(TABLE_LP_VIEW);
$sql = "SELECT count(id) AS number FROM $tbl_stats
WHERE c_id = ".$this->course_id." AND lp_id = ".$this->get_ref_id();
$result = Database::query($sql);
$number = Database::fetch_array($result, 'NUM');
return 0 != $number[0];
}
/**
* Get the progress of this learnpath. Only the last attempt are taken into account.
*
* @param $stud_id student id (default: all students who have results - then the average is returned)
* @param $type The type of score we want to get: best|average|ranking
*
* @return array (score, max) if student is given
* array (sum of scores, number of scores) otherwise
* or null if no scores available
*/
public function calc_score($stud_id = null, $type = null)
{
$tbl_stats = Database::get_course_table(TABLE_LP_VIEW);
$session_id = $this->get_session_id();
if (empty($session_id)) {
$session_id = api_get_session_id();
}
$sql = "SELECT * FROM $tbl_stats
WHERE
c_id = ".$this->course_id." AND
lp_id = ".$this->get_ref_id()." AND
session_id = $session_id ";
if (isset($stud_id)) {
$sql .= ' AND user_id = '.intval($stud_id);
}
// order by id, that way the student's first attempt is accessed first
$sql .= ' ORDER BY view_count DESC';
$scores = Database::query($sql);
// for 1 student
if (isset($stud_id)) {
if ($data = Database::fetch_assoc($scores)) {
return [$data['progress'], 100];
} else {
return null;
}
} else {
// all students -> get average
$students = []; // user list, needed to make sure we only
// take first attempts into account
$rescount = 0;
$sum = 0;
$bestResult = 0;
$sumResult = 0;
while ($data = Database::fetch_array($scores)) {
if (!(array_key_exists($data['user_id'], $students))) {
$students[$data['user_id']] = $data['progress'];
$rescount++;
$sum += $data['progress'] / 100;
$sumResult += $data['progress'];
if ($data['progress'] > $bestResult) {
$bestResult = $data['progress'];
}
}
}
if (0 == $rescount) {
return [null, null];
} else {
switch ($type) {
case 'best':
return [$bestResult, 100];
break;
case 'average':
return [$sumResult / $rescount, 100];
break;
case 'ranking':
return AbstractLink::getCurrentUserRanking($stud_id, $students);
break;
default:
return [$sum, $rescount];
break;
}
}
}
}
/**
* Get URL where to go to if the user clicks on the link.
*/
public function get_link()
{
$session_id = $this->get_session_id();
$url = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq_params(
$this->get_course_code(),
$session_id
).'&gradebook=view';
if (!api_is_allowed_to_edit() || null == $this->calc_score(api_get_user_id())) {
$url .= '&action=view&lp_id='.$this->get_ref_id();
} else {
$url .= '&action=build&lp_id='.$this->get_ref_id();
}
return $url;
}
/**
* Get name to display: same as learnpath title.
*/
public function get_name()
{
$data = $this->get_learnpath_data();
return $data['name'];
}
/**
* Get description to display: same as learnpath description.
*/
public function get_description()
{
$data = $this->get_learnpath_data();
return $data['description'];
}
/**
* Check if this still links to a learnpath.
*/
public function is_valid_link()
{
$sql = 'SELECT count(id) FROM '.$this->get_learnpath_table().'
WHERE c_id = '.$this->course_id.' AND id = '.$this->get_ref_id().' ';
$result = Database::query($sql);
$number = Database::fetch_row($result, 'NUM');
return 0 != $number[0];
}
public function get_type_name()
{
return get_lang('LearningPaths');
}
public function needs_name_and_description()
{
return false;
}
public function needs_max()
{
return false;
}
public function needs_results()
{
return false;
}
public function is_allowed_to_change_name()
{
return false;
}
public function get_icon_name()
{
return 'learnpath';
}
/**
* Lazy load function to get the database table of the learnpath.
*/
private function get_learnpath_table()
{
$this->learnpath_table = Database::get_course_table(TABLE_LP_MAIN);
return $this->learnpath_table;
}
/**
* Lazy load function to get the database contents of this learnpath.
*/
private function get_learnpath_data()
{
if (!isset($this->learnpath_data)) {
$sql = 'SELECT * FROM '.$this->get_learnpath_table().'
WHERE c_id = '.$this->course_id.' AND id = '.$this->get_ref_id().' ';
$result = Database::query($sql);
$this->learnpath_data = Database::fetch_array($result);
}
return $this->learnpath_data;
}
}

View File

@@ -0,0 +1,132 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class LinkFactory
* Factory for link objects.
*
* @author Bert Steppé
*/
class LinkFactory
{
/**
* Retrieve links and return them as an array of extensions of AbstractLink.
*
* @param int $id link id
* @param int $type link type
* @param int $ref_id reference id
* @param int $user_id user id (link owner)
* @param string $course_code course code
* @param int $category_id parent category
* @param int $visible visible
*
* @return array
*/
public static function load(
$id = null,
$type = null,
$ref_id = null,
$user_id = null,
$course_code = null,
$category_id = null,
$visible = null
) {
return AbstractLink::load(
$id,
$type,
$ref_id,
$user_id,
$course_code,
$category_id,
$visible
);
}
/**
* Get the link object referring to an evaluation.
*/
public function get_evaluation_link($eval_id)
{
$links = AbstractLink::load(null, null, $eval_id);
foreach ($links as $link) {
if (is_a($link, 'EvalLink')) {
return $link;
}
}
return null;
}
/**
* Find links by name.
*
* @param string $name_mask search string
*
* @return array link objects matching the search criterium
*/
public function find_links($name_mask, $selectcat)
{
return AbstractLink::find_links($name_mask, $selectcat);
}
/**
* Static method to create specific link objects.
*
* @param int $type link type
*/
public static function create($type)
{
$type = (int) $type;
switch ($type) {
case LINK_EXERCISE:
return new ExerciseLink();
case LINK_HOTPOTATOES:
return new ExerciseLink(1);
case LINK_DROPBOX:
return new DropboxLink();
case LINK_STUDENTPUBLICATION:
return new StudentPublicationLink();
case LINK_LEARNPATH:
return new LearnpathLink();
case LINK_FORUM_THREAD:
return new ForumThreadLink();
case LINK_ATTENDANCE:
return new AttendanceLink();
case LINK_SURVEY:
return new SurveyLink();
case LINK_PORTFOLIO:
return new PortfolioLink();
}
return null;
}
/**
* Return an array of all known link types.
*
* @return array
*/
public static function get_all_types()
{
$types = [
LINK_EXERCISE,
//LINK_DROPBOX,
LINK_HOTPOTATOES,
LINK_STUDENTPUBLICATION,
LINK_LEARNPATH,
LINK_FORUM_THREAD,
LINK_ATTENDANCE,
LINK_SURVEY,
];
if (api_get_configuration_value('allow_portfolio_tool')) {
$types[] = LINK_PORTFOLIO;
}
return $types;
}
public function delete()
{
}
}

View File

@@ -0,0 +1,323 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Defines a gradebook Result object.
*
* @author Bert Steppé, Stijn Konings
*/
class Result
{
private $id;
private $user_id;
private $evaluation;
private $created_at;
private $score;
/**
* Result constructor.
*/
public function __construct()
{
$this->created_at = api_get_utc_datetime();
}
public function get_id()
{
return $this->id;
}
public function get_user_id()
{
return $this->user_id;
}
public function get_evaluation_id()
{
return $this->evaluation;
}
public function get_date()
{
return $this->created_at;
}
public function get_score()
{
return $this->score;
}
public function set_id($id)
{
$this->id = $id;
}
public function set_user_id($user_id)
{
$this->user_id = $user_id;
}
public function set_evaluation_id($evaluation_id)
{
$this->evaluation = $evaluation_id;
}
/**
* @param string $creation_date
*/
public function set_date($creation_date)
{
$this->created_at = $creation_date;
}
/**
* @param float $score
*/
public function set_score($score)
{
$this->score = $score;
}
/**
* Retrieve results and return them as an array of Result objects.
*
* @param $id result id
* @param $user_id user id (student)
* @param $evaluation_id evaluation where this is a result for
*
* @return array
*/
public static function load($id = null, $user_id = null, $evaluation_id = null)
{
$tbl_user = Database::get_main_table(TABLE_MAIN_USER);
$tbl_grade_results = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT);
$tbl_course_rel_course = Database::get_main_table(TABLE_MAIN_COURSE_USER);
$tbl_session_rel_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
$sessionId = api_get_session_id();
$list_user_course_list = [];
if (is_null($id) && is_null($user_id) && !is_null($evaluation_id)) {
// Verified_if_exist_evaluation
$sql = 'SELECT COUNT(*) AS count
FROM '.$tbl_grade_results.'
WHERE evaluation_id="'.Database::escape_string($evaluation_id).'"';
$result = Database::query($sql);
$existEvaluation = Database::result($result, 0, 0);
if (0 != $existEvaluation) {
if ($sessionId) {
$sql = 'SELECT c_id, user_id as user_id, status
FROM '.$tbl_session_rel_course_user.'
WHERE
status= 0 AND
c_id = "'.api_get_course_int_id().'" AND
session_id = '.$sessionId;
} else {
$sql = 'SELECT c_id, user_id, status
FROM '.$tbl_course_rel_course.'
WHERE status ="'.STUDENT.'" AND c_id = "'.api_get_course_int_id().'" ';
}
$res_course_rel_user = Database::query($sql);
while ($row_course_rel_user = Database::fetch_array($res_course_rel_user, 'ASSOC')) {
$list_user_course_list[] = $row_course_rel_user;
}
$current_date = api_get_utc_datetime();
for ($i = 0; $i < count($list_user_course_list); $i++) {
$sql_verified = 'SELECT COUNT(*) AS count
FROM '.$tbl_grade_results.'
WHERE
user_id="'.intval($list_user_course_list[$i]['user_id']).'" AND
evaluation_id="'.intval($evaluation_id).'";';
$res_verified = Database::query($sql_verified);
$info_verified = Database::result($res_verified, 0, 0);
if (0 == $info_verified) {
$sql_insert = 'INSERT INTO '.$tbl_grade_results.'(user_id,evaluation_id,created_at,score)
VALUES ("'.intval($list_user_course_list[$i]['user_id']).'","'.intval($evaluation_id).'","'.$current_date.'",0);';
Database::query($sql_insert);
}
}
}
}
$userIdList = [];
foreach ($list_user_course_list as $data) {
$userIdList[] = $data['user_id'];
}
$userIdListToString = implode("', '", $userIdList);
$sql = "SELECT lastname, gr.id, gr.user_id, gr.evaluation_id, gr.created_at, gr.score
FROM $tbl_grade_results gr
INNER JOIN $tbl_user u
ON gr.user_id = u.user_id ";
if (!empty($userIdList)) {
$sql .= " AND u.user_id IN ('$userIdListToString')";
}
$paramcount = 0;
if (!empty($id)) {
$sql .= ' WHERE gr.id = '.intval($id);
$paramcount++;
}
if (!empty($user_id)) {
if (0 != $paramcount) {
$sql .= ' AND';
} else {
$sql .= ' WHERE';
}
$sql .= ' gr.user_id = '.intval($user_id);
$paramcount++;
}
if (!empty($evaluation_id)) {
if (0 != $paramcount) {
$sql .= ' AND';
} else {
$sql .= ' WHERE';
}
$sql .= ' gr.evaluation_id = '.intval($evaluation_id);
}
$sql .= ' ORDER BY u.lastname, u.firstname';
$result = Database::query($sql);
$allres = [];
while ($data = Database::fetch_array($result)) {
$res = new Result();
$res->set_id($data['id']);
$res->set_user_id($data['user_id']);
$res->set_evaluation_id($data['evaluation_id']);
$res->set_date(api_get_local_time($data['created_at']));
$res->set_score($data['score']);
$allres[] = $res;
}
return $allres;
}
/**
* Insert this result into the database.
*/
public function add()
{
if (isset($this->user_id) && isset($this->evaluation)) {
$tbl_grade_results = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT);
$sql = "INSERT INTO ".$tbl_grade_results
." (user_id, evaluation_id,
created_at";
if (isset($this->score)) {
$sql .= ",score";
}
$sql .= ") VALUES
(".(int) $this->get_user_id().", ".(int) $this->get_evaluation_id()
.", '".$this->get_date()."' ";
if (isset($this->score)) {
$sql .= ", ".$this->get_score();
}
$sql .= ")";
Database::query($sql);
} else {
exit('Error in Result add: required field empty');
}
}
/**
* insert log result.
*/
public function addResultLog($userid, $evaluationid)
{
if (isset($userid) && isset($evaluationid)) {
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT_LOG);
$result = new Result();
$arr_result = $result->load(null, $userid, $evaluationid);
$arr = get_object_vars($arr_result[0]);
$sql = 'INSERT INTO '.$table
.' (id_result,user_id, evaluation_id,created_at';
if (isset($arr['score'])) {
$sql .= ',score';
}
$sql .= ') VALUES
('.(int) $arr['id'].','.(int) $arr['user_id'].', '.(int) $arr['evaluation']
.", '".api_get_utc_datetime()."'";
if (isset($arr['score'])) {
$sql .= ', '.$arr['score'];
}
$sql .= ')';
Database::query($sql);
} else {
exit('Error in Result add: required field empty');
}
}
/**
* Update the properties of this result in the database.
*/
public function save()
{
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT);
$sql = 'UPDATE '.$table.'
SET user_id = '.$this->get_user_id()
.', evaluation_id = '.$this->get_evaluation_id()
.', score = ';
if (isset($this->score)) {
$sql .= $this->get_score();
} else {
$sql .= 'null';
}
if (isset($this->id)) {
$sql .= " WHERE id = {$this->id}";
} else {
$sql .= " WHERE evaluation_id = {$this->evaluation}
AND user_id = {$this->user_id}
";
}
// no need to update creation date
Database::query($sql);
Evaluation::generateStats($this->get_evaluation_id());
}
/**
* Delete this result from the database.
*/
public function delete()
{
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT);
$sql = 'DELETE FROM '.$table.' WHERE id = '.$this->id;
Database::query($sql);
$allowMultipleAttempts = api_get_configuration_value('gradebook_multiple_evaluation_attempts');
if ($allowMultipleAttempts) {
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT_ATTEMPT);
$sql = "DELETE FROM $table WHERE result_id = ".$this->id;
Database::query($sql);
}
Evaluation::generateStats($this->get_evaluation_id());
}
/**
* Check if exists a result with its user and evaluation.
*
* @throws \Doctrine\ORM\Query\QueryException
*
* @return bool
*/
public function exists()
{
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT);
$sql = "SELECT COUNT(*) AS count
FROM $table gr
WHERE gr.evaluation_id = {$this->evaluation}
AND gr.user_id = {$this->user_id}
";
$result = Database::query($sql);
$row = Database::fetch_array($result);
$count = (int) $row['count'];
return $count > 0;
}
}

View File

@@ -0,0 +1,427 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Gradebook link to student publication item.
*
* @author Bert Steppé
*/
class StudentPublicationLink extends AbstractLink
{
private $studpub_table;
private $itemprop_table;
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
$this->set_type(LINK_STUDENTPUBLICATION);
}
/**
* Returns the URL of a document
* This function is loaded when using a gradebook as a tab (gradebook = -1)
* see issue #2705.
*/
public function get_view_url($stud_id)
{
return null;
// find a file uploaded by the given student,
// with the same title as the evaluation name
$eval = $this->get_evaluation();
$stud_id = (int) $stud_id;
$itemProperty = $this->get_itemprop_table();
$workTable = $this->get_studpub_table();
$courseId = $this->course_id;
$sql = "SELECT pub.url
FROM $itemProperty prop
INNER JOIN $workTable pub
ON (prop.c_id = pub.c_id AND prop.ref = pub.id)
WHERE
prop.c_id = $courseId AND
pub.c_id = $courseId AND
prop.tool = 'work' AND
prop.insert_user_id = $stud_id AND
pub.title = '".Database::escape_string($eval->get_name())."' AND
pub.session_id=".api_get_session_id();
$result = Database::query($sql);
if ($fileurl = Database::fetch_row($result)) {
return null;
} else {
return null;
}
}
/**
* @return string
*/
public function get_type_name()
{
return get_lang('Works');
}
public function is_allowed_to_change_name()
{
return false;
}
/**
* Generate an array of all exercises available.
*
* @return array 2-dimensional array - every element contains 2 subelements (id, name)
*/
public function get_all_links()
{
if (empty($this->course_code)) {
return [];
}
$em = Database::getManager();
$sessionId = $this->get_session_id();
$session = $em->find('ChamiloCoreBundle:Session', $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 != ''
$links = $em
->getRepository('ChamiloCourseBundle:CStudentPublication')
->findBy([
'cId' => $this->course_id,
'active' => true,
'filetype' => 'folder',
'session' => $session,
]);
foreach ($links as $data) {
$work_name = $data->getTitle();
if (empty($work_name)) {
$work_name = basename($data->getUrl());
}
$cats[] = [$data->getId(), $work_name];
}
$cats = isset($cats) ? $cats : [];
return $cats;
}
/**
* Has anyone done this exercise yet ?
*/
public function has_results()
{
$data = $this->get_exercise_data();
if (empty($data)) {
return '';
}
$id = $data['id'];
$em = Database::getManager();
$session = $em->find('ChamiloCoreBundle:Session', $this->get_session_id());
$results = $em
->getRepository('ChamiloCourseBundle:CStudentPublication')
->findBy([
'cId' => $this->course_id,
'parentId' => $id,
'session' => $session,
]);
return 0 != count($results);
}
/**
* @param null $stud_id
*
* @return array
*/
public function calc_score($stud_id = null, $type = null)
{
$stud_id = (int) $stud_id;
$em = Database::getManager();
$data = $this->get_exercise_data();
if (empty($data)) {
return [];
}
$id = $data['id'];
$session = api_get_session_entity($this->get_session_id());
$assignment = $em
->getRepository('ChamiloCourseBundle:CStudentPublication')
->findOneBy([
'cId' => $this->course_id,
'id' => $id,
'session' => $session,
])
;
$parentId = !$assignment ? 0 : $assignment->getId();
if (empty($session)) {
$dql = 'SELECT a FROM ChamiloCourseBundle:CStudentPublication a
WHERE
a.cId = :course AND
a.active = :active AND
a.parentId = :parent AND
a.session is null AND
a.qualificatorId <> 0
';
$params = [
'course' => $this->course_id,
'parent' => $parentId,
'active' => true,
];
} else {
$dql = 'SELECT a FROM ChamiloCourseBundle:CStudentPublication a
WHERE
a.cId = :course AND
a.active = :active AND
a.parentId = :parent AND
a.session = :session AND
a.qualificatorId <> 0
';
$params = [
'course' => $this->course_id,
'parent' => $parentId,
'session' => $session,
'active' => true,
];
}
if (!empty($stud_id)) {
$dql .= ' AND a.userId = :student ';
$params['student'] = $stud_id;
}
$order = api_get_setting('student_publication_to_take_in_gradebook');
switch ($order) {
case 'last':
// latest attempt
$dql .= ' ORDER BY a.sentDate DESC';
break;
case 'first':
default:
// first attempt
$dql .= ' ORDER BY a.id';
break;
}
$scores = $em->createQuery($dql)->execute($params);
// for 1 student
if (!empty($stud_id)) {
if (!count($scores)) {
return [null, null];
}
$data = $scores[0];
return [
$data->getQualification(),
$assignment->getQualification(),
api_get_local_time($assignment->getDateOfQualification()),
1,
];
}
$students = []; // user list, needed to make sure we only
// take first attempts into account
$rescount = 0;
$sum = 0;
$bestResult = 0;
$weight = 0;
$sumResult = 0;
foreach ($scores as $data) {
if (!(array_key_exists($data->getUserId(), $students))) {
if (0 != $assignment->getQualification()) {
$students[$data->getUserId()] = $data->getQualification();
$rescount++;
$sum += $data->getQualification() / $assignment->getQualification();
$sumResult += $data->getQualification();
if ($data->getQualification() > $bestResult) {
$bestResult = $data->getQualification();
}
$weight = $assignment->getQualification();
}
}
}
if (0 == $rescount) {
return [null, null];
}
switch ($type) {
case 'best':
return [$bestResult, $weight];
break;
case 'average':
return [$sumResult / $rescount, $weight];
break;
case 'ranking':
return AbstractLink::getCurrentUserRanking($stud_id, $students);
break;
default:
return [$sum, $rescount];
break;
}
}
public function needs_name_and_description()
{
return false;
}
public function get_name()
{
$this->get_exercise_data();
$name = isset($this->exercise_data['title']) && !empty($this->exercise_data['title']) ? $this->exercise_data['title'] : get_lang('Untitled');
return $name;
}
public function get_description()
{
$this->get_exercise_data();
return isset($this->exercise_data['description']) ? $this->exercise_data['description'] : null;
}
public function get_link()
{
$sessionId = $this->get_session_id();
$url = api_get_path(WEB_PATH).'main/work/work.php?'.api_get_cidreq_params($this->get_course_code(), $sessionId).'&id='.$this->exercise_data['id'].'&gradebook=view';
return $url;
}
public function needs_max()
{
return false;
}
public function needs_results()
{
return false;
}
public function is_valid_link()
{
$data = $this->get_exercise_data();
if (empty($data)) {
return '';
}
$id = $data['id'];
$sql = 'SELECT count(id) FROM '.$this->get_studpub_table().'
WHERE
c_id = "'.$this->course_id.'" AND
id = '.$id;
$result = Database::query($sql);
$number = Database::fetch_row($result);
return 0 != $number[0];
}
public function get_icon_name()
{
return 'studentpublication';
}
public function save_linked_data()
{
$data = $this->get_exercise_data();
if (empty($data)) {
return '';
}
$id = $data['id'];
$weight = api_float_val($this->get_weight());
if (!empty($id)) {
//Cleans works
$sql = 'UPDATE '.$this->get_studpub_table().'
SET weight= '.$weight.'
WHERE c_id = '.$this->course_id.' AND id ='.$id;
Database::query($sql);
}
}
/**
* @return string
*/
public function delete_linked_data()
{
$data = $this->get_exercise_data();
if (empty($data)) {
return '';
}
if (!empty($id)) {
//Cleans works
$sql = 'UPDATE '.$this->get_studpub_table().'
SET weight = 0
WHERE c_id = '.$this->course_id.' AND id ='.$id;
Database::query($sql);
}
}
/**
* Lazy load function to get the database table of the student publications.
*/
private function get_studpub_table()
{
return $this->studpub_table = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
}
/**
* Lazy load function to get the database table of the item properties.
*/
private function get_itemprop_table()
{
return $this->itemprop_table = Database::get_course_table(TABLE_ITEM_PROPERTY);
}
/**
* @return array
*/
private function get_exercise_data()
{
$course_info = api_get_course_info($this->get_course_code());
if (!isset($this->exercise_data)) {
$sql = 'SELECT * FROM '.$this->get_studpub_table()."
WHERE
c_id ='".$course_info['real_id']."' AND
id = '".$this->get_ref_id()."' ";
$query = Database::query($sql);
$this->exercise_data = Database::fetch_array($query);
// Try with iid
if (empty($this->exercise_data)) {
$sql = 'SELECT * FROM '.$this->get_studpub_table()."
WHERE
c_id ='".$course_info['real_id']."' AND
iid = '".$this->get_ref_id()."' ";
$query = Database::query($sql);
$this->exercise_data = Database::fetch_array($query);
}
}
return $this->exercise_data;
}
}

View File

@@ -0,0 +1,313 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Gradebook link to a survey item.
*
* @author Ivan Tcholakov <ivantcholakov@gmail.com>, 2010
*/
class SurveyLink extends AbstractLink
{
private $survey_table;
private $survey_data = [];
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
$this->set_type(LINK_SURVEY);
}
/**
* @return string
*/
public function get_name()
{
$this->get_survey_data();
return $this->survey_data['code'].': '.self::html_to_text($this->survey_data['title']);
}
/**
* @return string
*/
public function get_description()
{
$this->get_survey_data();
return $this->survey_data['subtitle'];
}
/**
* @return string
*/
public function get_type_name()
{
return get_lang('Survey');
}
public function is_allowed_to_change_name()
{
return false;
}
public function needs_name_and_description()
{
return false;
}
public function needs_max()
{
return false;
}
public function needs_results()
{
return false;
}
/**
* Generates an array of all surveys available.
*
* @return array 2-dimensional array - every element contains 2 subelements (id, name)
*/
public function get_all_links()
{
if (empty($this->course_code)) {
exit('Error in get_all_links() : course code not set');
}
$tbl_survey = $this->get_survey_table();
$sessionId = $this->get_session_id();
$course_id = $this->getCourseId();
$sql = 'SELECT survey_id, title, code FROM '.$tbl_survey.'
WHERE c_id = '.$course_id.' AND session_id = '.$sessionId;
$result = Database::query($sql);
while ($data = Database::fetch_array($result)) {
$links[] = [
$data['survey_id'],
api_trunc_str(
$data['code'].': '.self::html_to_text($data['title']),
80
),
];
}
return isset($links) ? $links : [];
}
/**
* Has anyone done this survey yet?
* Implementation of the AbstractLink class, mainly used dynamically in gradebook/lib/fe.
*/
public function has_results()
{
$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);
$sql = "SELECT
COUNT(i.answered)
FROM $tbl_survey AS s
JOIN $tbl_survey_invitation AS i ON s.code = i.survey_code
WHERE
s.c_id = $courseId AND
i.c_id = $courseId AND
s.survey_id = $ref_id AND
i.session_id = $sessionId";
$sql_result = Database::query($sql);
$data = Database::fetch_array($sql_result);
return 0 != $data[0];
}
/**
* Calculate score for a student (to show in the gradebook).
*
* @param int $stud_id
* @param string $type Type of result we want (best|average|ranking)
*
* @return array|null
*/
public function calc_score($stud_id = null, $type = null)
{
// 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($stud_id);
$sql = "SELECT i.answered
FROM $tbl_survey AS s
JOIN $tbl_survey_invitation AS i
ON s.code = i.survey_code
WHERE
s.c_id = $courseId AND
i.c_id = $courseId AND
s.survey_id = $ref_id AND
i.session_id = $sessionId
";
if ($get_individual_score) {
$sql .= ' AND i.user = '.intval($stud_id);
}
$sql_result = Database::query($sql);
if ($get_individual_score) {
// for 1 student
if ($data = Database::fetch_array($sql_result)) {
return [$data['answered'] ? $max_score : 0, $max_score];
}
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;
if (0 == $rescount) {
return [null, null];
}
switch ($type) {
case 'best':
return [$bestResult, $rescount];
break;
case 'average':
return [$sum, $rescount];
break;
case 'ranking':
return null;
break;
default:
return [$sum, $rescount];
break;
}
}
}
/**
* Check if this still links to a survey.
*/
public function is_valid_link()
{
$sessionId = $this->get_session_id();
$courseId = $this->getCourseId();
$sql = 'SELECT count(survey_id) FROM '.$this->get_survey_table().'
WHERE
c_id = '.$courseId.' AND
survey_id = '.$this->get_ref_id().' AND
session_id = '.$sessionId;
$result = Database::query($sql);
$number = Database::fetch_row($result);
return 0 != $number[0];
}
public function get_link()
{
if (api_get_configuration_value('hide_survey_reporting_button')) {
return null;
}
if (api_is_allowed_to_edit()) {
// Let students make access only through "Surveys" tool.
$tbl_name = $this->get_survey_table();
$sessionId = $this->get_session_id();
$courseId = $this->getCourseId();
if ('' != $tbl_name) {
$sql = 'SELECT survey_id
FROM '.$this->get_survey_table().'
WHERE
c_id = '.$courseId.' AND
survey_id = '.$this->get_ref_id().' AND
session_id = '.$sessionId;
$result = Database::query($sql);
$row = Database::fetch_array($result, 'ASSOC');
$survey_id = $row['survey_id'];
return api_get_path(WEB_PATH).'main/survey/reporting.php?'.api_get_cidreq_params($this->get_course_code(), $sessionId).'&survey_id='.$survey_id;
}
}
return null;
}
/**
* Get the name of the icon for this tool.
*
* @return string
*/
public function get_icon_name()
{
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 mixed
*/
private function get_survey_data()
{
$tbl_name = $this->get_survey_table();
if ('' == $tbl_name) {
return false;
} elseif (empty($this->survey_data)) {
$courseId = $this->getCourseId();
$sessionId = $this->get_session_id();
$sql = 'SELECT * FROM '.$tbl_name.'
WHERE
c_id = '.$courseId.' AND
survey_id = '.$this->get_ref_id().' AND
session_id = '.$sessionId;
$query = Database::query($sql);
$this->survey_data = Database::fetch_array($query);
}
return $this->survey_data;
}
/**
* @param string $string
*
* @return string
*/
private static function html_to_text($string)
{
return strip_tags($string);
}
}

View File

@@ -0,0 +1,478 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class CatForm.
*
* @author Stijn Konings
*/
class CatForm extends FormValidator
{
public const TYPE_ADD = 1;
public const TYPE_EDIT = 2;
public const TYPE_MOVE = 3;
public const TYPE_SELECT_COURSE = 4;
/** @var Category */
private $category_object;
/**
* CatForm constructor.
* Builds a form containing form items based on a given parameter.
*
* @param string $form_type 1=add, 2=edit,3=move,4=browse
* @param string $category_object
* @param string $form_name
* @param string $method
* @param null $action
*/
public function __construct(
$form_type,
$category_object,
$form_name,
$method = 'post',
$action = null
) {
parent::__construct($form_name, $method, $action);
$this->form_type = $form_type;
if (isset($category_object)) {
$this->category_object = $category_object;
}
switch ($this->form_type) {
case self::TYPE_EDIT:
$this->build_editing_form();
break;
case self::TYPE_ADD:
$this->build_add_form();
break;
case self::TYPE_MOVE:
$this->build_move_form();
break;
case self::TYPE_SELECT_COURSE:
$this->build_select_course_form();
break;
}
$this->setDefaults();
}
/**
* This function will build a move form that will allow the user to move a category to
* a another.
*/
protected function build_move_form()
{
$renderer = &$this->defaultRenderer();
$renderer->setCustomElementTemplate('<span>{element}</span> ');
$this->addElement(
'static',
null,
null,
'"'.$this->category_object->get_name().'" '
);
$this->addElement('static', null, null, get_lang('MoveTo').' : ');
$select = $this->addElement('select', 'move_cat', null, null);
$line = null;
foreach ($this->category_object->get_target_categories() as $cat) {
for ($i = 0; $i < $cat[2]; $i++) {
$line .= '--';
}
if ($cat[0] != $this->category_object->get_parent_id()) {
$select->addOption($line.' '.$cat[1], $cat[0]);
} else {
$select->addOption($line.' '.$cat[1], $cat[0], 'disabled');
}
$line = '';
}
$this->addElement('submit', null, get_lang('Ok'));
}
/**
* This function builds an 'add category form, if parent id is 0, it will only
* show courses.
*/
protected function build_add_form()
{
// check if we are a root category
// if so, you can only choose between courses
if ('0' == $this->category_object->get_parent_id()) {
$this->setDefaults(
[
'select_course' => $this->category_object->get_course_code(),
'hid_user_id' => $this->category_object->get_user_id(),
'hid_parent_id' => $this->category_object->get_parent_id(),
]
);
} else {
$this->setDefaults(
[
'hid_user_id' => $this->category_object->get_user_id(),
'hid_parent_id' => $this->category_object->get_parent_id(),
]
);
$this->addElement(
'hidden',
'course_code',
$this->category_object->get_course_code()
);
}
$this->build_basic_form();
}
/**
* Builds an form to edit a category.
*/
protected function build_editing_form()
{
$skills = $this->category_object->getSkillsForSelect();
$course_code = api_get_course_id();
$session_id = api_get_session_id();
$test_cats = Category::load(
null,
null,
$course_code,
null,
null,
$session_id,
false
);
$links = null;
if (isset($test_cats[0])) {
$links = $test_cats[0]->get_links();
}
$grade_model_id = $this->category_object->get_grade_model_id();
if (empty($links)) {
$grade_model_id = 0;
}
$category_name = $this->category_object->get_name();
// The main course category:
if (isset($this->category_object) && 0 == $this->category_object->get_parent_id()) {
if (empty($category_name)) {
$category_name = $course_code;
}
}
$this->setDefaults(
[
'name' => $category_name,
'description' => $this->category_object->get_description(),
'hid_user_id' => $this->category_object->get_user_id(),
'hid_parent_id' => $this->category_object->get_parent_id(),
'grade_model_id' => $grade_model_id,
'skills' => $skills,
'weight' => $this->category_object->get_weight(),
'visible' => $this->category_object->is_visible(),
'certif_min_score' => $this->category_object->getCertificateMinScore(),
'generate_certificates' => $this->category_object->getGenerateCertificates(),
'is_requirement' => $this->category_object->getIsRequirement(),
]
);
$this->addElement('hidden', 'hid_id', $this->category_object->get_id());
$this->addElement(
'hidden',
'course_code',
$this->category_object->get_course_code()
);
$this->build_basic_form();
}
/**
* This function builds an 'select course' form in the add category process,
* if parent id is 0, it will only show courses.
*/
protected function build_select_course_form()
{
$select = $this->addElement(
'select',
'select_course',
[get_lang('PickACourse'), 'test'],
null
);
$courses = Category::get_all_courses(api_get_user_id());
//only return courses that are not yet created by the teacher
foreach ($courses as $row) {
$select->addOption($row[1], $row[0]);
}
$this->setDefaults([
'hid_user_id' => $this->category_object->get_user_id(),
'hid_parent_id' => $this->category_object->get_parent_id(),
]);
$this->addElement('hidden', 'hid_user_id');
$this->addElement('hidden', 'hid_parent_id');
$this->addElement('submit', null, get_lang('Ok'));
}
private function build_basic_form()
{
$this->addText(
'name',
get_lang('CategoryName'),
true,
['maxlength' => '50']
);
$this->addRule('name', get_lang('ThisFieldIsRequired'), 'required');
if (isset($this->category_object) &&
$this->category_object->get_parent_id() == 0
) {
// we can't change the root category
$this->freeze('name');
}
$global_weight = api_get_setting('gradebook_default_weight');
$value = 100;
if (isset($global_weight)) {
$value = $global_weight;
}
$this->addFloat(
'weight',
[
get_lang('TotalWeight'),
get_lang('TotalSumOfWeights'),
],
true,
['value' => $value, 'maxlength' => '5']
);
$skillsDefaults = [];
$allowSkillEdit = api_is_platform_admin() || api_is_drh();
if (api_get_configuration_value('skills_teachers_can_assign_skills')) {
$allowSkillEdit = $allowSkillEdit || api_is_allowed_to_edit();
}
if ($allowSkillEdit) {
if (Skill::isToolAvailable()) {
$skillSelect = $this->addElement(
'select_ajax',
'skills',
[
get_lang('Skills'),
get_lang('SkillsAchievedWhenAchievingThisGradebook'),
],
null,
[
'id' => 'skills',
'multiple' => 'multiple',
'url' => api_get_path(WEB_AJAX_PATH).'skill.ajax.php?a=search_skills',
]
);
// The magic should be here
$skills = $this->category_object->get_skills();
foreach ($skills as $skill) {
$skillsDefaults[] = $skill['id'];
$skillSelect->addOption($skill['name'], $skill['id']);
}
}
}
$defaultCertification = 0;
if (!empty($this->category_object)) {
$defaultCertification = $this->category_object->getCertificateMinScore();
}
if (isset($this->category_object) &&
0 == $this->category_object->get_parent_id()
) {
$model = ExerciseLib::getCourseScoreModel();
if (empty($model)) {
$this->addText(
'certif_min_score',
get_lang('CertificateMinScore'),
true,
['maxlength' => '5']
);
if (true === api_get_configuration_value('gradebook_enable_subcategory_skills_independant_assignement')) {
// It allows the acquisition of competencies independently in the subcategories
$allowSkillsBySubCategory = $this->addCheckBox(
'allow_skills_by_subcategory',
[
null,
get_lang('ItAllowsTheAcquisitionOfSkillsBySubCategories'),
],
get_lang('AllowsSkillsBySubCategories')
);
$allowSkillsBySubCategory->setChecked($this->category_object->getAllowSkillBySubCategory());
}
} else {
$questionWeighting = $value;
$defaultCertification = api_number_format($this->category_object->getCertificateMinScore(), 2);
$select = $this->addSelect(
'certif_min_score',
get_lang('CertificateMinScore'),
[],
['disable_js' => true]
);
foreach ($model['score_list'] as $item) {
$i = api_number_format($item['score_to_qualify'] / 100 * $questionWeighting, 2);
$model = ExerciseLib::getModelStyle($item, $i);
$attributes = ['class' => $item['css_class']];
if ($defaultCertification == $i) {
$attributes['selected'] = 'selected';
}
$select->addOption($model, $i, $attributes);
}
$select->updateSelectWithSelectedOption($this);
}
$this->addRule(
'certif_min_score',
get_lang('OnlyNumbers'),
'numeric'
);
$this->addRule(
'certif_min_score',
get_lang('NegativeValue'),
'compare',
'>=',
'server',
false,
false,
0
);
} else {
// It enables minimun score to get the skills independant assigment
if (true === api_get_configuration_value('gradebook_enable_subcategory_skills_independant_assignement')) {
$allowSkillsBySubCategory = $this->category_object->getAllowSkillBySubCategory($this->category_object->get_parent_id());
if ($allowSkillsBySubCategory) {
$this->addText(
'certif_min_score',
get_lang('SkillMinScore'),
true,
['maxlength' => '5']
);
$this->addRule(
'certif_min_score',
get_lang('OnlyNumbers'),
'numeric'
);
$this->addRule(
'certif_min_score',
get_lang('NegativeValue'),
'compare',
'>=',
'server',
false,
false,
0
);
}
}
$this->addElement('checkbox', 'visible', null, get_lang('Visible'));
}
$this->addElement('hidden', 'hid_user_id');
$this->addElement('hidden', 'hid_parent_id');
$this->addElement(
'textarea',
'description',
get_lang('Description')
);
if (isset($this->category_object) &&
$this->category_object->get_parent_id() == 0 &&
(api_is_platform_admin() || api_get_setting('teachers_can_change_grade_model_settings') == 'true')
) {
// Getting grade models
$obj = new GradeModel();
$obj->fill_grade_model_select_in_form(
$this,
'grade_model_id',
$this->category_object->get_grade_model_id()
);
// Freeze or not
$course_code = api_get_course_id();
$session_id = api_get_session_id();
$test_cats = Category::load(
null,
null,
$course_code,
null,
null,
$session_id,
false
);
$links = null;
if (!empty($test_cats[0])) {
$links = $test_cats[0]->get_links();
}
if (count($test_cats) > 1 || !empty($links)) {
if ('true' == api_get_setting('gradebook_enable_grade_model')) {
$this->freeze('grade_model_id');
}
}
$generateCertificatesParams = [];
if ($this->category_object->getGenerateCertificates()) {
$generateCertificatesParams['checked'] = 'checked';
}
$this->addElement(
'checkbox',
'generate_certificates',
null,
get_lang('GenerateCertificates'),
$generateCertificatesParams
);
}
//if (!empty($session_id)) {
$isRequirementCheckbox = $this->addCheckBox(
'is_requirement',
[
null,
get_lang('ConsiderThisGradebookAsRequirementForASessionSequence'),
],
get_lang('IsRequirement')
);
//}
if ($this->category_object->getIsRequirement()) {
$isRequirementCheckbox->setChecked(true);
}
$documentId = $this->category_object->getDocumentId();
if (!empty($documentId)) {
$documentData = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
if (!empty($documentData)) {
$this->addLabel(get_lang('Certificate'), $documentData['title']);
}
}
if ($this->form_type == self::TYPE_ADD) {
$this->addButtonCreate(get_lang('AddCategory'));
} else {
$this->addElement('hidden', 'editcat', intval($_GET['editcat']));
$this->addButtonUpdate(get_lang('EditCategory'));
}
$setting = api_get_setting('tool_visible_by_default_at_creation');
$visibility_default = 1;
if (isset($setting['gradebook']) && $setting['gradebook'] == 'false') {
$visibility_default = 0;
}
$this->setDefaults(
[
'visible' => $visibility_default,
'skills' => $skillsDefaults,
'certif_min_score' => (string) $defaultCertification,
]
);
}
}

View File

@@ -0,0 +1,126 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Extends FormValidator with import and export forms.
*
* @author Stijn Konings
*/
class DataForm extends FormValidator
{
public const TYPE_IMPORT = 1;
public const TYPE_EXPORT = 2;
public const TYPE_EXPORT_PDF = 3;
/**
* Builds a form containing form items based on a given parameter.
*
* @param int form_type 1=import, 2=export
* @param obj cat_obj the category object
* @param obj res_obj the result object
* @param string form name
* @param method
* @param action
*/
public function __construct(
$form_type,
$form_name,
$method = 'post',
$action = null,
$target = '',
$locked_status
) {
parent::__construct($form_name, $method, $action, $target);
$this->form_type = $form_type;
if ($this->form_type == self::TYPE_IMPORT) {
$this->build_import_form();
} elseif ($this->form_type == self::TYPE_EXPORT) {
if ($locked_status == 0) {
$this->build_export_form_option(false);
} else {
$this->build_export_form();
}
} elseif ($this->form_type == self::TYPE_EXPORT_PDF) {
$this->build_pdf_export_form();
}
$this->setDefaults();
}
public function display()
{
parent::display();
}
public function setDefaults($defaults = [], $filter = null)
{
parent::setDefaults($defaults, $filter);
}
protected function build_pdf_export_form()
{
$renderer = &$this->defaultRenderer();
$renderer->setCustomElementTemplate('<span>{element}</span>');
$this->addElement('header', get_lang('ChooseOrientation'));
$this->addElement('radio', 'orientation', null, get_lang('Portrait'), 'portrait');
$this->addElement('radio', 'orientation', null, get_lang('Landscape'), 'landscape');
$this->addButtonExport(get_lang('Export'));
$this->setDefaults([
'orientation' => 'portrait',
]);
}
protected function build_export_form()
{
$this->addElement('header', get_lang('ChooseFormat'));
$this->addElement('radio', 'file_type', get_lang('OutputFileType'), 'CSV (Comma-Separated Values)', 'csv');
$this->addElement('radio', 'file_type', null, 'XML (Extensible Markup Language)', 'xml');
$this->addElement('radio', 'file_type', null, 'PDF (Portable Document Format)', 'pdf');
$this->addButtonExport(get_lang('Export'));
$this->setDefaults([
'file_type' => 'csv',
]);
}
protected function build_export_form_option($show_pdf = true)
{
$this->addElement('header', get_lang('ChooseFormat'));
$this->addElement('radio', 'file_type', get_lang('OutputFileType'), 'CSV (Comma-Separated Values)', 'csv');
$this->addElement('radio', 'file_type', null, 'XML (Extensible Markup Language)', 'xml');
$this->addElement(
'radio',
'file_type',
Display::return_icon('info3.gif', get_lang('ToExportMustLockEvaluation')),
'PDF (Portable Document Format)',
'pdf',
['disabled']
);
$this->addButtonExport(get_lang('Export'));
$this->setDefaults([
'file_type' => 'csv',
]);
}
protected function build_import_form()
{
$this->addElement('hidden', 'formSent');
$this->addElement('header', get_lang('ImportFileLocation'));
$this->addElement('file', 'import_file', get_lang('Location'));
$this->addElement(
'radio',
'file_type',
get_lang('FileType'),
'CSV (<a href="docs/example_csv.html" target="_blank" download>'
.get_lang('ExampleCSVFile')
.'</a>)',
'csv'
);
//$this->addElement('radio', 'file_type', null, 'XML (<a href="docs/example_xml.html" target="_blank" download>'.get_lang('ExampleXMLFile').'</a>)', 'xml');
$this->addElement('checkbox', 'overwrite', null, get_lang('OverwriteScores'));
$this->addElement('checkbox', 'ignoreerrors', null, get_lang('IgnoreErrors'));
$this->addButtonImport(get_lang('Ok'));
$this->setDefaults([
'formSent' => '1',
'file_type' => 'csv',
]);
}
}

View File

@@ -0,0 +1,735 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class DisplayGradebook.
*/
class DisplayGradebook
{
/**
* Displays the header for the result page containing the navigation tree and links.
*
* @param Evaluation $evalobj
* @param int $selectcat
* @param string $page
*/
public static function display_header_result($evalobj, $selectcat, $page)
{
$header = null;
if (api_is_allowed_to_edit(null, true)) {
$header = '<div class="actions">';
if ('statistics' !== $page) {
$header .= '<a href="'.Category::getUrl().'selectcat='.$selectcat.'">'.
Display::return_icon('back.png', get_lang('FolderView'), '', ICON_SIZE_MEDIUM)
.'</a>';
if (($evalobj->get_course_code() != null) && !$evalobj->has_results()) {
$header .= '<a href="gradebook_add_result.php?'.api_get_cidreq().'&selectcat='.$selectcat.'&selecteval='.$evalobj->get_id().'">
'.Display::return_icon('evaluation_rate.png', get_lang('AddResult'), '', ICON_SIZE_MEDIUM).'</a>';
}
if (api_is_platform_admin() || $evalobj->is_locked() == false) {
$header .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&selecteval='.$evalobj->get_id().'&import=">'.
Display::return_icon('import_evaluation.png', get_lang('ImportResult'), '', ICON_SIZE_MEDIUM).'</a>';
}
if ($evalobj->has_results()) {
$header .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&selecteval='.$evalobj->get_id().'&export=">'.
Display::return_icon('export_evaluation.png', get_lang('ExportResult'), '', ICON_SIZE_MEDIUM).'</a>';
if (api_is_platform_admin() || $evalobj->is_locked() == false) {
$header .= '<a href="gradebook_edit_result.php?'.api_get_cidreq().'&selecteval='.$evalobj->get_id().'">'.
Display::return_icon('edit.png', get_lang('EditResult'), '', ICON_SIZE_MEDIUM).'</a>';
$header .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&selecteval='.$evalobj->get_id().'&deleteall=" onclick="return confirmationall();">'.
Display::return_icon('delete.png', get_lang('DeleteResult'), '', ICON_SIZE_MEDIUM).'</a>';
}
}
$header .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&print=&selecteval='.$evalobj->get_id().'" target="_blank">'.
Display::return_icon('printer.png', get_lang('Print'), '', ICON_SIZE_MEDIUM).'</a>';
} else {
$header .= '<a href="gradebook_view_result.php?'.api_get_cidreq().'&selecteval='.Security::remove_XSS($_GET['selecteval']).'"> '.
Display::return_icon('back.png', get_lang('FolderView'), '', ICON_SIZE_MEDIUM).'</a>';
}
$header .= '</div>';
}
$scoredisplay = ScoreDisplay::instance();
$student_score = '';
$average = '';
if ($evalobj->has_results()) {
// TODO this check needed ?
$score = $evalobj->calc_score();
if (null != $score) {
$model = ExerciseLib::getCourseScoreModel();
if (empty($model)) {
$average = get_lang('Average').' :<b> '.$scoredisplay->display_score($score, SCORE_AVERAGE).'</b>';
$student_score = $evalobj->calc_score(api_get_user_id());
$student_score = Display::tag(
'h3',
get_lang('Score').': '.$scoredisplay->display_score($student_score, SCORE_DIV_PERCENT)
);
$allowMultipleAttempts = api_get_configuration_value('gradebook_multiple_evaluation_attempts');
if ($allowMultipleAttempts) {
$results = Result::load(null, api_get_user_id(), $evalobj->get_id());
if (!empty($results)) {
/** @var Result $resultData */
foreach ($results as $resultData) {
$student_score .= ResultTable::getResultAttemptTable($resultData);
}
}
}
}
}
}
$description = '';
if ('' == !$evalobj->get_description()) {
$description = get_lang('Description').' :<b> '.Security::remove_XSS($evalobj->get_description()).'</b><br>';
}
if ($evalobj->get_course_code() == null) {
$course = get_lang('CourseIndependent');
} else {
$course = CourseManager::getCourseNameFromCode($evalobj->get_course_code());
}
$evalinfo = '<table width="100%" border="0"><tr><td>';
$evalinfo .= '<h2>'.Security::remove_XSS($evalobj->get_name()).'</h2><hr>';
$evalinfo .= $description;
$evalinfo .= get_lang('Course').' :<b> '.$course.'</b><br />';
if (empty($model)) {
$evalinfo .= get_lang('QualificationNumeric').' :<b> '.$evalobj->get_max().'</b><br>'.$average;
}
if (!api_is_allowed_to_edit()) {
$evalinfo .= $student_score;
}
if (!$evalobj->has_results()) {
$evalinfo .= '<br /><i>'.get_lang('NoResultsInEvaluation').'</i>';
}
if ($page != 'statistics') {
if (api_is_allowed_to_edit(null, true)) {
$evalinfo .= '<br /><a href="gradebook_statistics.php?'.api_get_cidreq().'&selecteval='.Security::remove_XSS($_GET['selecteval']).'"> '.
Display::return_icon(
'statistics.png',
get_lang('ViewStatistics'),
'',
ICON_SIZE_MEDIUM
).'</a>';
}
}
$evalinfo .= '</td><td>'.
Display::return_icon(
'tutorial.gif',
'',
['style' => 'float:right; position:relative;']
)
.'</td></table>';
echo $evalinfo;
echo $header;
}
/**
* Displays the header for the flatview page containing filters.
*
* @param $catobj
* @param $showeval
* @param $showlink
*/
public static function display_header_reduce_flatview($catobj, $showeval, $showlink, $simple_search_form)
{
$header = '<div class="actions">';
if ($catobj->get_parent_id() == 0) {
$select_cat = $catobj->get_id();
$url = Category::getUrl();
} else {
$select_cat = $catobj->get_parent_id();
$url = 'gradebook_flatview.php';
}
$header .= '<a href="'.$url.'?'.api_get_cidreq().'&selectcat='.$select_cat.'">'.
Display::return_icon('back.png', get_lang('FolderView'), '', ICON_SIZE_MEDIUM).'</a>';
$pageNum = isset($_GET['flatviewlist_page_nr']) ? (int) $_GET['flatviewlist_page_nr'] : null;
$perPage = isset($_GET['flatviewlist_per_page']) ? (int) $_GET['flatviewlist_per_page'] : null;
$offset = isset($_GET['offset']) ? $_GET['offset'] : '0';
$exportCsvUrl = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
'export_format' => 'csv',
'export_report' => 'export_report',
'selectcat' => $catobj->get_id(),
]);
$scoreRanking = ScoreDisplay::instance()->get_custom_score_display_settings();
$attributes = [];
if (!empty($scoreRanking)) {
$attributes = ['class' => 'export_opener'];
}
$header .= Display::url(
Display::return_icon(
'export_csv.png',
get_lang('ExportAsCSV'),
'',
ICON_SIZE_MEDIUM
),
$exportCsvUrl,
$attributes
);
$exportXlsUrl = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
'export_format' => 'xls',
'export_report' => 'export_report',
'selectcat' => $catobj->get_id(),
]);
$header .= Display::url(
Display::return_icon(
'export_excel.png',
get_lang('ExportAsXLS'),
'',
ICON_SIZE_MEDIUM
),
$exportXlsUrl,
$attributes
);
$exportDocUrl = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
'export_format' => 'doc',
'export_report' => 'export_report',
'selectcat' => $catobj->get_id(),
]);
$header .= Display::url(
Display::return_icon(
'export_doc.png',
get_lang('ExportAsDOC'),
'',
ICON_SIZE_MEDIUM
),
$exportDocUrl,
$attributes
);
$exportPrintUrl = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
'print' => '',
'selectcat' => $catobj->get_id(),
]);
$header .= Display::url(
Display::return_icon(
'printer.png',
get_lang('Print'),
'',
ICON_SIZE_MEDIUM
),
$exportPrintUrl,
['target' => '_blank']
);
$exportPdfUrl = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
'exportpdf' => '',
'selectcat' => $catobj->get_id(),
'offset' => $offset,
'flatviewlist_page_nr' => $pageNum,
'flatviewlist_per_page' => $perPage,
]);
$header .= Display::url(
Display::return_icon(
'pdf.png',
get_lang('ExportToPDF'),
'',
ICON_SIZE_MEDIUM
),
$exportPdfUrl
);
$header .= '</div>';
$dialog = '';
if (!empty($scoreRanking)) {
$dialog = '<div id="dialog-confirm" style="display:none" title="'.get_lang('ConfirmYourChoice').'">';
$form = new FormValidator(
'report',
'post',
null,
null,
['class' => 'form-vertical']
);
$form->addCheckBox(
'only_score',
null,
get_lang('OnlyNumbers')
);
$dialog .= $form->returnForm();
$dialog .= '</div>';
}
echo $header.$dialog;
}
/**
* Displays the header for the gradebook containing the navigation tree and links.
*
* @param Category $catobj
* @param int $showtree '1' will show the browse tree and naviation buttons
* @param $selectcat
* @param bool $is_course_admin
* @param bool $is_platform_admin
* @param bool $simple_search_form
* @param bool $show_add_qualification Whether to show or not the link to add a new qualification
* (we hide it in case of the course-embedded tool where we have only one
* per course or session)
* @param bool $show_add_link Whether to show or not the link to add a new item inside
* the qualification (we hide it in case of the course-embedded tool
* where we have only one qualification per course or session)
* @param array $certificateLinkInfo
*/
public static function header(
$catobj,
$showtree,
$selectcat,
$is_course_admin,
$is_platform_admin,
$simple_search_form,
$show_add_qualification = true,
$show_add_link = true,
$certificateLinkInfo = []
) {
$userId = api_get_user_id();
$courseId = api_get_course_int_id();
$sessionId = api_get_session_id();
if (!$is_course_admin) {
$model = ExerciseLib::getCourseScoreModel();
if (!empty($model)) {
return '';
}
}
// Student.
$status = CourseManager::getUserInCourseStatus($userId, $courseId);
$sessionStatus = 0;
if (!empty($sessionId)) {
$sessionStatus = SessionManager::get_user_status_in_course_session(
$userId,
$courseId,
$sessionId
);
}
$objcat = new Category();
$course_id = CourseManager::get_course_by_category($selectcat);
$message_resource = $objcat->show_message_resource_delete($course_id);
$grade_model_id = $catobj->get_grade_model_id();
$header = null;
if (isset($catobj) && !empty($catobj)) {
$categories = Category::load(
null,
null,
null,
$catobj->get_id(),
null,
$sessionId
);
}
if (!$is_course_admin && ($status != 1 || $sessionStatus == 0) && $selectcat != 0) {
$catcourse = Category::load($catobj->get_id());
/** @var Category $category */
$category = $catcourse[0];
$main_weight = $category->get_weight();
$scoredisplay = ScoreDisplay::instance();
$allevals = $category->get_evaluations($userId, true);
$alllinks = $category->get_links($userId, true);
$allEvalsLinks = array_merge($allevals, $alllinks);
$item_value_total = 0;
$scoreinfo = null;
for ($count = 0; $count < count($allEvalsLinks); $count++) {
$item = $allEvalsLinks[$count];
$score = $item->calc_score($userId);
if (!empty($score)) {
$divide = $score[1] == 0 ? 1 : $score[1];
$item_value = $score[0] / $divide * $item->get_weight();
$item_value_total += $item_value;
}
}
$item_total = $main_weight;
$total_score = [$item_value_total, $item_total];
$scorecourse_display = $scoredisplay->display_score($total_score, SCORE_DIV_PERCENT);
if (!$catobj->get_id() == '0' && !isset($_GET['studentoverview']) && !isset($_GET['search'])) {
$additionalButtons = null;
if (!empty($certificateLinkInfo)) {
$additionalButtons .= '<div class="btn-group pull-right">';
$additionalButtons .= isset($certificateLinkInfo['certificate_link']) ? $certificateLinkInfo['certificate_link'] : '';
$additionalButtons .= isset($certificateLinkInfo['badge_link']) ? $certificateLinkInfo['badge_link'] : '';
$additionalButtons .= '</div>';
}
$scoreinfo .= '<strong>'.sprintf(get_lang('TotalX'), $scorecourse_display.$additionalButtons).'</strong>';
}
echo Display::return_message($scoreinfo, 'normal', false);
}
// show navigation tree and buttons?
if ($showtree == '1' || isset($_GET['studentoverview'])) {
$header = '<div class="actions"><table>';
$header .= '<tr>';
if (!$selectcat == '0') {
$header .= '<td><a href="'.api_get_self().'?selectcat='.$catobj->get_parent_id().'">'.
Display::return_icon(
'back.png',
get_lang('BackTo').' '.get_lang('RootCat'),
'',
ICON_SIZE_MEDIUM
).
'</a></td>';
}
$header .= '<td>'.get_lang('CurrentCategory').'</td>'.
'<td><form name="selector"><select name="selectcat" onchange="document.selector.submit()">';
$cats = Category::load();
$tree = $cats[0]->get_tree();
unset($cats);
$line = null;
foreach ($tree as $cat) {
for ($i = 0; $i < $cat[2]; $i++) {
$line .= '&mdash;';
}
$line = isset($line) ? $line : '';
if (isset($_GET['selectcat']) && $_GET['selectcat'] == $cat[0]) {
$header .= '<option selected value='.$cat[0].'>'.$line.' '.$cat[1].'</option>';
} else {
$header .= '<option value='.$cat[0].'>'.$line.' '.$cat[1].'</option>';
}
$line = '';
}
$header .= '</select></form></td>';
if (!empty($simple_search_form) && $message_resource === false) {
$header .= '<td style="vertical-align: top;">'.$simple_search_form->toHtml().'</td>';
} else {
$header .= '<td></td>';
}
if (!($is_course_admin &&
$message_resource === false &&
isset($_GET['selectcat']) && $_GET['selectcat'] != 0) &&
isset($_GET['studentoverview'])
) {
$header .= '<td style="vertical-align: top;">
<a href="'.api_get_self().'?'.api_get_cidreq().'&studentoverview=&exportpdf=&selectcat='.$catobj->get_id().'" target="_blank">
'.Display::return_icon('pdf.png', get_lang('ExportPDF'), [], ICON_SIZE_MEDIUM).'
'.get_lang('ExportPDF').'</a>';
}
$header .= '</td></tr>';
$header .= '</table></div>';
}
// for course admin & platform admin add item buttons are added to the header
$actionsLeft = '';
$actionsRight = '';
$my_api_cidreq = api_get_cidreq();
$isCoach = api_is_coach(api_get_session_id(), api_get_course_int_id());
$accessToRead = api_is_allowed_to_edit(null, true) || $isCoach;
$accessToEdit = api_is_allowed_to_edit(null, true);
$courseCode = api_get_course_id();
if ($accessToRead) {
$my_category = $catobj->showAllCategoryInfo($catobj->get_id());
if ($selectcat != '0' && $accessToEdit) {
if ($my_api_cidreq == '') {
$my_api_cidreq = 'cidReq='.$my_category['course_code'];
}
if ($show_add_link && !$message_resource) {
$actionsLeft .= '<a href="gradebook_add_eval.php?'.$my_api_cidreq.'&selectcat='.$catobj->get_id().'" >'.
Display::return_icon('new_evaluation.png', get_lang('NewEvaluation'), '',
ICON_SIZE_MEDIUM).'</a>';
$cats = Category::load($selectcat);
if ($cats[0]->get_course_code() != null && !$message_resource) {
$actionsLeft .= '<a href="gradebook_add_link.php?'.$my_api_cidreq.'&selectcat='.$catobj->get_id().'">'.
Display::return_icon('new_online_evaluation.png', get_lang('MakeLink'), '',
ICON_SIZE_MEDIUM).'</a>';
} else {
$actionsLeft .= '<a href="gradebook_add_link_select_course.php?'.$my_api_cidreq.'&selectcat='.$catobj->get_id().'">'.
Display::return_icon('new_online_evaluation.png', get_lang('MakeLink'), '',
ICON_SIZE_MEDIUM).'</a>';
}
}
}
if ((empty($grade_model_id) || $grade_model_id == -1) && $accessToEdit) {
$actionsLeft .= '<a href="gradebook_add_cat.php?'.api_get_cidreq().'&selectcat='.$catobj->get_id().'">'.
Display::return_icon(
'new_folder.png',
get_lang('AddGradebook'),
[],
ICON_SIZE_MEDIUM
).'</a></td>';
}
if ($selectcat != '0' && $accessToRead) {
if (!$message_resource) {
$actionsLeft .= '<a href="gradebook_flatview.php?'.$my_api_cidreq.'&selectcat='.$catobj->get_id().'">'.
Display::return_icon('statistics.png', get_lang('FlatView'), '', ICON_SIZE_MEDIUM).'</a>';
if ($my_category['generate_certificates'] == 1) {
$actionsLeft .= Display::url(
Display::return_icon(
'certificate_list.png',
get_lang('GradebookSeeListOfStudentsCertificates'),
'',
ICON_SIZE_MEDIUM
),
"gradebook_display_certificate.php?$my_api_cidreq&cat_id=".$selectcat
);
}
$actionsLeft .= Display::url(
Display::return_icon(
'user.png',
get_lang('GradebookListOfStudentsReports'),
'',
ICON_SIZE_MEDIUM
),
"gradebook_display_summary.php?$my_api_cidreq&selectcat=".$selectcat
);
$allow = api_get_configuration_value('gradebook_custom_student_report');
if ($allow) {
$actionsLeft .= Display::url(
get_lang('GenerateCustomReport'),
api_get_path(WEB_AJAX_PATH)."gradebook.ajax.php?$my_api_cidreq&a=generate_custom_report",
['class' => 'btn btn-default ajax']
);
}
// Right icons
if ($accessToEdit) {
$actionsRight = '<a href="gradebook_edit_cat.php?editcat='.$catobj->get_id(
).'&cidReq='.$catobj->get_course_code().'&id_session='.$catobj->get_session_id().'">'.
Display::return_icon('edit.png', get_lang('Edit'), '', ICON_SIZE_MEDIUM).'</a>';
if (api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate') === 'true' &&
api_get_course_setting('customcertificate_course_enable') == 1
) {
$actionsRight .= '<a href="'.api_get_path(
WEB_PLUGIN_PATH
).'customcertificate/src/index.php?'.
$my_api_cidreq.'&origin=gradebook&selectcat='.$catobj->get_id().'">'.
Display::return_icon(
'certificate.png',
get_lang('AttachCertificate'),
'',
ICON_SIZE_MEDIUM
).'</a>';
} else {
$actionsRight .= '<a href="'.api_get_path(WEB_CODE_PATH).
'document/document.php?curdirpath=/certificates&'.
$my_api_cidreq.'&origin=gradebook&selectcat='.$catobj->get_id().'">'.
Display::return_icon(
'certificate.png',
get_lang('AttachCertificate'),
'',
ICON_SIZE_MEDIUM
).'</a>';
}
if (empty($categories)) {
$actionsRight .= '<a href="gradebook_edit_all.php?id_session='.api_get_session_id(
).'&'.$my_api_cidreq.'&selectcat='.$catobj->get_id().'">'.
Display::return_icon(
'percentage.png',
get_lang('EditAllWeights'),
'',
ICON_SIZE_MEDIUM
).'</a>';
}
$score_display_custom = api_get_setting('gradebook_score_display_custom');
if (api_get_setting('teachers_can_change_score_settings') == 'true' &&
$score_display_custom['my_display_custom'] == 'true'
) {
$actionsRight .= '<a href="gradebook_scoring_system.php?'.$my_api_cidreq.'&selectcat='.$catobj->get_id().'">'.
Display::return_icon('ranking.png', get_lang('ScoreEdit'), '', ICON_SIZE_MEDIUM).'</a>';
}
}
}
}
} elseif (isset($_GET['search'])) {
echo $header = '<b>'.get_lang('SearchResults').' :</b>';
}
$isDrhOfCourse = CourseManager::isUserSubscribedInCourseAsDrh(
api_get_user_id(),
api_get_course_info()
);
if ($isDrhOfCourse) {
$actionsLeft .= '<a href="gradebook_flatview.php?'.$my_api_cidreq.'&selectcat='.$catobj->get_id().'">'.
Display::return_icon(
'statistics.png',
get_lang('FlatView'),
'',
ICON_SIZE_MEDIUM
).
'</a>';
}
if ($isCoach || api_is_allowed_to_edit(null, true)) {
echo $toolbar = Display::toolbarAction(
'gradebook-actions',
[$actionsLeft, $actionsRight]
);
}
if ($accessToEdit || api_is_allowed_to_edit(null, true)) {
$weight = intval($catobj->get_weight()) > 0 ? $catobj->get_weight() : 0;
$weight = '<strong>'.get_lang('TotalWeight').' : </strong>'.$weight;
$min_certification = intval($catobj->getCertificateMinScore() > 0) ? $catobj->getCertificateMinScore() : 0;
if (!empty($min_certification)) {
$model = ExerciseLib::getCourseScoreModel();
if (!empty($model)) {
$defaultCertification = api_number_format($min_certification, 2);
$questionWeighting = $catobj->get_weight();
foreach ($model['score_list'] as $item) {
$i = api_number_format($item['score_to_qualify'] / 100 * $questionWeighting, 2);
$model = ExerciseLib::getModelStyle($item, $i);
if ($defaultCertification == $i) {
$min_certification = $model;
break;
}
}
}
}
$min_certification = get_lang('CertificateMinScore').' : '.$min_certification;
$edit_icon = '<a href="gradebook_edit_cat.php?editcat='.$catobj->get_id().'&cidReq='.$catobj->get_course_code().'&id_session='.$catobj->get_session_id().'">'.
Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL).'</a>';
$msg = $weight.' - '.$min_certification.$edit_icon;
//@todo show description
$description = (($catobj->get_description() == '' || is_null($catobj->get_description())) ? '' : '<strong>'.get_lang('GradebookDescriptionLog').'</strong>'.': '.$catobj->get_description());
echo Display::return_message($msg, 'normal', false);
if (!empty($description)) {
echo Display::div($description, []);
}
}
}
/**
* @param Category $catobj
* @param $is_course_admin
* @param $is_platform_admin
* @param $simple_search_form
* @param bool $show_add_qualification
* @param bool $show_add_link
*/
public function display_reduce_header_gradebook(
$catobj,
$is_course_admin,
$is_platform_admin,
$simple_search_form,
$show_add_qualification = true,
$show_add_link = true
) {
//student
if (!$is_course_admin) {
$user = api_get_user_info(api_get_user_id());
$catcourse = Category::load($catobj->get_id());
$scoredisplay = ScoreDisplay::instance();
$scorecourse = $catcourse[0]->calc_score(api_get_user_id());
$scorecourse_display = isset($scorecourse) ? $scoredisplay->display_score($scorecourse, SCORE_AVERAGE) : get_lang('NoResultsAvailable');
$cattotal = Category::load(0);
$scoretotal = $cattotal[0]->calc_score(api_get_user_id());
$scoretotal_display = isset($scoretotal) ? $scoredisplay->display_score($scoretotal, SCORE_PERCENT) : get_lang('NoResultsAvailable');
$scoreinfo = get_lang('StatsStudent').' :<b> '.$user['complete_name'].'</b><br />';
if ((!$catobj->get_id() == '0') && (!isset($_GET['studentoverview'])) && (!isset($_GET['search']))) {
$scoreinfo .= '<br />'.get_lang('TotalForThisCategory').' : <b>'.$scorecourse_display.'</b>';
}
$scoreinfo .= '<br />'.get_lang('Total').' : <b>'.$scoretotal_display.'</b>';
Display::addFlash(
Display::return_message($scoreinfo, 'normal', false)
);
}
// show navigation tree and buttons?
$header = '<div class="actions">';
if ($is_course_admin) {
$header .= '<a href="gradebook_flatview.php?'.api_get_cidreq().'&selectcat='.$catobj->get_id().'">'.
Display::return_icon('statistics.png', get_lang('FlatView'), '', ICON_SIZE_MEDIUM).'</a>';
$header .= '<a href="gradebook_scoring_system.php?'.api_get_cidreq().'&selectcat='.$catobj->get_id().'">'.
Display::return_icon('settings.png', get_lang('ScoreEdit'), '', ICON_SIZE_MEDIUM).'</a>';
} elseif (!(isset($_GET['studentoverview']))) {
$header .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&studentoverview=&selectcat='.$catobj->get_id().'">'.
Display::return_icon('view_list.gif', get_lang('FlatView')).' '.get_lang('FlatView').'</a>';
} else {
$header .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&studentoverview=&exportpdf=&selectcat='.$catobj->get_id().'" target="_blank">'.
Display::return_icon('pdf.png', get_lang('ExportPDF'), '', ICON_SIZE_MEDIUM).'</a>';
}
$header .= '</div>';
echo $header;
}
/**
* @param int $userId
* @param int $categoryId
*
* @return string
*/
public static function display_header_user($userId, $categoryId)
{
$user = api_get_user_info($userId);
if (empty($user)) {
return '';
}
$catcourse = Category::load($categoryId);
$scoredisplay = ScoreDisplay::instance();
// generating the total score for a course
$allevals = $catcourse[0]->get_evaluations(
$userId,
true,
api_get_course_id()
);
$alllinks = $catcourse[0]->get_links(
$userId,
true,
api_get_course_id()
);
$evals_links = array_merge($allevals, $alllinks);
$item_value = 0;
$item_total = 0;
for ($count = 0; $count < count($evals_links); $count++) {
$item = $evals_links[$count];
$score = $item->calc_score($userId);
if ($score) {
$my_score_denom = ($score[1] == 0) ? 1 : $score[1];
$item_value += $score[0] / $my_score_denom * $item->get_weight();
}
$item_total += $item->get_weight();
}
$item_value = api_number_format($item_value, 2);
$total_score = [$item_value, $item_total];
$scorecourse_display = $scoredisplay->display_score($total_score, SCORE_DIV_PERCENT);
$info = '<div class="row"><div class="col-md-3">';
$info .= '<div class="thumbnail"><img src="'.$user['avatar'].'" /></div>';
$info .= '</div>';
$info .= '<div class="col-md-6">';
$info .= get_lang('Name').' : '.$user['complete_name_with_message_link'].'<br />';
if (api_get_setting('show_email_addresses') === 'true') {
$info .= get_lang('Email').' : <a href="mailto:'.$user['email'].'">'.$user['email'].'</a><br />';
}
$info .= get_lang('TotalUser').' : <b>'.$scorecourse_display.'</b>';
$info .= '</div>';
$info .= '</div>';
echo $info;
}
}

View File

@@ -0,0 +1,768 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class EvalForm.
*
* Extends FormValidator with add&edit forms for evaluations
*
* @author Stijn Konings
*/
class EvalForm extends FormValidator
{
public const TYPE_ADD = 1;
public const TYPE_EDIT = 2;
public const TYPE_MOVE = 3;
public const TYPE_RESULT_ADD = 4;
public const TYPE_RESULT_EDIT = 5;
public const TYPE_ALL_RESULTS_EDIT = 6;
public const TYPE_ADD_USERS_TO_EVAL = 7;
protected $evaluation_object;
private $result_object;
private $extra;
/**
* Builds a form containing form items based on a given parameter.
*
* @param int $form_type 1=add, 2=edit,3=move,4=result_add
* @param Evaluation $evaluation_object the category object
* @param obj $result_object the result object
* @param string $form_name
* @param string $method
* @param string $action
*/
public function __construct(
$form_type,
$evaluation_object,
$result_object,
$form_name,
$method = 'post',
$action = null,
$extra1 = null,
$extra2 = null
) {
parent::__construct($form_name, $method, $action);
if (isset($evaluation_object)) {
$this->evaluation_object = $evaluation_object;
}
if (isset($result_object)) {
$this->result_object = $result_object;
}
if (isset($extra1)) {
$this->extra = $extra1;
}
switch ($form_type) {
case self::TYPE_EDIT:
$this->build_editing_form();
break;
case self::TYPE_ADD:
$this->build_add_form();
break;
case self::TYPE_MOVE:
$this->build_editing_form();
break;
case self::TYPE_RESULT_ADD:
$this->build_result_add_form();
break;
case self::TYPE_RESULT_EDIT:
$this->build_result_edit_form();
break;
case self::TYPE_ALL_RESULTS_EDIT:
$this->build_all_results_edit_form();
break;
case self::TYPE_ADD_USERS_TO_EVAL:
$this->build_add_user_to_eval();
break;
}
$this->protect();
$this->setDefaults();
}
public function display()
{
parent::display();
}
public function setDefaults($defaults = [], $filter = null)
{
parent::setDefaults($defaults, $filter);
}
public function sort_by_user($item1, $item2)
{
$user1 = $item1['user'];
$user2 = $item2['user'];
if (api_sort_by_first_name()) {
$result = api_strcmp($user1['firstname'], $user2['firstname']);
if (0 == $result) {
return api_strcmp($user1['lastname'], $user2['lastname']);
}
} else {
$result = api_strcmp($user1['lastname'], $user2['lastname']);
if (0 == $result) {
return api_strcmp($user1['firstname'], $user2['firstname']);
}
}
return $result;
}
/**
* This form will build a form to add users to an evaluation.
*/
protected function build_add_user_to_eval()
{
$this->addElement('header', get_lang('ChooseUser'));
$select = $this->addElement(
'select',
'firstLetterUser',
get_lang('FirstLetter'),
null,
[
'onchange' => 'document.add_users_to_evaluation.submit()',
]
);
$select->addOption('', '');
for ($i = 65; $i <= 90; $i++) {
$letter = chr($i);
if (isset($this->extra) && $this->extra == $letter) {
$select->addOption($letter, $letter, 'selected');
} else {
$select->addOption($letter, $letter);
}
}
$select = $this->addElement(
'select',
'add_users',
null,
null,
[
'multiple' => 'multiple',
'size' => '15',
'style' => 'width:250px',
]
);
foreach ($this->evaluation_object->get_not_subscribed_students() as $user) {
if ((!isset($this->extra)) || empty($this->extra) || api_strtoupper(api_substr($user[1], 0, 1)) == $this->extra
) {
$select->addOption($user[1].' '.$user[2].' ('.$user[3].')', $user[0]);
}
}
$this->addButtonCreate(get_lang('AddUserToEval'), 'submit_button');
}
/**
* This function builds a form to edit all results in an evaluation.
*/
protected function build_all_results_edit_form()
{
//extra field for check on maxvalue
$this->addElement('header', get_lang('EditResult'));
$renderer = &$this->defaultRenderer();
// set new form template
$form_template = '<form{attributes}>
<div class="table-responsive">
<table class="table table-hover table-striped data_table" border="0" cellpadding="5" cellspacing="5">{content}</table>
</div>
</form>';
$renderer->setFormTemplate($form_template);
$skillRelItemsEnabled = api_get_configuration_value('allow_skill_rel_items');
$columnSkill = '';
if ($skillRelItemsEnabled) {
$columnSkill = '<th>'.get_lang('Skills').'</th>';
}
if (api_is_western_name_order()) {
$renderer->setHeaderTemplate(
'<tr>
<th>'.get_lang('OfficialCode').'</th>
<th>'.get_lang('UserName').'</th>
<th>'.get_lang('FirstName').'</th>
<th>'.get_lang('LastName').'</th>
<th>'.get_lang('Qualify').'</th>
'.$columnSkill.'
</tr>'
);
} else {
$renderer->setHeaderTemplate(
'<tr>
<th>'.get_lang('OfficialCode').'</th>
<th>'.get_lang('UserName').'</th>
<th>'.get_lang('LastName').'</th>
<th>'.get_lang('FirstName').'</th>
<th>'.get_lang('Qualify').'</th>
'.$columnSkill.'
</tr>'
);
}
$template_submit = '<tr>
<td colspan="4" ></td>
<td>
{element}
<!-- BEGIN error --><br /><span style="color: #ff0000;font-size:10px">{error}</span><!-- END error -->
</td>
'.($skillRelItemsEnabled ? '<td></td>' : '').'
</tr>';
$results_and_users = [];
foreach ($this->result_object as $result) {
$user = api_get_user_info($result->get_user_id());
$results_and_users[] = ['result' => $result, 'user' => $user];
}
usort($results_and_users, ['EvalForm', 'sort_by_user']);
$defaults = [];
$model = ExerciseLib::getCourseScoreModel();
foreach ($results_and_users as $result_and_user) {
$user = $result_and_user['user'];
/** @var \Result $result */
$result = $result_and_user['result'];
$renderer = &$this->defaultRenderer();
$columnSkillResult = '';
if ($skillRelItemsEnabled) {
$columnSkillResult = '<td>'.Skill::getAddSkillsToUserBlock(ITEM_TYPE_GRADEBOOK_EVALUATION, $result->get_evaluation_id(), $result->get_user_id(), $result->get_id()).'</td>';
}
if (api_is_western_name_order()) {
$user_info = '<td align="left" >'.$user['firstname'].'</td>';
$user_info .= '<td align="left" >'.$user['lastname'].'</td>';
} else {
$user_info = '<td align="left" >'.$user['lastname'].'</td>';
$user_info .= '<td align="left" >'.$user['firstname'].'</td>';
}
$template = '<tr>
<td align="left" >'.$user['official_code'].'</td>
<td align="left" >'.$user['username'].'</td>
'.$user_info.'
<td align="left">{element} / '.$this->evaluation_object->get_max().'
<!-- BEGIN error --><br /><span style="color: #ff0000;font-size:10px">{error}</span><!-- END error -->
</td>
'.$columnSkillResult.'
</tr>';
if (empty($model)) {
$this->addFloat(
'score['.$result->get_id().']',
$this->build_stud_label($user['user_id'], $user['username'], $user['lastname'], $user['firstname']),
false,
[
'maxlength' => 5,
],
false,
0,
$this->evaluation_object->get_max()
);
$defaults['score['.$result->get_id().']'] = $result->get_score();
} else {
$questionWeighting = $this->evaluation_object->get_max();
$select = $this->addSelect(
'score['.$result->get_id().']',
get_lang('Score'),
[],
['disable_js' => true, 'id' => 'score_'.$result->get_id()]
);
foreach ($model['score_list'] as $item) {
$i = api_number_format($item['score_to_qualify'] / 100 * $questionWeighting, 2);
$modelStyle = ExerciseLib::getModelStyle($item, $i);
$attributes = ['class' => $item['css_class']];
if ($result->get_score() == $i) {
$attributes['selected'] = 'selected';
}
$select->addOption($modelStyle, $i, $attributes);
}
$select->updateSelectWithSelectedOption($this);
$template = '<tr>
<td align="left" >'.$user['official_code'].'</td>
<td align="left" >'.$user['username'].'</td>
'.$user_info.'
<td align="left">{element} <!-- BEGIN error --><br /><span style="color: #ff0000;font-size:10px">{error}</span><!-- END error -->
</td>
'.$columnSkillResult.'
</tr>';
}
$renderer->setElementTemplate($template, 'score['.$result->get_id().']');
}
if (empty($model)) {
$this->setDefaults($defaults);
}
$this->addButtonSave(get_lang('EditResult'));
$renderer->setElementTemplate($template_submit, 'submit');
}
/**
* This function builds a form to move an item to another category.
*/
protected function build_move_form()
{
$renderer = &$this->defaultRenderer();
$renderer->setCustomElementTemplate('<span>{element}</span> ');
$this->addElement('static', null, null, '"'.$this->evaluation_object->get_name().'" ');
$this->addElement('static', null, null, get_lang('MoveTo').' : ');
$select = $this->addElement('select', 'move_cat', null, null);
$line = '';
foreach ($this->evaluation_object->get_target_categories() as $cat) {
for ($i = 0; $i < $cat[2]; $i++) {
$line .= '&mdash;';
}
$select->addOption($line.' '.$cat[1], $cat[0]);
$line = '';
}
$this->addButtonSave(get_lang('Ok'), 'submit');
}
/**
* Builds a result form containing inputs for all students with a given course_code.
*/
protected function build_result_add_form()
{
$renderer = &$this->defaultRenderer();
$renderer->setFormTemplate(
'<form{attributes}>
<div class="table-responsive">
<table class="table table-hover table-striped data_table">
{content}
</table>
</div>
</form>'
);
$users = GradebookUtils::get_users_in_course($this->evaluation_object->get_course_code());
$nr_users = 0;
//extra field for check on maxvalue
$this->addElement('hidden', 'maxvalue', $this->evaluation_object->get_max());
$this->addElement('hidden', 'minvalue', 0);
$this->addElement('header', get_lang('AddResult'));
if (api_is_western_name_order()) {
$renderer->setHeaderTemplate(
'<tr>
<th>'.get_lang('OfficialCode').'</th>
<th>'.get_lang('UserName').'</th>
<th>'.get_lang('FirstName').'</th>
<th>'.get_lang('LastName').'</th>
<th>'.get_lang('Qualify').'</th>
</tr>'
);
} else {
$renderer->setHeaderTemplate(
'<tr>
<th>'.get_lang('OfficialCode').'</th>
<th>'.get_lang('UserName').'</th>
<th>'.get_lang('LastName').'</th>
<th>'.get_lang('FirstName').'</th>
<th>'.get_lang('Qualify').'</th>
</tr>'
);
}
$firstUser = true;
foreach ($users as $user) {
$element_name = 'score['.$user[0].']';
$scoreColumnProperties = ['maxlength' => 5];
if ($firstUser) {
$scoreColumnProperties['autofocus'] = '';
$firstUser = false;
}
//user_id, user.username, lastname, firstname
$this->addFloat(
$element_name,
$this->build_stud_label($user[0], $user[1], $user[2], $user[3]),
false,
$scoreColumnProperties,
false,
0,
$this->evaluation_object->get_max()
);
if (api_is_western_name_order()) {
$user_info = '<td align="left" >'.$user[3].'</td>';
$user_info .= '<td align="left" >'.$user[2].'</td>';
} else {
$user_info = '<td align="left" >'.$user[2].'</td>';
$user_info .= '<td align="left" >'.$user[3].'</td>';
}
$nr_users++;
$template = '<tr>
<td align="left" >'.$user[4].'</td>
<td align="left" >'.$user[1].'</td>
'.$user_info.'
<td align="left">{element} / '.$this->evaluation_object->get_max().'
<!-- BEGIN error --><br /><span style="color: #ff0000;font-size:10px">{error}</span><!-- END error -->
</td>
</tr>';
$renderer->setElementTemplate($template, $element_name);
}
$this->addElement('hidden', 'nr_users', $nr_users);
$this->addElement('hidden', 'evaluation_id', $this->result_object->get_evaluation_id());
$this->addButtonSave(get_lang('AddResult'), 'submit');
$template_submit = '<tr>
<td colspan="4" ></td>
<td >
{element}
<!-- BEGIN error --><br /><span style="color: #ff0000;font-size:10px">{error}</span><!-- END error -->
</td>
</tr>';
$renderer->setElementTemplate($template_submit, 'submit');
}
/**
* Builds a form to edit a result.
*/
protected function build_result_edit_form()
{
$userInfo = api_get_user_info($this->result_object->get_user_id());
$this->addHeader(get_lang('User').': '.$userInfo['complete_name']);
$model = ExerciseLib::getCourseScoreModel();
if (empty($model)) {
$this->addFloat(
'score',
[
get_lang('Score'),
null,
'/ '.$this->evaluation_object->get_max(),
],
false,
[
'size' => '4',
'maxlength' => '5',
],
false,
0,
$this->evaluation_object->get_max()
);
$this->setDefaults(
[
'score' => $this->result_object->get_score(),
'maximum' => $this->evaluation_object->get_max(),
]
);
} else {
$questionWeighting = $this->evaluation_object->get_max();
$select = $this->addSelect('score', get_lang('Score'), [], ['disable_js' => true]);
foreach ($model['score_list'] as $item) {
$i = api_number_format($item['score_to_qualify'] / 100 * $questionWeighting, 2);
$model = ExerciseLib::getModelStyle($item, $i);
$attributes = ['class' => $item['css_class']];
if ($this->result_object->get_score() == $i) {
$attributes['selected'] = 'selected';
}
$select->addOption($model, $i, $attributes);
}
$select->updateSelectWithSelectedOption($this);
}
$allowMultipleAttempts = api_get_configuration_value('gradebook_multiple_evaluation_attempts');
if ($allowMultipleAttempts) {
$this->addTextarea('comment', get_lang('Comment'));
}
$this->addButtonSave(get_lang('Edit'));
$this->addElement('hidden', 'hid_user_id', $this->result_object->get_user_id());
}
/**
* Builds a form to add an evaluation.
*/
protected function build_add_form()
{
$this->setDefaults([
'hid_user_id' => $this->evaluation_object->get_user_id(),
'hid_category_id' => $this->evaluation_object->get_category_id(),
'hid_course_code' => $this->evaluation_object->get_course_code(),
'created_at' => api_get_utc_datetime(),
]);
$this->build_basic_form();
if ($this->evaluation_object->get_course_code() == null) {
$this->addElement('checkbox', 'adduser', null, get_lang('AddUserToEval'));
} else {
$this->addElement('checkbox', 'addresult', null, get_lang('AddResult'));
}
Skill::addSkillsToForm(
$this,
api_get_course_int_id(),
api_get_session_id(),
ITEM_TYPE_GRADEBOOK_EVALUATION
);
$this->addButtonCreate(get_lang('AddAssessment'));
}
/**
* Builds a form to edit an evaluation.
*/
protected function build_editing_form()
{
$parent_cat = Category::load($this->evaluation_object->get_category_id());
//@TODO $weight_mask is replaced?
if ($parent_cat[0]->get_parent_id() == 0) {
$weight_mask = $this->evaluation_object->get_weight();
} else {
$cat = Category::load($parent_cat[0]->get_parent_id());
$global_weight = $cat[0]->get_weight();
$weight_mask = $global_weight * $this->evaluation_object->get_weight() / $parent_cat[0]->get_weight();
}
$weight = $weight_mask = $this->evaluation_object->get_weight();
$evaluationId = $this->evaluation_object->get_id();
$this->addHidden('hid_id', $evaluationId);
$this->setDefaults([
'hid_id' => $evaluationId,
'name' => $this->evaluation_object->get_name(),
'description' => $this->evaluation_object->get_description(),
'hid_user_id' => $this->evaluation_object->get_user_id(),
'hid_course_code' => $this->evaluation_object->get_course_code(),
'hid_category_id' => $this->evaluation_object->get_category_id(),
'created_at' => api_get_utc_datetime($this->evaluation_object->get_date()),
'weight' => $weight,
'weight_mask' => $weight_mask,
'max' => $this->evaluation_object->get_max(),
'visible' => $this->evaluation_object->is_visible(),
]);
$this->build_basic_form(1);
if (!empty($evaluationId)) {
Skill::addSkillsToForm(
$this,
api_get_course_int_id(),
api_get_session_id(),
ITEM_TYPE_GRADEBOOK_EVALUATION,
$evaluationId
);
}
$this->addButtonSave(get_lang('ModifyEvaluation'));
}
/**
* Builds a basic form that is used in add and edit.
*
* @param int $edit
*/
private function build_basic_form($edit = 0)
{
$form_title = get_lang('NewEvaluation');
if (!empty($_GET['editeval'])) {
$form_title = get_lang('EditEvaluation');
}
$this->addHeader($form_title);
$this->addElement('hidden', 'hid_user_id');
$this->addElement('hidden', 'hid_course_code');
$this->addText(
'name',
get_lang('EvaluationName'),
true,
[
'maxlength' => '50',
'id' => 'evaluation_title',
]
);
$cat_id = $this->evaluation_object->get_category_id();
$session_id = api_get_session_id();
$course_code = api_get_course_id();
$all_categories = Category::load(
null,
null,
$course_code,
null,
null,
$session_id,
false
);
if (1 == count($all_categories)) {
$this->addElement('hidden', 'hid_category_id', $cat_id);
} else {
$select_gradebook = $this->addElement(
'select',
'hid_category_id',
get_lang('SelectGradebook'),
[],
['id' => 'hid_category_id']
);
$this->addRule('hid_category_id', get_lang('ThisFieldIsRequired'), 'nonzero');
$default_weight = 0;
if (!empty($all_categories)) {
foreach ($all_categories as $my_cat) {
if ($my_cat->get_course_code() == api_get_course_id()) {
$grade_model_id = $my_cat->get_grade_model_id();
if (empty($grade_model_id)) {
if ($my_cat->get_parent_id() == 0) {
$default_weight = $my_cat->get_weight();
$select_gradebook->addOption(get_lang('Default'), $my_cat->get_id());
$cats_added[] = $my_cat->get_id();
} else {
$select_gradebook->addOption(Security::remove_XSS($my_cat->get_name()), $my_cat->get_id());
$cats_added[] = $my_cat->get_id();
}
} else {
$select_gradebook->addOption(get_lang('Select'), 0);
}
if ($this->evaluation_object->get_category_id() == $my_cat->get_id()) {
$default_weight = $my_cat->get_weight();
}
}
}
}
}
$this->addFloat(
'weight_mask',
[
get_lang('Weight'),
null,
' [0 .. <span id="max_weight">'.$all_categories[0]->get_weight().'</span>] ',
],
true,
[
'size' => '4',
'maxlength' => '5',
]
);
$model = ExerciseLib::getCourseScoreModel();
if ($edit) {
if (empty($model)) {
if (!$this->evaluation_object->has_results()) {
$this->addText(
'max',
get_lang('QualificationNumeric'),
true,
[
'maxlength' => '5',
]
);
} else {
$this->addText(
'max',
[get_lang('QualificationNumeric'), get_lang('CannotChangeTheMaxNote')],
false,
[
'maxlength' => '5',
'disabled' => 'disabled',
]
);
}
} else {
$class = '';
foreach ($model['score_list'] as $item) {
$class = $item['css_class'];
}
$this->addText(
'max',
get_lang('QualificationNumeric'),
false,
[
'maxlength' => '5',
'class' => $class,
'disabled' => 'disabled',
]
);
$defaults['max'] = $item['max'];
$this->setDefaults($defaults);
}
} else {
if (empty($model)) {
$this->addText(
'max',
get_lang('QualificationNumeric'),
true,
[
'maxlength' => '5',
]
);
$default_max = api_get_setting('gradebook_default_weight');
$defaults['max'] = isset($default_max) ? $default_max : 100;
$this->setDefaults($defaults);
} else {
$class = '';
foreach ($model['score_list'] as $item) {
$class = $item['css_class'];
}
$this->addText(
'max',
get_lang('QualificationNumeric'),
false,
[
'maxlength' => '5',
'class' => $class,
'disabled' => 'disabled',
]
);
$defaults['max'] = $item['max'];
$this->setDefaults($defaults);
}
}
$this->addElement('textarea', 'description', get_lang('Description'));
$this->addRule('hid_category_id', get_lang('ThisFieldIsRequired'), 'required');
$this->addElement('checkbox', 'visible', null, get_lang('Visible'));
$this->addRule('max', get_lang('OnlyNumbers'), 'numeric');
$this->addRule(
'max',
get_lang('NegativeValue'),
'compare',
'>=',
'server',
false,
false,
0
);
$setting = api_get_setting('tool_visible_by_default_at_creation');
$visibility_default = 1;
if (isset($setting['gradebook']) && $setting['gradebook'] == 'false') {
$visibility_default = 0;
}
$this->setDefaults(['visible' => $visibility_default]);
}
/**
* @param $id
* @param $username
* @param $lastname
* @param $firstname
*
* @return string
*/
private function build_stud_label($id, $username, $lastname, $firstname)
{
$opendocurl_start = '';
$opendocurl_end = '';
// evaluation's origin is a link
if ($this->evaluation_object->get_category_id() < 0) {
$link = LinkFactory::get_evaluation_link($this->evaluation_object->get_id());
$doc_url = $link->get_view_url($id);
if (null != $doc_url) {
$opendocurl_start .= '<a href="'.$doc_url.'" target="_blank">';
$opendocurl_end = '</a>';
}
}
return $opendocurl_start.api_get_person_name($firstname, $lastname).' ('.$username.')'.$opendocurl_end;
}
}

View File

@@ -0,0 +1,168 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Script.
*/
/**
* Prints an HTML page with a table containing the gradebook data.
*
* @param array Array containing the data to be printed in the table
* @param array Table headers
* @param string View to print as a title for the table
* @param string Course name to print as title for the table
*
* @return string
*/
function print_table($data_array, $header_names, $view, $coursename)
{
$styleWebPath = api_get_path(WEB_PUBLIC_PATH).'assets/bootstrap/dist/css/bootstrap.min.css';
$printdata = '<!DOCTYPE html>
<html lang="'.api_get_language_isocode().'">
<head>
<title>'.get_lang('Print').'</title>
<meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
'.api_get_css(api_get_cdn_path($styleWebPath), 'screen,print').'
</head>
<body dir="'.api_get_text_direction().'"><div id="main">';
$printdata .= '<h2>'.$view.' : '.$coursename.'</h2>';
$table = new HTML_Table(['class' => 'table table-bordered']);
$table->setHeaders($header_names);
$table->setData($data_array);
$printdata .= $table->toHtml();
$printdata .= '</div></body></html>';
return $printdata;
}
/**
* This function get a content html for export inside a pdf file.
*
* @param array table headers
* @param array table body
* @param array pdf headers
* @param array pdf footers
*/
function export_pdf_with_html($headers_table, $data_table, $headers_pdf, $footers_pdf, $title_pdf)
{
$headers_in_pdf = '<img src="'.api_get_path(WEB_CSS_PATH).api_get_setting('stylesheets').'/images/header-logo.png">';
if (is_array($headers_pdf)) {
// preparing headers pdf
$header = '<br/><br/>
<table width="100%" cellspacing="1" cellpadding="5" border="0" class="strong">
<tr>
<td width="100%" style="text-align: center;" class="title" colspan="4">
<h1>'.$title_pdf.'</h1></td></tr>';
foreach ($headers_pdf as $header_pdf) {
if (!empty($header_pdf[0]) && !empty($header_pdf[1])) {
$header .= '<tr><td><strong>'.$header_pdf[0].'</strong> </td><td>'.$header_pdf[1].'</td></tr>';
}
}
$header .= '</table><br />';
}
// preparing footer pdf
$footer = '<table width="100%" cellspacing="2" cellpadding="10" border="0">';
if (is_array($footers_pdf)) {
$footer .= '<tr>';
foreach ($footers_pdf as $foot_pdf) {
$footer .= '<td width="33%" style="text-align: center;">'.$foot_pdf.'</td>';
}
$footer .= '</tr>';
}
$footer .= '</table>';
$footer .= '<div align="right" style="font-weight: bold;">{PAGENO}/{nb}</div>';
// preparing content pdf
$css = api_get_print_css();
$items_per_page = 30;
$count_pages = ceil(count($data_table) / $items_per_page);
for ($x = 0; $x < $count_pages; $x++) {
$content_table .= '<table width="100%" border="1" style="border-collapse:collapse">';
// header table
$content_table .= '<tr>';
$i = 0;
if (is_array($headers_table)) {
foreach ($headers_table as $head_table) {
if (!empty($head_table[0])) {
$width = (!empty($head_table[1]) ? $head_table[1].'%' : '');
$content_table .= '<th width="'.$width.'">'.$head_table[0].'</th>';
$i++;
}
}
}
$content_table .= '</tr>';
// body table
if (is_array($data_table) && count($data_table) > 0) {
$offset = $x * $items_per_page;
$data_table = array_slice($data_table, $offset, count($data_table));
$i = 1;
$item = $offset + 1;
foreach ($data_table as $data) {
$content_table .= '<tr>';
$content_table .= '<td>'.($item < 10 ? '0'.$item : $item).'</td>';
foreach ($data as $key => $content) {
if (isset($content)) {
$key == 1 ? $align = 'align="left"' : $align = 'align="center"';
$content_table .= '<td '.$align.' style="padding:4px;" >'.$content.'</td>';
}
}
$content_table .= '</tr>';
$i++;
$item++;
if ($i > $items_per_page) {
break;
}
}
} else {
$content_table .= '<tr colspan="'.$i.'"><td>'.get_lang('Empty').'</td></tr>';
}
$content_table .= '</table>';
if ($x < ($count_pages - 1)) {
$content_table .= '<pagebreak />';
}
}
$pdf = new PDF();
$pdf->set_custom_footer($footer);
$pdf->set_custom_header($headers_in_pdf);
$pdf->content_to_pdf($header.$content_table, $css, $title_pdf);
exit;
}
/**
* Exports the data as a table on a PDF page.
*
* @param resource The PDF object (ezpdf class) used to generate the file
* @param array The data array
* @param array Table headers
* @param string Format (portrait or landscape)
*/
function export_pdf($pdf, $newarray, $header_names, $format)
{
$pdf->selectFont(api_get_path(LIBRARY_PATH).'ezpdf/fonts/Courier.afm');
$pdf->ezSetCmMargins(0, 0, 0, 0);
$pdf->ezSetY(($format == 'portrait') ? '820' : '570');
$pdf->selectFont(api_get_path(LIBRARY_PATH).'ezpdf/fonts/Courier.afm');
if ('portrait' == $format) {
$pdf->line(40, 790, 540, 790);
$pdf->line(40, 40, 540, 40);
} else {
$pdf->line(40, 540, 790, 540);
$pdf->line(40, 40, 790, 40);
}
$pdf->ezSetY(($format == 'portrait') ? '750' : '520');
$pdf->ezTable($newarray, $header_names, '', [
'showHeadings' => 1,
'shaded' => 1,
'showLines' => 1,
'rowGap' => 3,
'width' => (($format == 'portrait') ? '500' : '750'),
]);
$pdf->ezStream();
}

View File

@@ -0,0 +1,517 @@
<?php
/* For licensing terms, see /license.txt */
set_time_limit(0);
use CpChart\Cache as pCache;
use CpChart\Data as pData;
use CpChart\Image as pImage;
/**
* Class FlatViewTable
* Table to display flat view (all evaluations and links for all students).
*
* @author Stijn Konings
* @author Bert Steppé - (refactored, optimised)
* @author Julio Montoya Armas - Gradebook Graphics
*/
class FlatViewTable extends SortableTable
{
public $datagen;
private $selectcat;
private $limit_enabled;
private $offset;
private $mainCourseCategory;
/**
* @param Category $selectcat
* @param array $users
* @param array $evals
* @param array $links
* @param bool $limit_enabled
* @param int $offset
* @param null $addparams
* @param Category $mainCourseCategory
*/
public function __construct(
$selectcat,
$users = [],
$evals = [],
$links = [],
$limit_enabled = false,
$offset = 0,
$addparams = null,
$mainCourseCategory = null
) {
parent::__construct(
'flatviewlist',
null,
null,
api_is_western_name_order() ? 1 : 0
);
$this->selectcat = $selectcat;
$this->datagen = new FlatViewDataGenerator(
$users,
$evals,
$links,
['only_subcat' => $this->selectcat->get_id()],
$mainCourseCategory
);
$this->limit_enabled = $limit_enabled;
$this->offset = $offset;
if (isset($addparams)) {
$this->set_additional_parameters($addparams ?: []);
}
// step 2: generate rows: students
$this->datagen->category = $this->selectcat;
$this->mainCourseCategory = $mainCourseCategory;
}
/**
* @param bool $value
*/
public function setLimitEnabled($value)
{
$this->limit_enabled = (bool) $value;
}
/**
* @return Category
*/
public function getMainCourseCategory()
{
return $this->mainCourseCategory;
}
/**
* Display gradebook graphs.
*/
public function display_graph_by_resource()
{
$headerName = $this->datagen->get_header_names();
$total_users = $this->datagen->get_total_users_count();
$customdisplays = ScoreDisplay::instance()->get_custom_score_display_settings();
if (empty($customdisplays)) {
echo get_lang('ToViewGraphScoreRuleMustBeEnabled');
return '';
}
$user_results = $this->datagen->get_data_to_graph2(false);
if (empty($user_results) || empty($total_users)) {
echo get_lang('NoResults');
return '';
}
// Removing first name
array_shift($headerName);
// Removing last name
array_shift($headerName);
// Removing username
array_shift($headerName);
$pre_result = [];
foreach ($user_results as $result) {
for ($i = 0; $i < count($headerName); $i++) {
if (isset($result[$i + 1])) {
$pre_result[$i + 3][] = $result[$i + 1];
}
}
}
$i = 0;
$resource_list = [];
$pre_result2 = [];
foreach ($pre_result as $key => $res_array) {
rsort($res_array);
$pre_result2[] = $res_array;
}
//@todo when a display custom does not exist the order of the color does not match
//filling all the answer that are not responded with 0
rsort($customdisplays);
if ($total_users > 0) {
foreach ($pre_result2 as $key => $res_array) {
$key_list = [];
foreach ($res_array as $user_result) {
$userResult = isset($user_result[1]) ? $user_result[1] : null;
if (!isset($resource_list[$key][$userResult])) {
$resource_list[$key][$userResult] = 0;
}
$resource_list[$key][$userResult]++;
$key_list[] = $userResult;
}
foreach ($customdisplays as $display) {
if (!in_array($display['display'], $key_list)) {
$resource_list[$key][$display['display']] = 0;
}
}
$i++;
}
}
//fixing $resource_list
$max = 0;
$new_list = [];
foreach ($resource_list as $key => $value) {
$new_value = [];
foreach ($customdisplays as $item) {
if ($value[$item['display']] > $max) {
$max = $value[$item['display']];
}
$new_value[$item['display']] = strip_tags($value[$item['display']]);
}
$new_list[] = $new_value;
}
$resource_list = $new_list;
$i = 1;
// Cache definition
$cachePath = api_get_path(SYS_ARCHIVE_PATH);
foreach ($resource_list as $key => $resource) {
// Reverse array, otherwise we get highest values first
$resource = array_reverse($resource, true);
$dataSet = new pData();
$dataSet->addPoints($resource, 'Serie');
$dataSet->addPoints(array_keys($resource), 'Labels');
$header = $headerName[$i - 1];
if (is_array($header) && isset($header['header'])) {
$header = $header['header'];
}
$header = strip_tags(api_html_entity_decode($header));
$dataSet->setSerieDescription('Labels', $header);
$dataSet->setAbscissa('Labels');
$dataSet->setAbscissaName(get_lang('GradebookSkillsRanking'));
$dataSet->setAxisName(0, get_lang('Students'));
$palette = [
'0' => ['R' => 186, 'G' => 206, 'B' => 151, 'Alpha' => 100],
'1' => ['R' => 210, 'G' => 148, 'B' => 147, 'Alpha' => 100],
'2' => ['R' => 148, 'G' => 170, 'B' => 208, 'Alpha' => 100],
'3' => ['R' => 221, 'G' => 133, 'B' => 61, 'Alpha' => 100],
'4' => ['R' => 65, 'G' => 153, 'B' => 176, 'Alpha' => 100],
'5' => ['R' => 114, 'G' => 88, 'B' => 144, 'Alpha' => 100],
'6' => ['R' => 138, 'G' => 166, 'B' => 78, 'Alpha' => 100],
'7' => ['R' => 171, 'G' => 70, 'B' => 67, 'Alpha' => 100],
'8' => ['R' => 69, 'G' => 115, 'B' => 168, 'Alpha' => 100],
];
$myCache = new pCache(['CacheFolder' => substr($cachePath, 0, strlen($cachePath) - 1)]);
$chartHash = $myCache->getHash($dataSet);
if ($myCache->isInCache($chartHash)) {
$imgPath = api_get_path(SYS_ARCHIVE_PATH).$chartHash;
$myCache->saveFromCache($chartHash, $imgPath);
$imgPath = api_get_path(WEB_ARCHIVE_PATH).$chartHash;
} else {
/* Create the pChart object */
$widthSize = 480;
$heightSize = 250;
$myPicture = new pImage($widthSize, $heightSize, $dataSet);
/* Turn of Antialiasing */
$myPicture->Antialias = false;
/* Add a border to the picture */
$myPicture->drawRectangle(
0,
0,
$widthSize - 1,
$heightSize - 1,
[
'R' => 0,
'G' => 0,
'B' => 0,
]
);
/* Set the default font */
$myPicture->setFontProperties(
[
'FontName' => api_get_path(SYS_FONTS_PATH).'opensans/OpenSans-Regular.ttf',
'FontSize' => 10,
]
);
/* Write the chart title */
$myPicture->drawText(
250,
30,
$header,
[
'FontSize' => 12,
'Align' => TEXT_ALIGN_BOTTOMMIDDLE,
]
);
/* Define the chart area */
$myPicture->setGraphArea(50, 40, $widthSize - 20, $heightSize - 50);
/* Draw the scale */
$scaleSettings = [
'GridR' => 200,
'GridG' => 200,
'GridB' => 200,
'DrawSubTicks' => true,
'CycleBackground' => true,
'Mode' => SCALE_MODE_START0,
];
$myPicture->drawScale($scaleSettings);
/* Turn on shadow computing */
$myPicture->setShadow(
true,
[
'X' => 1,
'Y' => 1,
'R' => 0,
'G' => 0,
'B' => 0,
'Alpha' => 10,
]
);
/* Draw the chart */
$myPicture->setShadow(
true,
[
'X' => 1,
'Y' => 1,
'R' => 0,
'G' => 0,
'B' => 0,
'Alpha' => 10,
]
);
$settings = [
'OverrideColors' => $palette,
'Gradient' => false,
'GradientMode' => GRADIENT_SIMPLE,
'DisplayPos' => LABEL_POS_TOP,
'DisplayValues' => true,
'DisplayR' => 0,
'DisplayG' => 0,
'DisplayB' => 0,
'DisplayShadow' => true,
'Surrounding' => 10,
];
$myPicture->drawBarChart($settings);
/* Render the picture (choose the best way) */
$myCache->writeToCache($chartHash, $myPicture);
$imgPath = api_get_path(SYS_ARCHIVE_PATH).$chartHash;
$myCache->saveFromCache($chartHash, $imgPath);
$imgPath = api_get_path(WEB_ARCHIVE_PATH).$chartHash;
}
echo '<img src="'.$imgPath.'" >';
if ($i % 2 == 0 && $i != 0) {
echo '<br /><br />';
} else {
echo '&nbsp;&nbsp;&nbsp;';
}
$i++;
}
}
/**
* Function used by SortableTable to get total number of items in the table.
*/
public function get_total_number_of_items()
{
return $this->datagen->get_total_users_count();
}
/**
* Function used by SortableTable to generate the data to display.
*/
public function get_table_data(
$from = 1,
$per_page = null,
$column = null,
$direction = null,
$sort = null
) {
$is_western_name_order = api_is_western_name_order();
// create page navigation if needed
$totalitems = $this->datagen->get_total_items_count();
if ($this->limit_enabled && $totalitems > GRADEBOOK_ITEM_LIMIT) {
$selectlimit = GRADEBOOK_ITEM_LIMIT;
} else {
$selectlimit = $totalitems;
}
$header = null;
if ($this->limit_enabled && $totalitems > GRADEBOOK_ITEM_LIMIT) {
$header .= '<table
style="width: 100%; text-align: right; margin-left: auto; margin-right: auto;"
border="0" cellpadding="2"><tbody>
<tr>';
// previous X
$header .= '<td style="width:100%;">';
if ($this->offset >= GRADEBOOK_ITEM_LIMIT) {
$header .= '<a
href="'.api_get_self().'?selectcat='.(int) $_GET['selectcat'].'&offset='.(($this->offset) - GRADEBOOK_ITEM_LIMIT)
.(isset($_GET['search']) ? '&search='.Security::remove_XSS($_GET['search']) : '').'">'
.Display::return_icon(
'action_prev.png',
get_lang('PreviousPage'),
[],
ICON_SIZE_MEDIUM
)
.'</a>';
} else {
$header .= Display::return_icon(
'action_prev_na.png',
get_lang('PreviousPage'),
[],
ICON_SIZE_MEDIUM
);
}
$header .= ' ';
// next X
$calcnext = (($this->offset + (2 * GRADEBOOK_ITEM_LIMIT)) > $totalitems) ?
($totalitems - (GRADEBOOK_ITEM_LIMIT + $this->offset)) : GRADEBOOK_ITEM_LIMIT;
if ($calcnext > 0) {
$header .= '<a href="'.api_get_self()
.'?selectcat='.Security::remove_XSS($_GET['selectcat'])
.'&offset='.($this->offset + GRADEBOOK_ITEM_LIMIT)
.(isset($_GET['search']) ? '&search='.Security::remove_XSS($_GET['search']) : '').'">'
.Display::return_icon('action_next.png', get_lang('NextPage'), [], ICON_SIZE_MEDIUM)
.'</a>';
} else {
$header .= Display::return_icon(
'action_next_na.png',
get_lang('NextPage'),
[],
ICON_SIZE_MEDIUM
);
}
$header .= '</td>';
$header .= '</tbody></table>';
echo $header;
}
// retrieve sorting type
if ($is_western_name_order) {
$users_sorting = ($this->column == 0 ? FlatViewDataGenerator::FVDG_SORT_FIRSTNAME : FlatViewDataGenerator::FVDG_SORT_LASTNAME);
} else {
$users_sorting = ($this->column == 0 ? FlatViewDataGenerator::FVDG_SORT_LASTNAME : FlatViewDataGenerator::FVDG_SORT_FIRSTNAME);
}
if ('DESC' === $this->direction) {
$users_sorting |= FlatViewDataGenerator::FVDG_SORT_DESC;
} else {
$users_sorting |= FlatViewDataGenerator::FVDG_SORT_ASC;
}
// step 1: generate columns: evaluations and links
$header_names = $this->datagen->get_header_names($this->offset, $selectlimit);
$userRowSpan = false;
foreach ($header_names as $item) {
if (is_array($item)) {
$userRowSpan = true;
break;
}
}
$thAttributes = '';
if ($userRowSpan) {
$thAttributes = 'rowspan=2';
}
$this->set_header(0, $header_names[0], true, $thAttributes);
$this->set_header(1, $header_names[1], true, $thAttributes);
$column = 2;
$firstHeader = [];
while ($column < count($header_names)) {
$headerData = $header_names[$column];
if (is_array($headerData)) {
$countItems = count($headerData['items']);
$this->set_header(
$column,
$headerData['header'],
false,
'colspan="'.$countItems.'"'
);
if (count($headerData['items']) > 0) {
foreach ($headerData['items'] as $item) {
$firstHeader[] = '<span class="text-center">'.$item.'</span>';
}
} else {
$firstHeader[] = '&mdash;';
}
} else {
$this->set_header($column, $headerData, false, $thAttributes);
}
$column++;
}
$data_array = $this->datagen->get_data(
$users_sorting,
$from,
$this->per_page,
$this->offset,
$selectlimit
);
$table_data = [];
if (!empty($firstHeader)) {
$table_data[] = $firstHeader;
}
$columnOffset = empty($this->datagen->params['show_official_code']) ? 0 : 1;
foreach ($data_array as $user_row) {
$user_id = $user_row[0];
unset($user_row[0]);
$userInfo = api_get_user_info($user_id);
if ($is_western_name_order) {
$user_row[1 + $columnOffset] = $this->build_name_link(
$user_id,
$userInfo['firstname']
);
$user_row[2 + $columnOffset] = $this->build_name_link(
$user_id,
$userInfo['lastname']
);
} else {
$user_row[1 + $columnOffset] = $this->build_name_link(
$user_id,
$userInfo['lastname']
);
$user_row[2 + $columnOffset] = $this->build_name_link(
$user_id,
$userInfo['firstname']
);
}
$user_row = array_values($user_row);
$table_data[] = $user_row;
}
return $table_data;
}
/**
* @param $userId
* @param $name
*
* @return string
*/
private function build_name_link($userId, $name)
{
return '<a
href="user_stats.php?userid='.$userId.'&selectcat='.$this->selectcat->get_id().'&'.api_get_cidreq().'">'.
$name.'</a>';
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
<html>
<head>
<meta http-equiv="refresh" content="0; url=../../gradebook.php">
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,211 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Form used to add or edit links.
*
* @author Stijn Konings
* @author Bert Steppé
*/
class LinkAddEditForm extends FormValidator
{
public const TYPE_ADD = 1;
public const TYPE_EDIT = 2;
/**
* Constructor
* To add link, define category_object and link_type
* To edit link, define link_object.
*/
public function __construct(
$form_type,
$category_object,
$link_type,
$link_object,
$form_name,
$action = null
) {
parent::__construct($form_name, 'post', $action);
// set or create link object
if (isset($link_object)) {
$link = $link_object;
} elseif (isset($link_type) && isset($category_object)) {
$link = LinkFactory::create($link_type);
$link->set_course_code(api_get_course_id());
$link->set_session_id(api_get_session_id());
$link->set_category_id($category_object[0]->get_id());
} else {
exit('LinkAddEditForm error: define link_type/category_object or link_object');
}
$defaults = [];
if (!empty($_GET['editlink'])) {
$this->addElement('header', '', get_lang('EditLink'));
}
// ELEMENT: name
if ($form_type == self::TYPE_ADD || $link->is_allowed_to_change_name()) {
if ($link->needs_name_and_description()) {
$this->addText('name', get_lang('Name'), true, ['size' => '40', 'maxlength' => '40']);
} else {
$select = $this->addElement('select', 'select_link', get_lang('ChooseItem'));
foreach ($link->get_all_links() as $newlink) {
$name = strip_tags(Exercise::get_formated_title_variable($newlink[1]));
$select->addOption($name, $newlink[0]);
}
}
} else {
$this->addElement(
'label',
get_lang('Name'),
'<span class="freeze">'.$link->get_name().' ['.$link->get_type_name().']</span>'
);
$this->addElement(
'hidden',
'name_link',
$link->get_name(),
['id' => 'name_link']
);
}
if (1 == count($category_object)) {
$this->addElement('hidden', 'select_gradebook', $category_object[0]->get_id());
} else {
$select_gradebook = $this->addElement(
'select',
'select_gradebook',
get_lang('SelectGradebook'),
[],
['id' => 'hide_category_id']
);
$this->addRule('select_gradebook', get_lang('ThisFieldIsRequired'), 'nonzero');
$default_weight = 0;
if (!empty($category_object)) {
foreach ($category_object as $my_cat) {
if ($my_cat->get_course_code() == api_get_course_id()) {
$grade_model_id = $my_cat->get_grade_model_id();
if (empty($grade_model_id)) {
if (0 == $my_cat->get_parent_id()) {
$default_weight = $my_cat->get_weight();
$select_gradebook->addOption(get_lang('Default'), $my_cat->get_id());
} else {
$select_gradebook->addOption(Security::remove_XSS($my_cat->get_name()), $my_cat->get_id());
}
} else {
$select_gradebook->addOption(get_lang('Select'), 0);
}
if ($link->get_category_id() == $my_cat->get_id()) {
$default_weight = $my_cat->get_weight();
}
}
}
}
}
$this->addFloat(
'weight_mask',
[
get_lang('Weight'),
null,
' [0 .. <span id="max_weight">'.$category_object[0]->get_weight(
).'</span>] ',
],
true,
[
'size' => '4',
'maxlength' => '5',
]
);
$this->addElement('hidden', 'weight');
if (self::TYPE_EDIT == $form_type) {
$parent_cat = Category::load($link->get_category_id());
if (0 == $parent_cat[0]->get_parent_id()) {
$values['weight'] = $link->get_weight();
} else {
$cat = Category::load($parent_cat[0]->get_parent_id());
$values['weight'] = $link->get_weight();
}
$defaults['weight_mask'] = $values['weight'];
$defaults['select_gradebook'] = $link->get_category_id();
}
// ELEMENT: max
if ($link->needs_max()) {
if ($form_type == self::TYPE_EDIT && $link->has_results()) {
$this->addText(
'max',
get_lang('QualificationNumeric'),
false,
[
'size' => '4',
'maxlength' => '5',
'disabled' => 'disabled',
]
);
} else {
$this->addText('max', get_lang('QualificationNumeric'), true, ['size' => '4', 'maxlength' => '5']);
$this->addRule('max', get_lang('OnlyNumbers'), 'numeric');
$this->addRule(
'max',
get_lang('NegativeValue'),
'compare',
'>=',
'server',
false,
false,
0
);
}
if ($form_type == self::TYPE_EDIT) {
$defaults['max'] = $link->get_max();
}
}
// ELEMENT: description
if ($link->needs_name_and_description()) {
$this->addElement(
'textarea',
'description',
get_lang('Description'),
['rows' => '3', 'cols' => '34']
);
if ($form_type == self::TYPE_EDIT) {
$defaults['description'] = $link->get_description();
}
}
// ELEMENT: visible
$visible = ($form_type == self::TYPE_EDIT && $link->is_visible()) ? '1' : '0';
$this->addElement('checkbox', 'visible', null, get_lang('Visible'), $visible);
if ($form_type == self::TYPE_EDIT) {
$defaults['visible'] = $link->is_visible();
}
// ELEMENT: add results
if ($form_type == self::TYPE_ADD && $link->needs_results()) {
$this->addElement('checkbox', 'addresult', get_lang('AddResult'));
}
// submit button
if ($form_type == self::TYPE_ADD) {
$this->addButtonCreate(get_lang('CreateLink'));
} else {
$this->addButtonUpdate(get_lang('LinkMod'));
}
if ($form_type == self::TYPE_ADD) {
$setting = api_get_setting('tool_visible_by_default_at_creation');
$visibility_default = 1;
if (isset($setting['gradebook']) && $setting['gradebook'] === 'false') {
$visibility_default = 0;
}
$defaults['visible'] = $visibility_default;
}
// set default values
$this->setDefaults($defaults);
}
}

View File

@@ -0,0 +1,158 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class LinkForm
* Forms related to links.
*
* @author Stijn Konings
* @author Bert Steppé (made more generic)
*/
class LinkForm extends FormValidator
{
public const TYPE_CREATE = 1;
public const TYPE_MOVE = 2;
/** @var Category */
private $category_object;
private $link_object;
private $extra;
/**
* Builds a form containing form items based on a given parameter.
*
* @param int $form_type 1=choose link
* @param Category $category_object the category object
* @param AbstractLink $link_object
* @param string $form_name name
* @param string $method
* @param string $action
*/
public function __construct(
$form_type,
$category_object,
$link_object,
$form_name,
$method = 'post',
$action = null,
$extra = null
) {
parent::__construct($form_name, $method, $action);
if (isset($category_object)) {
$this->category_object = $category_object;
} else {
if (isset($link_object)) {
$this->link_object = $link_object;
}
}
if (isset($extra)) {
$this->extra = $extra;
}
if (self::TYPE_CREATE == $form_type) {
$this->build_create();
} elseif (self::TYPE_MOVE == $form_type) {
$this->build_move();
}
}
protected function build_move()
{
$renderer = &$this->defaultRenderer();
$renderer->setCustomElementTemplate('<span>{element}</span> ');
$this->addElement(
'static',
null,
null,
'"'.$this->link_object->get_name().'" '
);
$this->addElement('static', null, null, get_lang('MoveTo').' : ');
$select = $this->addElement('select', 'move_cat', null, null);
$line = '';
foreach ($this->link_object->get_target_categories() as $cat) {
for ($i = 0; $i < $cat[2]; $i++) {
$line .= '&mdash;';
}
$select->addOption($line.' '.$cat[1], $cat[0]);
$line = '';
}
$this->addElement('submit', null, get_lang('Ok'));
}
/**
* Builds the form.
*/
protected function build_create()
{
$this->addHeader(get_lang('MakeLink'));
$select = $this->addElement(
'select',
'select_link',
get_lang('ChooseLink'),
null,
['onchange' => 'document.create_link.submit()']
);
$select->addOption('['.get_lang('ChooseLink').']', 0);
$courseCode = $this->category_object->get_course_code();
$linkTypes = LinkFactory::get_all_types();
foreach ($linkTypes as $linkType) {
// The hot potatoe link will be added "inside" the exercise option.
if ($linkType == LINK_HOTPOTATOES) {
continue;
}
$link = $this->createLink($linkType, $courseCode);
/* configure the session id within the gradebook evaluation*/
$link->set_session_id(api_get_session_id());
// disable this element if the link works with a dropdownlist
// and if there are no links left
if (!$link->needs_name_and_description() && count($link->get_all_links()) == '0') {
$select->addOption($link->get_type_name(), $linkType, 'disabled');
} else {
$select->addOption($link->get_type_name(), $linkType);
}
if ($link->get_type() == LINK_EXERCISE) {
// Adding hot potatoes
$linkHot = $this->createLink(LINK_HOTPOTATOES, $courseCode);
$linkHot->setHp(true);
if ($linkHot->get_all_links(true)) {
$select->addOption(
'&nbsp;&nbsp;&nbsp;'.$linkHot->get_type_name(),
LINK_HOTPOTATOES
);
} else {
$select->addOption(
'&nbsp;&nbsp;&nbsp;'.$linkHot->get_type_name(),
LINK_HOTPOTATOES,
'disabled'
);
}
}
}
if (isset($this->extra)) {
$this->setDefaults(['select_link' => $this->extra]);
}
}
/**
* @param int $link
* @param string|null $courseCode
*
* @return AttendanceLink|DropboxLink|ExerciseLink|ForumThreadLink|LearnpathLink|StudentPublicationLink|SurveyLink|null
*/
private function createLink($link, $courseCode)
{
$link = LinkFactory::create($link);
if (!empty($courseCode)) {
$link->set_course_code($courseCode);
} elseif (!empty($_GET['course_code'])) {
$link->set_course_code(Database::escape_string($_GET['course_code'], null, false));
}
return $link;
}
}

View File

@@ -0,0 +1,275 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class ResultTable
* Table to display results for an evaluation.
*
* @author Stijn Konings
* @author Bert Steppé
*/
class ResultTable extends SortableTable
{
private $datagen;
private $evaluation;
private $allresults;
private $iscourse;
/**
* ResultTable constructor.
*
* @param string $evaluation
* @param array $results
* @param string|null $iscourse
* @param array $addparams
* @param bool $forprint
*/
public function __construct(
$evaluation,
$results,
$iscourse,
$addparams = [],
$forprint = false
) {
parent::__construct(
'resultlist',
null,
null,
api_is_western_name_order() ? 1 : 2
);
$this->datagen = new ResultsDataGenerator($evaluation, $results, true);
$this->evaluation = $evaluation;
$this->iscourse = $iscourse;
$this->forprint = $forprint;
if (isset($addparams)) {
$this->set_additional_parameters($addparams);
}
$scoredisplay = ScoreDisplay::instance();
$column = 0;
if ('1' == $this->iscourse) {
$this->set_header($column++, '', false);
$this->set_form_actions([
'delete' => get_lang('Delete'),
]);
}
if (api_is_western_name_order()) {
$this->set_header($column++, get_lang('FirstName'));
$this->set_header($column++, get_lang('LastName'));
} else {
$this->set_header($column++, get_lang('LastName'));
$this->set_header($column++, get_lang('FirstName'));
}
$model = ExerciseLib::getCourseScoreModel();
if (empty($model)) {
$this->set_header($column++, get_lang('Score'));
}
if ($scoredisplay->is_custom()) {
$this->set_header($column++, get_lang('Display'));
}
if (!$this->forprint) {
$this->set_header($column++, get_lang('Modify'), false);
}
}
/**
* Function used by SortableTable to get total number of items in the table.
*/
public function get_total_number_of_items()
{
return $this->datagen->get_total_results_count();
}
/**
* Function used by SortableTable to generate the data to display.
*/
public function get_table_data(
$from = 1,
$per_page = null,
$column = null,
$direction = null,
$sort = null
) {
$isWesternNameOrder = api_is_western_name_order();
$scoredisplay = ScoreDisplay::instance();
// determine sorting type
$col_adjust = $this->iscourse == '1' ? 1 : 0;
switch ($this->column) {
// first name or last name
case 0 + $col_adjust:
if ($isWesternNameOrder) {
$sorting = ResultsDataGenerator::RDG_SORT_FIRSTNAME;
} else {
$sorting = ResultsDataGenerator::RDG_SORT_LASTNAME;
}
break;
// first name or last name
case 1 + $col_adjust:
if ($isWesternNameOrder) {
$sorting = ResultsDataGenerator::RDG_SORT_LASTNAME;
} else {
$sorting = ResultsDataGenerator::RDG_SORT_FIRSTNAME;
}
break;
// Score
case 2 + $col_adjust:
$sorting = ResultsDataGenerator::RDG_SORT_SCORE;
break;
case 3 + $col_adjust:
$sorting = ResultsDataGenerator::RDG_SORT_MASK;
break;
}
if ($this->direction === 'DESC') {
$sorting |= ResultsDataGenerator::RDG_SORT_DESC;
} else {
$sorting |= ResultsDataGenerator::RDG_SORT_ASC;
}
$data_array = $this->datagen->get_data($sorting, $from, $this->per_page);
$model = ExerciseLib::getCourseScoreModel();
// generate the data to display
$sortable_data = [];
foreach ($data_array as $item) {
$row = [];
if ('1' == $this->iscourse) {
$row[] = $item['result_id'];
}
if ($isWesternNameOrder) {
$row[] = $item['firstname'];
$row[] = $item['lastname'];
} else {
$row[] = $item['lastname'];
$row[] = $item['firstname'];
}
if (empty($model)) {
$row[] = Display::bar_progress(
$item['percentage_score'],
false,
$item['score']
);
}
if ($scoredisplay->is_custom()) {
$row[] = $item['display'];
}
if (!$this->forprint) {
$row[] = $this->build_edit_column($item);
}
$sortable_data[] = $row;
}
return $sortable_data;
}
/**
* @param Result $result
* @param string $url
*
* @return string
*/
public static function getResultAttemptTable($result, $url = '')
{
if (empty($result)) {
return '';
}
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_RESULT_ATTEMPT);
$sql = "SELECT * FROM $table WHERE result_id = ".$result->get_id().' ORDER BY created_at DESC';
$resultQuery = Database::query($sql);
$list = Database::store_result($resultQuery);
$htmlTable = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
$htmlTable->setHeaderContents(0, 0, get_lang('Score'));
$htmlTable->setHeaderContents(0, 1, get_lang('Comment'));
$htmlTable->setHeaderContents(0, 2, get_lang('CreatedAt'));
if (!empty($url)) {
$htmlTable->setHeaderContents(0, 3, get_lang('Actions'));
}
$row = 1;
foreach ($list as $data) {
$htmlTable->setCellContents($row, 0, $data['score']);
$htmlTable->setCellContents($row, 1, $data['comment']);
$htmlTable->setCellContents($row, 2, Display::dateToStringAgoAndLongDate($data['created_at']));
if (!empty($url)) {
$htmlTable->setCellContents(
$row,
3,
Display::url(
Display::return_icon('delete.png', get_lang('Delete')),
$url.'&action=delete_attempt&result_attempt_id='.$data['id']
)
);
}
$row++;
}
return $htmlTable->toHtml();
}
/**
* @param array $item
*
* @return string
*/
private function build_edit_column($item)
{
$locked_status = $this->evaluation->get_locked();
$allowMultipleAttempts = api_get_configuration_value('gradebook_multiple_evaluation_attempts');
$baseUrl = api_get_self().'?selecteval='.$this->evaluation->get_id().'&'.api_get_cidreq();
$editColumn = '';
if (api_is_allowed_to_edit(null, true) && $locked_status == 0) {
if ($allowMultipleAttempts) {
if (!empty($item['percentage_score'])) {
$editColumn .=
Display::url(
Display::return_icon('add.png', get_lang('AddAttempt'), '', '22'),
$baseUrl.'&action=add_attempt&editres='.$item['result_id']
);
} else {
$editColumn .= '<a href="'.api_get_self().'?editres='.$item['result_id'].'&selecteval='.$this->evaluation->get_id().'&'.api_get_cidreq().'">'.
Display::return_icon('edit.png', get_lang('Modify'), '', '22').'</a>';
}
} else {
$editColumn .= '<a href="'.api_get_self().'?editres='.$item['result_id'].'&selecteval='.$this->evaluation->get_id().'&'.api_get_cidreq().'">'.
Display::return_icon('edit.png', get_lang('Modify'), '', '22').'</a>';
}
$editColumn .= ' <a href="'.api_get_self().'?delete_mark='.$item['result_id'].'&selecteval='.$this->evaluation->get_id().'&'.api_get_cidreq().'">'.
Display::return_icon('delete.png', get_lang('Delete'), '', '22').'</a>';
}
if ($this->evaluation->get_course_code() == null) {
$editColumn .= '&nbsp;<a href="'.api_get_self().'?resultdelete='.$item['result_id'].'&selecteval='.$this->evaluation->get_id().'" onclick="return confirmationuser();">';
$editColumn .= Display::return_icon('delete.png', get_lang('Delete'));
$editColumn .= '</a>';
$editColumn .= '&nbsp;<a href="user_stats.php?userid='.$item['id'].'&selecteval='.$this->evaluation->get_id().'&'.api_get_cidreq().'">';
$editColumn .= Display::return_icon('statistics.gif', get_lang('Statistics'));
$editColumn .= '</a>';
}
// Evaluation's origin is a link
if ($this->evaluation->get_category_id() < 0) {
$link = LinkFactory::get_evaluation_link($this->evaluation->get_id());
$doc_url = $link->get_view_url($item['id']);
if ($doc_url != null) {
$editColumn .= '&nbsp;<a href="'.$doc_url.'" target="_blank">';
$editColumn .= Display::return_icon('link.gif', get_lang('OpenDocument')).'</a>';
}
}
return $editColumn;
}
}

View File

@@ -0,0 +1,160 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class ScoreDisplayForm
* Form for the score display dialog.
*
* @author Stijn Konings
* @author Bert Steppé
*/
class ScoreDisplayForm extends FormValidator
{
/**
* @param $form_name
* @param null $action
*/
public function __construct($form_name, $action = null)
{
parent::__construct($form_name, 'post', $action);
$displayscore = ScoreDisplay::instance();
$customdisplays = $displayscore->get_custom_score_display_settings();
$nr_items = ('0' != count($customdisplays)) ? count($customdisplays) : '1';
$this->setDefaults(
[
'scorecolpercent' => $displayscore->get_color_split_value(),
]
);
$this->addElement('hidden', 'maxvalue', '100');
$this->addElement('hidden', 'minvalue', '0');
$counter = 1;
// Setting the default values
if (is_array($customdisplays)) {
foreach ($customdisplays as $customdisplay) {
$this->setDefaults(
[
'endscore['.$counter.']' => $customdisplay['score'],
'displaytext['.$counter.']' => $customdisplay['display'],
]
);
$counter++;
}
}
// Settings for the colored score
$this->addElement('header', get_lang('ScoreEdit'));
if ($displayscore->is_coloring_enabled()) {
$this->addElement('html', '<b>'.get_lang('ScoreColor').'</b>');
$this->addElement(
'text',
'scorecolpercent',
[get_lang('Below'), get_lang('WillColorRed'), '%'],
[
'size' => 5,
'maxlength' => 5,
'input-size' => 2,
]
);
if (api_get_setting('teachers_can_change_score_settings') != 'true') {
$this->freeze('scorecolpercent');
}
$this->addRule('scorecolpercent', get_lang('OnlyNumbers'), 'numeric');
$this->addRule(['scorecolpercent', 'maxvalue'], get_lang('Over100'), 'compare', '<=');
$this->addRule(['scorecolpercent', 'minvalue'], get_lang('UnderMin'), 'compare', '>');
}
// Settings for the scoring system
if ($displayscore->is_custom()) {
$this->addElement('html', '<br /><b>'.get_lang('ScoringSystem').'</b>');
$this->addElement('static', null, null, get_lang('ScoreInfo'));
$this->setDefaults([
'beginscore' => '0',
]);
$this->addElement('text', 'beginscore', [get_lang('Between'), null, '%'], [
'size' => 5,
'maxlength' => 5,
'disabled' => 'disabled',
'input-size' => 2,
]);
for ($counter = 1; $counter <= 20; $counter++) {
$renderer = &$this->defaultRenderer();
$elementTemplateTwoLabel =
'<div id='.$counter.' style="display: '.(($counter <= $nr_items) ? 'inline' : 'none').';">
<!-- BEGIN required --><span class="form_required">*</span> <!-- END required -->
<label class="control-label">{label}</label>
<div class="form-group">
<label class="col-sm-2 control-label">
</label>
<div class="col-sm-1">
<!-- BEGIN error --><span class="form_error">{error}</span><br />
<!-- END error -->&nbsp<b>'.get_lang('And').'</b>
</div>
<div class="col-sm-2">
{element}
</div>
<div class="col-sm-1">
=
</div>';
$elementTemplateTwoLabel2 = '
<div class="col-sm-2">
<!-- BEGIN error --><span class="form_error">{error}</span>
<!-- END error -->
{element}
</div>
<div class="col-sm-1">
<a href="javascript:plusItem('.($counter + 1).')">
<img style="display: '.(($counter >= $nr_items) ? 'inline' : 'none').';" id="plus-'.($counter + 1).'" src="'.Display::returnIconPath('add.png').'" alt="'.get_lang('Add').'" title="'.get_lang('Add').'"></a>
<a href="javascript:minItem('.($counter).')">
<img style="display: '.(($counter >= $nr_items && $counter != 1) ? 'inline' : 'none').';" id="min-'.$counter.'" src="'.Display::returnIconPath('delete.png').'" alt="'.get_lang('Delete').'" title="'.get_lang('Delete').'"></a>
</div>
</div>
</div>';
$this->addElement(
'text',
'endscore['.$counter.']',
null,
[
'size' => 5,
'maxlength' => 5,
'id' => 'txta-'.$counter,
'input-size' => 2,
]
);
$this->addElement(
'text',
'displaytext['.$counter.']',
null,
[
'size' => 40,
'maxlength' => 40,
'id' => 'txtb-'.$counter,
]
);
$renderer->setElementTemplate($elementTemplateTwoLabel, 'endscore['.$counter.']');
$renderer->setElementTemplate($elementTemplateTwoLabel2, 'displaytext['.$counter.']');
$this->addRule('endscore['.$counter.']', get_lang('OnlyNumbers'), 'numeric');
$this->addRule(['endscore['.$counter.']', 'maxvalue'], get_lang('Over100'), 'compare', '<=');
$this->addRule(['endscore['.$counter.']', 'minvalue'], get_lang('UnderMin'), 'compare', '>');
}
}
if ($displayscore->is_custom()) {
$this->addButtonSave(get_lang('Ok'));
}
}
public function validate()
{
return parent::validate();
}
}

View File

@@ -0,0 +1,85 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class UserForm
* Extends formvalidator with import and export forms.
*
* @author Stijn Konings
*/
class UserForm extends FormValidator
{
public const TYPE_USER_INFO = 1;
public const TYPE_SIMPLE_SEARCH = 3;
/**
* Builds a form containing form items based on a given parameter.
*
* @param int form_type 1 = user_info
* @param user array
* @param string form name
* @param string $method
* @param string $action
*/
public function __construct($form_type, $user, $form_name, $method = 'post', $action = null)
{
parent::__construct($form_name, $method, $action);
$this->form_type = $form_type;
if (isset($user)) {
$this->user_info = $user;
}
if (isset($result_object)) {
$this->result_object = $result_object;
}
if (self::TYPE_USER_INFO == $this->form_type) {
$this->build_user_info_form();
} elseif (self::TYPE_SIMPLE_SEARCH == $this->form_type) {
$this->build_simple_search();
}
$this->setDefaults();
}
public function display()
{
parent::display();
}
public function setDefaults($defaults = [], $filter = null)
{
parent::setDefaults($defaults, $filter);
}
protected function build_simple_search()
{
if (isset($_GET['search']) && (!empty($_GET['search']))) {
$this->setDefaults([
'keyword' => Security::remove_XSS($_GET['search']),
]);
}
$renderer = &$this->defaultRenderer();
$renderer->setCustomElementTemplate('<span>{element}</span> ');
$this->addElement('text', 'keyword', '');
$this->addButtonSearch(get_lang('Search'), 'submit');
}
protected function build_user_info_form()
{
if (api_is_western_name_order()) {
$this->addElement('static', 'fname', get_lang('FirstName'), $this->user_info['firstname']);
$this->addElement('static', 'lname', get_lang('LastName'), $this->user_info['lastname']);
} else {
$this->addElement('static', 'lname', get_lang('LastName'), $this->user_info['lastname']);
$this->addElement('static', 'fname', get_lang('FirstName'), $this->user_info['firstname']);
}
$this->addElement('static', 'uname', get_lang('UserName'), $this->user_info['username']);
$this->addElement(
'static',
'email',
get_lang('Email'),
'<a href="mailto:'.$this->user_info['email'].'">'.$this->user_info['email'].'</a>'
);
$this->addElement('static', 'ofcode', get_lang('OfficialCode'), $this->user_info['official_code']);
$this->addElement('static', 'phone', get_lang('Phone'), $this->user_info['phone']);
$this->addButtonSave(get_lang('Back'), 'submit');
}
}

View File

@@ -0,0 +1,141 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class UserTable
* Table to display flat view of a student's evaluations and links.
*
* @author Stijn Konings
* @author Bert Steppé (refactored, optimised, use of caching, datagenerator class)
*/
class UserTable extends SortableTable
{
private $userid;
private $datagen;
/**
* Constructor.
*/
public function __construct($userid, $evals = [], $links = [], $addparams = null)
{
parent::__construct('userlist', null, null, 0);
$this->userid = $userid;
$this->datagen = new UserDataGenerator($userid, $evals, $links);
if (isset($addparams)) {
$this->set_additional_parameters($addparams ?: []);
}
$column = 0;
$this->set_header($column++, get_lang('Type'));
$this->set_header($column++, get_lang('Evaluation'));
$this->set_header($column++, get_lang('Course'));
$this->set_header($column++, get_lang('Category'));
$this->set_header($column++, get_lang('EvaluationAverage'));
$this->set_header($column++, get_lang('Result'));
$scoredisplay = ScoreDisplay::instance();
if ($scoredisplay->is_custom()) {
$this->set_header($column++, get_lang('Display'));
}
}
/**
* Function used by SortableTable to get total number of items in the table.
*/
public function get_total_number_of_items()
{
return $this->datagen->get_total_items_count();
}
/**
* Function used by SortableTable to generate the data to display.
*/
public function get_table_data($from = 1, $per_page = null, $column = null, $direction = null, $sort = null)
{
$scoredisplay = ScoreDisplay::instance();
// determine sorting type
switch ($this->column) {
// Type
case 0:
$sorting = UserDataGenerator::UDG_SORT_TYPE;
break;
case 1:
$sorting = UserDataGenerator::UDG_SORT_NAME;
break;
case 2:
$sorting = UserDataGenerator::UDG_SORT_COURSE;
break;
case 3:
$sorting = UserDataGenerator::UDG_SORT_CATEGORY;
break;
case 4:
$sorting = UserDataGenerator::UDG_SORT_AVERAGE;
break;
case 5:
$sorting = UserDataGenerator::UDG_SORT_SCORE;
break;
case 6:
$sorting = UserDataGenerator::UDG_SORT_MASK;
break;
}
if ('DESC' === $this->direction) {
$sorting |= UserDataGenerator::UDG_SORT_DESC;
} else {
$sorting |= UserDataGenerator::UDG_SORT_ASC;
}
$data_array = $this->datagen->get_data($sorting, $from, $this->per_page);
// generate the data to display
$sortable_data = [];
foreach ($data_array as $data) {
if ('' != $data[2]) {
// filter by course removed
$row = [];
$row[] = $this->build_type_column($data[0]);
$row[] = $this->build_name_link($data[0]);
$row[] = $data[2];
$row[] = $data[3];
$row[] = $data[4];
$row[] = $data[5];
if ($scoredisplay->is_custom()) {
$row[] = $data[6];
}
$sortable_data[] = $row;
}
}
return $sortable_data;
}
/**
* @param $item
*
* @return string
*/
private function build_type_column($item)
{
return GradebookUtils::build_type_icon_tag($item->get_icon_name());
}
/**
* @param $item
*
* @return string
*/
private function build_name_link($item)
{
switch ($item->get_item_type()) {
// evaluation
case 'E':
return '&nbsp;'
.'<a href="gradebook_view_result.php?selecteval='.$item->get_id().'&'.api_get_cidreq().'">'
.$item->get_name()
.'</a>';
// link
case 'L':
return '&nbsp;<a href="'.$item->get_link().'">'
.$item->get_name()
.'</a>'
.'&nbsp;['.$item->get_type_name().']';
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,843 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* Class GradebookDataGenerator
* Class to select, sort and transform object data into array data,
* used for the general gradebook view.
*
* @author Bert Steppé
*/
class GradebookDataGenerator
{
// Sorting types constants
public const GDG_SORT_TYPE = 1;
public const GDG_SORT_NAME = 2;
public const GDG_SORT_DESCRIPTION = 4;
public const GDG_SORT_WEIGHT = 8;
public const GDG_SORT_DATE = 16;
public const GDG_SORT_ASC = 32;
public const GDG_SORT_DESC = 64;
public const GDG_SORT_ID = 128;
public $userId;
public $hidePercentage = false;
public $items;
public $preLoadDataKey;
public $exportToPdf;
private $evals_links;
/**
* @param array $cats
* @param array $evals
* @param array $links
*/
public function __construct($cats = [], $evals = [], $links = [])
{
$allcats = isset($cats) ? $cats : [];
$allevals = isset($evals) ? $evals : [];
$alllinks = isset($links) ? $links : [];
$this->exportToPdf = false;
// if we are in the root category and if there are sub categories
// display only links depending of the root category and not link that belongs
// to a sub category https://support.chamilo.org/issues/6602
$tabLinkToDisplay = $alllinks;
if (count($allcats) > 0) {
// get sub categories id
$tabCategories = [];
for ($i = 0; $i < count($allcats); $i++) {
$tabCategories[] = $allcats[$i]->get_id();
}
// dont display links that belongs to a sub category
$tabLinkToDisplay = [];
for ($i = 0; $i < count($alllinks); $i++) {
if (!in_array($alllinks[$i]->get_category_id(), $tabCategories)) {
$tabLinkToDisplay[] = $alllinks[$i];
}
}
}
// merge categories, evaluations and links
$this->items = array_merge($allcats, $allevals, $tabLinkToDisplay);
$this->evals_links = array_merge($allevals, $tabLinkToDisplay);
$this->userId = api_get_user_id();
$this->hidePercentage = api_get_configuration_value('hide_gradebook_percentage_user_result');
}
/**
* Get total number of items (rows).
*
* @return int
*/
public function get_total_items_count()
{
return count($this->items);
}
/**
* Get actual array data.
*
* @param int $count
*
* @return array 2-dimensional array - each array contains the elements:
* 0: cat/eval/link object
* 1: item name
* 2: description
* 3: weight
* 4: date
* 5: student's score (if student logged in)
*/
public function get_data(
$sorting = 0,
$start = 0,
$count = null,
$ignore_score_color = false,
$studentList = [],
$loadStats = true
) {
// do some checks on count, redefine if invalid value
if (!isset($count)) {
$count = count($this->items) - $start;
}
if ($count < 0) {
$count = 0;
}
$allitems = $this->items;
usort($allitems, ['GradebookDataGenerator', 'sort_by_name']);
$userId = $this->userId;
$visibleItems = array_slice($allitems, $start, $count);
$userCount = !empty($studentList) ? count($studentList) : 0;
// Generate the data to display
$data = [];
$allowStats = api_get_configuration_value('allow_gradebook_stats');
$scoreDisplay = ScoreDisplay::instance();
$defaultData = Session::read($this->preLoadDataKey);
$model = ExerciseLib::getCourseScoreModel();
$useExerciseScoreInTotal = api_get_configuration_value('gradebook_use_exercise_score_settings_in_total');
/** @var GradebookItem $item */
foreach ($visibleItems as $item) {
$row = [];
$row[] = $item;
$row[] = Security::remove_XSS($item->get_name());
// display the 2 first line of description and all description
// on mouseover (https://support.chamilo.org/issues/6588)
$row[] = '<span title="'.api_remove_tags_with_space($item->get_description()).'">'.
api_get_short_text_from_html($item->get_description(), 160).'</span>';
$row[] = $item->get_weight();
$item->setStudentList($studentList);
$itemType = get_class($item);
switch ($itemType) {
case 'Evaluation':
// Items inside a category.
$resultColumn = $this->build_result_column(
$userId,
$item,
$ignore_score_color,
false,
$useExerciseScoreInTotal
);
$row[] = $resultColumn['display'];
$row['result_score'] = $resultColumn['score'];
$row['result_score_weight'] = $resultColumn['score_weight'];
// Best
if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['best'])) {
$best = $defaultData[$item->get_id()]['best'];
} else {
$best = $this->buildBestResultColumn($item, $useExerciseScoreInTotal);
}
if (empty($model)) {
$row['best'] = $best['display'];
$row['best_score'] = $best['score'];
// Average
if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['average'])) {
$average = $defaultData[$item->get_id()]['average'];
} else {
$average = $this->buildBestResultColumn($item, $useExerciseScoreInTotal);
}
$row['average'] = $average['display'];
$row['average_score'] = $average['score'];
// Ranking
$ranking = $this->buildRankingColumn($item, $userId, $userCount);
$row['ranking'] = $ranking['display'];
$row['ranking_score'] = $ranking['score'];
}
$row[] = $item;
break;
case 'ExerciseLink':
/** @var ExerciseLink $item */
// Category.
$result = $this->build_result_column(
$userId,
$item,
$ignore_score_color,
true,
$useExerciseScoreInTotal
);
$row[] = $result['display'];
$row['result_score'] = $result['score'];
$row['result_score_weight'] = $result['score'];
if (empty($model)) {
// Best
if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['best'])) {
$best = $defaultData[$item->get_id()]['best'];
} else {
$best = $this->buildBestResultColumn($item);
}
$row['best'] = $best['display'];
$row['best_score'] = $best['score'];
$rankingStudentList = [];
$invalidateResults = false;
// Average
if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['average'])) {
$average = $defaultData[$item->get_id()]['average'];
} else {
$average = $this->buildAverageResultColumn($item);
}
$row['average'] = $average['display'];
$row['average_score'] = $average['score'];
// Ranking.
if ($allowStats) {
// Ranking
if (isset($defaultData[$item->get_id()]) &&
isset($defaultData[$item->get_id()]['ranking'])
) {
$rankingStudentList = $defaultData[$item->get_id()]['ranking'];
$invalidateResults = $defaultData[$item->get_id()]['ranking_invalidate'];
$score = AbstractLink::getCurrentUserRanking($userId, $rankingStudentList);
} else {
if (!empty($studentList)) {
foreach ($studentList as $user) {
$score = $this->build_result_column(
$user['user_id'],
$item,
$ignore_score_color,
true
);
if (!empty($score['score'][0])) {
$invalidateResults = false;
}
$rankingStudentList[$user['user_id']] = $score['score'][0];
}
$defaultData[$item->get_id()]['ranking'] = $rankingStudentList;
$defaultData[$item->get_id()]['ranking_invalidate'] = $invalidateResults;
Session::write($this->preLoadDataKey, $defaultData);
}
$score = AbstractLink::getCurrentUserRanking($userId, $rankingStudentList);
}
} else {
if (!empty($studentList)) {
$session_id = api_get_session_id();
$evals = [];
$links = [];
if ('C' === $item->get_item_type()) {
$evals = $item->get_evaluations(null);
$links = $item->get_links(null);
foreach ($studentList as $user) {
$ressum = 0;
$weightsum = 0;
$bestResult = 0;
if (!empty($evals)) {
foreach ($evals as $eval) {
$evalres = $eval->calc_score($user['user_id'], null);
$eval->setStudentList($studentList);
if (isset($evalres) && 0 != $eval->get_weight()) {
$evalweight = $eval->get_weight();
$weightsum += $evalweight;
if (!empty($evalres[1])) {
$ressum += $evalres[0] / $evalres[1] * $evalweight;
}
if ($ressum > $bestResult) {
$bestResult = $ressum;
}
} else {
if (0 != $eval->get_weight()) {
$evalweight = $eval->get_weight();
$weightsum += $evalweight;
}
}
}
}
if (!empty($links)) {
foreach ($links as $link) {
$link->setStudentList($studentList);
if ($session_id) {
$link->set_session_id($session_id);
}
$linkres = $link->calc_score($user['user_id'], null);
if (!empty($linkres) && 0 != $link->get_weight()) {
$linkweight = $link->get_weight();
$link_res_denom = 0 == $linkres[1] ? 1 : $linkres[1];
$weightsum += $linkweight;
$ressum += $linkres[0] / $link_res_denom * $linkweight;
if ($ressum > $bestResult) {
$bestResult = $ressum;
}
} else {
// Adding if result does not exists
if (0 != $link->get_weight()) {
$linkweight = $link->get_weight();
$weightsum += $linkweight;
}
}
}
}
if (!empty($ressum)) {
$invalidateResults = false;
}
$rankingStudentList[$user['user_id']] = $ressum;
}
}
if (empty($rankingStudentList)) {
foreach ($studentList as $user) {
$score = $this->build_result_column(
$user['user_id'],
$item,
$ignore_score_color,
true
);
if (!empty($score['score'][0])) {
$invalidateResults = false;
}
$rankingStudentList[$user['user_id']] = 0;
if ($score['score']) {
$rankingStudentList[$user['user_id']] = $score['score'][0];
}
}
}
}
$score = AbstractLink::getCurrentUserRanking($userId, $rankingStudentList);
}
$row['ranking'] = $scoreDisplay->display_score(
$score,
SCORE_DIV,
SCORE_BOTH,
true,
true
);
if ($invalidateResults) {
$row['ranking'] = null;
}
}
break;
default:
// Category.
$result = $this->build_result_column(
$userId,
$item,
$ignore_score_color,
true,
$useExerciseScoreInTotal
);
$row[] = $result['display'];
$row['result_score'] = $result['score'];
$row['result_score_weight'] = $result['score'];
$showPercentage = true;
if ($this->hidePercentage) {
$showPercentage = false;
}
if (empty($model)) {
// Best
if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['best'])) {
$best = $defaultData[$item->get_id()]['best'];
if ($useExerciseScoreInTotal) {
$bestScore = $best['score'];
$best['display'] = ExerciseLib::show_score($bestScore[0], $bestScore[1], $showPercentage);
} else {
$best = $defaultData[$item->get_id()]['best'];
}
} else {
$best = $this->buildBestResultColumn($item, $useExerciseScoreInTotal);
}
$row['best'] = $best['display'];
$row['best_score'] = $best['score'];
$rankingStudentList = [];
$invalidateResults = true;
// Average
if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['average'])) {
$average = $defaultData[$item->get_id()]['average'];
if ($useExerciseScoreInTotal) {
$averageScore = $average['score'];
$average['display'] = ExerciseLib::show_score($averageScore[0], $averageScore[1], $showPercentage);
}
} else {
$average = $this->buildAverageResultColumn($item, $useExerciseScoreInTotal);
}
$row['average'] = $average['display'];
$row['average_score'] = $average['score'];
// Ranking
if (isset($defaultData[$item->get_id()]) && isset($defaultData[$item->get_id()]['ranking'])) {
$rankingStudentList = $defaultData[$item->get_id()]['ranking'];
$invalidateResults = $defaultData[$item->get_id()]['ranking_invalidate'];
$invalidateResults = false;
$score = AbstractLink::getCurrentUserRanking($userId, $rankingStudentList);
} else {
if (!empty($studentList)) {
foreach ($studentList as $user) {
$score = $this->build_result_column(
$user['user_id'],
$item,
$ignore_score_color,
true
);
if (!empty($score['score'][0])) {
$invalidateResults = false;
}
$rankingStudentList[$user['user_id']] = $score['score'][0];
}
}
$score = AbstractLink::getCurrentUserRanking($userId, $rankingStudentList);
}
$row['ranking'] = $scoreDisplay->display_score(
$score,
SCORE_DIV,
SCORE_BOTH,
true,
true,
true
);
if ($invalidateResults) {
$row['ranking'] = null;
}
}
break;
}
$data[] = $row;
}
return $data;
}
/**
* Returns the link to the certificate generation, if the score is enough, otherwise
* returns an empty string. This only works with categories.
*
* @param object Item
*
* @return string
*/
public function get_certificate_link($item)
{
if (is_a($item, 'Category')) {
if ($item->is_certificate_available(api_get_user_id())) {
$link = '<a
href="'.Category::getUrl().'export_certificate=1&cat='.$item->get_id().'&user='.api_get_user_id().'">'.
get_lang('Certificate').'</a>';
return $link;
}
}
return '';
}
/**
* @param GradebookItem $item1
* @param GradebookItem $item2
*
* @return int
*/
public static function sort_by_name($item1, $item2)
{
return api_strnatcmp($item1->get_name(), $item2->get_name());
}
/**
* @param GradebookItem $item1
* @param GradebookItem $item2
*
* @return int
*/
public function sort_by_id($item1, $item2)
{
return api_strnatcmp($item1->get_id(), $item2->get_id());
}
/**
* @param GradebookItem $item1
* @param GradebookItem $item2
*
* @return int
*/
public function sort_by_type($item1, $item2)
{
if ($item1->get_item_type() == $item2->get_item_type()) {
return $this->sort_by_name($item1, $item2);
}
return $item1->get_item_type() < $item2->get_item_type() ? -1 : 1;
}
/**
* @param GradebookItem $item1
* @param GradebookItem $item2
*
* @return int
*/
public function sort_by_description($item1, $item2)
{
$result = api_strcmp($item1->get_description(), $item2->get_description());
if (0 == $result) {
return $this->sort_by_name($item1, $item2);
}
return $result;
}
/**
* @param GradebookItem $item1
* @param GradebookItem $item2
*
* @return int
*/
public function sort_by_weight($item1, $item2)
{
if ($item1->get_weight() == $item2->get_weight()) {
return $this->sort_by_name($item1, $item2);
}
return $item1->get_weight() < $item2->get_weight() ? -1 : 1;
}
/**
* @param GradebookItem $item1
* @param GradebookItem $item2
*
* @return int
*/
public function sort_by_date($item1, $item2)
{
if (is_int($item1->get_date())) {
$timestamp1 = $item1->get_date();
} else {
$date = $item1->get_date();
if (!empty($date)) {
$timestamp1 = api_strtotime($date, 'UTC');
} else {
$timestamp1 = null;
}
}
if (is_int($item2->get_date())) {
$timestamp2 = $item2->get_date();
} else {
$timestamp2 = api_strtotime($item2->get_date(), 'UTC');
}
if ($timestamp1 == $timestamp2) {
return $this->sort_by_name($item1, $item2);
}
return $timestamp1 < $timestamp2 ? -1 : 1;
}
/**
* Get best result of an item.
*
* @return array
*/
public function buildBestResultColumn(GradebookItem $item, $userExerciseSettings = false)
{
$score = $item->calc_score(
null,
'best',
api_get_course_id(),
api_get_session_id()
);
if (empty($score)) {
return [
'display' => '',
'score' => '',
];
}
$scoreMode = SCORE_DIV_PERCENT_WITH_CUSTOM;
$showPercentage = true;
if ($this->hidePercentage) {
$scoreMode = SCORE_DIV;
$showPercentage = false;
}
$scoreDisplay = ScoreDisplay::instance();
$display = $scoreDisplay->display_score(
$score,
$scoreMode,
SCORE_BOTH,
true,
false,
true
);
$type = $item->get_item_type();
if ('L' === $type && 'ExerciseLink' === get_class($item)) {
$display = ExerciseLib::show_score(
$score[0],
$score[1],
$showPercentage
);
}
if ($userExerciseSettings) {
$display = ExerciseLib::show_score(
$score[0],
$score[1],
$showPercentage
);
}
return [
'display' => $display,
'score' => $score,
];
}
/**
* @return array
*/
public function buildAverageResultColumn(GradebookItem $item, $userExerciseSettings = false)
{
$score = $item->calc_score(null, 'average');
if (empty($score)) {
return [
'display' => '',
'score' => '',
];
}
$scoreDisplay = ScoreDisplay::instance();
$scoreMode = SCORE_DIV_PERCENT_WITH_CUSTOM;
$showPercentage = true;
if ($this->hidePercentage) {
$scoreMode = SCORE_DIV;
$showPercentage = false;
}
$display = $scoreDisplay->display_score(
$score,
$scoreMode,
SCORE_BOTH,
true,
false,
true
);
$type = $item->get_item_type();
if ('L' === $type && 'ExerciseLink' === get_class($item)) {
$display = ExerciseLib::show_score($score[0], $score[1], $showPercentage);
$result = ExerciseLib::convertScoreToPlatformSetting($score[0], $score[1]);
$score[0] = $result['score'];
$score[1] = $result['weight'];
} else {
if ($userExerciseSettings) {
$display = ExerciseLib::show_score(
$score[0],
$score[1],
$showPercentage
);
}
}
return [
'display' => $display,
'score' => $score,
];
}
/**
* @param int $userId
* @param int $userCount
*
* @return array
*/
public function buildRankingColumn(GradebookItem $item, $userId = null, $userCount = 0)
{
$score = $item->calc_score($userId, 'ranking');
$score[1] = $userCount;
$scoreDisplay = null;
if (isset($score[0])) {
$scoreDisplay = ScoreDisplay::instance();
$scoreDisplay = $scoreDisplay->display_score(
$score,
SCORE_DIV,
SCORE_BOTH,
true,
true,
true
);
}
return [
'display' => $scoreDisplay,
'score' => $score,
];
}
/**
* @param int $userId
* @param GradebookItem $item
* @param bool $ignore_score_color
*
* @return string|null
*/
public function build_result_column(
$userId,
$item,
$ignore_score_color,
$forceSimpleResult = false,
$useExerciseScoreInTotal = false
) {
$scoreDisplay = ScoreDisplay::instance();
$score = $item->calc_score($userId);
$model = ExerciseLib::getCourseScoreModel();
if (!empty($score)) {
switch ($item->get_item_type()) {
// category
case 'C':
if (null != $score) {
if (empty($model)) {
if ($useExerciseScoreInTotal) {
$display = ExerciseLib::show_score($score[0], $score[1], false);
} else {
$display = $scoreDisplay->display_score(
$score,
SCORE_DIV,
null,
false,
false,
true
);
}
return [
'display' => $display,
'score' => $score,
'score_weight' => $score,
];
} else {
$display = ExerciseLib::show_score(
$score[0],
$score[1],
false
);
return [
'display' => $display,
'score' => $score,
'score_weight' => $score,
];
}
} else {
return [
'display' => null,
'score' => $score,
'score_weight' => $score,
];
}
break;
case 'E':
case 'L':
$scoreWeight = [
($score[1] > 0) ? $score[0] / $score[1] * $item->get_weight() : 0,
$item->get_weight(),
];
if (empty($model)) {
if ($useExerciseScoreInTotal) {
$display = ExerciseLib::show_score($score[0], $score[1], false);
} else {
$display = $scoreDisplay->display_score(
$score,
SCORE_DIV_PERCENT_WITH_CUSTOM,
null,
false,
false,
true
);
}
/*$type = $item->get_item_type();
if ('L' === $type && 'ExerciseLink' === get_class($item)) {
$display = ExerciseLib::show_score(
$score[0],
$score[1],
false,
true,
false,
false,
null,
null,
false,
true
);
}*/
} else {
$display = ExerciseLib::show_score(
$score[0],
$score[1],
false
);
}
return [
'display' => $display,
'score' => $score,
'score_weight' => $scoreWeight,
];
}
}
return [
'display' => null,
'score' => null,
'score_weight' => null,
];
}
/**
* @param GradebookItem $item
*
* @return string
*/
private function build_date_column($item)
{
$date = $item->get_date();
if (!isset($date) || empty($date)) {
return '';
} else {
if (is_int($date)) {
return api_convert_and_format_date($date);
}
return api_format_date($date);
}
}
}

View File

@@ -0,0 +1,171 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Gradebook results class.
*
* @author Yannick Warnier
*/
class GradeBookResult
{
private $gradebook_list = []; //stores the list of exercises
private $results = []; //stores the results
/**
* constructor of the class.
*/
public function __construct($get_questions = false, $get_answers = false)
{
}
/**
* Exports the complete report as a CSV file.
*
* @param string $dato Document path inside the document tool
*
* @return bool False on error
*/
public function exportCompleteReportCSV($dato)
{
$filename = 'gradebook_results_'.gmdate('YmdGis').'.csv';
$data = '';
foreach ($dato[0] as $header_col) {
if (!empty($header_col)) {
if (is_array($header_col)) {
if (isset($header_col['header'])) {
$data .= str_replace(
"\r\n",
' ',
api_html_entity_decode(strip_tags($header_col['header']))
).';';
}
} else {
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($header_col))).';';
}
}
}
$data .= "\r\n";
$cant_students = count($dato[1]);
for ($i = 0; $i < $cant_students; $i++) {
foreach ($dato[1][$i] as $col_name) {
$data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($col_name))).';';
}
$data .= "\r\n";
}
// output the results
$len = strlen($data);
header('Content-type: application/octet-stream');
header('Content-Type: application/force-download');
header('Content-length: '.$len);
if (preg_match("/MSIE 5.5/", $_SERVER['HTTP_USER_AGENT'])) {
header('Content-Disposition: filename= '.$filename);
} else {
header('Content-Disposition: attachment; filename= '.$filename);
}
if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
header('Pragma: ');
header('Cache-Control: ');
header('Cache-Control: public'); // IE cannot download from sessions without a cache
}
header('Content-Description: '.$filename);
header('Content-transfer-encoding: binary');
echo $data;
return true;
}
/**
* Exports the complete report as an XLS file.
*
* @param array $data
*
* @throws PHPExcel_Exception
* @throws PHPExcel_Writer_Exception
*/
public function exportCompleteReportXLS($data)
{
$filename = 'gradebook-results-'.api_get_local_time().'.xlsx';
$spreadsheet = new PHPExcel();
$spreadsheet->setActiveSheetIndex(0);
$worksheet = $spreadsheet->getActiveSheet();
$line = 1;
$column = 0;
// headers.
foreach ($data[0] as $headerData) {
$title = $headerData;
if (isset($headerData['header'])) {
$title = $headerData['header'];
}
$title = html_entity_decode(strip_tags($title));
$worksheet->SetCellValueByColumnAndRow(
$column,
$line,
$title
);
$column++;
}
$line++;
$cant_students = count($data[1]);
for ($i = 0; $i < $cant_students; $i++) {
$column = 0;
foreach ($data[1][$i] as $col_name) {
$worksheet->SetCellValueByColumnAndRow(
$column,
$line,
html_entity_decode(strip_tags($col_name))
);
$column++;
}
$line++;
}
$file = api_get_path(SYS_ARCHIVE_PATH).api_replace_dangerous_char($filename);
$writer = new PHPExcel_Writer_Excel2007($spreadsheet);
$writer->save($file);
DocumentManager::file_send_for_download($file, true, $filename);
exit;
}
/**
* Exports the complete report as a DOCX file.
*
* @param array $data The table data
*
* @return bool
*/
public function exportCompleteReportDOC($data)
{
$filename = 'gradebook_results_'.api_get_local_time().'.docx';
$doc = new \PhpOffice\PhpWord\PhpWord();
$section = $doc->addSection(['orientation' => 'landscape']);
$table = $section->addTable();
$table->addRow();
for ($i = 0; $i < count($data[0]); $i++) {
$title = $data[0][$i];
if (isset($data[0][$i]['header'])) {
$title = $data[0][$i]['header'];
}
$title = strip_tags($title);
$table->addCell(1750)->addText($title);
}
foreach ($data[1] as $dataLine) {
$table->addRow();
for ($i = 0; $i < count($dataLine); $i++) {
$table->addCell(1750)->addText(strip_tags($dataLine[$i]));
}
}
$file = api_get_path(SYS_ARCHIVE_PATH).api_replace_dangerous_char($filename);
$doc->save($file, 'Word2007');
DocumentManager::file_send_for_download($file, true, $filename);
return true;
}
}

View File

@@ -0,0 +1,7 @@
<html>
<head>
<meta http-equiv="refresh" content="0; url=../gradebook.php">
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,231 @@
<?php
/* For licensing terms, see /license.txt */
/**
* ResultsDataGenerator Class
* Class to select, sort and transform object data into array data,
* used for the teacher's evaluation results view.
*
* @author Bert Steppé
*/
class ResultsDataGenerator
{
// Sorting types constants
public const RDG_SORT_LASTNAME = 1;
public const RDG_SORT_FIRSTNAME = 2;
public const RDG_SORT_SCORE = 4;
public const RDG_SORT_MASK = 8;
public const RDG_SORT_ASC = 16;
public const RDG_SORT_DESC = 32;
private $evaluation;
private $results;
private $is_course_ind;
private $include_edit;
/**
* Constructor.
*/
public function __construct(
$evaluation,
$results = [],
$include_edit = false
) {
$this->evaluation = $evaluation;
$this->results = isset($results) ? $results : [];
}
/**
* Get total number of results (rows).
*/
public function get_total_results_count()
{
return count($this->results);
}
/**
* Get actual array data.
*
* @param int $count
*
* @return array 2-dimensional array - each array contains the elements:
* 0 ['id'] : user id
* 1 ['result_id'] : result id
* 2 ['lastname'] : user lastname
* 3 ['firstname'] : user firstname
* 4 ['score'] : student's score
* 5 ['display'] : custom score display (only if custom scoring enabled)
*/
public function get_data(
$sorting = 0,
$start = 0,
$count = null,
$ignore_score_color = false,
$pdf = false
) {
// do some checks on count, redefine if invalid value
$number_decimals = api_get_setting('gradebook_number_decimals');
if (!isset($count)) {
$count = count($this->results) - $start;
}
if ($count < 0) {
$count = 0;
}
$model = ExerciseLib::getCourseScoreModel();
$scoreDisplay = ScoreDisplay::instance();
// generate actual data array
$table = [];
foreach ($this->results as $result) {
$user = [];
$info = api_get_user_info($result->get_user_id());
$user['id'] = $result->get_user_id();
if ($pdf) {
$user['username'] = $info['username'];
}
$user['result_id'] = $result->get_id();
$user['lastname'] = $info['lastname'];
$user['firstname'] = $info['firstname'];
if ($pdf) {
$user['score'] = $result->get_score();
} else {
$user['score'] = $this->get_score_display(
$result->get_score(),
true,
$ignore_score_color
);
}
$user['percentage_score'] = (int) $scoreDisplay->display_score(
[$result->get_score(), $this->evaluation->get_max()],
SCORE_PERCENT,
SCORE_BOTH,
true
);
if ($pdf && null == $number_decimals) {
$user['scoreletter'] = $result->get_score();
}
if ($scoreDisplay->is_custom()) {
$user['display'] = $this->get_score_display(
$result->get_score(),
false,
$ignore_score_color
);
if (!empty($model)) {
$user['display'] .= '&nbsp;'.
ExerciseLib::show_score(
$result->get_score(),
$this->evaluation->get_max()
)
;
}
}
$table[] = $user;
}
// sort array
if ($sorting & self::RDG_SORT_LASTNAME) {
usort($table, ['ResultsDataGenerator', 'sort_by_last_name']);
} elseif ($sorting & self::RDG_SORT_FIRSTNAME) {
usort($table, ['ResultsDataGenerator', 'sort_by_first_name']);
} elseif ($sorting & self::RDG_SORT_SCORE) {
usort($table, ['ResultsDataGenerator', 'sort_by_score']);
} elseif ($sorting & self::RDG_SORT_MASK) {
usort($table, ['ResultsDataGenerator', 'sort_by_mask']);
}
if ($sorting & self::RDG_SORT_DESC) {
$table = array_reverse($table);
}
$return = array_slice($table, $start, $count);
return $return;
}
// Sort functions - used internally
/**
* @param array $item1
* @param array $item2
*
* @return int
*/
public function sort_by_last_name($item1, $item2)
{
return api_strcmp($item1['lastname'], $item2['lastname']);
}
/**
* @param array $item1
* @param array $item2
*
* @return int
*/
public function sort_by_first_name($item1, $item2)
{
return api_strcmp($item1['firstname'], $item2['firstname']);
}
/**
* @param array $item1
* @param array $item2
*
* @return int
*/
public function sort_by_score($item1, $item2)
{
if ($item1['percentage_score'] == $item2['percentage_score']) {
return 0;
} else {
return $item1['percentage_score'] < $item2['percentage_score'] ? -1 : 1;
}
}
/**
* @param array $item1
* @param array $item2
*
* @return int
*/
public function sort_by_mask($item1, $item2)
{
$score1 = (isset($item1['score']) ? [$item1['score'], $this->evaluation->get_max()] : null);
$score2 = (isset($item2['score']) ? [$item2['score'], $this->evaluation->get_max()] : null);
return ScoreDisplay::compare_scores_by_custom_display($score1, $score2);
}
/**
* Re-formats the score to show percentage ("2/4 (50 %)") or letters ("A").
*
* @param float Current absolute score (max score is taken from $this->evaluation->get_max()
* @param bool Whether we want the real score (2/4 (50 %)) or the transformation (A, B, C, etc)
* @param bool Whether we want to ignore the score color
* @param bool $realscore
*
* @return string The score as we want to show it
*/
private function get_score_display(
$score,
$realscore,
$ignore_score_color = false
) {
if (null != $score) {
$scoreDisplay = ScoreDisplay::instance();
$type = SCORE_CUSTOM;
if (true === $realscore) {
$type = SCORE_DIV_PERCENT;
}
return $scoreDisplay->display_score(
[$score, $this->evaluation->get_max()],
$type,
SCORE_BOTH,
$ignore_score_color
);
}
return '';
}
}

View File

@@ -0,0 +1,679 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class ScoreDisplay
* Display scores according to the settings made by the platform admin.
* This class works as a singleton: call instance() to retrieve an object.
*
* @author Bert Steppé
*/
class ScoreDisplay
{
private $coloring_enabled;
private $color_split_value;
private $custom_enabled;
private $upperlimit_included;
private $custom_display;
private $custom_display_conv;
/**
* Protected constructor - call instance() to instantiate.
*
* @param int $category_id
*/
public function __construct($category_id = 0)
{
if (!empty($category_id)) {
$this->category_id = $category_id;
}
// Loading portal settings + using standard functions.
$value = api_get_setting('gradebook_score_display_coloring');
$value = $value['my_display_coloring'];
// Setting coloring.
$this->coloring_enabled = $value === 'true' ? true : false;
if ($this->coloring_enabled) {
$value = api_get_setting('gradebook_score_display_colorsplit');
if (isset($value)) {
$this->color_split_value = $value;
}
}
// Setting custom enabled
$value = api_get_setting('gradebook_score_display_custom');
$value = $value['my_display_custom'];
$this->custom_enabled = $value === 'true' ? true : false;
if ($this->custom_enabled) {
$params = ['category = ?' => ['Gradebook']];
$displays = api_get_settings_params($params);
$portal_displays = [];
if (!empty($displays)) {
foreach ($displays as $display) {
$data = explode('::', $display['selected_value']);
if (empty($data[1])) {
$data[1] = '';
}
$portal_displays[$data[0]] = [
'score' => $data[0],
'display' => $data[1],
];
}
sort($portal_displays);
}
$this->custom_display = $portal_displays;
if (count($this->custom_display) > 0) {
$value = api_get_setting('gradebook_score_display_upperlimit');
$value = $value['my_display_upperlimit'];
$this->upperlimit_included = $value === 'true' ? true : false;
$this->custom_display_conv = $this->convert_displays($this->custom_display);
}
}
// If teachers can override the portal parameters
if (api_get_setting('teachers_can_change_score_settings') === 'true') {
//Load course settings
if ($this->custom_enabled) {
$this->custom_display = $this->get_custom_displays();
if (count($this->custom_display) > 0) {
$this->custom_display_conv = $this->convert_displays($this->custom_display);
}
}
if ($this->coloring_enabled) {
$this->color_split_value = $this->get_score_color_percent();
}
}
}
/**
* Get the instance of this class.
*
* @param int $categoryId
*
* @return ScoreDisplay
*/
public static function instance($categoryId = 0)
{
static $instance;
if (!isset($instance)) {
$instance = new ScoreDisplay($categoryId);
}
return $instance;
}
/**
* Compare the custom display of 2 scores, can be useful in sorting.
*/
public static function compare_scores_by_custom_display($score1, $score2)
{
if (!isset($score1)) {
return isset($score2) ? 1 : 0;
}
if (!isset($score2)) {
return -1;
}
$scoreDisplay = self::instance();
$custom1 = $scoreDisplay->display_custom($score1);
$custom2 = $scoreDisplay->display_custom($score2);
if ($custom1 == $custom2) {
return 0;
}
return ($score1[0] / $score1[1]) < ($score2[0] / $score2[1]) ? -1 : 1;
}
/**
* Is coloring enabled ?
*/
public function is_coloring_enabled()
{
return $this->coloring_enabled;
}
/**
* Is custom score display enabled ?
*/
public function is_custom()
{
return $this->custom_enabled;
}
/**
* Is upperlimit included ?
*/
public function is_upperlimit_included()
{
return $this->upperlimit_included;
}
/**
* If custom score display is enabled, this will return the current settings.
* See also updateCustomScoreDisplaySettings.
*
* @return array current settings (or null if feature not enabled)
*/
public function get_custom_score_display_settings()
{
return $this->custom_display;
}
/**
* If coloring is enabled, scores below this value will be displayed in red.
*
* @return int color split value, in percent (or null if feature not enabled)
*/
public function get_color_split_value()
{
return $this->color_split_value;
}
/**
* Update custom score display settings.
*
* @param array $displays 2-dimensional array - every sub array must have keys (score, display)
* @param int score color percent (optional)
* @param int gradebook category id (optional)
*/
public function updateCustomScoreDisplaySettings(
$displays,
$scorecolpercent = 0,
$category_id = null
) {
$this->custom_display = $displays;
$this->custom_display_conv = $this->convert_displays($this->custom_display);
if (isset($category_id)) {
$category_id = (int) $category_id;
} else {
$category_id = $this->get_current_gradebook_category_id();
}
// remove previous settings
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_DISPLAY);
$sql = 'DELETE FROM '.$table.' WHERE category_id = '.$category_id;
Database::query($sql);
// add new settings
foreach ($displays as $display) {
$params = [
'score' => $display['score'],
'display' => $display['display'],
'category_id' => $category_id,
'score_color_percent' => $scorecolpercent,
];
Database::insert($table, $params);
}
}
/**
* @param int $category_id
*
* @return false|null
*/
public function insert_defaults($category_id)
{
if (empty($category_id)) {
return false;
}
//Get this from DB settings
$display = [
50 => get_lang('GradebookFailed'),
60 => get_lang('GradebookPoor'),
70 => get_lang('GradebookFair'),
80 => get_lang('GradebookGood'),
90 => get_lang('GradebookOutstanding'),
100 => get_lang('GradebookExcellent'),
];
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_DISPLAY);
foreach ($display as $value => $text) {
$params = [
'score' => $value,
'display' => $text,
'category_id' => $category_id,
'score_color_percent' => 0,
];
Database::insert($table, $params);
}
}
/**
* @return int
*/
public function get_number_decimals()
{
$number_decimals = api_get_setting('gradebook_number_decimals');
if (!isset($number_decimals)) {
$number_decimals = 0;
}
return $number_decimals;
}
/**
* Formats a number depending of the number of decimals.
*
* @param float $score
* @param bool $ignoreDecimals
* @param string $decimalSeparator
* @param string $thousandSeparator
* @param bool $removeEmptyDecimals Converts 100.00 to 100, 53.00 to 53
*
* @return float the score formatted
*/
public function format_score(
$score,
$ignoreDecimals = false,
$decimalSeparator = '.',
$thousandSeparator = ',',
$removeEmptyDecimals = false
) {
$decimals = $this->get_number_decimals();
if ($ignoreDecimals) {
$decimals = 0;
}
if ($removeEmptyDecimals) {
if ($score && self::hasEmptyDecimals($score)) {
$score = round($score);
$decimals = 0;
}
}
return api_number_format($score, $decimals, $decimalSeparator, $thousandSeparator);
}
public static function hasEmptyDecimals($score)
{
$hasEmptyDecimals = false;
if (is_float($score)) {
$check = fmod($score, 1);
if (0 === bccomp(0, $check)) {
$score = round($score);
$hasEmptyDecimals = true;
}
}
if (is_int($score) || is_string($score)) {
$score = (float) $score;
$check = fmod($score, 1);
if (0 === bccomp(0, $check)) {
$hasEmptyDecimals = true;
}
}
return $hasEmptyDecimals;
}
/**
* Display a score according to the current settings.
*
* @param array $score data structure, as returned by the calc_score functions
* @param int $type one of the following constants:
* SCORE_DIV, SCORE_PERCENT, SCORE_DIV_PERCENT, SCORE_AVERAGE
* (ignored for student's view if custom score display is enabled)
* @param int $what one of the following constants:
* SCORE_BOTH, SCORE_ONLY_DEFAULT, SCORE_ONLY_CUSTOM (default: SCORE_BOTH)
* (only taken into account if custom score display is enabled and for course/platform admin)
* @param bool $disableColor
* @param bool $ignoreDecimals
* @param bool $removeEmptyDecimals Replaces 100.00 to 100
*
* @return string
*/
public function display_score(
$score,
$type = SCORE_DIV_PERCENT,
$what = SCORE_BOTH,
$disableColor = false,
$ignoreDecimals = false,
$removeEmptyDecimals = false
) {
// No score available.
if (!is_array($score)) {
return '-';
}
$my_score = $score == 0 ? [] : $score;
switch ($type) {
case SCORE_BAR:
$percentage = $my_score[0] / $my_score[1] * 100;
return Display::bar_progress($percentage);
break;
case SCORE_NUMERIC:
$percentage = $my_score[0] / $my_score[1] * 100;
return round($percentage);
break;
}
if (false === $disableColor && $this->custom_enabled && isset($this->custom_display_conv)) {
$display = $this->displayDefault($my_score, $type, $ignoreDecimals, $removeEmptyDecimals, true);
} else {
// if no custom display set, use default display
$display = $this->displayDefault($my_score, $type, $ignoreDecimals, $removeEmptyDecimals);
}
if ($this->coloring_enabled && false === $disableColor) {
$denom = isset($score[1]) && !empty($score[1]) && $score[1] > 0 ? $score[1] : 1;
$scoreCleaned = isset($score[0]) ? $score[0] : 0;
if (($scoreCleaned / $denom) < ($this->color_split_value / 100)) {
$display = Display::tag(
'font',
$display,
['color' => 'red']
);
}
}
return $display;
}
/**
* Depends on the teacher's configuration of thresholds. i.e. [0 50] "Bad", [50:100] "Good".
*
* @param array $score
*
* @return string
*/
public function display_custom($score)
{
if (empty($score)) {
return null;
}
$denom = $score[1] == 0 ? 1 : $score[1];
$scaledscore = $score[0] / $denom;
if ($this->upperlimit_included) {
foreach ($this->custom_display_conv as $displayitem) {
if ($scaledscore <= $displayitem['score']) {
return $displayitem['display'];
}
}
} else {
if (!empty($this->custom_display_conv)) {
foreach ($this->custom_display_conv as $displayitem) {
if ($scaledscore < $displayitem['score'] || $displayitem['score'] == 1) {
return $displayitem['display'];
}
}
}
}
}
/**
* Get current gradebook category id.
*
* @return int Category id
*/
private function get_current_gradebook_category_id()
{
$table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
$courseCode = api_get_course_id();
$sessionId = api_get_session_id();
$sessionCondition = api_get_session_condition($sessionId, true);
$sql = "SELECT id FROM $table
WHERE course_code = '$courseCode' $sessionCondition";
$rs = Database::query($sql);
$categoryId = 0;
if (Database::num_rows($rs) > 0) {
$row = Database::fetch_row($rs);
$categoryId = $row[0];
}
return $categoryId;
}
/**
* @param array $score
* @param int $type
* @param bool $ignoreDecimals
* @param bool $removeEmptyDecimals
* @param bool $addScoreLabel
*
* @return string
*/
private function displayDefault($score, $type, $ignoreDecimals = false, $removeEmptyDecimals = false, $addScoreLabel = false)
{
$scoreLabel = '';
if ($addScoreLabel) {
$scoreLabel = $this->display_custom($score);
if (!empty($scoreLabel)) {
$scoreLabel = ' - '.$scoreLabel;
}
}
switch ($type) {
case SCORE_DIV: // X / Y
return $this->display_as_div($score, $ignoreDecimals, $removeEmptyDecimals).$scoreLabel;
case SCORE_PERCENT: // XX %
return $this->display_as_percent($score).$scoreLabel;
case SCORE_DIV_PERCENT: // X / Y (XX %)
//return $this->display_as_div($score).' ('.$this->display_as_percent($score).')';
// 2020-10 Changed to XX % (X / Y)
return $this->display_as_percent($score).' ('.$this->display_as_div($score).')'.$scoreLabel;
case SCORE_AVERAGE: // XX %
return $this->display_as_percent($score).$scoreLabel;
case SCORE_DECIMAL: // 0.50 (X/Y)
return $this->display_as_decimal($score).$scoreLabel;
case SCORE_DIV_PERCENT_WITH_CUSTOM: // X / Y (XX %) - Good!
$div = $this->display_as_div($score, false, $removeEmptyDecimals);
return
$this->display_as_percent($score).PHP_EOL
.Display::div("($div)", ['class' => 'small'])
.$scoreLabel;
case SCORE_DIV_SIMPLE_WITH_CUSTOM: // X - Good!
return $this->display_simple_score($score).$scoreLabel;
break;
case SCORE_DIV_SIMPLE_WITH_CUSTOM_LETTERS:
$score = $this->display_simple_score($score);
// Needs sudo apt-get install php5-intl
if (class_exists('NumberFormatter')) {
$iso = api_get_language_isocode();
$f = new NumberFormatter($iso, NumberFormatter::SPELLOUT);
$letters = $f->format($score);
$letters = api_strtoupper($letters);
$letters = " ($letters) ";
}
return $score.$letters.$scoreLabel;
case SCORE_CUSTOM: // Good!
return $this->display_custom($score);
case SCORE_SIMPLE:
if (!isset($score[0])) {
$score[0] = 0;
}
return $this->format_score($score[0], $ignoreDecimals).$scoreLabel;
}
}
/**
* @param array $score
*
* @return float|string
*/
private function display_simple_score($score)
{
if (isset($score[0])) {
return $this->format_score($score[0]);
}
return '';
}
/**
* Returns "1" for array("100", "100").
*
* @param array $score
*
* @return float
*/
private function display_as_decimal($score)
{
$score_denom = $score[1] == 0 ? 1 : $score[1];
return $this->format_score($score[0] / $score_denom);
}
/**
* Returns "100 %" for array("100", "100").
*/
private function display_as_percent($score)
{
if (empty($score)) {
return null;
}
$scoreDenom = $score[1] == 0 ? 1 : $score[1];
return $this->format_score($score[0] / $scoreDenom * 100).' %';
}
/**
* Returns 10.00 / 10.00 for array("100", "100").
*
* @param array $score
* @param bool $ignoreDecimals
* @param bool $removeEmptyDecimals
*
* @return string
*/
private function display_as_div($score, $ignoreDecimals = false, $removeEmptyDecimals = false)
{
if ($score == 1) {
return '0 / 0';
}
if (empty($score)) {
return '0 / 0';
}
$score[0] = isset($score[0]) ? $this->format_score($score[0], $ignoreDecimals) : 0;
$score[1] = isset($score[1]) ? $this->format_score(
$score[1],
$ignoreDecimals,
'.',
',',
$removeEmptyDecimals
) : 0;
return $score[0].' / '.$score[1];
}
/**
* Get score color percent by category.
*
* @param int Gradebook category id
*
* @return int Score
*/
private function get_score_color_percent($category_id = null)
{
$tbl_display = Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_DISPLAY);
if (isset($category_id)) {
$category_id = (int) $category_id;
} else {
$category_id = $this->get_current_gradebook_category_id();
}
$sql = 'SELECT score_color_percent FROM '.$tbl_display.'
WHERE category_id = '.$category_id.'
LIMIT 1';
$result = Database::query($sql);
$score = 0;
if (Database::num_rows($result) > 0) {
$row = Database::fetch_row($result);
$score = $row[0];
}
return $score;
}
/**
* Get current custom score display settings.
*
* @param int Gradebook category id
*
* @return array 2-dimensional array every element contains 3 subelements (id, score, display)
*/
private function get_custom_displays($category_id = null)
{
$tbl_display = Database::get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_DISPLAY);
if (isset($category_id)) {
$category_id = (int) $category_id;
} else {
$category_id = $this->get_current_gradebook_category_id();
}
$sql = 'SELECT * FROM '.$tbl_display.'
WHERE category_id = '.$category_id.'
ORDER BY score';
$result = Database::query($sql);
return Database::store_result($result, 'ASSOC');
}
/**
* Convert display settings to internally used values.
*/
private function convert_displays($custom_display)
{
if (isset($custom_display)) {
// get highest score entry, and copy each element to a new array
$converted = [];
$highest = 0;
foreach ($custom_display as $element) {
if ($element['score'] > $highest) {
$highest = (float) $element['score'];
}
$converted[] = $element;
}
// sort the new array (ascending)
usort($converted, ['ScoreDisplay', 'sort_display']);
// adjust each score in such a way that
// each score is scaled between 0 and 1
// the highest score in this array will be equal to 1
$converted2 = [];
foreach ($converted as $element) {
$newelement = [];
if (isset($highest) && !empty($highest) && $highest > 0) {
$newelement['score'] = (float) $element['score'] / $highest;
} else {
$newelement['score'] = 0;
}
$newelement['display'] = $element['display'];
$converted2[] = $newelement;
}
return $converted2;
}
return null;
}
/**
* @param array $item1
* @param array $item2
*
* @return int
*/
private function sort_display($item1, $item2)
{
if ($item1['score'] === $item2['score']) {
return 0;
} else {
return $item1['score'] < $item2['score'] ? -1 : 1;
}
}
}

View File

@@ -0,0 +1,447 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class UserDataGenerator
* Class to select, sort and transform object data into array data,
* used for a student's general view.
*
* @author Bert Steppé
*/
class UserDataGenerator
{
// Sorting types constants
public const UDG_SORT_TYPE = 1;
public const UDG_SORT_NAME = 2;
public const UDG_SORT_COURSE = 4;
public const UDG_SORT_CATEGORY = 8;
public const UDG_SORT_AVERAGE = 16;
public const UDG_SORT_SCORE = 32;
public const UDG_SORT_MASK = 64;
public const UDG_SORT_ASC = 128;
public const UDG_SORT_DESC = 256;
private $items;
private $userid;
private $coursecodecache;
private $categorycache;
private $scorecache;
private $avgcache;
/**
* UserDataGenerator constructor.
*
* @param int $userid
* @param array $evals
* @param array $links
*/
public function __construct($userid, $evals = [], $links = [])
{
$this->userid = $userid;
$result = [];
foreach ($evals as $eval) {
$toadd = true;
$coursecode = $eval->get_course_code();
if (isset($coursecode)) {
$result = Result::load(null, $userid, $eval->get_id());
if (0 == count($result)) {
$toadd = false;
}
}
if ($toadd) {
$evals_filtered_copy = $evals;
}
}
if (0 == count($result)) {
$evals_filtered = $evals;
} else {
$evals_filtered = $evals_filtered_copy;
}
$this->items = array_merge($evals_filtered, $links);
$this->coursecodecache = [];
$this->categorycache = [];
$this->scorecache = null;
$this->avgcache = null;
}
/**
* Get total number of items (rows).
*/
public function get_total_items_count()
{
return count($this->items);
}
/**
* Get actual array data.
*
* @return array 2-dimensional array - each array contains the elements:
* 0: eval/link object
* 1: item name
* 2: course name
* 3: category name
* 4: average score
* 5: student's score
* 6: student's score as custom display (only if custom scoring enabled)
*/
public function get_data(
$sorting = 0,
$start = 0,
$count = null,
$ignore_score_color = false
) {
// do some checks on count, redefine if invalid value
if (!isset($count)) {
$count = count($this->items) - $start;
}
if ($count < 0) {
$count = 0;
}
$allitems = $this->items;
// sort users array
if ($sorting & self::UDG_SORT_TYPE) {
usort($allitems, ['UserDataGenerator', 'sort_by_type']);
} elseif ($sorting & self::UDG_SORT_NAME) {
usort($allitems, ['UserDataGenerator', 'sort_by_name']);
} elseif ($sorting & self::UDG_SORT_COURSE) {
usort($allitems, ['UserDataGenerator', 'sort_by_course']);
} elseif ($sorting & self::UDG_SORT_CATEGORY) {
usort($allitems, ['UserDataGenerator', 'sort_by_category']);
} elseif ($sorting & self::UDG_SORT_AVERAGE) {
// if user sorts on average scores, first calculate them and cache them
foreach ($allitems as $item) {
$this->avgcache[$item->get_item_type().$item->get_id()] = $item->calc_score();
}
usort($allitems, ['UserDataGenerator', 'sort_by_average']);
} elseif ($sorting & self::UDG_SORT_SCORE) {
// if user sorts on student's scores, first calculate them and cache them
foreach ($allitems as $item) {
$this->scorecache[$item->get_item_type().$item->get_id()] = $item->calc_score($this->userid);
}
usort($allitems, ['UserDataGenerator', 'sort_by_score']);
} elseif ($sorting & self::UDG_SORT_MASK) {
// if user sorts on student's masks, first calculate scores and cache them
foreach ($allitems as $item) {
$this->scorecache[$item->get_item_type().$item->get_id()] = $item->calc_score($this->userid);
}
usort($allitems, ['UserDataGenerator', 'sort_by_mask']);
}
if ($sorting & self::UDG_SORT_DESC) {
$allitems = array_reverse($allitems);
}
// select the items we have to display
$visibleitems = array_slice($allitems, $start, $count);
// fill score cache if not done yet
if (!isset($this->scorecache)) {
foreach ($visibleitems as $item) {
$this->scorecache[$item->get_item_type().$item->get_id()] = $item->calc_score($this->userid);
}
}
// generate the data to display
$scoredisplay = ScoreDisplay::instance();
$data = [];
$model = ExerciseLib::getCourseScoreModel();
foreach ($visibleitems as $item) {
$row = [];
$row[] = $item;
$row[] = $item->get_name();
$row[] = $this->build_course_name($item);
$row[] = $this->build_category_name($item);
if (!empty($model)) {
if (isset($this->avgcache)) {
$avgscore = $this->avgcache[$item->get_item_type().$item->get_id()];
} else {
$avgscore = $item->calc_score();
}
$row[] = ExerciseLib::show_score($avgscore[0], $avgscore[1]);
$score = $this->scorecache[$item->get_item_type().$item->get_id()];
$displayScore = ExerciseLib::show_score($score[0], $score[1]);
$row[] = $displayScore;
if ($scoredisplay->is_custom()) {
$row[] = $displayScore;
}
} else {
$row[] = $this->build_average_column($item, $ignore_score_color);
$row[] = $this->build_result_column($item, $ignore_score_color);
if ($scoredisplay->is_custom()) {
$row[] = $this->build_mask_column($item, $ignore_score_color);
}
}
$data[] = $row;
}
return $data;
}
/**
* @param $item1
* @param $item2
*
* @return int
*/
public function sort_by_type($item1, $item2)
{
if ($item1->get_item_type() == $item2->get_item_type()) {
return $this->sort_by_name($item1, $item2);
} else {
return $item1->get_item_type() < $item2->get_item_type() ? -1 : 1;
}
}
/**
* @param $item1
* @param $item2
*
* @return int
*/
public function sort_by_course($item1, $item2)
{
$name1 = api_strtolower(
$this->get_course_name_from_code_cached($item1->get_course_code())
);
$name2 = api_strtolower(
$this->get_course_name_from_code_cached($item2->get_course_code())
);
return api_strnatcmp($name1, $name2);
}
/**
* @param $item1
* @param $item2
*
* @return int
*/
public function sort_by_category($item1, $item2)
{
$cat1 = $this->get_category_cached($item1->get_category_id());
$cat2 = $this->get_category_cached($item2->get_category_id());
$name1 = api_strtolower($this->get_category_name_to_display($cat1));
$name2 = api_strtolower($this->get_category_name_to_display($cat2));
return api_strnatcmp($name1, $name2);
}
/**
* @param $item1
* @param $item2
*
* @return int
*/
public function sort_by_name($item1, $item2)
{
return api_strnatcmp($item1->get_name(), $item2->get_name());
}
/**
* @param $item1
* @param $item2
*
* @return int
*/
public function sort_by_average($item1, $item2)
{
$score1 = $this->avgcache[$item1->get_item_type().$item1->get_id()];
$score2 = $this->avgcache[$item2->get_item_type().$item2->get_id()];
return $this->compare_scores($score1, $score2);
}
/**
* @param $item1
* @param $item2
*
* @return int
*/
public function sort_by_score($item1, $item2)
{
$score1 = $this->scorecache[$item1->get_item_type().$item1->get_id()];
$score2 = $this->scorecache[$item2->get_item_type().$item2->get_id()];
return $this->compare_scores($score1, $score2);
}
/**
* @param $item1
* @param $item2
*
* @return int
*/
public function sort_by_mask($item1, $item2)
{
$score1 = $this->scorecache[$item1->get_item_type().$item1->get_id()];
$score2 = $this->scorecache[$item2->get_item_type().$item2->get_id()];
return ScoreDisplay::compare_scores_by_custom_display($score1, $score2);
}
/**
* @param $score1
* @param $score2
*
* @return int
*/
public function compare_scores($score1, $score2)
{
if (!isset($score1)) {
return isset($score2) ? 1 : 0;
} elseif (!isset($score2)) {
return -1;
} elseif (($score1[0] / $score1[1]) == ($score2[0] / $score2[1])) {
return 0;
} else {
return ($score1[0] / $score1[1]) < ($score2[0] / $score2[1]) ? -1 : 1;
}
}
/**
* @param $item
*
* @return mixed
*/
private function build_course_name($item)
{
return $this->get_course_name_from_code_cached($item->get_course_code());
}
/**
* @param $item
*
* @return string
*/
private function build_category_name($item)
{
$cat = $this->get_category_cached($item->get_category_id());
return $this->get_category_name_to_display($cat);
}
/**
* @param $item
* @param $ignore_score_color
*
* @return string
*/
private function build_average_column($item, $ignore_score_color)
{
if (isset($this->avgcache)) {
$avgscore = $this->avgcache[$item->get_item_type().$item->get_id()];
} else {
$avgscore = $item->calc_score('', 'average');
}
$scoredisplay = ScoreDisplay::instance();
$displaytype = SCORE_AVERAGE;
return $scoredisplay->display_score($avgscore, $displaytype);
}
/**
* @param $item
* @param $ignore_score_color
*
* @return string
*/
private function build_result_column($item, $ignore_score_color)
{
$studscore = $this->scorecache[$item->get_item_type().$item->get_id()];
$scoredisplay = ScoreDisplay::instance();
$displaytype = SCORE_DIV_PERCENT;
if ($ignore_score_color) {
$displaytype |= SCORE_IGNORE_SPLIT;
}
return $scoredisplay->display_score(
$studscore,
$displaytype,
SCORE_ONLY_DEFAULT
);
}
/**
* @param $item
* @param $ignore_score_color
*
* @return string
*/
private function build_mask_column($item, $ignore_score_color)
{
$studscore = $this->scorecache[$item->get_item_type().$item->get_id()];
$scoredisplay = ScoreDisplay::instance();
$displaytype = SCORE_DIV_PERCENT;
if ($ignore_score_color) {
$displaytype |= SCORE_IGNORE_SPLIT;
}
return $scoredisplay->display_score(
$studscore,
$displaytype,
SCORE_ONLY_CUSTOM
);
}
/**
* @param string $coursecode
*
* @return mixed
*/
private function get_course_name_from_code_cached($coursecode)
{
if (isset($this->coursecodecache) &&
isset($this->coursecodecache[$coursecode])
) {
return $this->coursecodecache[$coursecode];
} else {
$name = CourseManager::getCourseNameFromCode($coursecode);
$this->coursecodecache[$coursecode] = $name;
return $name;
}
}
/**
* @param int $category_id
*/
private function get_category_cached($category_id)
{
if (isset($this->categorycache) &&
isset($this->categorycache[$category_id])
) {
return $this->categorycache[$category_id];
} else {
$cat = Category::load($category_id);
if (isset($cat)) {
$this->categorycache[$category_id] = $cat[0];
return $cat[0];
} else {
return null;
}
}
}
/**
* @param $cat
*
* @return string
*/
private function get_category_name_to_display($cat)
{
if (isset($cat)) {
if ('0' == $cat->get_parent_id() || null == $cat->get_parent_id()) {
return '';
} else {
return $cat->get_name();
}
}
return '';
}
}