mirror of
https://github.com/moodle/moodle.git
synced 2025-08-06 01:16:44 +02:00
MDL-29538 core_condition: initial commit to add support for making things conditional on user profile fields
This commit is contained in:
parent
6be7840ce6
commit
76af15bb4c
11 changed files with 399 additions and 10 deletions
|
@ -408,7 +408,8 @@ abstract class condition_info_base {
|
|||
$this->gotdata = true;
|
||||
|
||||
// Missing extra data
|
||||
if (!isset($item->conditionsgrade) || !isset($item->conditionscompletion)) {
|
||||
if (!isset($item->conditionsgrade) || !isset($item->conditionscompletion)
|
||||
|| !isset($item->conditionsfield)) {
|
||||
if ($expectingmissing<CONDITION_MISSING_EXTRATABLE) {
|
||||
debugging('Performance warning: condition_info constructor is ' .
|
||||
'faster if you pass in a $item from get_fast_modinfo or ' .
|
||||
|
@ -446,9 +447,11 @@ abstract class condition_info_base {
|
|||
}
|
||||
|
||||
// Does nothing if the variables are already present
|
||||
if (!isset($item->conditionsgrade) || !isset($item->conditionscompletion)) {
|
||||
if (!isset($item->conditionsgrade) || !isset($item->conditionscompletion)
|
||||
|| !isset($item->conditionsfield)) {
|
||||
$item->conditionsgrade = array();
|
||||
$item->conditionscompletion = array();
|
||||
$item->conditionsfield = array();
|
||||
|
||||
$conditions = $DB->get_records_sql('
|
||||
SELECT
|
||||
|
@ -470,6 +473,32 @@ abstract class condition_info_base {
|
|||
$item->conditionsgrade[$condition->gradeitemid] = $minmax;
|
||||
}
|
||||
}
|
||||
// For user fields
|
||||
$conditions = $DB->get_records_sql($sql="
|
||||
SELECT
|
||||
cma.*
|
||||
FROM
|
||||
{course_modules_availability_field} cma
|
||||
WHERE
|
||||
coursemoduleid=?",array($cm->id));
|
||||
foreach ($conditions as $condition) {
|
||||
// If the condition field is numeric, check
|
||||
// the user profile field is available
|
||||
if (is_numeric($condition->field)) {
|
||||
if ($field = $DB->get_record('user_info_field', array('id'=>$condition->field))) {
|
||||
$fieldname = $field->name;
|
||||
} else {
|
||||
$fieldname = '!missing';
|
||||
}
|
||||
} else {
|
||||
$fieldname = $condition->field;
|
||||
}
|
||||
$details = new stdClass;
|
||||
$details->fieldname = $fieldname;
|
||||
$details->operator = $condition->operator;
|
||||
$details->value = $condition->value;
|
||||
$cm->conditionsfield[$condition->field] = $details;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -503,6 +532,63 @@ abstract class condition_info_base {
|
|||
return $this->item;
|
||||
}
|
||||
|
||||
/**
|
||||
* The operators that provide the relationship
|
||||
* between a field and a value.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_condition_user_field_operators() {
|
||||
return array(
|
||||
0 => get_string('contains', 'filters'),
|
||||
1 => get_string('doesnotcontain','filters'),
|
||||
2 => get_string('isequalto','filters'),
|
||||
3 => get_string('startswith','filters'),
|
||||
4 => get_string('endswith','filters'),
|
||||
5 => get_string('isempty','filters')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The user fields we can compare
|
||||
*
|
||||
* @global $DB
|
||||
* @return array
|
||||
*/
|
||||
public function get_condition_user_fields() {
|
||||
global $DB;
|
||||
|
||||
$userfields = array(
|
||||
'firstname' => get_string('firstname'),
|
||||
'lastname' => get_string('lastname'),
|
||||
'email' => get_string('email'),
|
||||
'city' => get_string('city'),
|
||||
'country' => get_string('country'),
|
||||
'interests' => get_string('interests'),
|
||||
'url' => get_string('webpage'),
|
||||
'icq' => get_string('icqnumber'),
|
||||
'skype' => get_string('skypeid'),
|
||||
'aim' => get_string('aimid'),
|
||||
'yahoo' => get_string('yahooid'),
|
||||
'msn' => get_string('msnid'),
|
||||
'idnumber' => get_string('idnumber'),
|
||||
'institution' => get_string('institution'),
|
||||
'department' => get_string('department'),
|
||||
'phone' => get_string('phone'),
|
||||
'phone2' => get_string('phone2'),
|
||||
'address' => get_string('address')
|
||||
);
|
||||
|
||||
// Go through the custom profile fields now
|
||||
if ($user_info_fields = $DB->get_records('user_info_field')) {
|
||||
foreach ($user_info_fields as $field) {
|
||||
$userfields[$field->id] = $field->name;
|
||||
}
|
||||
}
|
||||
|
||||
return $userfields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds to the database a condition based on completion of another module.
|
||||
*
|
||||
|
@ -522,6 +608,29 @@ abstract class condition_info_base {
|
|||
$this->item->conditionscompletion[$cmid] = $requiredcompletion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds user fields condition
|
||||
*
|
||||
* @global object
|
||||
* @param mixed $field numeric if it is a user profile field, character
|
||||
* if it is a column in the user table
|
||||
* @param int $operator specifies the relationship between field and value
|
||||
* @param char $value the value of the field
|
||||
*/
|
||||
public function add_user_field_condition($field, $operator, $value) {
|
||||
// Add to DB
|
||||
global $DB;
|
||||
$DB->insert_record('course_modules_availability_field',
|
||||
(object)array('coursemoduleid'=>$this->cm->id,
|
||||
'field'=>$field,'operator'=>$operator,
|
||||
'value'=>$value),
|
||||
false);
|
||||
|
||||
// Store in memory too
|
||||
$this->cm->conditionsfield[$field] = (object)array(
|
||||
'field'=>$field,'operator'=>$operator,'value'=>$value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds to the database a condition based on the value of a grade item.
|
||||
*
|
||||
|
@ -565,11 +674,15 @@ abstract class condition_info_base {
|
|||
public function wipe_conditions() {
|
||||
// Wipe from DB
|
||||
global $DB;
|
||||
|
||||
$DB->delete_records($this->availtable, array($this->idfieldname => $this->item->id));
|
||||
$DB->delete_records('course_modules_availability_field',
|
||||
array('coursemoduleid'=>$this->cm->id));
|
||||
|
||||
// And from memory
|
||||
$this->item->conditionsgrade = array();
|
||||
$this->item->conditionscompletion = array();
|
||||
$this->item->conditionsfield = array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -630,6 +743,19 @@ abstract class condition_info_base {
|
|||
}
|
||||
}
|
||||
|
||||
// User field conditions
|
||||
if (count($this->cm->conditionsfield)>0) {
|
||||
// Need the array of operators
|
||||
$arroperators = $this->get_condition_user_field_operators();
|
||||
foreach ($this->cm->conditionsfield as $field=>$details) {
|
||||
$a = new stdclass;
|
||||
$a->field = $details->fieldname;
|
||||
$a->operator = $arroperators[$details->operator];
|
||||
$a->value = $details->value;
|
||||
$information .= get_string('requires_user_field_restriction', 'condition', $a);
|
||||
}
|
||||
}
|
||||
|
||||
// The date logic is complicated. The intention of this logic is:
|
||||
// 1) display date without time where possible (whenever the date is
|
||||
// midnight)
|
||||
|
@ -822,6 +948,67 @@ abstract class condition_info_base {
|
|||
}
|
||||
}
|
||||
|
||||
// Check if user field condition
|
||||
if (count($this->cm->conditionsfield)>0) {
|
||||
$arroperators = $this->get_condition_user_field_operators();
|
||||
foreach ($this->cm->conditionsfield as $field=>$details) {
|
||||
// Need the array of operators
|
||||
$operator = $details->operator;
|
||||
$value = $details->value;
|
||||
$uservalue = $this->get_cached_user_profile_field($userid, $field, $grabthelot);
|
||||
// Assume that it passed
|
||||
$fieldconditionmet = true;
|
||||
switch($operator) {
|
||||
case 0: // contains
|
||||
$pos = strpos($uservalue, $value);
|
||||
if ($pos === false) {
|
||||
$fieldconditionmet = false;
|
||||
}
|
||||
break;
|
||||
case 1: // does not contain
|
||||
if (!empty($value)) {
|
||||
$pos = strpos($uservalue, $value);
|
||||
if ($pos !== false) {
|
||||
$fieldconditionmet = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 2: // equal to
|
||||
if ($value !== $uservalue) {
|
||||
$fieldconditionmet = false;
|
||||
}
|
||||
break;
|
||||
case 3: // starts with
|
||||
$length = strlen($value);
|
||||
if ((substr($uservalue, 0, $length) !== $value)) {
|
||||
$fieldconditionmet = false;
|
||||
}
|
||||
break;
|
||||
case 4: // ends with
|
||||
$length = strlen($value);
|
||||
$start = $length * -1; //negative
|
||||
if (substr($uservalue, $start) !== $value) {
|
||||
$fieldconditionmet = false;
|
||||
}
|
||||
break;
|
||||
case 5: // empty
|
||||
if (!empty($uservalue)) {
|
||||
$fieldconditionmet = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!$fieldconditionmet) {
|
||||
// Set available to false
|
||||
$available = false;
|
||||
$a = new stdClass();
|
||||
$a->field = $details->fieldname;
|
||||
$a->operator = $arroperators[$operator];
|
||||
$a->value = $details->value;
|
||||
$information .= get_string('requires_user_field_restriction', 'condition', $a).' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test dates
|
||||
if ($this->item->availablefrom) {
|
||||
if (time() < $this->item->availablefrom) {
|
||||
|
@ -983,6 +1170,100 @@ abstract class condition_info_base {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value for a user's profile field
|
||||
*
|
||||
* @global object
|
||||
* @global object
|
||||
* @global object
|
||||
* @param int $userid Set if requesting grade for a different user (does
|
||||
* not use cache)
|
||||
* @param int $fieldid the user profile field id
|
||||
* @param bool $grabthelot If true, grabs all the user profile fields for
|
||||
* current user on this course, so that later ones come from cache
|
||||
* @return string the user value
|
||||
*/
|
||||
private function get_cached_user_profile_field($userid, $fieldid, $grabthelot) {
|
||||
global $USER, $DB, $SESSION;
|
||||
// Check if the field is a custom profile field
|
||||
$iscustomprofilefield = is_numeric($fieldid) ? true : false;
|
||||
if ($userid==0 || $userid==$USER->id) {
|
||||
if ($iscustomprofilefield) {
|
||||
// For current user, go via cache in session
|
||||
if (empty($SESSION->userfieldcache) || $SESSION->userfieldcacheuserid!=$USER->id) {
|
||||
$SESSION->userfieldcache = array();
|
||||
$SESSION->userfieldcacheuserid = $USER->id;
|
||||
}
|
||||
if (!array_key_exists($fieldid, $SESSION->userfieldcache)) {
|
||||
if ($grabthelot) {
|
||||
// Get all custom profile field values for user
|
||||
$rs = $DB->get_recordset_sql("
|
||||
SELECT
|
||||
uf.id, ud.data
|
||||
FROM
|
||||
{user_info_field} uf
|
||||
LEFT JOIN {user_info_data} ud
|
||||
ON uf.id = ud.fieldid
|
||||
WHERE
|
||||
ud.userid=?", array($USER->id));
|
||||
foreach ($rs as $record) {
|
||||
$SESSION->userfieldcache[$record->id] = $record->data;
|
||||
}
|
||||
$rs->close();
|
||||
// And if it's still not set, well it doesn't exist (eg
|
||||
// the user may have no entry for the profile field)
|
||||
if (!array_key_exists($fieldid, $SESSION->userfieldcache)) {
|
||||
$SESSION->userfieldcache[$fieldid] = false;
|
||||
}
|
||||
} else {
|
||||
// Just get specified user field
|
||||
if ($record = $DB->get_record_sql("
|
||||
SELECT
|
||||
ud.data
|
||||
FROM
|
||||
{user_info_data} ud
|
||||
INNER JOIN {user_info_field} uf
|
||||
ON ud.fieldid = uf.id
|
||||
WHERE
|
||||
uf.id = ?
|
||||
AND
|
||||
ud.userid = ?", array($fieldid, $USER->id))) {
|
||||
$field = $record->data;
|
||||
} else {
|
||||
$field = false;
|
||||
}
|
||||
$SESSION->userfieldcache[$fieldid]=$field;
|
||||
}
|
||||
}
|
||||
return $SESSION->userfieldcache[$fieldid];
|
||||
} else {
|
||||
return $USER->$fieldid;
|
||||
}
|
||||
} else {
|
||||
if ($iscustomprofilefield) {
|
||||
if ($record = $DB->get_record_sql("
|
||||
SELECT
|
||||
ud.data
|
||||
FROM
|
||||
{user_info_data} ud
|
||||
INNER JOIN {user_info_field} uf
|
||||
ON ud.fieldid = uf.id
|
||||
WHERE
|
||||
uf.id = ?
|
||||
AND
|
||||
ud.userid = ?", array($fieldid, $userid))) {
|
||||
return $record->data;
|
||||
}
|
||||
} else {
|
||||
if ($user = $DB->get_record('user', array('id' => $userid), $fieldid)) {
|
||||
return $user->$fieldid;
|
||||
}
|
||||
}
|
||||
// If it reaches here, then no matches found
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For testing only. Wipes information cached in user session.
|
||||
*
|
||||
|
@ -992,6 +1273,7 @@ abstract class condition_info_base {
|
|||
global $SESSION;
|
||||
unset($SESSION->gradescorecache);
|
||||
unset($SESSION->gradescorecacheuserid);
|
||||
unset($SESSION->userfieldcache);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1023,6 +1305,13 @@ abstract class condition_info_base {
|
|||
unformat_float($record['conditiongrademin']), unformat_float($record['conditiongrademax']));
|
||||
}
|
||||
}
|
||||
foreach ($fromform->conditionfieldgroup as $record) {
|
||||
if($record['conditionfield']) {
|
||||
$ci->add_user_field_condition($record['conditionfield'],
|
||||
$record['conditionfieldoperator'],
|
||||
$record['conditionfieldvalue']);
|
||||
}
|
||||
}
|
||||
if(isset ($fromform->conditioncompletiongroup)) {
|
||||
foreach($fromform->conditioncompletiongroup as $record) {
|
||||
if($record['conditionsourcecmid']) {
|
||||
|
|
|
@ -330,7 +330,7 @@
|
|||
<INDEX NAME="idnumber-course" UNIQUE="false" FIELDS="idnumber, course" COMMENT="non unique index (although programatically we are guarantying some sort of uniqueness both under this table and the grade_items one). TODO: We need a central store of module idnumbers in the future." PREVIOUS="instance"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="course_modules_availability" COMMENT="Table stores conditions that affect whether a module/activity is currently available to students or not." PREVIOUS="course_modules" NEXT="course_modules_completion">
|
||||
<TABLE NAME="course_modules_availability" COMMENT="Table stores conditions that affect whether a module/activity is currently available to students or not." PREVIOUS="course_modules" NEXT="course_modules_availability_field">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" NEXT="coursemoduleid"/>
|
||||
<FIELD NAME="coursemoduleid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="ID of the module whose availability is being restricted by this condition." PREVIOUS="id" NEXT="sourcecmid"/>
|
||||
|
@ -347,7 +347,20 @@
|
|||
<KEY NAME="gradeitemid" TYPE="foreign" FIELDS="gradeitemid" REFTABLE="grade_items" REFFIELDS="id" PREVIOUS="sourcecmid"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="course_modules_completion" COMMENT="Stores the completion state (completed or not completed, etc) of each user on each activity." PREVIOUS="course_modules_availability" NEXT="course_sections">
|
||||
<TABLE NAME="course_modules_availability_field" COMMENT="Table stores user field conditions that affect whether a module/activity is currently available to students or not." PREVIOUS="course_modules_availability" NEXT="course_modules_completion">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="coursemoduleid"/>
|
||||
<FIELD NAME="coursemoduleid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="ID of the module whose availability is being restricted by this condition." PREVIOUS="id" NEXT="field"/>
|
||||
<FIELD NAME="field" TYPE="char" LENGTH="50" NOTNULL="false" SEQUENCE="false" COMMENT="If this is numeric it is a user custom profile field, else it is a user field" PREVIOUS="coursemoduleid" NEXT="operator"/>
|
||||
<FIELD NAME="operator" TYPE="int" LENGTH="2" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="The integer that represents the operator, such as less than or equal to, between the field and the value" PREVIOUS="field" NEXT="value"/>
|
||||
<FIELD NAME="value" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="The required value of the field" PREVIOUS="operator"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="coursemoduleid"/>
|
||||
<KEY NAME="coursemoduleid" TYPE="foreign" FIELDS="coursemoduleid" REFTABLE="course_modules" REFFIELDS="id" PREVIOUS="primary"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="course_modules_completion" COMMENT="Stores the completion state (completed or not completed, etc) of each user on each activity." PREVIOUS="course_modules_availability_field" NEXT="course_sections">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" NEXT="coursemoduleid"/>
|
||||
<FIELD NAME="coursemoduleid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Activity that has been completed (or not)." PREVIOUS="id" NEXT="userid"/>
|
||||
|
@ -2875,4 +2888,4 @@
|
|||
</KEYS>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
</XMLDB>
|
||||
|
|
|
@ -586,6 +586,22 @@ function xmldb_main_upgrade($oldversion) {
|
|||
$table->add_key('sourcecmid', XMLDB_KEY_FOREIGN, array('sourcecmid'), 'course_modules', array('id'));
|
||||
$table->add_key('gradeitemid', XMLDB_KEY_FOREIGN, array('gradeitemid'), 'grade_items', array('id'));
|
||||
|
||||
// Define table course_modules_availability to be created
|
||||
$table = new xmldb_table('course_modules_avail_fields');
|
||||
|
||||
// Adding fields to table course_modules_avail_fields
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('coursemoduleid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('userfield', XMLDB_TYPE_CHAR, '50', null, null, null, null);
|
||||
$table->add_field('customfieldid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null);
|
||||
$table->add_field('operator', XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('value', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
|
||||
|
||||
// Adding keys to table course_modules_avail_fields
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
$table->add_key('coursemoduleid', XMLDB_KEY_FOREIGN, array('coursemoduleid'), 'course_modules', array('id'));
|
||||
|
||||
// Conditionally launch create table for course_modules_availability
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
|
|
@ -596,6 +596,12 @@ class cm_info extends stdClass {
|
|||
*/
|
||||
public $conditionsgrade;
|
||||
|
||||
/**
|
||||
* Availability conditions for this course-module based on user fields
|
||||
* @var array
|
||||
*/
|
||||
public $conditionsfield;
|
||||
|
||||
/**
|
||||
* Plural name of module type, e.g. 'Forums' - from lang file
|
||||
* @deprecated Do not use this value (you can obtain it by calling get_string instead); it
|
||||
|
@ -996,6 +1002,8 @@ class cm_info extends stdClass {
|
|||
? $mod->conditionscompletion : array();
|
||||
$this->conditionsgrade = isset($mod->conditionsgrade)
|
||||
? $mod->conditionsgrade : array();
|
||||
$this->conditionsfield = isset($mod->conditionsfield)
|
||||
? $mod->conditionsfield : array();
|
||||
|
||||
// Get module plural name.
|
||||
// TODO This was a very old performance hack and should now be removed as the information
|
||||
|
|
|
@ -4584,6 +4584,9 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
|
|||
$DB->delete_records_select('course_modules_availability',
|
||||
'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
|
||||
array($courseid));
|
||||
$DB->delete_records_select('course_modules_availability_field',
|
||||
'coursemoduleid IN (SELECT id from {course_modules} WHERE course=?)',
|
||||
array($courseid));
|
||||
|
||||
// Remove course-module data.
|
||||
$cms = $DB->get_records('course_modules', array('course'=>$course->id));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue