diff --git a/lib/myprofilelib.php b/lib/myprofilelib.php index 54ea27c5cfc..2a95a140fbf 100644 --- a/lib/myprofilelib.php +++ b/lib/myprofilelib.php @@ -367,19 +367,16 @@ function core_myprofile_navigation(core_user\output\myprofile\tree $tree, $user, $tree->add_node($node); } - if ($categories = $DB->get_records('user_info_category', null, 'sortorder ASC')) { - foreach ($categories as $category) { - if ($fields = $DB->get_records('user_info_field', array('categoryid' => $category->id), 'sortorder ASC')) { - foreach ($fields as $field) { - require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); - $newfield = 'profile_field_'.$field->datatype; - $formfield = new $newfield($field->id, $user->id); - if ($formfield->is_visible() and !$formfield->is_empty()) { - $node = new core_user\output\myprofile\node('contact', 'custom_field_' . $formfield->field->shortname, - format_string($formfield->field->name), null, null, $formfield->display_data()); - $tree->add_node($node); - } - } + $categories = profile_get_user_fields_with_data_by_category($user->id); + foreach ($categories as $categoryid => $fields) { + foreach ($fields as $field) { + require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); + $newfield = 'profile_field_'.$field->datatype; + $formfield = new $newfield($field->id, $user->id, $field); + if ($formfield->is_visible() and !$formfield->is_empty()) { + $node = new core_user\output\myprofile\node('contact', 'custom_field_' . $formfield->field->shortname, + format_string($formfield->field->name), null, null, $formfield->display_data()); + $tree->add_node($node); } } } diff --git a/user/lib.php b/user/lib.php index 5074fc2f796..e9bbef3c757 100644 --- a/user/lib.php +++ b/user/lib.php @@ -331,34 +331,31 @@ function user_get_user_details($user, $course = null, array $userfields = array( $userdetails['fullname'] = fullname($user); if (in_array('customfields', $userfields)) { - $fields = $DB->get_recordset_sql("SELECT f.* - FROM {user_info_field} f - JOIN {user_info_category} c - ON f.categoryid=c.id - ORDER BY c.sortorder ASC, f.sortorder ASC"); + $categories = profile_get_user_fields_with_data_by_category($user->id); $userdetails['customfields'] = array(); - foreach ($fields as $field) { - require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); - $newfield = 'profile_field_'.$field->datatype; - $formfield = new $newfield($field->id, $user->id); - if ($formfield->is_visible() and !$formfield->is_empty()) { + foreach ($categories as $categoryid => $fields) { + foreach ($fields as $field) { + require_once($CFG->dirroot . '/user/profile/field/' . $field->datatype . '/field.class.php'); + $newfield = 'profile_field_' . $field->datatype; + $formfield = new $newfield($field->id, $user->id, $field); + if ($formfield->is_visible() and !$formfield->is_empty()) { - // TODO: Part of MDL-50728, this conditional coding must be moved to - // proper profile fields API so they are self-contained. - // We only use display_data in fields that require text formatting. - if ($field->datatype == 'text' or $field->datatype == 'textarea') { - $fieldvalue = $formfield->display_data(); - } else { - // Cases: datetime, checkbox and menu. - $fieldvalue = $formfield->data; + // TODO: Part of MDL-50728, this conditional coding must be moved to + // proper profile fields API so they are self-contained. + // We only use display_data in fields that require text formatting. + if ($field->datatype == 'text' or $field->datatype == 'textarea') { + $fieldvalue = $formfield->display_data(); + } else { + // Cases: datetime, checkbox and menu. + $fieldvalue = $formfield->data; + } + + $userdetails['customfields'][] = + array('name' => $formfield->field->name, 'value' => $fieldvalue, + 'type' => $field->datatype, 'shortname' => $formfield->field->shortname); } - - $userdetails['customfields'][] = - array('name' => $formfield->field->name, 'value' => $fieldvalue, - 'type' => $field->datatype, 'shortname' => $formfield->field->shortname); } } - $fields->close(); // Unset customfields if it's empty. if (empty($userdetails['customfields'])) { unset($userdetails['customfields']); diff --git a/user/profile/field/checkbox/field.class.php b/user/profile/field/checkbox/field.class.php index 7e08b368277..d6d9943157c 100644 --- a/user/profile/field/checkbox/field.class.php +++ b/user/profile/field/checkbox/field.class.php @@ -30,39 +30,6 @@ */ class profile_field_checkbox extends profile_field_base { - /** - * Constructor method. - * Pulls out the options for the checkbox from the database and sets the - * the corresponding key for the data if it exists - * - * @param int $fieldid - * @param int $userid - */ - public function __construct($fieldid=0, $userid=0) { - global $DB; - // First call parent constructor. - parent::__construct($fieldid, $userid); - - if (!empty($this->field)) { - $datafield = $DB->get_field('user_info_data', 'data', array('userid' => $this->userid, 'fieldid' => $this->fieldid)); - if ($datafield !== false) { - $this->data = $datafield; - } else { - $this->data = $this->field->defaultdata; - } - } - } - - /** - * Old syntax of class constructor. Deprecated in PHP7. - * - * @deprecated since Moodle 3.1 - */ - public function profile_field_checkbox($fieldid=0, $userid=0) { - debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); - self::__construct($fieldid, $userid); - } - /** * Add elements for editing the profile field value. * @param moodleform $mform diff --git a/user/profile/field/menu/field.class.php b/user/profile/field/menu/field.class.php index e662c490ba7..db63d099fd1 100644 --- a/user/profile/field/menu/field.class.php +++ b/user/profile/field/menu/field.class.php @@ -43,10 +43,11 @@ class profile_field_menu extends profile_field_base { * * @param int $fieldid * @param int $userid + * @param object $fielddata */ - public function __construct($fieldid = 0, $userid = 0) { + public function __construct($fieldid = 0, $userid = 0, $fielddata = null) { // First call parent constructor. - parent::__construct($fieldid, $userid); + parent::__construct($fieldid, $userid, $fielddata); // Param 1 for menu type is the options. if (isset($this->field->param1)) { @@ -78,9 +79,9 @@ class profile_field_menu extends profile_field_base { * * @deprecated since Moodle 3.1 */ - public function profile_field_menu($fieldid=0, $userid=0) { + public function profile_field_menu($fieldid=0, $userid=0, $fielddata=null) { debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); - self::__construct($fieldid, $userid); + self::__construct($fieldid, $userid, $fielddata); } /** diff --git a/user/profile/lib.php b/user/profile/lib.php index 36a63e20d34..f3a394973e0 100644 --- a/user/profile/lib.php +++ b/user/profile/lib.php @@ -60,13 +60,14 @@ class profile_field_base { * Constructor method. * @param int $fieldid id of the profile from the user_info_field table * @param int $userid id of the user for whom we are displaying data + * @param object $fielddata optional data for the field object. */ - public function __construct($fieldid=0, $userid=0) { + public function __construct($fieldid=0, $userid=0, $fielddata=null) { global $USER; $this->set_fieldid($fieldid); $this->set_userid($userid); - $this->load_data(); + $this->load_data($fielddata); } /** @@ -289,36 +290,67 @@ class profile_field_base { } /** - * Accessor method: Load the field record and user data associated with the - * object's fieldid and userid + * Accessor method: Load the field record and user data associated with the object's fieldid and userid. + * If $fielddata is provided, it must be an object structure as follows: + * - $fielddata->field The user_info_field record for this field. + * - $fielddata->data The data column from user_info_data for this user. + * - $fielddata->dataformat The dataformat column from user_info_data for this user. + * * @internal This method should not generally be overwritten by child classes. + * @param object $fielddata Optional data for the field object. */ - public function load_data() { + public function load_data($fielddata = null) { global $DB; - // Load the field object. - if (($this->fieldid == 0) or (!($field = $DB->get_record('user_info_field', array('id' => $this->fieldid))))) { - $this->field = null; - $this->inputname = ''; - } else { + if (($fielddata !== null) && ($this->validate_fielddata($fielddata))) { + // If the field data was passed in, use it and return. + // Note that $fielddata->data will be in both $this->field->data and $this->data, but since PHP copies objects + // by reference, this adds no extra memory strain. + $this->field = $fielddata; + $this->inputname = 'profile_field_'.$fielddata->shortname; + $this->data = $fielddata->data; + $this->dataformat = $fielddata->dataformat; + + } else if (($this->fieldid != 0) && ($field = $DB->get_record('user_info_field', ['id' => $this->fieldid]))) { + // If no data is passed in, and the field id is known, get the data from the database. $this->field = $field; $this->inputname = 'profile_field_'.$field->shortname; - } - - if (!empty($this->field)) { - $params = array('userid' => $this->userid, 'fieldid' => $this->fieldid); - if ($data = $DB->get_record('user_info_data', $params, 'data, dataformat')) { + // Check for instance data and include it if present. Otherwise set to default. + if ($data = $DB->get_record('user_info_data', ['userid' => $this->userid, 'fieldid' => $this->fieldid], + 'data, dataformat')) { $this->data = $data->data; $this->dataformat = $data->dataformat; } else { $this->data = $this->field->defaultdata; $this->dataformat = FORMAT_HTML; } + } else { + $this->field = null; + $this->inputname = ''; $this->data = null; + $this->dataformat = null; } } + /** + * Validate that fielddata parameter used by 'load_data' is structured properly. + * @param object $fielddata + * @return bool + * @throws coding_exception + */ + private function validate_fielddata($fielddata) { + $properties = ['id', 'shortname', 'name', 'datatype', 'description', 'descriptionformat', 'categoryid', 'sortorder', + 'required', 'locked', 'visible', 'forceunique', 'signup', 'defaultdata', 'defaultdataformat', 'param1', 'param2', + 'param3', 'param4', 'param5', 'data', 'dataformat']; + foreach ($properties as $property) { + if (!property_exists($fielddata, $property)) { + throw new \coding_exception('The \'' . $property . '\' property must be set.'); + } + } + return true; + } + /** * Check if the field data is visible to the current user * @internal This method should not generally be overwritten by child classes. @@ -411,20 +443,60 @@ class profile_field_base { } } +/** + * Returns an array of all custom field records with any defined data (or empty data), for the specified user id. + * @param int $userid + * @return array + */ +function profile_get_user_fields_with_data($userid) { + global $DB; + + // Join any user info data present with each user info field for the user object. + $sql = 'SELECT uif.*, uid.data, uid.dataformat '; + $sql .= 'FROM {user_info_field} uif '; + $sql .= 'LEFT JOIN {user_info_data} uid ON uif.id = uid.fieldid AND uid.userid = :userid '; + return $DB->get_records_sql($sql, ['userid' => $userid]); +} + +/** + * Returns an array of all custom field records with any defined data (or empty data), for the specified user id, by category. + * @param int $userid + * @return array + */ +function profile_get_user_fields_with_data_by_category($userid) { + global $DB; + + // Join any user info data present with each user info field for the user object. + $sql = 'SELECT uif.*, uic.name AS categoryname, uid.data, uid.dataformat '; + $sql .= 'FROM {user_info_field} uif '; + $sql .= 'INNER JOIN {user_info_category} uic ON uif.categoryid = uic.id '; + $sql .= 'LEFT JOIN {user_info_data} uid ON uif.id = uid.fieldid AND uid.userid = :userid '; + $sql .= 'ORDER BY uic.sortorder ASC, uif.sortorder ASC '; + $fields = $DB->get_records_sql($sql, ['userid' => $userid]); + $catid = 0; + $data = []; + foreach ($fields as $field) { + if ($field->categoryid != $catid) { + $catid = $field->categoryid; + } + $data[$catid][] = $field; + } + return $data; +} + /** * Loads user profile field data into the user object. * @param stdClass $user */ function profile_load_data($user) { - global $CFG, $DB; + global $CFG; - if ($fields = $DB->get_records('user_info_field')) { - foreach ($fields as $field) { - require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); - $newfield = 'profile_field_'.$field->datatype; - $formfield = new $newfield($field->id, $user->id); - $formfield->edit_load_user_data($user); - } + $fields = profile_get_user_fields_with_data($user->id); + foreach ($fields as $field) { + require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); + $newfield = 'profile_field_'.$field->datatype; + $formfield = new $newfield($field->id, $user->id, $field); + $formfield->edit_load_user_data($user); } } @@ -440,28 +512,24 @@ function profile_definition($mform, $userid = 0) { // If user is "admin" fields are displayed regardless. $update = has_capability('moodle/user:update', context_system::instance()); - if ($categories = $DB->get_records('user_info_category', null, 'sortorder ASC')) { - foreach ($categories as $category) { - if ($fields = $DB->get_records('user_info_field', array('categoryid' => $category->id), 'sortorder ASC')) { + $categories = profile_get_user_fields_with_data_by_category($userid); + foreach ($categories as $categoryid => $fields) { + // Check first if *any* fields will be displayed. + $display = false; + foreach ($fields as $field) { + if ($field->visible != PROFILE_VISIBLE_NONE) { + $display = true; + } + } - // Check first if *any* fields will be displayed. - $display = false; - foreach ($fields as $field) { - if ($field->visible != PROFILE_VISIBLE_NONE) { - $display = true; - } - } - - // Display the header and the fields. - if ($display or $update) { - $mform->addElement('header', 'category_'.$category->id, format_string($category->name)); - foreach ($fields as $field) { - require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); - $newfield = 'profile_field_'.$field->datatype; - $formfield = new $newfield($field->id, $userid); - $formfield->edit_field($mform); - } - } + // Display the header and the fields. + if ($display or $update) { + $mform->addElement('header', 'category_'.$categoryid, format_string($field->categoryname)); + foreach ($fields as $field) { + require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); + $newfield = 'profile_field_'.$field->datatype; + $formfield = new $newfield($field->id, $userid, $field); + $formfield->edit_field($mform); } } } @@ -473,17 +541,16 @@ function profile_definition($mform, $userid = 0) { * @param int $userid */ function profile_definition_after_data($mform, $userid) { - global $CFG, $DB; + global $CFG; $userid = ($userid < 0) ? 0 : (int)$userid; - if ($fields = $DB->get_records('user_info_field')) { - foreach ($fields as $field) { - require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); - $newfield = 'profile_field_'.$field->datatype; - $formfield = new $newfield($field->id, $userid); - $formfield->edit_after_data($mform); - } + $fields = profile_get_user_fields_with_data($userid); + foreach ($fields as $field) { + require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); + $newfield = 'profile_field_'.$field->datatype; + $formfield = new $newfield($field->id, $userid, $field); + $formfield->edit_after_data($mform); } } @@ -494,16 +561,15 @@ function profile_definition_after_data($mform, $userid) { * @return array */ function profile_validation($usernew, $files) { - global $CFG, $DB; + global $CFG; $err = array(); - if ($fields = $DB->get_records('user_info_field')) { - foreach ($fields as $field) { - require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); - $newfield = 'profile_field_'.$field->datatype; - $formfield = new $newfield($field->id, $usernew->id); - $err += $formfield->edit_validate_field($usernew, $files); - } + $fields = profile_get_user_fields_with_data($usernew->id); + foreach ($fields as $field) { + require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); + $newfield = 'profile_field_'.$field->datatype; + $formfield = new $newfield($field->id, $usernew->id, $field); + $err += $formfield->edit_validate_field($usernew, $files); } return $err; } @@ -513,15 +579,14 @@ function profile_validation($usernew, $files) { * @param stdClass $usernew */ function profile_save_data($usernew) { - global $CFG, $DB; + global $CFG; - if ($fields = $DB->get_records('user_info_field')) { - foreach ($fields as $field) { - require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); - $newfield = 'profile_field_'.$field->datatype; - $formfield = new $newfield($field->id, $usernew->id); - $formfield->edit_save_data($usernew); - } + $fields = profile_get_user_fields_with_data($usernew->id); + foreach ($fields as $field) { + require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); + $newfield = 'profile_field_'.$field->datatype; + $formfield = new $newfield($field->id, $usernew->id, $field); + $formfield->edit_save_data($usernew); } } @@ -532,18 +597,15 @@ function profile_save_data($usernew) { function profile_display_fields($userid) { global $CFG, $USER, $DB; - if ($categories = $DB->get_records('user_info_category', null, 'sortorder ASC')) { - foreach ($categories as $category) { - if ($fields = $DB->get_records('user_info_field', array('categoryid' => $category->id), 'sortorder ASC')) { - foreach ($fields as $field) { - require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); - $newfield = 'profile_field_'.$field->datatype; - $formfield = new $newfield($field->id, $userid); - if ($formfield->is_visible() and !$formfield->is_empty()) { - echo html_writer::tag('dt', format_string($formfield->field->name)); - echo html_writer::tag('dd', $formfield->display_data()); - } - } + $categories = profile_get_user_fields_with_data_by_category($userid); + foreach ($categories as $categoryid => $fields) { + foreach ($fields as $field) { + require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); + $newfield = 'profile_field_'.$field->datatype; + $formfield = new $newfield($field->id, $userid, $field); + if ($formfield->is_visible() and !$formfield->is_empty()) { + echo html_writer::tag('dt', format_string($formfield->field->name)); + echo html_writer::tag('dd', $formfield->display_data()); } } } @@ -611,18 +673,17 @@ function profile_signup_fields($mform) { * @return stdClass */ function profile_user_record($userid, $onlyinuserobject = true) { - global $CFG, $DB; + global $CFG; $usercustomfields = new stdClass(); - if ($fields = $DB->get_records('user_info_field')) { - foreach ($fields as $field) { - require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); - $newfield = 'profile_field_'.$field->datatype; - $formfield = new $newfield($field->id, $userid); - if (!$onlyinuserobject || $formfield->is_user_object_data()) { - $usercustomfields->{$field->shortname} = $formfield->data; - } + $fields = profile_get_user_fields_with_data($userid); + foreach ($fields as $field) { + require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); + $newfield = 'profile_field_'.$field->datatype; + $formfield = new $newfield($field->id, $userid, $field); + if (!$onlyinuserobject || $formfield->is_user_object_data()) { + $usercustomfields->{$field->shortname} = $formfield->data; } }