Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
KNIGHT
AP4 Adaptive Tests
Commits
25215446
Commit
25215446
authored
Aug 12, 2024
by
0815-xyz
Browse files
Initial commit
parents
Changes
130
Hide whitespace changes
Inline
Side-by-side
classes/local/questionanalysis/statistics/question_statistic_result.php
0 → 100644
View file @
25215446
<?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/>.
/**
* This interface defines the methods required for pluggable statistic-results that may be added to the question analysis.
*
* @copyright 2013 Middlebury College {@link http://www.middlebury.edu/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\questionanalysis\statistics
;
interface
question_statistic_result
{
/**
* A sortable version of the result.
*
* @return mixed string or numeric
*/
public
function
sortable
();
/**
* A printable version of the result.
*
* @param numeric $result
* @return mixed string or numeric
*/
public
function
printable
();
}
classes/local/questionanalysis/statistics/times_used_statistic.php
0 → 100644
View file @
25215446
<?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/>.
/**
* This interface defines the methods required for pluggable statistics that may be added to the question analysis.
*
* @copyright 2013 Middlebury College {@link http://www.middlebury.edu/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\questionanalysis\statistics
;
use
mod_adaptivequiz\local\questionanalysis\question_analyser
;
class
times_used_statistic
implements
question_statistic
{
/**
* Answer a display-name for this statistic.
*
* @return string
*/
public
function
get_display_name
()
{
return
get_string
(
'times_used_display_name'
,
'adaptivequiz'
);
}
/**
* Calculate this statistic for a question's results
*
* @param question_analyser $analyser
* @return question_statistic_result
*/
public
function
calculate
(
question_analyser
$analyser
)
{
return
new
times_used_statistic_result
(
count
(
$analyser
->
get_results
()));
}
}
classes/local/questionanalysis/statistics/times_used_statistic_result.php
0 → 100644
View file @
25215446
<?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/>.
/**
* This interface defines the methods required for pluggable statistic-results that may be added to the question analysis.
*
* @copyright 2013 Middlebury College {@link http://www.middlebury.edu/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\questionanalysis\statistics
;
class
times_used_statistic_result
implements
question_statistic_result
{
/** @var int $count */
protected
$count
=
null
;
/**
* Constructor
*
* @param int $count
* @return void
*/
public
function
__construct
(
$count
)
{
$this
->
count
=
$count
;
}
/**
* A sortable version of the result.
*
* @return mixed string or numeric
*/
public
function
sortable
()
{
return
$this
->
count
;
}
/**
* A printable version of the result.
*
* @param numeric $result
* @return mixed string or numeric
*/
public
function
printable
()
{
return
$this
->
count
;
}
}
classes/local/report/attempt_report_helper.php
0 → 100644
View file @
25215446
<?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
mod_adaptivequiz\local\report
;
use
core_tag_tag
;
use
mod_adaptivequiz
\
local\catalgo
;
use
question_engine
;
use
question_usage_by_activity
;
use
stdClass
;
/**
* Provides data for attempt reports.
*
* The class is intended to be used in exporting of renderable objects only.
*
* @package mod_adaptivequiz
* @copyright 2024 Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class
attempt_report_helper
{
/**
* Returns data to build the answers distribution report for the given attempt.
*
* The returned data has the following format:
* array(
* int => (object) array(
* 'numcorrect' => int,
* 'numwrong' => int,
* )
* )
* where the int key is a question difficulty level, 'numcorrect' and 'numwrong' are the numbers of correct and wrong answers
* for the key level respectively.
*
* @param int $attemptid
* @return stdClass[]
*/
public
static
function
prepare_answers_distribution_data
(
int
$attemptid
):
array
{
global
$DB
;
$attemptrecord
=
$DB
->
get_record
(
'adaptivequiz_attempt'
,
[
'id'
=>
$attemptid
],
'*'
,
MUST_EXIST
);
$adaptivequiz
=
$DB
->
get_record
(
'adaptivequiz'
,
[
'id'
=>
$attemptrecord
->
instance
],
'*'
,
MUST_EXIST
);
$quba
=
question_engine
::
load_questions_usage_by_activity
(
$attemptrecord
->
uniqueid
);
$data
=
[];
// This step is required to ensure we have an entry for each difficulty level, even when no questions of
// such level were administered.
for
(
$i
=
$adaptivequiz
->
lowestlevel
;
$i
<=
$adaptivequiz
->
highestlevel
;
$i
++
)
{
$dataitem
=
new
stdClass
();
$dataitem
->
numcorrect
=
0
;
$dataitem
->
numwrong
=
0
;
$data
[
$i
]
=
$dataitem
;
}
foreach
(
$quba
->
get_slots
()
as
$slot
)
{
$question
=
$quba
->
get_question
(
$slot
);
$tags
=
core_tag_tag
::
get_item_tags_array
(
'core_question'
,
'question'
,
$question
->
id
);
$difficulty
=
adaptivequiz_get_difficulty_from_tags
(
$tags
);
// KNIGHT: Questions are considered correct if the marked fraction is at least at the acceptance threshold determined by the plugin settings
$answeredcorrectly
=
(
$quba
->
get_question_fraction
(
$slot
)
>=
$adaptivequiz
->
acceptancethreshold
);
$answeredcorrectly
?
$data
[
$difficulty
]
->
numcorrect
++
:
$data
[
$difficulty
]
->
numwrong
++
;
}
return
$data
;
}
/**
* Returns data to build the questions administration report for the given attempt.
*
* The returned data has the following format:
* array(
* int => (object) array(
* 'targetdifficulty' => int,
* 'administereddifficulty' => int,
* 'abilitymeasure' => float,
* 'standarderrormax' => float,
* 'standarderrormin' => float,
* 'standarderror' => float,
* 'answeredcorrectly' => bool,
* )
* )
* where the int key is a question slot number (sequence number), the properties have the following meaning:
* targetdifficulty - the difficulty level the algorithm has suggested for the administered question
* administereddifficulty - the difficulty level the actually administered question had
* standarderrormax - the maximum possible value of ability measure given the current standard error value
* standarderrormin - the opposite to the above
* standarderror - the percentage value
* answeredcorrectly - whether the administered question was answered correctly
*
* @param int $attemptid
* @return stdClass[]
*/
public
static
function
prepare_administration_data
(
int
$attemptid
):
array
{
global
$DB
;
$attemptrecord
=
$DB
->
get_record
(
'adaptivequiz_attempt'
,
[
'id'
=>
$attemptid
],
'*'
,
MUST_EXIST
);
$adaptivequiz
=
$DB
->
get_record
(
'adaptivequiz'
,
[
'id'
=>
$attemptrecord
->
instance
],
'*'
,
MUST_EXIST
);
$quba
=
question_engine
::
load_questions_usage_by_activity
(
$attemptrecord
->
uniqueid
);
$data
=
[];
$numattempted
=
0
;
$difficultysum
=
0
;
$sumcorrect
=
0
;
$sumincorrect
=
0
;
foreach
(
$quba
->
get_slots
()
as
$i
=>
$slot
)
{
$targetlevel
=
(
$i
>
0
)
?
self
::
compute_target_difficulty_level
(
$quba
,
$adaptivequiz
,
$slot
,
$numattempted
)
:
$adaptivequiz
->
startinglevel
;
$question
=
$quba
->
get_question
(
$slot
);
$tags
=
core_tag_tag
::
get_item_tags_array
(
'core_question'
,
'question'
,
$question
->
id
);
$difficulty
=
adaptivequiz_get_difficulty_from_tags
(
$tags
);
// KNIGHT: Questions are considered correct if the marked fraction is at least at the acceptance threshold determined by the plugin settings
$answeredcorrectly
=
(
$quba
->
get_question_fraction
(
$slot
)
>=
$adaptivequiz
->
acceptancethreshold
);
$answeredcorrectly
?
$sumcorrect
++
:
$sumincorrect
++
;
$qdifficultylogits
=
catalgo
::
convert_linear_to_logit
(
$difficulty
,
$adaptivequiz
->
lowestlevel
,
$adaptivequiz
->
highestlevel
);
$difficultysum
=
$difficultysum
+
$qdifficultylogits
;
$numattempted
++
;
$abilitylogits
=
catalgo
::
estimate_measure
(
$difficultysum
,
$numattempted
,
$sumcorrect
,
$sumincorrect
);
$abilityfraction
=
1
/
(
1
+
exp
(
(
-
1
*
$abilitylogits
)
)
);
$ability
=
((
$adaptivequiz
->
highestlevel
-
$adaptivequiz
->
lowestlevel
)
*
$abilityfraction
)
+
$adaptivequiz
->
lowestlevel
;
$stderrlogits
=
catalgo
::
estimate_standard_error
(
$numattempted
,
$sumcorrect
,
$sumincorrect
);
$stderr
=
catalgo
::
convert_logit_to_percent
(
$stderrlogits
);
$errormax
=
min
(
$adaptivequiz
->
highestlevel
,
$ability
+
(
$stderr
*
(
$adaptivequiz
->
highestlevel
-
$adaptivequiz
->
lowestlevel
)));
$errormin
=
max
(
$adaptivequiz
->
lowestlevel
,
$ability
-
(
$stderr
*
(
$adaptivequiz
->
highestlevel
-
$adaptivequiz
->
lowestlevel
)));
$dataitem
=
new
stdClass
();
$dataitem
->
targetdifficulty
=
$targetlevel
;
$dataitem
->
administereddifficulty
=
$difficulty
;
$dataitem
->
abilitymeasure
=
$ability
;
$dataitem
->
standarderrormax
=
$errormax
;
$dataitem
->
standarderrormin
=
$errormin
;
$dataitem
->
standarderror
=
$stderr
;
$dataitem
->
answeredcorrectly
=
$answeredcorrectly
;
$data
[
$i
+
1
]
=
$dataitem
;
}
return
$data
;
}
/**
* Computes the target difficulty level based on the answer to previous question.
*
* @param question_usage_by_activity $quba
* @param stdClass $adaptivequiz A record from {adaptivequiz}.
* @param int $slot Slot of the question to be administered.
* @param int $numattempted Number of questions already attempted.
* @return int
*/
private
static
function
compute_target_difficulty_level
(
question_usage_by_activity
$quba
,
stdClass
$adaptivequiz
,
int
$slot
,
int
$numattempted
):
int
{
$previousslot
=
$slot
-
1
;
$previousquestion
=
$quba
->
get_question
(
$previousslot
);
$previousqtags
=
core_tag_tag
::
get_item_tags_array
(
'core_question'
,
'question'
,
$previousquestion
->
id
);
$previousdifficulty
=
adaptivequiz_get_difficulty_from_tags
(
$previousqtags
);
$difficultylogits
=
catalgo
::
convert_linear_to_logit
(
$previousdifficulty
,
$adaptivequiz
->
lowestlevel
,
$adaptivequiz
->
highestlevel
);
// KNIGHT: Questions are considered correct if the marked fraction is at least at the acceptance threshold determined by the plugin settings
$answeredcorrectly
=
(
$quba
->
get_question_fraction
(
$previousslot
)
>=
$adaptivequiz
->
acceptancethreshold
);
if
(
$answeredcorrectly
)
{
$targetlevel
=
round
(
catalgo
::
map_logit_to_scale
(
$difficultylogits
+
2
/
$numattempted
,
$adaptivequiz
->
highestlevel
,
$adaptivequiz
->
lowestlevel
));
if
(
$targetlevel
==
$previousdifficulty
&&
$targetlevel
<
$adaptivequiz
->
highestlevel
)
{
$targetlevel
++
;
}
return
$targetlevel
;
}
$targetlevel
=
round
(
catalgo
::
map_logit_to_scale
(
$difficultylogits
-
2
/
$numattempted
,
$adaptivequiz
->
highestlevel
,
$adaptivequiz
->
lowestlevel
));
if
(
$targetlevel
==
$previousdifficulty
&&
$targetlevel
>
$adaptivequiz
->
lowestlevel
)
{
$targetlevel
--
;
}
return
$targetlevel
;
}
}
classes/local/report/individual_user_attempts/filter.php
0 → 100644
View file @
25215446
<?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/>.
/**
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\report\individual_user_attempts
;
final
class
filter
{
/**
* @var int $adaptivequizid
*/
public
$adaptivequizid
;
/**
* @var int $userid
*/
public
$userid
;
public
static
function
from_vars
(
int
$adaptivequizid
,
int
$userid
):
self
{
$return
=
new
self
();
$return
->
adaptivequizid
=
$adaptivequizid
;
$return
->
userid
=
$userid
;
return
$return
;
}
}
classes/local/report/individual_user_attempts/table.php
0 → 100644
View file @
25215446
<?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/>.
/**
* Contains definition of the table class for the user attempts report.
*
* @package mod_adaptivequiz
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\report\individual_user_attempts
;
defined
(
'MOODLE_INTERNAL'
)
||
die
();
require_once
(
$CFG
->
libdir
.
'/tablelib.php'
);
use
mod_adaptivequiz\local\attempt\attempt_state
;
use
mod_adaptivequiz\local\report\questions_difficulty_range
;
use
mod_adaptivequiz_renderer
;
use
moodle_url
;
use
stdClass
;
use
table_sql
;
/**
* Definition of the table class for the user attempts report.
*
* @package mod_adaptivequiz
*/
final
class
table
extends
table_sql
{
/**
* @var mod_adaptivequiz_renderer $renderer
*/
private
$renderer
;
/**
* @var filter $filter
*/
private
$filter
;
/**
* @var questions_difficulty_range $questionsdifficultyrange
*/
private
$questionsdifficultyrange
;
/**
* @var int $cmid
*/
private
$cmid
;
public
function
__construct
(
mod_adaptivequiz_renderer
$renderer
,
filter
$filter
,
moodle_url
$baseurl
,
questions_difficulty_range
$questionsdifficultyrange
,
int
$cmid
,
//KNIGHT: Modifications with the purpose of hiding the column "reason for stopping the attempt" in the student review
bool
$showattemptstopcriteria
)
{
parent
::
__construct
(
'individualuserattemptstable'
);
$this
->
renderer
=
$renderer
;
$this
->
filter
=
$filter
;
$this
->
questionsdifficultyrange
=
$questionsdifficultyrange
;
//KNIGHT
$this
->
showattemptstopcriteria
=
$showattemptstopcriteria
;
$this
->
cmid
=
$cmid
;
$this
->
init
(
$baseurl
);
}
public
function
get_sql_sort
()
{
return
'timemodified DESC'
;
}
protected
function
col_attemptstate
(
stdClass
$row
):
string
{
if
(
0
==
strcmp
(
attempt_state
::
IN_PROGRESS
,
$row
->
attemptstate
))
{
return
get_string
(
'recentinprogress'
,
'adaptivequiz'
);
}
return
get_string
(
'recentcomplete'
,
'adaptivequiz'
);
}
protected
function
col_score
(
stdClass
$row
):
string
{
if
(
$row
->
measure
===
null
||
$row
->
stderror
===
null
||
$row
->
stderror
==
0.0
)
{
return
'n/a'
;
}
$formatmeasureparams
=
new
stdClass
();
$formatmeasureparams
->
measure
=
$row
->
measure
;
$formatmeasureparams
->
highestlevel
=
$this
->
questionsdifficultyrange
->
highest_level
();
$formatmeasureparams
->
lowestlevel
=
$this
->
questionsdifficultyrange
->
lowest_level
();
return
$this
->
renderer
->
format_measure
(
$formatmeasureparams
)
.
' '
.
$this
->
renderer
->
format_standard_error
(
$row
);
}
protected
function
col_timecreated
(
stdClass
$row
):
string
{
return
userdate
(
$row
->
timecreated
);
}
protected
function
col_timemodified
(
stdClass
$row
):
string
{
return
userdate
(
$row
->
timemodified
);
}
/**
* A hook to format the output of actions column for an attempt row.
*
* @param stdClass $row A row from the {adaptivequiz_attempt}.
*/
protected
function
col_actions
(
stdClass
$row
):
string
{
return
$this
->
renderer
->
individual_user_attempt_actions
(
$row
,
$this
->
showattemptstopcriteria
);
}
private
function
init
(
moodle_url
$baseurl
):
void
{
$columns
=
[
'attemptstate'
,
'questionsattempted'
,
'score'
,
'timecreated'
,
'timemodified'
,
'actions'
];
$headers
=
[
get_string
(
'attemptstate'
,
'adaptivequiz'
),
get_string
(
'questionsattempted'
,
'adaptivequiz'
),
get_string
(
'score'
,
'adaptivequiz'
),
get_string
(
'attemptstarttime'
,
'adaptivequiz'
),
get_string
(
'attemptfinishedtimestamp'
,
'adaptivequiz'
),
''
,
];
$selectionstring
=
'id, userid, uniqueid, attemptstate, questionsattempted, timemodified, timecreated, measure, standarderror AS stderror'
;
//KNIGHT
if
(
$this
->
showattemptstopcriteria
){
array_splice
(
$columns
,
1
,
0
,
array
(
'attemptstopcriteria'
));
array_splice
(
$headers
,
1
,
0
,
array
(
get_string
(
'attemptstopcriteria'
,
'adaptivequiz'
)));
$selectionstring
=
'id, userid, uniqueid, attemptstopcriteria, attemptstate, questionsattempted, timemodified, timecreated, measure, standarderror AS stderror'
;
}
$this
->
define_columns
(
$columns
);
$this
->
define_headers
(
$headers
);
$this
->
set_content_alignment_in_columns
();
$this
->
define_baseurl
(
$baseurl
);
$this
->
set_attribute
(
'class'
,
$this
->
attributes
[
'class'
]
.
' '
.
$this
->
uniqueid
);
$this
->
is_downloadable
(
false
);
$this
->
collapsible
(
false
);
$this
->
sortable
(
false
);
$this
->
set_sql
(
$selectionstring
,
'{adaptivequiz_attempt}'
,
'instance = :adaptivequiz AND userid = :userid'
,
[
'adaptivequiz'
=>
$this
->
filter
->
adaptivequizid
,
'userid'
=>
$this
->
filter
->
userid
]
);
}
private
function
set_content_alignment_in_columns
():
void
{
foreach
(
array_keys
(
$this
->
columns
)
as
$column
)
{
$this
->
column_class
[
$column
]
.
=
' text-center'
;
}
}
}
classes/local/report/questions_difficulty_range.php
0 → 100644
View file @
25215446
<?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/>.
/**
* The class defines a configuration object with range of questions difficulty. Acts as a container for related pieces
* of data - a value object. Normally is set from a corresponding activity record's values, thus, it doesn't perform
* any validation of the parameters when instantiated.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\report
;
use
stdClass
;
final
class
questions_difficulty_range
{
/**
* @var int $lowestlevel
*/
private
$lowestlevel
;
/**
* @var int $highestlevel
*/
private
$highestlevel
;
private
function
__construct
(
int
$lowestlevel
,
int
$highestlevel
)
{
$this
->
lowestlevel
=
$lowestlevel
;
$this
->
highestlevel
=
$highestlevel
;
}
public
function
lowest_level
():
int
{
return
$this
->
lowestlevel
;
}
public
function
highest_level
():
int
{
return
$this
->
highestlevel
;
}
/**
* @param stdClass $instance A record from {adaptivequiz}.
*/
public
static
function
from_activity_instance
(
stdClass
$instance
):
self
{
return
new
self
(
$instance
->
lowestlevel
,
$instance
->
highestlevel
);
}
}
classes/local/report/users_attempts/filter/filter.php
0 → 100644
View file @
25215446
<?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/>.
/**
* A class containing the data to filter the list of users with attempts by.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\report\users_attempts\filter
;
use
mod_adaptivequiz\local\report\users_attempts\user_preferences\filter_user_preferences
;
final
class
filter
{
/**
* @var int $adaptivequizid
*/
public
$adaptivequizid
;
/**
* @var int $groupid
*/
public
$groupid
;
/**
* @var int $users
*/
public
$users
;
/**
* @var int $includeinactiveenrolments Represents a bool value, as bool values normally come as int (0 or 1)
* from a request.
*/
public
$includeinactiveenrolments
;
public
function
fill_from_array
(
array
$request
):
void
{
foreach
(
$request
as
$propertyname
=>
$propertyvalue
)
{
if
(
property_exists
(
$this
,
$propertyname
))
{
$this
->
$propertyname
=
$propertyvalue
;
}
}
}
public
function
fill_from_preference
(
filter_user_preferences
$filter
):
void
{
$this
->
users
=
$filter
->
users
();
$this
->
includeinactiveenrolments
=
$filter
->
include_inactive_enrolments
();
}
public
static
function
from_vars
(
int
$adaptivequizid
,
int
$groupid
):
self
{
$return
=
new
self
();
$return
->
adaptivequizid
=
$adaptivequizid
;
$return
->
groupid
=
$groupid
;
return
$return
;
}
}
classes/local/report/users_attempts/filter/filter_form.php
0 → 100644
View file @
25215446
<?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/>.
/**
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\report\users_attempts\filter
;
defined
(
'MOODLE_INTERNAL'
)
||
die
();
require_once
(
$CFG
->
libdir
.
'/formslib.php'
);
use
html_writer
;
use
moodle_url
;
use
moodleform
;
final
class
filter_form
extends
moodleform
{
/**
* Overrides the parent method to remove mandatory closing of fieldset before the submit button.
* Ignores the arguments, as it contains its own logic to display the button.
*
* @inheritDoc
*/
public
function
add_action_buttons
(
$cancel
=
true
,
$submitlabel
=
null
)
{
$form
=&
$this
->
_form
;
$form
->
addElement
(
'submit'
,
'prefssubmit'
,
get_string
(
'reportattemptsfilterformsubmit'
,
'adaptivequiz'
));
}
protected
function
definition
()
{
$form
=
$this
->
_form
;
$form
->
addElement
(
'header'
,
'filterheader'
,
get_string
(
'reportattemptsfilterformheader'
,
'adaptivequiz'
));
$enrolmentoptions
=
[
filter_options
::
ENROLLED_USERS_WITH_NO_ATTEMPTS
=>
get_string
(
'reportattemptsenrolledwithnoattempts'
,
'adaptivequiz'
),
filter_options
::
ENROLLED_USERS_WITH_ATTEMPTS
=>
get_string
(
'reportattemptsenrolledwithattempts'
,
'adaptivequiz'
),
filter_options
::
BOTH_ENROLLED_AND_NOT_ENROLLED_USERS_WITH_ATTEMPTS
=>
get_string
(
'reportattemptsbothenrolledandnotenrolled'
,
'adaptivequiz'
),
filter_options
::
NOT_ENROLLED_USERS_WITH_ATTEMPTS
=>
get_string
(
'reportattemptsnotenrolled'
,
'adaptivequiz'
)
];
$form
->
addElement
(
'select'
,
'users'
,
get_string
(
'reportattemptsfilterusers'
,
'adaptivequiz'
),
$enrolmentoptions
);
$form
->
setDefault
(
'users'
,
filter_options
::
users_option_default
());
$form
->
addElement
(
'advcheckbox'
,
'includeinactiveenrolments'
,
get_string
(
'reportattemptsfilterincludeinactiveenrolments'
,
'adaptivequiz'
),
' '
,
null
,
[
0
,
1
]);
$form
->
setDefault
(
'includeinactiveenrolments'
,
filter_options
::
INCLUDE_INACTIVE_ENROLMENTS_DEFAULT
);
$form
->
addHelpButton
(
'includeinactiveenrolments'
,
'reportattemptsfilterincludeinactiveenrolments'
,
'adaptivequiz'
);
$form
->
disabledIf
(
'includeinactiveenrolments'
,
'users'
,
'eq'
,
filter_options
::
BOTH_ENROLLED_AND_NOT_ENROLLED_USERS_WITH_ATTEMPTS
);
$this
->
add_action_buttons
();
}
}
classes/local/report/users_attempts/filter/filter_options.php
0 → 100644
View file @
25215446
<?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/>.
/**
* Emulates an enum type to keep available filtering options. Defines default values for the options as well.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\report\users_attempts\filter
;
final
class
filter_options
{
public
const
ENROLLED_USERS_WITH_NO_ATTEMPTS
=
1
;
public
const
ENROLLED_USERS_WITH_ATTEMPTS
=
2
;
public
const
BOTH_ENROLLED_AND_NOT_ENROLLED_USERS_WITH_ATTEMPTS
=
3
;
public
const
NOT_ENROLLED_USERS_WITH_ATTEMPTS
=
4
;
public
const
INCLUDE_INACTIVE_ENROLMENTS_DEFAULT
=
1
;
public
static
function
users_option_default
():
int
{
return
self
::
BOTH_ENROLLED_AND_NOT_ENROLLED_USERS_WITH_ATTEMPTS
;
}
public
static
function
users_option_exists
(
int
$option
):
bool
{
return
in_array
(
$option
,
[
self
::
ENROLLED_USERS_WITH_NO_ATTEMPTS
,
self
::
ENROLLED_USERS_WITH_ATTEMPTS
,
self
::
BOTH_ENROLLED_AND_NOT_ENROLLED_USERS_WITH_ATTEMPTS
,
self
::
NOT_ENROLLED_USERS_WITH_ATTEMPTS
]);
}
}
classes/local/report/users_attempts/sql/sql_and_params.php
0 → 100644
View file @
25215446
<?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/>.
/**
* The class contains all possible sql options needed to build the users' attempts table.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\report\users_attempts\sql
;
use
core\dml\sql_join
;
use
core_user
\
fields
;
use
mod_adaptivequiz\local\attempt\attempt_state
;
final
class
sql_and_params
{
/**
* @var string $fields
*/
private
$fields
;
/**
* @var string $from
*/
private
$from
;
/**
* @var string $where
*/
private
$where
;
/**
* @var string|null $where
*/
private
$groupby
;
/**
* @var array $params Normal array with query parameters as in {@link \moodle_database::get_records_sql()},
* for instance.
*/
private
$params
;
/**
* @var string $countsql Complete sql statement to pass to {@link \table_sql::set_count_sql()}.
*/
private
$countsql
;
/**
* @var array $params Same format as for {@link self::$params} above.
*/
private
$countsqlparams
;
private
function
__construct
(
string
$fields
,
string
$from
,
string
$where
,
?string
$groupby
,
array
$params
,
?string
$countsql
,
?array
$countsqlparams
)
{
$this
->
fields
=
$fields
;
$this
->
from
=
$from
;
$this
->
where
=
$where
;
$this
->
params
=
$params
;
$this
->
groupby
=
$groupby
;
$this
->
countsql
=
$countsql
;
$this
->
countsqlparams
=
$countsqlparams
;
}
public
function
fields
():
string
{
return
$this
->
fields
;
}
public
function
from
():
string
{
return
$this
->
from
;
}
public
function
where
():
string
{
return
$this
->
where
;
}
public
function
group_by
():
?string
{
return
$this
->
groupby
;
}
public
function
params
():
array
{
return
$this
->
params
;
}
public
function
count_sql
():
?string
{
return
$this
->
countsql
;
}
public
function
count_sql_params
():
?array
{
return
$this
->
countsqlparams
;
}
public
function
with_group_filtering
(
int
$groupid
):
self
{
$from
=
$this
->
from
.
' INNER JOIN {groups_members} gm ON u.id = gm.userid'
;
$where
=
$this
->
where
.
' AND gm.groupid = :groupid'
;
$params
=
array_merge
([
'groupid'
=>
$groupid
],
$this
->
params
);
return
new
self
(
$this
->
fields
,
$from
,
$where
,
$this
->
groupby
,
$params
,
$this
->
countsql
,
$this
->
countsqlparams
);
}
public
static
function
default
(
int
$adaptivequizid
):
self
{
list
(
$attemptsql
,
$params
)
=
self
::
attempt_sql_and_params
();
$fields
=
fields
::
for_name
()
->
including
(
'id'
,
'email'
)
->
get_sql
(
'u'
,
false
,
''
,
''
,
false
)
->
selects
.
', '
.
$attemptsql
;
$from
=
'{adaptivequiz_attempt} aa JOIN {user} u ON u.id = aa.userid'
;
$where
=
self
::
base_where_sql
()
.
' AND aa.instance = :instance'
;
$params
=
array_merge
(
$params
,
[
'instance'
=>
$adaptivequizid
]);
$sqlcount
=
"SELECT COUNT(DISTINCT u.id) FROM
$from
WHERE
$where
"
;
return
new
self
(
$fields
,
$from
,
$where
,
self
::
group_by_sql
(),
$params
,
$sqlcount
,
$params
);
}
public
static
function
for_enrolled_with_no_attempts
(
int
$adaptivequizid
,
sql_join
$enrolledjoin
):
self
{
$fields
=
'DISTINCT u.id'
.
fields
::
for_name
()
->
including
(
'email'
)
->
get_sql
(
'u'
)
->
selects
.
', NULL as attemptsnum, NULL AS uniqueid, NULL AS attempttimefinished, NULL AS measure, NULL AS stderror'
;
$from
=
"
{
user
}
u
$enrolledjoin->joins
LEFT JOIN
{
adaptivequiz_attempt
}
aa ON (aa.userid = u.id AND aa.instance = :instance)
"
;
$where
=
$enrolledjoin
->
wheres
.
' AND aa.id IS NULL'
;
$params
=
array_merge
([
'instance'
=>
$adaptivequizid
],
$enrolledjoin
->
params
);
$sqlcount
=
"SELECT COUNT(DISTINCT u.id) FROM
$from
WHERE
$where
"
;
return
new
self
(
$fields
,
$from
,
$where
,
null
,
$params
,
$sqlcount
,
$params
);
}
public
static
function
for_enrolled_with_attempts
(
int
$adaptivequizid
,
sql_join
$enrolledjoin
):
self
{
list
(
$attemptsql
,
$params
)
=
self
::
attempt_sql_and_params
();
$fields
=
'DISTINCT u.id'
.
fields
::
for_name
()
->
including
(
'email'
)
->
get_sql
(
'u'
)
->
selects
.
', '
.
$attemptsql
;
$from
=
"
{
user
}
u
$enrolledjoin->joins
JOIN
{
adaptivequiz_attempt
}
aa ON (aa.userid = u.id AND aa.instance = :instance)
"
;
$where
=
$enrolledjoin
->
wheres
;
$params
=
array_merge
(
$params
,
[
'instance'
=>
$adaptivequizid
],
$enrolledjoin
->
params
);
$sqlcount
=
"SELECT COUNT(DISTINCT u.id) FROM
$from
WHERE
$where
"
;
return
new
self
(
$fields
,
$from
,
$where
,
self
::
group_by_sql
(),
$params
,
$sqlcount
,
$params
);
}
public
static
function
for_not_enrolled_with_attempts
(
int
$adaptivequizid
,
sql_join
$enrolledjoin
):
self
{
list
(
$attemptsql
,
$params
)
=
self
::
attempt_sql_and_params
();
$fields
=
'DISTINCT u.id'
.
fields
::
for_name
()
->
including
(
'email'
)
->
get_sql
(
'u'
)
->
selects
.
', '
.
$attemptsql
;
$from
=
'{adaptivequiz_attempt} aa JOIN {user} u ON u.id = aa.userid'
;
$where
=
self
::
base_where_sql
()
.
"
AND aa.instance = :instance AND NOT EXISTS (
SELECT DISTINCT u.id FROM
{
user
}
u
$enrolledjoin->joins
WHERE u.id = aa.userid AND
$enrolledjoin->wheres
)
"
;
$params
=
array_merge
(
$params
,
[
'instance'
=>
$adaptivequizid
],
$enrolledjoin
->
params
);
$sqlcount
=
"SELECT COUNT(DISTINCT u.id) FROM
$from
WHERE
$where
"
;
return
new
self
(
$fields
,
$from
,
$where
,
self
::
group_by_sql
(),
$params
,
$sqlcount
,
$params
);
}
private
static
function
attempt_sql_and_params
():
array
{
return
[
'(
SELECT COUNT(*) FROM {adaptivequiz_attempt} caa
WHERE caa.userid = u.id AND caa.instance = aa.instance
) AS attemptsnum,
(
SELECT maa.measure FROM {adaptivequiz_attempt} maa
WHERE maa.instance = aa.instance AND maa.userid = u.id AND maa.attemptstate = :attemptstate1
AND maa.standarderror > 0.0
ORDER BY measure DESC
LIMIT 1
) AS measure,
(
SELECT saa.standarderror FROM {adaptivequiz_attempt} saa
WHERE saa.instance = aa.instance AND saa.userid = u.id AND saa.attemptstate = :attemptstate2
AND saa.standarderror > 0.0
ORDER BY measure DESC
LIMIT 1
) AS stderror,
(
SELECT taa.timemodified FROM {adaptivequiz_attempt} taa
WHERE taa.instance = aa.instance AND taa.userid = u.id AND taa.attemptstate = :attemptstate3
AND taa.standarderror > 0.0
ORDER BY measure DESC
LIMIT 1
) AS attempttimefinished,
(
SELECT iaa.id FROM {adaptivequiz_attempt} iaa
WHERE iaa.instance = aa.instance AND iaa.userid = u.id AND iaa.attemptstate = :attemptstate4
AND iaa.standarderror > 0.0
ORDER BY measure DESC
LIMIT 1
) AS attemptid'
,
[
'attemptstate1'
=>
attempt_state
::
COMPLETED
,
'attemptstate2'
=>
attempt_state
::
COMPLETED
,
'attemptstate3'
=>
attempt_state
::
COMPLETED
,
'attemptstate4'
=>
attempt_state
::
COMPLETED
,
]
];
}
private
static
function
group_by_sql
():
string
{
return
'u.id, aa.instance'
;
}
private
static
function
base_where_sql
():
string
{
return
'u.deleted = 0'
;
}
}
classes/local/report/users_attempts/sql/sql_resolver.php
0 → 100644
View file @
25215446
<?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/>.
/**
* A class to keep the logic of resolving what sql with its parameters should be used for the report
* depending on filtering requested, etc.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\report\users_attempts\sql
;
use
context
;
use
mod_adaptivequiz\local\report\users_attempts
\
filter\filter
;
use
mod_adaptivequiz\local\report\users_attempts\filter\filter_options
;
final
class
sql_resolver
{
public
static
function
sql_and_params
(
filter
$filter
,
context
$context
):
sql_and_params
{
if
(
!
$filter
->
users
||
$filter
->
users
==
filter_options
::
BOTH_ENROLLED_AND_NOT_ENROLLED_USERS_WITH_ATTEMPTS
)
{
$sqlandparams
=
sql_and_params
::
default
(
$filter
->
adaptivequizid
);
if
(
$filter
->
groupid
)
{
$sqlandparams
=
$sqlandparams
->
with_group_filtering
(
$filter
->
groupid
);
}
return
$sqlandparams
;
}
$enrolledjoin
=
get_enrolled_with_capabilities_join
(
$context
,
''
,
'mod/adaptivequiz:attempt'
,
$filter
->
groupid
,
self
::
resolve_active_enrolment_flag
(
$filter
->
users
,
$filter
->
includeinactiveenrolments
));
if
(
$filter
->
users
==
filter_options
::
ENROLLED_USERS_WITH_NO_ATTEMPTS
)
{
$sqlandparams
=
sql_and_params
::
for_enrolled_with_no_attempts
(
$filter
->
adaptivequizid
,
$enrolledjoin
);
}
if
(
$filter
->
users
==
filter_options
::
ENROLLED_USERS_WITH_ATTEMPTS
)
{
$sqlandparams
=
sql_and_params
::
for_enrolled_with_attempts
(
$filter
->
adaptivequizid
,
$enrolledjoin
);
}
if
(
$filter
->
users
==
filter_options
::
NOT_ENROLLED_USERS_WITH_ATTEMPTS
)
{
$sqlandparams
=
sql_and_params
::
for_not_enrolled_with_attempts
(
$filter
->
adaptivequizid
,
$enrolledjoin
);
}
if
(
$filter
->
groupid
)
{
$sqlandparams
=
$sqlandparams
->
with_group_filtering
(
$filter
->
groupid
);
}
return
$sqlandparams
;
}
private
static
function
resolve_active_enrolment_flag
(
int
$usersoption
,
int
$includeinactiveenrolmentsoption
):
bool
{
if
(
$usersoption
==
filter_options
::
ENROLLED_USERS_WITH_NO_ATTEMPTS
||
$usersoption
==
filter_options
::
ENROLLED_USERS_WITH_ATTEMPTS
)
{
return
!
$includeinactiveenrolmentsoption
;
}
return
$includeinactiveenrolmentsoption
;
}
}
classes/local/report/users_attempts/user_preferences/filter_user_preferences.php
0 → 100644
View file @
25215446
<?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/>.
/**
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\report\users_attempts\user_preferences
;
use
mod_adaptivequiz\local\report\users_attempts\filter\filter_options
;
final
class
filter_user_preferences
{
/**
* @var int $users
*/
private
$users
;
/**
* @var int $includeinactiveenrolments
*/
private
$includeinactiveenrolments
;
private
function
__construct
(
int
$users
,
int
$includeinactiveenrolments
)
{
$this
->
users
=
filter_options
::
users_option_exists
(
$users
)
?
$users
:
filter_options
::
users_option_default
();
$this
->
includeinactiveenrolments
=
in_array
(
$includeinactiveenrolments
,
[
0
,
1
])
?
$includeinactiveenrolments
:
filter_options
::
INCLUDE_INACTIVE_ENROLMENTS_DEFAULT
;
}
public
function
users
():
int
{
return
$this
->
users
;
}
public
function
include_inactive_enrolments
():
int
{
return
$this
->
includeinactiveenrolments
;
}
public
function
as_array
():
array
{
return
[
'users'
=>
$this
->
users
,
'includeinactiveenrolments'
=>
$this
->
includeinactiveenrolments
];
}
public
static
function
from_array
(
array
$filter
):
self
{
return
new
self
(
array_key_exists
(
'users'
,
$filter
)
?
$filter
[
'users'
]
:
filter_options
::
users_option_default
(),
array_key_exists
(
'includeinactiveenrolments'
,
$filter
)
?
$filter
[
'includeinactiveenrolments'
]
:
filter_options
::
INCLUDE_INACTIVE_ENROLMENTS_DEFAULT
);
}
}
classes/local/report/users_attempts/user_preferences/user_preferences.php
0 → 100644
View file @
25215446
<?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/>.
/**
* A value object encapsulating user preferences to set up the report table.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\report\users_attempts\user_preferences
;
use
stdClass
;
final
class
user_preferences
{
public
const
PER_PAGE_OPTIONS
=
[
5
,
10
,
15
,
20
,
25
,
50
];
public
const
PER_PAGE_DEFAULT
=
15
;
public
const
SHOW_INITIALS_BAR_DEFAULT
=
1
;
public
const
PERSISTENT_FILTER_DEFAULT
=
0
;
/**
* @var int $perpage
*/
private
$perpage
;
/**
* @var int $showinitialsbar Represents a boolean value, but defined as an int, as it's stored as an int.
*/
private
$showinitialsbar
;
/**
* @var int $persistentfilter Same as for $showinitialsbar above.
*/
private
$persistentfilter
;
/**
* @var filter_user_preferences|null $filter
*/
private
$filter
;
private
function
__construct
(
int
$perpage
,
int
$showinitialsbar
,
int
$persistentfilter
,
?filter_user_preferences
$filter
)
{
$this
->
perpage
=
in_array
(
$perpage
,
self
::
PER_PAGE_OPTIONS
)
?
$perpage
:
self
::
PER_PAGE_DEFAULT
;
$this
->
showinitialsbar
=
in_array
(
$showinitialsbar
,
[
0
,
1
])
?
$showinitialsbar
:
self
::
SHOW_INITIALS_BAR_DEFAULT
;
$this
->
persistentfilter
=
in_array
(
$persistentfilter
,
[
0
,
1
])
?
$persistentfilter
:
self
::
PERSISTENT_FILTER_DEFAULT
;
$this
->
filter
=
$filter
;
}
public
function
rows_per_page
():
int
{
return
$this
->
perpage
;
}
public
function
show_initials_bar
():
bool
{
return
(
bool
)
$this
->
showinitialsbar
;
}
public
function
persistent_filter
():
bool
{
return
(
bool
)
$this
->
persistentfilter
;
}
public
function
filter
():
?filter_user_preferences
{
return
$this
->
filter
;
}
public
function
has_filter_preference
():
bool
{
return
$this
->
filter
!==
null
;
}
public
function
with_filter_preference
(
filter_user_preferences
$preference
):
self
{
return
new
self
(
$this
->
perpage
,
$this
->
showinitialsbar
,
$this
->
persistentfilter
,
$preference
);
}
public
function
without_filter_preference
():
self
{
return
new
self
(
$this
->
perpage
,
$this
->
showinitialsbar
,
$this
->
persistentfilter
,
null
);
}
public
function
as_array
():
array
{
$return
=
[
'perpage'
=>
$this
->
perpage
,
'showinitialsbar'
=>
$this
->
showinitialsbar
,
'persistentfilter'
=>
$this
->
persistentfilter
];
$return
[
'filter'
]
=
(
$this
->
filter
===
null
)
?
null
:
$this
->
filter
->
as_array
();
return
$return
;
}
public
static
function
from_array
(
array
$prefs
):
self
{
$filter
=
array_key_exists
(
'filter'
,
$prefs
)
?
$prefs
[
'filter'
]
:
null
;
return
new
self
(
array_key_exists
(
'perpage'
,
$prefs
)
?
$prefs
[
'perpage'
]
:
self
::
PER_PAGE_DEFAULT
,
array_key_exists
(
'showinitialsbar'
,
$prefs
)
?
$prefs
[
'showinitialsbar'
]
:
self
::
SHOW_INITIALS_BAR_DEFAULT
,
array_key_exists
(
'persistentfilter'
,
$prefs
)
?
$prefs
[
'persistentfilter'
]
:
self
::
PERSISTENT_FILTER_DEFAULT
,
(
$filter
===
null
)
?
null
:
filter_user_preferences
::
from_array
(
$filter
)
);
}
public
static
function
from_plain_object
(
stdClass
$object
):
self
{
return
self
::
from_array
((
array
)
$object
);
}
public
static
function
defaults
():
self
{
return
new
self
(
self
::
PER_PAGE_DEFAULT
,
self
::
SHOW_INITIALS_BAR_DEFAULT
,
self
::
PERSISTENT_FILTER_DEFAULT
,
null
);
}
}
classes/local/report/users_attempts/user_preferences/user_preferences_form.php
0 → 100644
View file @
25215446
<?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/>.
/**
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\report\users_attempts\user_preferences
;
defined
(
'MOODLE_INTERNAL'
)
||
die
();
require_once
(
$CFG
->
libdir
.
'/formslib.php'
);
use
moodleform
;
class
user_preferences_form
extends
moodleform
{
/**
* Overrides the parent method to remove mandatory closing of fieldset before the submit button.
* Ignores the arguments, as it contains its own logic to display the button.
*
* @inheritDoc
*/
public
function
add_action_buttons
(
$cancel
=
true
,
$submitlabel
=
null
)
{
$form
=&
$this
->
_form
;
$form
->
addElement
(
'submit'
,
'prefssubmit'
,
get_string
(
'reportattemptsprefsformsubmit'
,
'adaptivequiz'
));
}
protected
function
definition
()
{
$form
=
$this
->
_form
;
$form
->
addElement
(
'header'
,
'prefsheader'
,
get_string
(
'reportattemptsprefsformheader'
,
'adaptivequiz'
));
$form
->
addElement
(
'select'
,
'perpage'
,
get_string
(
'reportattemptsusersperpage'
,
'adaptivequiz'
),
array_combine
(
user_preferences
::
PER_PAGE_OPTIONS
,
user_preferences
::
PER_PAGE_OPTIONS
));
$form
->
setDefault
(
'perpage'
,
user_preferences
::
PER_PAGE_DEFAULT
);
$form
->
addElement
(
'advcheckbox'
,
'showinitialsbar'
,
get_string
(
'reportattemptsshowinitialbars'
,
'adaptivequiz'
),
' '
,
null
,
[
0
,
1
]);
$form
->
setDefault
(
'showinitialsbar'
,
user_preferences
::
SHOW_INITIALS_BAR_DEFAULT
);
$form
->
addElement
(
'advcheckbox'
,
'persistentfilter'
,
get_string
(
'reportattemptspersistentfilter'
,
'adaptivequiz'
),
' '
,
null
,
[
0
,
1
]);
$form
->
setDefault
(
'persistentfilter'
,
user_preferences
::
PERSISTENT_FILTER_DEFAULT
);
$form
->
addHelpButton
(
'persistentfilter'
,
'reportattemptspersistentfilter'
,
'adaptivequiz'
);
$this
->
add_action_buttons
();
}
}
classes/local/report/users_attempts/user_preferences/user_preferences_repository.php
0 → 100644
View file @
25215446
<?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
mod_adaptivequiz\local\report\users_attempts\user_preferences
;
/**
* Provides methods for storing and fetching of the report user preferences from the storage.
*
* Operates on user session as well to avoid unnecessary database queries.
*
* @package mod_adaptivequiz
* @copyright 2022 Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final
class
user_preferences_repository
{
/**
* @var string Name of the preference.
*/
private
const
PREFERENCE_NAME
=
'adaptivequiz_users_attempts_report'
;
/**
* Stores the given preferences object in available storages.
*
* @param user_preferences $prefs
*/
public
static
function
save
(
user_preferences
$prefs
):
void
{
global
$SESSION
;
$prefsarr
=
$prefs
->
as_array
();
$SESSION
->
flextableextra
[
self
::
PREFERENCE_NAME
]
=
$prefsarr
;
set_user_preference
(
self
::
PREFERENCE_NAME
,
json_encode
(
$prefsarr
));
}
/**
* Instantiates user preferences from available storages or default.
*
* @return user_preferences
*/
public
static
function
get
():
user_preferences
{
global
$SESSION
;
if
(
!
empty
(
$SESSION
->
flextableextra
[
self
::
PREFERENCE_NAME
]))
{
return
user_preferences
::
from_array
(
$SESSION
->
flextableextra
[
self
::
PREFERENCE_NAME
]);
}
if
(
!
$json
=
get_user_preferences
(
self
::
PREFERENCE_NAME
))
{
return
user_preferences
::
defaults
();
}
return
user_preferences
::
from_array
(
json_decode
(
$json
,
true
));
}
}
classes/local/report/users_attempts/users_attempts_table.php
0 → 100644
View file @
25215446
<?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/>.
/**
* A class to display a table with users either with attempts or without them.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\report\users_attempts
;
use
coding_exception
;
use
context
;
use
dml_exception
;
use
html_writer
;
use
mod_adaptivequiz\local\report\questions_difficulty_range
;
use
mod_adaptivequiz\local\report\users_attempts
\
filter\filter
;
use
mod_adaptivequiz\local\report\users_attempts\sql\sql_resolver
;
use
mod_adaptivequiz_renderer
;
use
moodle_exception
;
use
moodle_url
;
use
stdClass
;
use
table_sql
;
final
class
users_attempts_table
extends
table_sql
{
private
const
UNIQUE_ID
=
'usersattemptstable'
;
/**
* @var mod_adaptivequiz_renderer $renderer
*/
private
$renderer
;
/**
* @var int $cmid
*/
private
$cmid
;
/**
* @var questions_difficulty_range $questionsdifficultyrange
*/
private
$questionsdifficultyrange
;
/**
* @throws coding_exception
*/
public
function
__construct
(
mod_adaptivequiz_renderer
$renderer
,
int
$cmid
,
questions_difficulty_range
$questionsdifficultyrange
,
moodle_url
$baseurl
,
context
$context
,
filter
$filter
)
{
parent
::
__construct
(
self
::
UNIQUE_ID
);
$this
->
renderer
=
$renderer
;
$this
->
cmid
=
$cmid
;
$this
->
questionsdifficultyrange
=
$questionsdifficultyrange
;
$this
->
init
(
$baseurl
,
$context
,
$filter
);
}
/**
* {@inheritdoc}
* @throws dml_exception
*/
public
function
query_db
(
$pagesize
,
$useinitialsbar
=
true
):
void
{
global
$DB
;
if
(
!
$this
->
is_downloading
())
{
if
(
$this
->
countsql
===
null
)
{
$this
->
countsql
=
'SELECT COUNT(1) FROM '
.
$this
->
sql
->
from
.
' WHERE '
.
$this
->
sql
->
where
;
$this
->
countparams
=
$this
->
sql
->
params
;
}
$grandtotal
=
$DB
->
count_records_sql
(
$this
->
countsql
,
$this
->
countparams
);
if
(
$useinitialsbar
&&
!
$this
->
is_downloading
())
{
$this
->
initialbars
(
true
);
}
list
(
$wsql
,
$wparams
)
=
$this
->
get_sql_where
();
if
(
$wsql
)
{
$this
->
countsql
.
=
' AND '
.
$wsql
;
$this
->
countparams
=
array_merge
(
$this
->
countparams
,
$wparams
);
$this
->
sql
->
where
.
=
' AND '
.
$wsql
;
$this
->
sql
->
params
=
array_merge
(
$this
->
sql
->
params
,
$wparams
);
$total
=
$DB
->
count_records_sql
(
$this
->
countsql
,
$this
->
countparams
);
}
else
{
$total
=
$grandtotal
;
}
$this
->
pagesize
(
$pagesize
,
$total
);
}
$sort
=
$this
->
get_sql_sort
();
if
(
$sort
)
{
$sort
=
"ORDER BY
$sort
"
;
}
$groupby
=
$this
->
sql
->
groupby
??
''
;
if
(
$groupby
)
{
$groupby
=
"GROUP BY
$groupby
"
;
}
$sql
=
"SELECT
{
$this
->
sql
->
fields
}
FROM
{
$this
->
sql
->
from
}
WHERE
{
$this
->
sql
->
where
}
{
$groupby
}
{
$sort
}
"
;
if
(
!
$this
->
is_downloading
())
{
$this
->
rawdata
=
$DB
->
get_records_sql
(
$sql
,
$this
->
sql
->
params
,
$this
->
get_page_start
(),
$this
->
get_page_size
());
}
else
{
$this
->
rawdata
=
$DB
->
get_records_sql
(
$sql
,
$this
->
sql
->
params
);
}
}
protected
function
col_attemptsnum
(
stdClass
$row
):
string
{
if
(
!
$row
->
attemptsnum
)
{
return
'-'
;
}
if
(
!
$this
->
is_downloading
())
{
return
html_writer
::
link
(
new
moodle_url
(
'/mod/adaptivequiz/viewattemptreport.php'
,
[
'userid'
=>
$row
->
id
,
'cmid'
=>
$this
->
cmid
]
),
$row
->
attemptsnum
);
}
return
$row
->
attemptsnum
;
}
/**
* @throws moodle_exception
*/
protected
function
col_measure
(
stdClass
$row
):
string
{
$formatmeasureparams
=
new
stdClass
();
$formatmeasureparams
->
measure
=
$row
->
measure
;
$formatmeasureparams
->
highestlevel
=
$this
->
questionsdifficultyrange
->
highest_level
();
$formatmeasureparams
->
lowestlevel
=
$this
->
questionsdifficultyrange
->
lowest_level
();
$measure
=
$this
->
renderer
->
format_measure
(
$formatmeasureparams
);
if
(
!
$row
->
attemptid
)
{
return
$measure
;
}
if
(
!
$this
->
is_downloading
())
{
return
html_writer
::
link
(
new
moodle_url
(
'/mod/adaptivequiz/reviewattempt.php'
,
[
'attempt'
=>
$row
->
attemptid
]),
$measure
);
}
return
$measure
;
}
protected
function
col_stderror
(
stdClass
$row
):
string
{
$rendered
=
$this
->
renderer
->
format_standard_error
(
$row
);
if
(
!
$this
->
is_downloading
())
{
return
$rendered
;
}
return
html_entity_decode
(
$rendered
,
ENT_QUOTES
,
'UTF-8'
);
}
/**
* @throws coding_exception
*/
protected
function
col_attempttimefinished
(
stdClass
$row
):
string
{
return
intval
(
$row
->
attempttimefinished
)
?
userdate
(
$row
->
attempttimefinished
)
:
get_string
(
'na'
,
'adaptivequiz'
);
}
/**
* A convenience method to call a bunch of init methods.
*
* @param moodle_url $baseurl
* @throws coding_exception
*/
private
function
init
(
moodle_url
$baseurl
,
context
$context
,
filter
$filter
):
void
{
$this
->
define_columns
([
'fullname'
,
'email'
,
'attemptsnum'
,
'measure'
,
'stderror'
,
'attempttimefinished'
,
]);
$this
->
define_headers
([
get_string
(
'fullname'
),
get_string
(
'email'
),
get_string
(
'numofattemptshdr'
,
'adaptivequiz'
),
get_string
(
'bestscore'
,
'adaptivequiz'
),
get_string
(
'bestscorestderror'
,
'adaptivequiz'
),
get_string
(
'attemptfinishedtimestamp'
,
'adaptivequiz'
),
]);
$this
->
define_baseurl
(
$baseurl
);
$this
->
set_attribute
(
'class'
,
$this
->
attributes
[
'class'
]
.
' usersattemptstable'
);
$this
->
set_content_alignment_in_columns
();
$this
->
collapsible
(
false
);
$this
->
sortable
(
true
,
'lastname'
);
$this
->
is_downloadable
(
true
);
$sqlandparams
=
sql_resolver
::
sql_and_params
(
$filter
,
$context
);
$this
->
set_sql
(
$sqlandparams
->
fields
(),
$sqlandparams
->
from
(),
$sqlandparams
->
where
(),
$sqlandparams
->
params
());
$this
->
set_group_by_sql
(
$sqlandparams
->
group_by
());
$this
->
set_count_sql
(
$sqlandparams
->
count_sql
(),
$sqlandparams
->
count_sql_params
());
}
private
function
set_group_by_sql
(
?string
$clause
):
void
{
$this
->
sql
->
groupby
=
$clause
;
}
private
function
set_content_alignment_in_columns
():
void
{
$this
->
column_class
[
'attemptsnum'
]
.
=
' text-center'
;
$this
->
column_class
[
'measure'
]
.
=
' text-center'
;
$this
->
column_class
[
'stderror'
]
.
=
' text-center'
;
}
}
classes/local/repository/questions_number_per_difficulty.php
0 → 100644
View file @
25215446
<?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/>.
/**
* The class represents questions number per each difficulty, this is what
* {@link questions_repository::count_questions_number_per_difficulty()} returns.
* The purpose of this class is keeping the related pieces of data together, as the client code normally requires both
* difficulty level and number of questions for this difficulty set to perform its task.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\repository
;
final
class
questions_number_per_difficulty
{
/**
* @var int $difficulty
*/
private
$difficulty
;
/**
* @var int $questionsnumber
*/
private
$questionsnumber
;
public
function
__construct
(
int
$difficulty
,
int
$questionsnumber
)
{
$this
->
difficulty
=
$difficulty
;
$this
->
questionsnumber
=
$questionsnumber
;
}
public
function
difficulty
():
int
{
return
$this
->
difficulty
;
}
public
function
questions_number
():
int
{
return
$this
->
questionsnumber
;
}
}
classes/local/repository/questions_repository.php
0 → 100644
View file @
25215446
<?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/>.
/**
* A class to wrap all database queries which are specific to questions and their related data. Normally should contain
* only static methods to call.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\repository
;
use
coding_exception
;
use
core_question\local\bank\question_version_status
;
use
core_tag_tag
;
use
dml_exception
;
use
question_finder
;
use
stdClass
;
final
class
questions_repository
{
/**
* Counts all questions in the pool tagged as 'adaptive' with a certain difficulty level.
*
* @param int[] $qcategoryidlist A list of id of questions categories.
* @param int $level Question difficulty which is contained in the question's tag.
*/
public
static
function
count_adaptive_questions_in_pool_with_level
(
array
$qcategoryidlist
,
int
$level
):
int
{
if
(
!
$raw
=
question_finder
::
get_instance
()
->
get_questions_from_categories
(
$qcategoryidlist
,
''
))
{
return
0
;
}
$questionstags
=
core_tag_tag
::
get_items_tags
(
'core_question'
,
'question'
,
array_keys
(
$raw
));
// Filter 'non-adaptive' and level mismatching tags out.
$questionstags
=
array_map
(
function
(
array
$tags
)
use
(
$level
)
{
return
array_filter
(
$tags
,
function
(
core_tag_tag
$tag
)
use
(
$level
)
{
return
substr
(
$tag
->
name
,
strlen
(
ADAPTIVEQUIZ_QUESTION_TAG
))
===
(
string
)
$level
;
});
},
$questionstags
);
// Filter empty tags arrays out.
$questionstags
=
array_filter
(
$questionstags
,
function
(
array
$tags
)
{
return
!
empty
(
$tags
);
});
return
count
(
$questionstags
);
}
/**
* @param int[] $tagidlist
* @param int[] $categoryidlist
* @return questions_number_per_difficulty[]
* @throws coding_exception
* @throws dml_exception
*/
public
static
function
count_questions_number_per_difficulty
(
array
$tagidlist
,
array
$categoryidlist
):
array
{
global
$DB
;
if
(
empty
(
$tagidlist
)
||
empty
(
$categoryidlist
))
{
return
[];
}
list
(
$tagidlistsql
,
$tagidlistparam
)
=
$DB
->
get_in_or_equal
(
$tagidlist
);
list
(
$categoryidlistsql
,
$categoryidlistparam
)
=
$DB
->
get_in_or_equal
(
$categoryidlist
);
$difficultyselect
=
$DB
->
sql_substr
(
't.name'
,
strlen
(
ADAPTIVEQUIZ_QUESTION_TAG
)
+
1
);
$sql
=
"SELECT
{
$difficultyselect
}
AS difficultylevel, COUNT(*) AS questionsnumber
FROM
{
tag
}
t
JOIN
{
tag_instance
}
ti ON t.id = ti.tagid
JOIN
{
question
}
q ON q.id = ti.itemid
JOIN
{
question_versions
}
qv ON qv.questionid = q.id
JOIN (
SELECT questionbankentryid, MAX(version)
FROM
{
question_versions
}
WHERE status = ?
GROUP BY questionbankentryid
) questionlatestversion ON questionlatestversion.questionbankentryid = qv.questionbankentryid
JOIN
{
question_bank_entries
}
qbe ON qbe.id = questionlatestversion.questionbankentryid
WHERE ti.itemtype = ?
AND ti.tagid
{
$tagidlistsql
}
AND qbe.questioncategoryid
{
$categoryidlistsql
}
GROUP BY t.name"
;
$params
=
array_merge
([
question_version_status
::
QUESTION_STATUS_READY
,
'question'
],
$tagidlistparam
,
$categoryidlistparam
);
$records
=
$DB
->
get_records_sql
(
$sql
,
$params
);
if
(
empty
(
$records
))
{
return
[];
}
$return
=
[];
foreach
(
$records
as
$record
)
{
$return
[]
=
new
questions_number_per_difficulty
(
$record
->
difficultylevel
,
$record
->
questionsnumber
);
}
return
$return
;
}
/**
* @param int[] $tagidlist
* @param int[] $categoryidlist
* @param int[] $excludequestionidlist
* @return stdClass[] A list of records from {question} table, the fields are id, name.
* @throws coding_exception
* @throws dml_exception
*/
public
static
function
find_questions_with_tags
(
array
$tagidlist
,
array
$categoryidlist
,
array
$excludequestionidlist
):
array
{
global
$DB
;
if
(
empty
(
$tagidlist
)
||
empty
(
$categoryidlist
))
{
return
[];
}
$params
=
[];
list
(
$tagswhere
,
$tempparam
)
=
$DB
->
get_in_or_equal
(
$tagidlist
,
SQL_PARAMS_NAMED
,
'tagids'
);
$params
+=
$tempparam
;
list
(
$categorywhere
,
$tempparam
)
=
$DB
->
get_in_or_equal
(
$categoryidlist
,
SQL_PARAMS_NAMED
,
'qcatids'
);
$params
+=
$tempparam
;
$excludequestionsclause
=
''
;
if
(
!
empty
(
$excludequestionidlist
))
{
list
(
$excludequestionssql
,
$tempparam
)
=
$DB
->
get_in_or_equal
(
$excludequestionidlist
,
SQL_PARAMS_NAMED
,
'excqids'
,
false
);
$excludequestionsclause
=
"AND q.id
{
$excludequestionssql
}
"
;
$params
+=
$tempparam
;
}
$sql
=
"SELECT q.id, q.name
FROM
{
question
}
q
JOIN
{
tag_instance
}
ti ON q.id = ti.itemid
JOIN
{
question_versions
}
qv ON qv.questionid = q.id
JOIN
{
question_bank_entries
}
qbe ON qbe.id = qv.questionbankentryid
JOIN (
SELECT qv.questionid
FROM
{
question_versions
}
qv
JOIN (
SELECT questionbankentryid, MAX(version) latestversion
FROM
{
question_versions
}
WHERE status = :questionstatus
GROUP BY questionbankentryid
) qbelv ON qv.version = qbelv.latestversion AND qv.questionbankentryid = qbelv.questionbankentryid
) qlv ON q.id = qlv.questionid
WHERE ti.itemtype = :itemtype
AND ti.tagid
{
$tagswhere
}
AND qbe.questioncategoryid
{
$categorywhere
}
{
$excludequestionsclause
}
ORDER BY q.id ASC"
;
$params
+=
[
'questionstatus'
=>
question_version_status
::
QUESTION_STATUS_READY
,
'itemtype'
=>
'question'
];
if
(
!
$records
=
$DB
->
get_records_sql
(
$sql
,
$params
))
{
return
[];
}
return
$records
;
}
}
classes/local/repository/tags_repository.php
0 → 100644
View file @
25215446
<?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/>.
/**
* A class to wrap all database queries which are specific to tags and their related data. Normally should contain
* only static methods to call.
*
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
mod_adaptivequiz\local\repository
;
use
coding_exception
;
use
dml_exception
;
use
stdClass
;
final
class
tags_repository
{
/**
* @param string[] $tagnames
* @return array Map of question difficulty level and tag id, same as what
* {@link moodle_database::get_records_menu()} would return.
* @throws dml_exception
* @throws coding_exception
*/
public
static
function
get_question_level_to_tag_id_mapping_by_tag_names
(
array
$tagnames
):
array
{
global
$DB
;
list
(
$tagnameselect
,
$tagnameparams
)
=
$DB
->
get_in_or_equal
(
$tagnames
);
$sql
=
'SELECT t.id, '
.
$DB
->
sql_substr
(
't.name'
,
strlen
(
ADAPTIVEQUIZ_QUESTION_TAG
)
+
1
)
.
' AS level
FROM {tag} t
JOIN {tag_instance} ti ON t.id = ti.tagid AND ti.itemtype = ?
WHERE t.name '
.
$tagnameselect
.
'
GROUP BY t.id'
;
$params
=
array_merge
([
'question'
],
$tagnameparams
);
if
(
!
$records
=
$DB
->
get_records_sql
(
$sql
,
$params
))
{
return
[];
}
return
array_flip
(
array_map
(
function
(
stdClass
$record
):
int
{
return
$record
->
level
;
},
$records
)
);
}
/**
* @param string[] Array of tag names.
* @return int[] Tag id list.
* @throws dml_exception
* @throws coding_exception
*/
public
static
function
get_tag_id_list_by_tag_names
(
array
$tagnames
):
array
{
global
$DB
;
list
(
$tagnameselect
,
$tagnameparams
)
=
$DB
->
get_in_or_equal
(
$tagnames
);
$sql
=
'SELECT t.id
FROM {tag} t
JOIN {tag_instance} ti ON t.id = ti.tagid AND ti.itemtype = ?
WHERE t.name '
.
$tagnameselect
.
'
GROUP BY t.id
ORDER BY t.id'
;
$params
=
array_merge
([
'question'
],
$tagnameparams
);
return
(
$fieldset
=
$DB
->
get_fieldset_sql
(
$sql
,
$params
))
?
$fieldset
:
[];
}
}
Prev
1
2
3
4
5
6
7
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment