Skip to content
Snippets Groups Projects
Commit a8e3d723 authored by Robin Mounié's avatar Robin Mounié
Browse files

Gamification functional, Teacher can disable/enable it and student choose to...

Gamification functional, Teacher can disable/enable it and student choose to be in the ranking board or not, improvement to do : optimize the DB structure and add the chart
parent bdbb0426
Branches
Tags
No related merge requests found
Showing
with 4235 additions and 889 deletions
......@@ -27,10 +27,11 @@ define('AJAX_SCRIPT', true);
require_once(dirname(__FILE__) . '/../../config.php');
require_once(dirname(__FILE__) . '/locallib.php');
global $USER, $COURSE, $DB;
global $USER, $COURSE, $DB;
$userid = required_param('userid', PARAM_INT);
$courseid = required_param('courseid', PARAM_INT);
$userid = required_param('userid', PARAM_INT);
$COURSE = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
$USER = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
......@@ -38,8 +39,8 @@ $USER = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
require_login($COURSE, false);
$context = context_course::instance($courseid);
require_capability('local/notemyprogress:ajax', $context);
$beginDate = optional_param('beginDate', false, PARAM_TEXT);
/* Start the déclaration of all the optional parrameters */
$beginDate = optional_param('beginDate', false, PARAM_TEXT); //optional_param('keyValue','DefaultValue','Type value');
$lastDate = optional_param('lastDate', false, PARAM_TEXT);
$sectionname = optional_param('sectionname', false, PARAM_TEXT);
......@@ -65,18 +66,27 @@ $modulename = optional_param('modulename', false ,PARAM_TEXT);
$newinstance = optional_param('newinstance', false, PARAM_BOOL);
//gamification parameters
$rules = optional_param('rules', false ,PARAM_TEXT);
$levels = optional_param('levels', false ,PARAM_TEXT);
$settings= optional_param('settings', false ,PARAM_TEXT);
$url= optional_param('url', false ,PARAM_TEXT);
//$courseid = optional_param('modulename', false ,PARAM_TEXT);
$enable = optional_param('enable', false ,PARAM_BOOL);
$params = array();
$func = null;
/* */
if($action == 'saveconfigweek') {
$params = array(); // Array containing the data
$func = null; // fuction which will be call at line 193
if($action == 'saveconfigweek') {//Exemple: if the action passed is saveconfigweek then my array contain (weeks,courseid,userid,newinstance)
array_push($params, $weeks);
array_push($params, $courseid);
array_push($params, $userid);
array_push($params, $newinstance);
if($weeks && $courseid && $userid){
$func = "local_notemyprogress_save_weeks_config";
$func = "local_notemyprogress_save_weeks_config"; //this function must be contained in localib.php
}
} elseif ($action == 'changegroup') {
array_push($params, $courseid);
......@@ -176,18 +186,33 @@ if($action == 'saveconfigweek') {
if($courseid && $userid && $sectionname && $actiontype && $objectType && $objectName && $objectDescription && $currentUrl) {
$func = "local_notemyprogress_addLogs";
}
}elseif($action =='savegamification'){
array_push($params, $courseid);
array_push($params, $userid);
array_push($params, $rules);
array_push($params, $levels);
array_push($params, $settings);
array_push($params, $url);
array_push($params, $enable);
if($courseid && $userid && $rules && $levels && $settings && $url) {
$func = "local_notemyprogress_save_gamification_config";
}
}elseif($action =='rankable'){
array_push($params, $courseid);
array_push($params, $userid);
if($courseid && $userid) {
$func = "local_notemyprogress_set_rankable_player";
}
}
if(isset($params) && isset($func)){
call_user_func_array($func, $params);
}else{
$message = get_string('api_invalid_data', 'local_notemyprogress');
local_notemyprogress_ajax_response(array($message), 400);
local_notemyprogress_ajax_response(array($message));//,$action,$courseid, $userid ,$rules , $levels, $settings, $url,$func), 442);
}
function local_notemyprogress_logsNMP($beginDate, $lastDate, $courseid, $userid, $currentUrl) {
$logs = new \local_notemyprogress\logs($courseid, $userid);
$logs->addLogsNMP("downloaded", "logfile", "LOGFILES", "nmp", $currentUrl, "File that contains all the activities performed on the moodle course in a time interval");
......@@ -296,7 +321,7 @@ function local_notemyprogress_generate_dropout_data($courseid, $userid, $profile
$dropout->generate_data();
local_notemyprogress_ajax_response([], "ok", true, 200);
}else{
local_notemyprogress_ajax_response([], "", false, 400);
local_notemyprogress_ajax_response([], "", false, 402);
}
}
......@@ -322,4 +347,32 @@ function local_notemyprogress_addLogs($sectionname, $actiontype, $courseid, $use
$logs = new \local_notemyprogress\logs($courseid, $userid);
$logs->addLogsNMP($actiontype, $objectType, $sectionname, $objectName, $currentUrl, $objectDescription);
local_notemyprogress_ajax_response(array("ok"=>"ok"));
}
function local_notemyprogress_save_gamification_config($courseid, $userid, $rules, $levels, $settings, $url,$enable){
// \local_notemyprogress\logs::create(
// "setgamification",
// "configweeks",
// "saved",
// "weeks_settings",
// $url,
// 4,
// $userid,
// $courseid
// );
$configLevels = new \local_notemyprogress\configgamification($courseid, $userid);
$configLevels->save_levels($levels, $settings, $rules);
$configLevels->save_enable($enable);
$message = get_string('fml_api_save_successful', 'local_notemyprogress');
local_notemyprogress_ajax_response($message);
}
function local_notemyprogress_set_rankable_player($courseid, $userid){
GLobal $DB;
// $sql = "update {notemyprogress_xp} set rankable = ? where courseid = ? and userid = ?";
// $DB->execute($sql, array(1,$courseid, $userid));
$sql = "UPDATE {notemyprogress_xp} SET rankable = 1 WHERE courseid = 2 AND userid = 3";
$DB->execute($sql);
$message = get_string('fml_api_save_successful', 'local_notemyprogress');
local_notemyprogress_ajax_response($message);
}
\ No newline at end of file
define(["local_notemyprogress/vue","local_notemyprogress/vuetify","local_notemyprogress/axios","local_notemyprogress/moment","local_notemyprogress/pagination","local_notemyprogress/chartdynamic","local_notemyprogress/pageheader","local_notemyprogress/emailform","local_notemyprogress/helpdialog"],function(s,e,t,i,n,o,a,r,c){"use strict";return{init:function(i){s.use(e),s.component("pagination",n),s.component("chart",o),s.component("pageheader",a),s.component("emailform",r),s.component("helpdialog",c);let l=new s({delimiters:["[[","]]"],el:"#submissions",vuetify:new e,data:()=>({dialog:!1,selected_users:[],modulename:"",moduleid:!1,strings:i.strings,groups:i.groups,userid:i.userid,courseid:i.courseid,timezone:i.timezone,render_has:i.profile_render,scriptname:i.scriptname,loading:!1,errors:[],pages:i.pages,submissions:i.submissions,email_strings:i.strings.email_strings,access:i.access,assigns_submissions_colors:i.assigns_submissions_colors,access_content_colors:i.access_content_colors,access_chart_categories:[],access_chart_series:[],access_chart_users:[],help_dialog:!1,help_contents:[],email_object_name:""}),beforeMount(){this.generate_access_content_data()},mounted(){document.querySelector("#sessions-loader").style.display="none",document.querySelector("#submissions").style.display="block",setTimeout(function(){l.setGraphicsEventListeners()},1e3)},methods:{get_help_content(){let s=[];return s.push({title:this.strings.section_help_title,description:this.strings.section_help_description}),s},update_interactions(s){this.loading=!0,this.errors=[];let e={action:"assignments",userid:this.userid,courseid:this.courseid,weekcode:s.weekcode,profile:this.render_has};return t({method:"get",url:M.cfg.wwwroot+"/local/notemyprogress/ajax.php",params:e}).then(s=>{200==s.status&&s.data.ok?(this.submissions=s.data.data.submissions,this.access=s.data.data.access,this.generate_access_content_data()):this.error_messages.push(this.strings.error_network)}).catch(s=>{this.errors.push(this.strings.api_error_network)}).finally(()=>{this.loading=!1,l.addLogsIntoDB("viewed","week_"+s.weekcode,"week_section","Week section that allows you to obtain information on a specific week"),l.setGraphicsEventListeners()}),this.data},build_assigns_submissions_chart(){let s=new Object;return s.chart={type:"column",backgroundColor:null,style:{fontFamily:"poppins"}},s.title={text:null},s.colors=this.assigns_submissions_colors,s.xAxis={categories:this.submissions.categories,crosshair:!0},s.yAxis={min:0,title:{text:this.strings.assignsubs_chart_yaxis},allowDecimals:!1},s.tooltip={formatter:function(){let s=this.x.split("</b>");s=(s=(s=s[0]||"").split("<b>"))[1]||"";let e=this.series.name,t=this.y,i=l.strings.students_text,n=l.strings.send_mail;return 1==t&&(i=l.strings.student_text),"<b>"+s+"</b><br/><b>"+e+": </b>"+t+" "+i+"<br/>"+n}},s.plotOptions={series:{cursor:"pointer",point:{events:{click:function(){l.email_object_name="assigns_submissions";let s=this.category.split("</b>");s=(s=(s=s[0]||"").split("<b>"))[1]||"",l.email_strings.subject=l.email_strings.subject_prefix+" - "+s;let e=this.x,t=this.series.colorIndex;l.dialog=!0,l.selected_users=l.submissions.users[e][t],l.moduleid=l.submissions.modules[e],l.modulename="assign",l.scriptname="test"}}}}},s.series=this.submissions.data,s},build_access_content_chart(){let s=new Object;return s.chart={type:"bar",backgroundColor:null,style:{fontFamily:"poppins"}},s.title={text:null},s.colors=this.access_content_colors,s.xAxis={categories:this.access_chart_categories,title:{text:null},crosshair:!0},s.yAxis={min:0,title:{text:this.strings.access_chart_yaxis_label},labels:{overflow:"justify"},allowDecimals:!1},s.tooltip={formatter:function(){let s=this.x,e=this.series.name,t=this.y,i=l.strings.students_text,n=l.strings.send_mail;return 1==t&&(i=l.strings.student_text),"<b>"+s+"</b><br/><b>"+e+": </b>"+t+" "+i+"<br/>"+n}},s.plotOptions={bar:{dataLabels:{enabled:!1}},series:{cursor:"pointer",point:{events:{click:function(){l.email_object_name="access_content";let s=this.category;l.email_strings.subject=l.email_strings.subject_prefix+" - "+s;let e=this.x,t=this.series.colorIndex,i=l.get_users(l.access_chart_users[e][t]);l.selected_users=i;let n=l.get_moduletype(this.category);l.modulename=n.type,l.moduleid=n.id,l.dialog=!0,l.scriptname="test"}}}}},s.series=this.access_chart_series,s},update_dialog(s){this.dialog=s},generate_access_content_data(){let s=[];this.access.users.forEach(e=>{s.push(Number(e.id))});let e=[];this.access.types.forEach(s=>{s.show&&e.push(s.type)});let t=[];this.access.modules.forEach(s=>{e.includes(s.type)&&t.push(s)});let i=[],n=[],o=[],a=[];t.forEach(e=>{i.push(e.name);let t=e.users,r=s.filter(s=>!t.includes(s));o.push(t.length),a.push(r.length),n.push([t,r])});let r=[{name:this.strings.access,data:o},{name:this.strings.no_access,data:a}];this.access_chart_categories=i,this.access_chart_series=r,this.access_chart_users=n},get_users(s){let e=[];return this.access.users.forEach(t=>{let i=Number(t.id);s.includes(i)&&e.push(t)}),e},get_moduletype(s){let e;return this.access.modules.forEach(t=>{t.name===s&&(e=t)}),e},open_chart_help(s){let e=[];var t="",i="",n="",o="";"assigns_submissions"==s?(e.push({title:this.strings.assigns_submissions_help_title,description:this.strings.assigns_submissions_help_description_p1}),e.push({description:this.strings.assigns_submissions_help_description_p2}),t="viewed",n="help",i="assigns_submissions",o="Help section that provides information about the invested time chart",l.addLogsIntoDB(t,i,n,o)):"access_content"==s&&(e.push({title:this.strings.access_content_help_title,description:this.strings.access_content_help_description_p1}),e.push({description:this.strings.access_content_help_description_p2}),t="viewed",n="help",i="access_content",o="Help section that provides information about the sessions per hour chart",l.addLogsIntoDB(t,i,n,o)),this.help_contents=e,this.help_contents.length&&(this.help_dialog=!0)},update_help_dialog(s){this.help_dialog=s},get_timezone(){return`${this.strings.ss_change_timezone} ${this.timezone}`},setGraphicsEventListeners(){let s=document.querySelectorAll(".highcharts-container");s.length<1?setTimeout(l.setGraphicsEventListeners,500):(s[0].id="submissions",s[1].id="accessContent",s.forEach(s=>{s.addEventListener("mouseenter",l.addLogsViewGraphic)}))},addLogsViewGraphic(s){event.stopPropagation();var e="",t="",i="",n="";switch(s.target.id){case"submissions":e="viewed",t="assigns_submissions",i="chart",n="Chart showing the work submited by the students";break;case"accessContent":e="viewed",t="access_content",i="chart",n="Chart showing the course content accessed by the students";break;default:e="viewed",t="",i="chart",n="A chart"}l.addLogsIntoDB(e,t,i,n)},addLogsIntoDB(s,e,n,o){let a={courseid:i.courseid,userid:i.userid,action:"addLogs",sectionname:"TASKS_MONITORING",actiontype:s,objectType:n,objectName:e,currentUrl:document.location.href,objectDescription:o};t({method:"get",url:M.cfg.wwwroot+"/local/notemyprogress/ajax.php",params:a}).then(s=>{200==s.status&&s.data.ok}).catch(s=>{})}}})}}});
//# sourceMappingURL=assignments.min.js.map
//# sourceMappingURL=assignments.min.js.map
\ No newline at end of file
{"version":3,"sources":["../src/axios.js"],"names":["define","unused","axios"],"mappings":"AAAAA,OAAM,4BAAC,CAAC,2BAAD,CAA8B,OAA9B,CAAD,CAAyC,SAASC,CAAT,CAAiBC,CAAjB,CAAwB,CAC/D,MAAOA,CAAAA,CACV,CAFC,CAAN","sourcesContent":["define(['local_notemyprogress/config', 'axios'], function(unused, axios) {\r\n return axios;\r\n }\r\n);"],"file":"axios.min.js"}
\ No newline at end of file
{
"version": 3,
"sources": [
"../src/axios.js"
],
"names": [
"define",
"unused",
"axios"
],
"mappings": "AAAAA,OAAM,4BAAC,CAAC,2BAAD,CAA8B,OAA9B,CAAD,CAAyC,SAASC,CAAT,CAAiBC,CAAjB,CAAwB,CAC/D,MAAOA,CAAAA,CACV,CAFC,CAAN",
"sourcesContent": [
"define(['local_notemyprogress/config', 'axios'], function(unused, axios) {\r\n return axios;\r\n }\r\n);"
],
"file": "axios.min.js"
}
{"version":3,"sources":["../src/draggable.js"],"names":["define","unused","draggable"],"mappings":"AAAAA,OAAM,gCAAC,CAAC,2BAAD,CAA8B,WAA9B,CAAD,CAA6C,SAASC,CAAT,CAAiBC,CAAjB,CAA4B,CACvE,MAAOA,CAAAA,CACV,CAFC,CAAN","sourcesContent":["define(['local_notemyprogress/config', 'draggable'], function(unused, draggable) {\r\n return draggable;\r\n }\r\n);"],"file":"draggable.min.js"}
\ No newline at end of file
{
"version": 3,
"sources": [
"../src/draggable.js"
],
"names": [
"define",
"unused",
"draggable"
],
"mappings": "AAAAA,OAAM,gCAAC,CAAC,2BAAD,CAA8B,WAA9B,CAAD,CAA6C,SAASC,CAAT,CAAiBC,CAAjB,CAA4B,CACvE,MAAOA,CAAAA,CACV,CAFC,CAAN",
"sourcesContent": [
"define(['local_notemyprogress/config', 'draggable'], function(unused, draggable) {\r\n return draggable;\r\n }\r\n);"
],
"file": "draggable.min.js"
}
This diff is collapsed.
define([
"local_notemyprogress/vue",
"local_notemyprogress/vuetify",
"local_notemyprogress/axios",
"local_notemyprogress/alertify",
"local_notemyprogress/pageheader",
], function (Vue, Vuetify, Axios, Alertify, PageHeader) {
"use strict";
function init(content) {
//console.log(content);
Vue.use(Vuetify);
Vue.component("pageheader", PageHeader);
const app = new Vue({
delimiters: ["[[", "]]"],
el: "#gamification",
vuetify: new Vuetify(),
data: {
strings: content.strings,
token: content.token,
render_has: content.profile_render,
ranking: content.ranking,
notifications: [],
loading: false,
tab: null,
levelsData: content.levels_data.levelsdata,
settings: content.levels_data.settings,
rulesData: content.levels_data.rules,
courseid: content.levels_data.courseid,
userid: content.levels_data.created_by,
enable: false,
events: content.events,
setPointsOption: "calculated",
pointsBase: 0,
pointsBaseOri: 0,
swDisableEnable: "",
},
beforeMount() {},
mounted() {
this.pointsBase = +this.settings.pointsbase;
this.pointsBaseOri = +this.settings.pointsbase;
},
computed: {},
methods: {
get_help_content() {
let help_contents = [];
let help = new Object();
help.title = this.strings.help_title;
help.description = this.strings.help_description;
help_contents.push(help);
return help_contents;
},
update_dialog(value) {
this.dialog = value;
},
update_help_dialog(value) {
this.help_dialog = value;
},
openHelpSectionModalEvent() {
this.saveInteraction(
this.pluginSectionName,
"viewed",
"section_help_dialog",
3
);
},
get_timezone() {
let information = `${this.strings.change_timezone} ${this.timezone}`;
return information;
},
saveInteraction(component, interaction, target, interactiontype) {
let data = {
action: "saveinteraction",
pluginsection: this.pluginSectionName,
component,
interaction,
target,
url: window.location.href,
interactiontype,
token: this.token,
};
Axios({
method: "post",
url: `${M.cfg.wwwroot}/local/notemyprogress/ajax.php`,
data: data,
})
.then((r) => {})
.catch((e) => {});
},
addLevel() {
let newLevel = this.levelsData.length + 1;
this.levelsData.push({
lvl: newLevel,
nam: `${this.strings.level} ${newLevel}`,
des: ``,
points: this.pointsBase * (newLevel - 1),
});
},
removeLevel() {
if (this.levelsData.length > 2) {
this.levelsData.pop();
}
},
calculatePoints(index) {
let points = this.isNumber(this.pointsBase) * index;
this.levelsData[index].points = points;
return points;
},
isNumber(x) {
if (x === "" || isNaN(x) || x == 0) {
return this.pointsBaseOri;
}
return parseInt(x);
},
save_changes() {
this.notifications = ["Do you want to save the changes"];
Alertify.confirm(this.strings.save_warning_content, () => {
this.saveGamificationConfig();
}) // ON CONFIRM
.set({ title: this.strings.save_warning_title })
.set({
labels: {
cancel: this.strings.confirm_cancel,
ok: this.strings.confirm_ok,
},
});
},
saveGamificationConfig() {
this.loading = true;
let settings = {
tit: this.settings.tit,
des: this.settings.des,
pointsbase: this.pointsBase,
};
let data = {
courseid: this.courseid,
userid: this.userid,
action: "savegamification",
levels: JSON.stringify(this.levelsData),
settings: JSON.stringify(settings),
rules: JSON.stringify(this.rulesData),
token: this.token,
enable: this.enable,
};
let url = { url: window.location.href };
Axios({
///! try resolve that with a listener
method: "post",
url:
M.cfg.wwwroot +
"/local/notemyprogress/ajax.php?courseid=" +
this.courseid +
"&userid=" +
this.userid +
"&action=savegamification&levels=" +
data.levels +
"&settings=" +
data.settings +
"&rules=" +
data.rules +
"&url=" +
url.url +
"&enable=" +
data.enable,
data: data,
})
.then((response) => {
// console.log(response);
// console.log(response.status);
// console.log(url.url);
if (response.status == 200 && response.data.ok) {
this.showNotifications(response.data.data, "success");
} else {
let message =
response.data.error || this.strings.api_error_network;
this.showNotifications(message, "error"); //in this line "error" define the kind of message send
}
})
.catch((e) => {
let message = e.response.data || this.strings.api_error_network;
this.showNotifications(message, "error");
})
.finally(() => {
this.loading = false;
});
},
showNotifications(message, type, alert = true, notify = true) {
if (alert) this.notifications.push({ message, type });
if (notify) Alertify.notify(message, type);
},
table_headers() {
let headers = [
{ text: this.strings.ranking_text, value: "ranking" },
{ text: this.strings.level, value: "level" },
{ text: this.strings.student, value: "student" },
{ text: this.strings.total, value: "total" },
{ text: this.strings.progress, value: "progress_percentage" },
];
return headers;
},
addRule() {
this.rulesData.push({
rule: "",
points: 0,
});
},
removeRule(index) {
if (this.rulesData.length > 2) {
this.rulesData.splice(index, 1);
}
},
disableEnable(swDisableEnable) {
//traitement
this.enable = swDisableEnable;
},
},
});
}
return {
init: init,
};
});
define([
"local_notemyprogress/vue",
"local_notemyprogress/vuetify",
"local_notemyprogress/axios",
"local_notemyprogress/alertify",
"local_notemyprogress/pageheader",
], function (Vue, Vuetify, Axios, Alertify, PageHeader) {
"use strict";
function init(content) {
// console.log(content);
Vue.use(Vuetify);
Vue.component("pageheader", PageHeader);
const app = new Vue({
delimiters: ["[[", "]]"],
el: "#student_gamification",
vuetify: new Vuetify(),
data: {
strings: content.strings,
token: content.token,
render_has: content.profile_render,
indicators: content.indicators,
levelInfo: content.indicators.levelInfo,
activity: content.indicators.activity,
courseid: content.indicators.cid,
userid: content.indicators.uid,
ranking: content.ranking,
tab: null,
levelsData: content.levels_data.levelsdata,
settings: content.levels_data.settings,
error_messages: [],
save_successful: false,
},
beforeMount() {},
computed: {},
methods: {
get_help_content() {
let help_contents = [];
let help = new Object();
help.title = this.strings.help_title;
help.description = this.strings.help_description;
help_contents.push(help);
return help_contents;
},
update_dialog(value) {
this.dialog = value;
},
update_help_dialog(value) {
this.help_dialog = value;
},
openHelpSectionModalEvent() {
this.saveInteraction(
this.pluginSectionName,
"viewed",
"section_help_dialog",
3
);
},
get_timezone() {
let information = `${this.strings.change_timezone} ${this.timezone}`;
return information;
},
saveInteraction(component, interaction, target, interactiontype) {
let data = {
action: "saveinteraction",
pluginsection: this.pluginSectionName,
component,
interaction,
target,
url: window.location.href,
interactiontype,
token: content.token,
};
Axios({
method: "post",
url: `${M.cfg.wwwroot}/local/notemyprogress/ajax.php`,
data: data,
})
.then((r) => {})
.catch((e) => {});
},
table_headers() {
let headers = [
{ text: this.strings.ranking_text, value: "ranking" },
{ text: this.strings.level, value: "level" },
{ text: this.strings.student, value: "student" },
{ text: this.strings.total, value: "total" },
{ text: this.strings.progress, value: "progress_percentage" },
];
return headers;
},
activateRanking() {
location.reload();
//console.log(" cid : " + this.courseid + " UID : " + this.userid);
let data = {
action: "rankable",
pluginsection: this.pluginSectionName,
url: window.location.href,
token: content.token,
courseid: this.courseid,
userid: this.userid,
};
Axios({
method: "post",
url:
`${M.cfg.wwwroot}/local/notemyprogress/ajax.php?courseid=` +
this.courseid +
"&userid=" +
this.userid,
params: data,
})
.then((r) => {
// console.log(r);
})
.catch((e) => {});
},
},
});
}
return {
init: init,
};
});
This diff is collapsed.
This diff is collapsed.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Config Gamification
*
* @package local_notemyprogress
* @autor Edisson Sigua, Bryan Aguilar
* @copyright 2020 Edisson Sigua <edissonf.sigua@gmail.com>, Bryan Aguilar <bryan.aguilar6174@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_notemyprogress;
require_once("lib_trait.php");
use stdClass;
use DirectoryIterator;
use core_collator;
use core_component;
use core_plugin_manager;
class configgamification {
use \lib_trait;
public $course;
public $user;
public $levelsData;
function __construct($course, $userid){
$this->course = self::get_course($course);
$this->user = self::get_user($userid);
$this->levelsData = self::get_levels_data();
$this->levelsData = self::get_first_levels();
}
/**
*verify That There Are Levels,If There Are No Creation ByDefault
*
* @return array A list with gaming levels of the course
* @throws coding_exception | dml_exception
*/
public function get_first_levels(){
$levelsData = $this->levelsData;
if(!$levelsData){
$levelsData = self::create_first_levels();
$this->levelsData = $levelsData;
}
return $levelsData;
}
/**
* Create the first two levels (default levels) of NoteMyProgress in a course.This function is executed
* automatically for each course.
*
* @return stdClass An object with the levels created
* @throws dml_exception
*/
private function create_first_levels(){
global $DB;
$levelsInfo[] = array('lvl' => 1, 'name' => 'Level 1', 'des' => '', 'points' => 0);
$levelsInfo[] = array('lvl' => 2, 'name' => 'Level 2', 'des' => '', 'points' => 120);
$settings = array(
'pointsbase' => 120,
'tit' => 'Raise your level while learning',
'des' => 'Interact with your course make you stronger'
);
$levelsData = new stdClass();
$levelsData->levelsdata = json_encode($levelsInfo);
$levelsData->settings = json_encode($settings);
$levelsData->rules = json_encode(self::default_rules());
$levelsData->courseid = $this->course->id;
$levelsData->created_by = $this->user->id;
$levelsData->modified_by = $this->user->id;
$levelsData->timecreated = self::now_timestamp();
$levelsData->timemodified = self::now_timestamp();
$id = $DB->insert_record("notemyprogress_levels_data", $levelsData, true);
$levelsData->id = $id;
return $levelsData;
}
/**
* Gets information about the level configuration
*
* @return array a list with the weeks configured in a course
*/
public function get_levels_data(){
global $DB;
$levelsData = $DB->get_record_select(
"notemyprogress_levels_data",
'courseid = ? AND timedeleted IS NULL',
array($this->course->id));
if($levelsData){
$levelsData->levelsdata = json_decode($levelsData->levelsdata);
$levelsData->settings = json_decode($levelsData->settings);
$levelsData->rules = json_decode($levelsData->rules);
}
return $levelsData;
}
/**
* Save the Gaming Levels of NotemyProgress configured in a course
*
* @param array $ Levels Levels to Save
*
* @return void
* @throws Exception
*/
public function save_levels($levelsInfo, $settings, $rules){
global $DB;
self::delete_levels();
$levelsData = new stdClass();
$levelsData->levelsdata = ($levelsInfo);
$levelsData->settings = ($settings);
$levelsData->rules = ($rules);
$levelsData->courseid = $this->course->id;
$levelsData->created_by = $this->user->id;
$levelsData->modified_by = $this->user->id;
$levelsData->timecreated = self::now_timestamp();
$levelsData->timemodified = self::now_timestamp();
$id = $DB->insert_record("notemyprogress_levels_data", $levelsData, true);
$levelsData->id = $id;
return $levelsData;
}
public function save_enable($enable){
global $DB;
$param = new stdClass();
$param->courseid = $this->course->id;
$param->userid = $this->user->id;
if ($enable){ $param->enablegamification =1;}else{ $param->enablegamification =0;}
$param->timecreated = self::now_timestamp();
//if ($timecreated == null){$result = 1;}
//if ($timecreated === null){$result = 2;}
//if ($timecreated == 'null'){$result = 3;}
//if ($timecreated->maximum == null){$result = 9;}
//if ($timecreated->maximum === null){$result = 10;}
$DB->insert_record("notemyprogress_gamification", $param,true);
}
/**
* Eliminates the Gaming Levels of NoteMyProgress configured in a course
*
* @return void
* @throws dml_exception
*/
public function delete_levels(){
global $DB;
$levelsData = $this->levelsData;
$id = $levelsData->id;
$sql = "update {notemyprogress_levels_data} set timedeleted = ? where id = ?";
$DB->execute($sql, array(self::now_timestamp() , $id));
}
/**
* Get the default gamification rules
*
* @return array a list of rules configured in a course
*/
public function default_rules() {
$ruleset[] = array('rule' => '\mod_book\event\course_module_viewed', 'points' => 5);
$ruleset[] = array('rule' => '\mod_page\event\course_module_viewed', 'points' => 10);
$ruleset[] = array('rule' => '\mod_forum\event\discussion_subscription_created', 'points' => 20);
$ruleset[] = array('rule' => '\core\event\course_viewed', 'points' => 2);
return $ruleset;
}
/**
* Get a list of events.
*/
public final function events_list() {
$list = [];
$coreevents = $this->get_core_events();
//! $list[] = [get_string('coresystem') => array_reduce(array_keys($coreevents), function($carry, $prefix) use ($coreevents) {
//! return array_merge($carry, array_reduce($coreevents[$prefix], function($carry, $eventclass) use ($prefix) {
//! $infos = self::get_event_infos($eventclass);
//! if ($infos) {
//! $carry[$infos['eventname']] = get_string('tg_colon', 'local_notemyprogress', (object) [
//! 'a' => $prefix,
// ! 'b' => $infos['name']
// ! ]);
// ! }
// ! return $carry;
// ! }, []));
// ! }, [])];
$coreevents = [get_string('coresystem') => array_reduce(array_keys($coreevents), function($carry, $prefix) use ($coreevents) {
return array_merge($carry, array_reduce($coreevents[$prefix], function($carry, $eventclass) use ($prefix) {
$infos = self::get_event_infos($eventclass);
if ($infos) {
$name = get_string('tg_colon', 'local_notemyprogress', [
'a' => $prefix,
'b' => $infos['name']
]);
$carry = array('name' => $name, 'event' => $infos['eventname']);
}
return $carry;
}, []));
}, [])];
foreach ($coreevents as $event) {
$list[] = $event;
}
// Get module events.
$list = array_merge($list, self::get_events_list_from_plugintype('mod'));
return $list;
}
/**
* Get the core events.
*
* @return array The keys are translated subsystem names, the values are the classes.
*/
protected function get_core_events() {
// Add some system events.
return [get_string('course') => [
'\\core\\event\\course_viewed',
]];
}
/**
* Return the info about an event.
*
* The key 'name' is added to contain the readable name of the event.
* It is done here because debugging is turned off and some events use
* deprecated strings.
*
* We also add the key 'isdeprecated' which indicates whether the event
* is obsolete or not.
*
* @param string $class The name of the event class.
* @return array|false
*/
public static function get_event_infos($class) {
global $CFG;
$infos = false;
// We need to disable debugging as some events can be deprecated.
$debuglevel = $CFG->debug;
$debugdisplay = $CFG->debugdisplay;
set_debugging(0, false);
// Check that the event exists, and is not an abstract event.
if (method_exists($class, 'get_static_info')) {
$ref = new \ReflectionClass($class);
if (!$ref->isAbstract()) {
$infos = $class::get_static_info();
$infos['name'] = method_exists($class, 'get_name_with_info') ? $class::get_name_with_info() : $class::get_name();
$infos['isdeprecated'] = method_exists($class, 'is_deprecated') ? $class::is_deprecated() : false;
}
}
// Restore debugging.
set_debugging($debuglevel, $debugdisplay);
return $infos;
}
/**
* Get events from plugin type.
*
* @param string $plugintype Plugin type.
* @return array
*/
protected static function get_events_list_from_plugintype($plugintype) {
$list = [];
// Loop over each plugin of the type.
$pluginlist = core_component::get_plugin_list($plugintype);
foreach ($pluginlist as $plugin => $directory) {
$component = $plugintype . '_' . $plugin;
$events = self::get_events_list_from_plugin($component);
// If we found events for this plugin, we add them to the list.
if (!empty($events)) {
//$pluginmanager = core_plugin_manager::instance();
//$plugininfo = $pluginmanager->get_plugin_info($component);
foreach ($events as $event) {
$list[] = $event;
}
}
}
return $list;
}
/**
* Get the events list from a plugin.
*
* From 3.1 we could be using core_component::get_component_classes_in_namespace().
*
* @param string $component The plugin's component name.
* @return array
*/
protected static function get_events_list_from_plugin($component) {
$directory = core_component::get_component_directory($component);
$plugindirectory = $directory . '/classes/event';
if (!is_dir($plugindirectory)) {
return [];
}
// Get the plugin's events.
$eventclasses = static::get_event_classes_from_component($component);
$pluginmanager = core_plugin_manager::instance();
$plugininfo = $pluginmanager->get_plugin_info($component);
// Reduce to the participating, non-deprecated event.
$events = array_reduce($eventclasses, function($carry, $class) use ($plugininfo) {
$infos = self::get_event_infos($class);
if (empty($infos)) {
// Skip rare case where infos aren't found.
return $carry;
} else if ($infos['edulevel'] != \core\event\base::LEVEL_PARTICIPATING) {
// Skip events that are not of level 'participating'.
return $carry;
}
// $carry[$infos['eventname']] = get_string('tg_colon', 'local_notemyprogress', [
// 'a' => $plugininfo->displayname,
// 'b' => $infos['name']
// ]);
$name = get_string('tg_colon', 'local_notemyprogress', [
'a' => $plugininfo->displayname,
'b' => $infos['name']
]);
$carry[] = array('name' => $name, 'event' => $infos['eventname']);
return $carry;
}, []);
// Order alphabetically.
//core_collator::asort($events, core_collator::SORT_NATURAL);
return $events;
}
/**
* Get the events classes from a component.
*
* @param string $component The component.
* @return Array of classes. Those may not be relevant (abstract, invalid, ...)
*/
public static function get_event_classes_from_component($component) {
$directory = core_component::get_component_directory($component);
$plugindirectory = $directory . '/classes/event';
if (!is_dir($plugindirectory)) {
return [];
}
$eventclasses = [];
$diriter = new DirectoryIterator($plugindirectory);
foreach ($diriter as $file) {
if ($file->isDot() || $file->isDir()) {
continue;
}
// It's a good idea to use the leading slashes because the event's property
// 'eventname' includes them as well, so for consistency sake... Also we do
// not check if the class exists because that would cause the class to be
// autoloaded which would potentially trigger debugging messages when
// it is deprecated.
$name = substr($file->getFileName(), 0, -4);
$classname = '\\' . $component . '\\event\\' . $name;
$eventclasses[] = $classname;
}
return $eventclasses;
}
}
\ No newline at end of file
......@@ -51,10 +51,10 @@ class configweeks {
}
/**
* Obtiene la última instancia configurada para las semanas de Note My Progress. Si aun no se han
* configurado semanas en un curso, la última instacia es la que crea por defecto el plugin.
* Gets the last instance configured for the weeks of Note My Progress.If you have not yet been
* Configured weeks in a course, the last instance is the one that creates the plugin by default.
*
* @return mixed un objeto fieldset que contiene el primer registro que hace match con la consulta
* @return mixed A FieldSet object that contains the first record that Match makes with the query
*/
public function last_instance(){
global $DB;
......@@ -67,10 +67,10 @@ class configweeks {
}
/**
* Crea una nueva instancia para la configuración de semanas de Note My Progress. Esta es la instancia
* que se crea por defecto.
* Create a new instance for the setting of weeks of Note My Progress.This is the instance
* That is created by default.
*
* @return mixed un objeto fieldset que contiene el registro creado
* @return mixed A FieldSet object that contains the created record
*/
public function create_instance(){
global $DB;
......@@ -84,10 +84,10 @@ class configweeks {
}
/**
* Obtiene las semanas configuradas en Note My Progress en base a los ids de los atributos de clase
* $course e $intance. Se puede especificar
* Obtain the weeks configured in Note My progress based on the IDs of class attributes
* $ Course and $ INTANCE.It can be specified
*
* @return array una lista con las semanas configuradas en un curso
* @return Array a list with the weeks configured in a course
*/
public function get_weeks(){
global $DB;
......@@ -99,10 +99,10 @@ class configweeks {
}
/**
* Agrega el campo sections a la variable de clase semanas para almacenar las secciones asignadas a cada semana.
* Add the Sections field to the class variable weeks to store the sections assigned to each week.
*
* @return array una lista con las semanas del curso y las secciones asignadas a cada una
* @throws coding_exception | dml_exception
* @return Array a list with the weeks of the course and sections assigned to each
* @throws Coding_Exception |DML_EXCEPTION.
*/
public function get_weeks_with_sections(){
$weeks = $this->weeks;
......@@ -139,10 +139,10 @@ class configweeks {
}
/**
* Crea la primera semana (semana por defecto) de Note My Progress en un curso. Esta funcion se ejecuta
* de manera automática para cada curso.
* Create the first week (default week) of Note My progress in a course.This function is executed
* automatically for each course.
*
* @return stdClass un objeto con la semana creada
* @return stdClass An object with the week created
* @throws dml_exception
*/
private function create_first_week(){
......@@ -336,7 +336,7 @@ class configweeks {
}
/**
* Elimina las semanas de Note My Progress configuradas en un curso
* Eliminates the weeks of Note My Progress configured in a course
*
* @return void
* @throws dml_exception
......@@ -352,9 +352,9 @@ class configweeks {
}
/**
* Elimina las secciones asignadas a una semana de Note My Progress
* Eliminates sections assigned to a week of Note My Progress
*
* @param string $weekcode id de la semana a eliminar
* @param string $weekcode ID of the week to eliminate
*
* @return void
* @throws dml_exception
......@@ -366,10 +366,10 @@ class configweeks {
}
/**
* Guarda una semana de Note My Progress configurada en un curso
* Save a week of Note My Progress configured in a course
*
* @param object $week semana a guardar
* @param int $position posicion de la semana
* @param Object $Week week to save
* @param int $Position position of the week
*
* @return void
* @throws Exception
......@@ -393,10 +393,10 @@ class configweeks {
}
/**
* Guarda las secciones asignadas a una semana de Note My Progress
* Save the sections assigned to a week of Note My Progress
*
* @param string $weekcode id de la semana a la que pertenece las secciones
* @param array $sections lista de secciones a guardar
* @param string $Weekcode ID of the week to which the sections belong
* @param array $sections List of Sections to Save
*
* @return void
* @throws dml_exception
......@@ -409,11 +409,11 @@ class configweeks {
}
/**
* Guarda una seccion asignada a una semana de Note My Progress
* Save a SECTION ASSIGNED A A WEEK OF NOTE MY PROGRAMS
*
* @param object $section sección a guardar
* @param int $weekcode id de la semana a la que pertenece la sección
* @param int $position posición de la sección
* @param object $section Save section
* @param int $weekcode ID of the week to which the section belongs
* @param int $position Position of the section
*
* @return void
*/
......@@ -431,10 +431,10 @@ class configweeks {
}
/**
* Obtiene el nombre de una sección dado su id
* Gets the name of a given section your ID
*
* @param int $sectionid id de sección
* @param int $position posición de la sección
* @param int $sectionid Section ID.
* @param int $position Position of the section
*
* @return void
*/
......@@ -446,13 +446,13 @@ class configweeks {
}
/**
* Devuelve la semana actual de las semanas configuradas de Note My Progress. En caso de que la consulta
* se realice despues de que el curso haya terminado, se retorna la última semana configurada
* Returns the current week of the scheduled weeks of Note My progress.In case the consultation
* It is done after the course is over, the last week is returned
*
* @param int $last_if_course_finished parámetro entero opcional para devolver la última semana configurada
* en caso de que el curso haya terminado
* @param int $last_if_course_finished Optional whole parameter to return the last week configured
* In case the course has finished
*
* @return object objeto con la semana actual a la última semana
* @return Object object with the current week at the last week
*/
public function get_current_week($last_if_course_finished = true){
$current = null;
......@@ -472,12 +472,12 @@ class configweeks {
}
/**
* Toma la fecha actual al momento de hacer la llamada a la funcion y le resta 7 días para obtener
* el día de la peticion de la semana pasada. Si el día obtenido esta dentro de alguna de las semanas
* configuradas de Note My Progress entonces se retorna esa semana, de lo contratio se retorna null
* Take the current date at the time of making the call to the function and it has 7 days to get
* On the day of the past week.If the day obtained is within some of the weeks
* Configured Note My Progress then returns that week, of the contracted Null returns
*
* @return object objeto con la semana a la que corresponde la fecha actual menos 7 días. En caso de
* no encontrarlo se retorna null
* @return object Object with the week to which the current date corresponds less 7 days.In case of
* Do not find it Returns NULL
*/
public function get_past_week(){
$past = null;
......
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Event Collector Observer
*
* @package local_notemyprogress
* @autor Edisson Sigua, Bryan Aguilar
* @copyright 2020 Edisson Sigua <edissonf.sigua@gmail.com>, Bryan Aguilar <bryan.aguilar6174@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_notemyprogress;
defined('MOODLE_INTERNAL') || die();
require_once("lib_trait.php");
use stdClass;
use DateTime;
use dml_exception;
use lang_string;
class event_strategy {
use \lib_trait;
protected $table_xp = 'notemyprogress_xp';
protected $table_log = 'notemyprogress_xp_log';
public $course;
public $user;
public $configgamification;
public function __construct($course, $userid) {
$this->course = self::get_course($course);
$this->user = self::get_user($userid);
$this->configgamification = new \local_notemyprogress\configgamification($course, $userid);
}
/**
* Manejador de eventos.
*
* @param \core\event\base $event The event.
* @return void
*/
public function collect_event(\core\event\base $event) {
if (!self::can_capture_event($event)) {
return;
}
$points = self::get_points_for_event($event);
if ($points === null) {
return;
}
if ($points > 0) {
self::increase($this->user->id, $this->course->id, $points);
self::save_log($this->user->id, $this->course->id, $points, $event->eventname);
}
}
/**
* Compruebe si el usuario puede capturar este evento.
*
* @param \core\event\base $event El evento.
* @return bool True cuando el evento es permitido.
*/
protected function can_capture_event(\core\event\base $event) {
global $SESSION;
$now = time();
$maxcount = 64;
/*
* El número máximo de acciones que contarán como puntos durante el período de tiempo dado.
* Cualquier acción posterior será ignorada. Cuando este valor está vacío o es igual a cero, no se aplica.*/
$maxactions = 10;
/* El período de tiempo (segundos) durante el cual el usuario no debe exceder un número máximo de acciones.*/
$maxtime = 60;
/*
* Se acepta nuevamente el tiempo mínimo requerido antes de que una acción que ya sucedió anteriormente.
* Una acción se considera idéntica si se colocó en el mismo contexto y objeto.
* Cuando este valor está vacío o es igual a cero, no se aplica.*/
$actiontime = 180;
$actionkey = $event->eventname . ':' . $event->contextid . ':' . $event->objectid . ':' . $event->relateduserid;
if (!isset($SESSION->local_notemyprogress_cheatguard)) {
// Init the session variable.
$SESSION->local_notemyprogress_cheatguard = [];
} else {
// Ensure that all entries are arrays, they may not be when we just upgraded the plugin.
$SESSION->local_notemyprogress_cheatguard = array_map(function($entry) {
return is_array($entry) ? $entry : [$entry];
}, $SESSION->local_notemyprogress_cheatguard);
}
// Perform the check.
if (!static::is_action_accepted($actionkey, $now, $SESSION->local_notemyprogress_cheatguard, $maxactions, $maxtime, $actiontime)) {
return false;
}
// Move the action at the end of the array.
$times = isset($SESSION->local_notemyprogress_cheatguard[$actionkey]) ? $SESSION->local_notemyprogress_cheatguard[$actionkey] : [];
unset($SESSION->local_notemyprogress_cheatguard[$actionkey]);
$SESSION->local_notemyprogress_cheatguard[$actionkey] = $times;
// Log the time at which this event happened.
$SESSION->local_notemyprogress_cheatguard[$actionkey][] = time();
// Limit the timestamps of each action to a maximum of $maxcount within the timeframe desired or
// the last 15min. We want to keep at least 15 min so that when teachers are testing changes,
// they do not get confused because actions they had just performed no longer gets blocked.
$timethreshold = $now - max([$maxtime, $actiontime, 900]);
$SESSION->local_notemyprogress_cheatguard = array_filter(array_map(function($times) use ($maxcount, $timethreshold) {
return array_slice(array_filter($times, function($time) use ($timethreshold) {
return $time > $timethreshold;
}), -$maxcount);
}, $SESSION->local_notemyprogress_cheatguard));
// Limit the array of events to $maxcount, we do not want to flood the session for no reason.
$SESSION->local_notemyprogress_cheatguard = array_slice($SESSION->local_notemyprogress_cheatguard, -$maxcount, null, true);
return true;
}
/**
* Comprueba si la acción es aceptada.
*
* @param string $action Clave de la acción.
* @param int $now Timestamp.
* @param array $log Array where keys are actions, and values are timestamp arrays.
* @param int $maxactions El máximo número de acciones.
* @param int $maxintime Tiempo durante el cual el máximo número de acciones es permitido.
* @param int $timebetweenrepeats Tiempo entre acciones repetidas.
* @return bool
*/
public static function is_action_accepted($action, $now, array $log, $maxactions, $maxintime, $timebetweenrepeats) {
if ($maxactions > 0 && $maxintime > 0) {
$timethreshold = $now - $maxintime;
$actionsintimeframe = array_reduce($log, function($carry, $times) use ($timethreshold) {
return $carry + array_reduce($times, function($carry, $time) use ($timethreshold) {
return $carry + ($time > $timethreshold ? 1 : 0);
});
}, 0);
if ($actionsintimeframe >= $maxactions) {
return false;
}
}
if ($timebetweenrepeats > 0) {
$timethreshold = $now - $timebetweenrepeats;
$times = isset($log[$action]) ? $log[$action] : [];
if (!empty($times) && max($times) > $timethreshold) {
return false;
}
}
return true;
}
/**
* Obtiene los puntos para un evento.
*
* @param \core\event\base $event event.
* @return int Points, or null.
*/
public function get_points_for_event(\core\event\base $event) {
$ruleset = $this->configgamification->get_levels_data()->rules;
foreach ($ruleset as $rule) {
if ($rule->rule == $event->eventname) {
return $rule->points;
}
}
return null;
}
/**
* Agregar puntos de experiencia.
*
* @param int $id The receiver.
* @param int $amount The amount.
*/
public function increase($userid, $courseid, $amount) {
global $DB;
$prexp = 0;
$postxp = $amount;
if ($record = self::get_user_data($userid, $courseid)) {
$prexp = (int)$record->points;
$postxp = $prexp + $amount;
$sql = "UPDATE {{$this->table_xp}}
SET points = points + :points
WHERE courseid = :courseid
AND userid = :userid";
$params = [
'points' => $amount,
'courseid' => $courseid,
'userid' => $userid
];
$DB->execute($sql, $params);
$newxp = (int)$record->points + $amount;
$newlevel = self::get_level_from_xp($newxp)->lvl;
if ($record->level != $newlevel) {
$DB->set_field($this->table_xp, 'level', $newlevel, ['courseid' => $courseid, 'userid' => $userid]);
}
} else {
self::insert($userid, $courseid, $amount);
}
}
/**
* Log a thing.
*
* @param int $userid The target.
* @param int $courseid The target.
* @param int $points The points.
* @param reason $reason The reason.
* @param DateTime|null $time When that happened.
* @return void
*/
public function save_log($userid, $courseid, $points, $eventName) {
global $DB;
$time = new DateTime();
$record = new stdClass();
$record->courseid = $courseid;
$record->userid = $userid;
$record->event = $eventName;
$record->points = $points;
$record->timecreated = $time->getTimestamp();
try {
$DB->insert_record($this->table_log, $record);
} catch (dml_exception $e) {}
}
/**
* Agregar puntos de experiencia a un usuario en la tabla notemyprogress_xp.
*
* @param int $id The receiver.
* @param int $amount The amount.
*/
public function insert($userid, $courseid, $amount) {
global $DB;
$record = new stdClass();
$record->courseid = $courseid;
$record->userid = $userid;
$record->points = $amount;
$record->level = self::get_level_from_xp($amount)->lvl;
try {
$DB->insert_record($this->table_xp, $record); //*
} catch (dml_exception $e) {}
}
/**
* Give the level from the xp points.
*
* @param int $xp The xp points.
* @return level
*/
public function get_level_from_xp($points) {
$levels = $this->configgamification->get_levels_data()->levelsdata;
for ($i = count($levels)-1; $i >= 0; $i--) {
$level = $levels[$i];
if ($level->points <= $points) {
return $level;
}
}
return $level;
}
/**
* Obtiene el próximo nivel de un usuario
*
* @return null|level
*/
public function get_next_level($userid) {
$configgamification = $this->configgamification->get_levels_data();
$levels = $configgamification->levelsdata;
$userData = self::get_user_data($userid, $this->course->id);
$levelnum = $userData->level;
if ($levelnum > count($levels)-1) {
$nextlevel = false;
} else {
$nextlevel = $levels[$levelnum];
}
// TODO: validar que pasa cuando supera el ultimo nivel
if ($nextlevel === false) {
return null;
}
return $nextlevel;
}
/**
* Obtiene la información de puntos de experiencia de un usuario
*
* @return null|response
*/
public function get_user_info() {
$response = new stdClass();
$recentCount = 3;
if (self::get_user_data($this->user->id, $this->course->id)) {
$userData = self::get_user_data($this->user->id, $this->course->id);
$nextlevel = self::get_next_level($this->user->id);
$activity = self::get_user_recent_activity($this->user->id, $this->course->id, $recentCount);
$levelInfo = self::get_level_from_xp($userData->points);
$response->points = $userData->points;
$response->level = $userData->level;
$response->activity = $activity;
$response->levelInfo = $levelInfo;
$response->uid=$this->user->id;
$response->cid=$this->course->id;
// $response->points = 1;
// $response->level = 2;
// $response->activity = 3;
// $response->levelInfo = $userData->points;
if(is_null($nextlevel)) {
// Si no existe un siguiente nivel
$response->pointsToNext = 0;
$response->progress = 100;
}else{
$response->pointsToNext = $nextlevel->points - $userData->points;
$response->progress = ($userData->points*100)/$nextlevel->points;
}
}else{return self::get_user_data($this->user->id, $this->course->id);}
return $response;
}
/**
* Obtiene un ranking con los puntos de experiencia de los usuarios
*
* @return users
*/
public function get_ranking() {
global $DB;
$users = array();
$sql = "SELECT * FROM {{$this->table_xp}} WHERE courseid = {$this->course->id} and rankable = 1 ORDER BY points DESC";
$rank = $DB->get_recordset_sql($sql);
$index = 1;
foreach($rank as $user){
$completeUser = self::get_user_from_id($user->userid);
$nextlevel = self::get_next_level($user->userid);
$aux = new stdClass();
$aux->ranking = $index;
$aux->level = $user->level;
$aux->student = $completeUser->firstname.' '.$completeUser->lastname;
$aux->total = $user->points;
if(is_null($nextlevel)) {
// Si no existe un siguiente nivel
$aux->progress_percentage = 100;
}else{
$aux->progress_percentage = ($user->points*100)/$nextlevel->points;
}
$users[] = $aux;
$index++;
}
$rank->close();
return $users;
}
/**
* Verifica si el usuario existe en la tabla de puntos de experiencia.
*
* @param int $id The receiver.
* @return stdClass|false
*/
public function get_user_data($userid, $courseid) {
global $DB;
$params = [];
$params['userid'] = $userid;
$params['courseid'] = $courseid;
return $DB->get_record($this->table_xp, array("userid"=>$userid,"courseid"=>$courseid));
}
/**
* Obtener la actividad reciente del usuario de la tabla notemyprogress_xp_log.
*
* @param int $userid El identificador del usuario.
* @param int $courseid El identificador del curso.
* @param int $count El número de registros a recuperar.
* @return activity
*/
public function get_user_recent_activity($userid, $courseid, $count = 0) {
global $DB;
$results = $DB->get_records_select($this->table_log, 'courseid = :courseid AND userid = :userid AND points > 0', [
'courseid' => $courseid,
'userid' => $userid,
], 'timecreated DESC, id DESC', '*', 0, $count);
$activities = array();
foreach($results as $row){
$desc = '';
$class = $row->event;
if (class_exists($class) && is_subclass_of($class, 'core\event\base')) {
$desc = $class::get_name();
} else {
$desc = new lang_string('somethinghappened', 'local_notemyprogress');
}
$activity = new stdClass();
$activity->timeago = self::time_ago(new DateTime('@' . $row->timecreated));
$activity->description = $desc;
$activity->points = $row->points;
$activities[] = $activity;
}
// return array_map(function($row) {
// $desc = '';
// $class = $row->event;
// if (class_exists($class) && is_subclass_of($class, 'core\event\base')) {
// $desc = $class::get_name();
// } else {
// $desc = new lang_string('somethinghappened', 'local_notemyprogress');
// }
// $activity = new stdClass();
// $activity->timeago = self::time_ago(new DateTime('@' . $row->timecreated));
// $activity->description = $desc;
// $activity->points = $row->points;
// return $activity;
// }, $results);
return $activities;
}
/**
* Obtener un timestamp de la forma hace 5 min.
*
* @param DateTime $dt Objeto de tipo DateTime.
* @return string
*/
public function time_ago(DateTime $dt) {
$now = new \DateTime();
$diff = $now->getTimestamp() - $dt->getTimestamp();
$ago = '?';
if ($diff < 15) {
$ago = get_string('tg_timenow', 'local_notemyprogress');
} else if ($diff < 45) {
$ago = get_string('tg_timeseconds', 'local_notemyprogress', $diff);
} else if ($diff < HOURSECS * 0.7) {
$ago = get_string('tg_timeminutes', 'local_notemyprogress', round($diff / 60));
} else if ($diff < DAYSECS * 0.7) {
$ago = get_string('tg_timehours', 'local_notemyprogress', round($diff / HOURSECS));
} else if ($diff < DAYSECS * 7 * 0.7) {
$ago = get_string('tg_timedays', 'local_notemyprogress', round($diff / DAYSECS));
} else if ($diff < DAYSECS * 30 * 0.7) {
$ago = get_string('tg_timeweeks', 'local_notemyprogress', round($diff / (DAYSECS * 7)));
} else if ($diff < DAYSECS * 365) {
$ago = userdate($dt->getTimestamp(), get_string('tg_timewithinayearformat', 'local_notemyprogress'));
} else {
$ago = userdate($dt->getTimestamp(), get_string('tg_timeolderyearformat', 'local_notemyprogress'));
}
return $ago;
}
}
\ No newline at end of file
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace local_notemyprogress\external;
use coding_exception;
use dml_exception;
use function end;
use external_api;
use external_function_parameters;
use external_multiple_structure;
use external_single_structure;
use external_value;
use invalid_parameter_exception;
use moodle_exception;
use restricted_context_exception;
use stdClass;
defined('MOODLE_INTERNAL') || die();
/**
* Class levels
*
* @package mod_millionaire\external
* @copyright 2019 Benedikt Kulmann <b@kulmann.biz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external extends external_api {
/**
* Definition of parameters for {@see get_levels}.
*
* @return external_function_parameters
*/
public static function get_levels_parameters() {
return new external_function_parameters([
'courseid' => new external_value(PARAM_INT, 'course id'),
]);
}
/**
* Definition of return type for {@see get_levels}.
*
* @return external_multiple_structure
*/
public static function get_levels_returns() {
return new external_multiple_structure(
level_dto::get_read_structure()
);
}
/**
* Get all levels.
*
* @param int $courseid
*
* @return array
* @throws coding_exception
* @throws dml_exception
* @throws invalid_parameter_exception
* @throws moodle_exception
* @throws restricted_context_exception
*/
public static function get_levels($courseid) {
$params = ['courseid' => $courseid];
self::validate_parameters(self::get_levels_parameters(), $params);
global $DB;
$levelsData = $DB->get_record_select(
"notemyprogress_levels_data",
'courseid = ? AND timedeleted IS NULL', $params);
if($levelsData){
$levelsData->levelsdata = json_decode($levelsData->levelsdata);
$levelsData->settings = json_decode($levelsData->settings);
$levelsData->rules = json_decode($levelsData->rules);
}
return $levelsData;
}
}
\ No newline at end of file
<?php
namespace local_notemyprogress\jwt;
class BeforeValidException extends \UnexpectedValueException
{
}
<?php
namespace local_notemyprogress\jwt;
class ExpiredException extends \UnexpectedValueException
{
}
<?php
namespace local_notemyprogress\jwt;
use DomainException;
use InvalidArgumentException;
use UnexpectedValueException;
/**
* JSON Web Key implementation, based on this spec:
* https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41
*
* PHP version 5
*
* @category Authentication
* @package Authentication_JWT
* @author Bui Sy Nguyen <nguyenbs@gmail.com>
* @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
* @link https://github.com/firebase/php-jwt
*/
class JWK
{
/**
* Parse a set of JWK keys
*
* @param array $jwks The JSON Web Key Set as an associative array
*
* @return array An associative array that represents the set of keys
*
* @throws InvalidArgumentException Provided JWK Set is empty
* @throws UnexpectedValueException Provided JWK Set was invalid
* @throws DomainException OpenSSL failure
*
* @uses parseKey
*/
public static function parseKeySet(array $jwks)
{
$keys = array();
if (!isset($jwks['keys'])) {
throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
}
if (empty($jwks['keys'])) {
throw new InvalidArgumentException('JWK Set did not contain any keys');
}
foreach ($jwks['keys'] as $k => $v) {
$kid = isset($v['kid']) ? $v['kid'] : $k;
if ($key = self::parseKey($v)) {
$keys[$kid] = $key;
}
}
if (0 === \count($keys)) {
throw new UnexpectedValueException('No supported algorithms found in JWK Set');
}
return $keys;
}
/**
* Parse a JWK key
*
* @param array $jwk An individual JWK
*
* @return resource|array An associative array that represents the key
*
* @throws InvalidArgumentException Provided JWK is empty
* @throws UnexpectedValueException Provided JWK was invalid
* @throws DomainException OpenSSL failure
*
* @uses createPemFromModulusAndExponent
*/
public static function parseKey(array $jwk)
{
if (empty($jwk)) {
throw new InvalidArgumentException('JWK must not be empty');
}
if (!isset($jwk['kty'])) {
throw new UnexpectedValueException('JWK must contain a "kty" parameter');
}
switch ($jwk['kty']) {
case 'RSA':
if (!empty($jwk['d'])) {
throw new UnexpectedValueException('RSA private keys are not supported');
}
if (!isset($jwk['n']) || !isset($jwk['e'])) {
throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"');
}
$pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']);
$publicKey = \openssl_pkey_get_public($pem);
if (false === $publicKey) {
throw new DomainException(
'OpenSSL error: ' . \openssl_error_string()
);
}
return $publicKey;
default:
// Currently only RSA is supported
break;
}
}
/**
* Create a public key represented in PEM format from RSA modulus and exponent information
*
* @param string $n The RSA modulus encoded in Base64
* @param string $e The RSA exponent encoded in Base64
*
* @return string The RSA public key represented in PEM format
*
* @uses encodeLength
*/
private static function createPemFromModulusAndExponent($n, $e)
{
$modulus = JWT::urlsafeB64Decode($n);
$publicExponent = JWT::urlsafeB64Decode($e);
$components = array(
'modulus' => \pack('Ca*a*', 2, self::encodeLength(\strlen($modulus)), $modulus),
'publicExponent' => \pack('Ca*a*', 2, self::encodeLength(\strlen($publicExponent)), $publicExponent)
);
$rsaPublicKey = \pack(
'Ca*a*a*',
48,
self::encodeLength(\strlen($components['modulus']) + \strlen($components['publicExponent'])),
$components['modulus'],
$components['publicExponent']
);
// sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
$rsaOID = \pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
$rsaPublicKey = \chr(0) . $rsaPublicKey;
$rsaPublicKey = \chr(3) . self::encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey;
$rsaPublicKey = \pack(
'Ca*a*',
48,
self::encodeLength(\strlen($rsaOID . $rsaPublicKey)),
$rsaOID . $rsaPublicKey
);
$rsaPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" .
\chunk_split(\base64_encode($rsaPublicKey), 64) .
'-----END PUBLIC KEY-----';
return $rsaPublicKey;
}
/**
* DER-encode the length
*
* DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
* {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
*
* @param int $length
* @return string
*/
private static function encodeLength($length)
{
if ($length <= 0x7F) {
return \chr($length);
}
$temp = \ltrim(\pack('N', $length), \chr(0));
return \pack('Ca*', 0x80 | \strlen($temp), $temp);
}
}
This diff is collapsed.
<?php
namespace local_notemyprogress\jwt;
class SignatureInvalidException extends \UnexpectedValueException
{
}
......@@ -62,14 +62,14 @@ trait lib_trait {
}
/**
* Obtiene el registro de un usuario en base al parámetro $user.
* Obtain the registration of a user based on the $ User parameter.
*
* Si el parámetro $user no es string ni entero, se retorma el mismo valor del
* parámetro recibido
* If the $ User parameter is not string or whole, the same value of the
* Parameter received
*
* @param string $user id del curso que se desea buscar en formato string, entero u objeto
* @Param String $ User Id of the course you want to search in String format, whole or object
*
* @return mixed un objeto fieldset que contiene el primer registro que hace match a la consulta
* @RETURN MIXED An Fieldset object containing the first record that matches the consultation
*/
public function get_user($user){
if(gettype($user) == "string"){
......@@ -251,13 +251,13 @@ trait lib_trait {
}
/**
* Retorna un string que representa la fecha ($timestamp) Unix formateada usando el parámetro $format
* y tomando como referencia la zona horaria obtenida con la función 'get_timezone'
* Returns a string that represents the date ($ Timestamp) formatted using the $ format parameter
* And taking as reference the time zone obtained with the 'Get_timezone' function
*
* @param $format string objeto que representa una sección de un curso
* @param $timestamp int entero que representa una marca temporal de Unix
* @param $ format string object represents a section of a course
* @param $ Timestamp INTRO that represents a temporary brand of UNIX
*
* @return string cadena de texto con la fecha formateada
* @return string text chain with the formatted date
*/
public function to_format($format, $timestamp){
$tz = self::get_timezone();
......@@ -270,9 +270,9 @@ trait lib_trait {
}
/**
* Retorna un entero que representa la cantidad de segundos desde la Época Unix (January 1 1970 00:00:00 GMT)
* hasta la fecha actual. La fecha actual se calcula en base a la zona horaria obtenida con la funcn
* 'get_timezone'.
* A integer returns that represents the amount of seconds since the UNIX era (January 1 1970 00:00:00 GMT)
* Until now.The current date is calculated based on the time zone obtained with the function
* 'Get_timezone'.
*
* @return int entero que representa la cantidad de segundos desde la Época Unix hasta la fecha actual
*/
......
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* notemyprogress Observer
*
* @package local_notemyprogress
* @autor Edisson Sigua, Bryan Aguilar
* @copyright 2020 Edisson Sigua <edissonf.sigua@gmail.com>, Bryan Aguilar <bryan.aguilar6174@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_notemyprogress\observer;
defined('MOODLE_INTERNAL') || die();
/**
* notemyprogress observer class.
*
* @package local_notemyprogress
* @autor Edisson Sigua, Bryan Aguilar
* @copyright 2020 Edisson Sigua <edissonf.sigua@gmail.com>, Bryan Aguilar <bryan.aguilar6174@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class observer
{
/**
* When a course is deleted.
*
* @param \core\event\course_deleted $event The event.
* @return void
*/
public static function course_deleted(\core\event\course_deleted $event)
{
global $DB;
$courseid = $event->objectid;
// Clean up the data that could be left behind.
$conditions = array('courseid' => $courseid);
// $DB->delete_records('block_xp', $conditions);
// $DB->delete_records('block_xp_config', $conditions);
// $DB->delete_records('block_xp_filters', $conditions);
// $DB->delete_records('block_xp_log', $conditions);
// Flags. Note that this is based on the actually implementation.
// $sql = $DB->sql_like('name', ':name');
// $DB->delete_records_select('user_preferences', $sql, [
// 'name' => 'block_xp-notice-block_intro_' . $courseid
// ]);
// $DB->delete_records_select('user_preferences', $sql, [
// 'name' => 'block_xp_notify_level_up_' . $courseid
// ]);
// Delete the files.
// $fs = get_file_storage();
// $fs->delete_area_files($event->contextid, 'block_xp', 'badges');
}
/**
* Observe all events.
*
* @param \core\event\base $event The event.
* @return void
*/
public static function catch_all(\core\event\base $event) {
global $COURSE, $USER;
$userid = $event->userid;
if ($event->component === 'local_notemyprogress') {
// omit your own events.
return;
} else if (!$userid || isguestuser($userid) || is_siteadmin($userid)) {
// Omitting users and invited that in Hayan started Sesión.
return;
}
else if ($event->anonymous) {
// Omit all events marked as anonymous.
return;
}
else if (!in_array($event->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE))) {
// Omit events that are not in the right context.
return;
} else if ($event->edulevel !== \core\event\base::LEVEL_PARTICIPATING) {
// Ignore events that are not participating.
return;
} else if (!$event->get_context()) {
// If for some reason the context does not exist ...
return;
}
try {
// It has been reported that this can generate an exception when the context is missing ...
$canearn = has_capability('local/notemyprogress:earnxp', $event->get_context(), $userid);
} catch (moodle_exception $e) {
return;
}
// Omit events if the user does not have the ability to earn points.
if (!$canearn) {
return;
}
$cs = new \local_notemyprogress\event_strategy($COURSE, $USER);
$cs->collect_event($event);
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment