MDL-26177 fix non-working unique custom field check
[moodle.git] / user / profile / lib.php
CommitLineData
8bdc9cac 1<?php
6b64d3b3 2
3/// Some constants
4
bb6d3d34 5define ('PROFILE_VISIBLE_ALL', '2'); // only visible for users with moodle/user:update capability
6define ('PROFILE_VISIBLE_PRIVATE', '1'); // either we are viewing our own profile or we have moodle/user:update capability
7define ('PROFILE_VISIBLE_NONE', '0'); // only visible for moodle/user:update capability
6b64d3b3 8
334415e9 9
10
d052a813 11/**
a1248ca4 12 * Base class for the customisable profile fields.
d052a813 13 */
6b64d3b3 14class profile_field_base {
15
334415e9 16 /// These 2 variables are really what we're interested in.
17 /// Everything else can be extracted from them
18 var $fieldid;
19 var $userid;
aa6c1ced 20
bb6d3d34 21 var $field;
22 var $inputname;
334415e9 23 var $data;
8bdc9cac 24 var $dataformat;
6b64d3b3 25
b1c70023 26 /**
27 * Constructor method.
28 * @param integer id of the profile from the user_info_field table
29 * @param integer id of the user for whom we are displaying data
30 */
334415e9 31 function profile_field_base($fieldid=0, $userid=0) {
32 global $USER;
b1c70023 33
334415e9 34 $this->set_fieldid($fieldid);
35 $this->set_userid($userid);
36 $this->load_data();
b1c70023 37 }
38
334415e9 39
40/***** The following methods must be overwritten by child classes *****/
41
6b64d3b3 42 /**
334415e9 43 * Abstract method: Adds the profile field to the moodle form class
44 * @param form instance of the moodleform class
6b64d3b3 45 */
334415e9 46 function edit_field_add(&$mform) {
d3248238 47 print_error('mustbeoveride', 'debug', '', 'edit_field_add');
6b64d3b3 48 }
49
aa6c1ced 50
334415e9 51/***** The following methods may be overwritten by child classes *****/
52
53 /**
54 * Display the data for this field
55 */
56 function display_data() {
bf718f50 57 $options = new stdClass();
3212d2f2 58 $options->para = false;
59 return format_text($this->data, FORMAT_MOODLE, $options);
334415e9 60 }
aa6c1ced 61
6b64d3b3 62 /**
334415e9 63 * Print out the form field in the edit profile page
b1c70023 64 * @param object instance of the moodleform class
6b64d3b3 65 * $return boolean
66 */
334415e9 67 function edit_field(&$mform) {
6b64d3b3 68
bb6d3d34 69 if ($this->field->visible != PROFILE_VISIBLE_NONE
ef35441d 70 or has_capability('moodle/user:update', get_context_instance(CONTEXT_SYSTEM))) {
6b64d3b3 71
334415e9 72 $this->edit_field_add($mform);
73 $this->edit_field_set_default($mform);
74 $this->edit_field_set_required($mform);
63466095 75 return true;
76 }
77 return false;
78 }
79
80 /**
81 * Tweaks the edit form
82 * @param object instance of the moodleform class
83 * $return boolean
84 */
85 function edit_after_data(&$mform) {
86
87 if ($this->field->visible != PROFILE_VISIBLE_NONE
88 or has_capability('moodle/user:update', get_context_instance(CONTEXT_SYSTEM))) {
334415e9 89 $this->edit_field_set_locked($mform);
17a99a13 90 return true;
9ac33264 91 }
17a99a13 92 return false;
b1c70023 93 }
6b64d3b3 94
b1c70023 95 /**
bb6d3d34 96 * Saves the data coming from form
97 * @param mixed data coming from the form
98 * @return mixed returns data id if success of db insert/update, false on fail, 0 if not permitted
b1c70023 99 */
334415e9 100 function edit_save_data($usernew) {
5d910388 101 global $DB;
6b64d3b3 102
bb6d3d34 103 if (!isset($usernew->{$this->inputname})) {
1bf65e58 104 // field not present in form, probably locked and invisible - skip it
bb6d3d34 105 return;
106 }
6b64d3b3 107
bf718f50 108 $data = new stdClass();
aa6c1ced 109
8bdc9cac
SH
110 $usernew->{$this->inputname} = $this->edit_save_data_preprocess($usernew->{$this->inputname}, $data);
111
bb6d3d34 112 $data->userid = $usernew->id;
113 $data->fieldid = $this->field->id;
114 $data->data = $usernew->{$this->inputname};
9ac33264 115
5d910388 116 if ($dataid = $DB->get_field('user_info_data', 'id', array('userid'=>$data->userid, 'fieldid'=>$data->fieldid))) {
bb6d3d34 117 $data->id = $dataid;
bf8e93d7 118 $DB->update_record('user_info_data', $data);
bb6d3d34 119 } else {
5d910388 120 $DB->insert_record('user_info_data', $data);
bb6d3d34 121 }
6b64d3b3 122 }
123
9ac33264 124 /**
bb6d3d34 125 * Validate the form field from profile page
126 * @return string contains error message otherwise NULL
127 **/
334415e9 128 function edit_validate_field($usernew) {
6a08c830 129 global $DB;
130
35a261eb 131 $errors = array();
132 /// Check for uniqueness of data if required
133 if ($this->is_unique()) {
af0a06cb
PS
134 $value = (is_array($usernew->{$this->inputname}) and isset($usernew->{$this->inputname}['text'])) ? $usernew->{$this->inputname}['text'] : $usernew->{$this->inputname};
135 $data = $DB->get_records_sql('
136 SELECT id, userid
25f44c72
TH
137 FROM {user_info_data}
138 WHERE fieldid = ?
139 AND ' . $DB->sql_compare_text('data') . ' = ?',
af0a06cb
PS
140 array($this->field->id, $value));
141 if ($data) {
142 $existing = false;
143 foreach ($data as $v) {
144 if ($v->userid == $usernew->id) {
145 $existing = true;
146 break;
147 }
148 }
149 if (!$existing) {
150 $errors[$this->inputname] = get_string('valuealreadyused');
151 }
35a261eb 152 }
153 }
154 return $errors;
9ac33264 155 }
156
9ac33264 157 /**
bb6d3d34 158 * Sets the default data for the field in the form object
159 * @param object instance of the moodleform class
9ac33264 160 */
334415e9 161 function edit_field_set_default(&$mform) {
bb6d3d34 162 if (!empty($default)) {
a5d3b072 163 $mform->setDefault($this->inputname, $this->field->defaultdata);
bb6d3d34 164 }
6b64d3b3 165 }
166
167 /**
bb6d3d34 168 * Sets the required flag for the field in the form object
169 * @param object instance of the moodleform class
6b64d3b3 170 */
334415e9 171 function edit_field_set_required(&$mform) {
ef35441d 172 if ($this->is_required() and !has_capability('moodle/user:update', get_context_instance(CONTEXT_SYSTEM))) {
a5d3b072 173 $mform->addRule($this->inputname, get_string('required'), 'required', null, 'client');
bb6d3d34 174 }
6b64d3b3 175 }
176
177 /**
bb6d3d34 178 * HardFreeze the field if locked.
179 * @param object instance of the moodleform class
6b64d3b3 180 */
334415e9 181 function edit_field_set_locked(&$mform) {
63466095 182 if (!$mform->elementExists($this->inputname)) {
183 return;
184 }
ef35441d 185 if ($this->is_locked() and !has_capability('moodle/user:update', get_context_instance(CONTEXT_SYSTEM))) {
a5d3b072 186 $mform->hardFreeze($this->inputname);
cc444336 187 $mform->setConstant($this->inputname, $this->data);
6b64d3b3 188 }
6b64d3b3 189 }
190
191 /**
192 * Hook for child classess to process the data before it gets saved in database
193 * @param mixed
8bdc9cac 194 * @param stdClass The object that will be used to save the record
6b64d3b3 195 * @return mixed
196 */
8bdc9cac 197 function edit_save_data_preprocess($data, &$datarecord) {
6b64d3b3 198 return $data;
199 }
6b64d3b3 200
334415e9 201 /**
202 * Loads a user object with data for this field ready for the edit profile
203 * form
204 * @param object a user object
205 */
206 function edit_load_user_data(&$user) {
207 if ($this->data !== NULL) {
208 $user->{$this->inputname} = $this->data;
209 }
210 }
211
91cddbc4 212 /**
213 * Check if the field data should be loaded into the user object
214 * By default it is, but for field types where the data may be potentially
215 * large, the child class should override this and return false
216 * @return boolean
217 */
218 function is_user_object_data() {
219 return true;
220 }
221
334415e9 222
223/***** The following methods generally should not be overwritten by child classes *****/
aa6c1ced 224
334415e9 225 /**
226 * Accessor method: set the userid for this instance
227 * @param integer id from the user table
228 */
229 function set_userid($userid) {
230 $this->userid = $userid;
231 }
232
233 /**
234 * Accessor method: set the fieldid for this instance
235 * @param integer id from the user_info_field table
236 */
237 function set_fieldid($fieldid) {
238 $this->fieldid = $fieldid;
239 }
240
241 /**
242 * Accessor method: Load the field record and user data associated with the
243 * object's fieldid and userid
244 */
245 function load_data() {
5d910388 246 global $DB;
247
334415e9 248 /// Load the field object
5d910388 249 if (($this->fieldid == 0) or (!($field = $DB->get_record('user_info_field', array('id'=>$this->fieldid))))) {
334415e9 250 $this->field = NULL;
251 $this->inputname = '';
252 } else {
253 $this->field = $field;
254 $this->inputname = 'profile_field_'.$field->shortname;
255 }
256
257 if (!empty($this->field)) {
8bdc9cac
SH
258 if ($data = $DB->get_record('user_info_data', array('userid'=>$this->userid, 'fieldid'=>$this->fieldid), 'data, dataformat')) {
259 $this->data = $data->data;
260 $this->dataformat = $data->dataformat;
334415e9 261 } else {
262 $this->data = $this->field->defaultdata;
8bdc9cac 263 $this->dataformat = FORMAT_HTML;
334415e9 264 }
265 } else {
266 $this->data = NULL;
267 }
268 }
269
270 /**
271 * Check if the field data is visible to the current user
272 * @return boolean
273 */
274 function is_visible() {
275 global $USER;
276
277 switch ($this->field->visible) {
278 case PROFILE_VISIBLE_ALL:
279 return true;
280 case PROFILE_VISIBLE_PRIVATE:
76727901 281 if ($this->userid == $USER->id) {
282 return true;
283 } else {
6d153e67 284 return has_capability('moodle/user:viewalldetails',
285 get_context_instance(CONTEXT_USER, $this->userid));
76727901 286 }
334415e9 287 default:
6d153e67 288 return has_capability('moodle/user:viewalldetails',
289 get_context_instance(CONTEXT_USER, $this->userid));
334415e9 290 }
291 }
292
4a7030e0 293 /**
294 * Check if the field data is considered empty
295 * return boolean
296 */
297 function is_empty() {
298 return ( ($this->data != '0') and empty($this->data));
299 }
300
334415e9 301 /**
302 * Check if the field is required on the edit profile page
303 * @return boolean
304 */
305 function is_required() {
306 return (boolean)$this->field->required;
307 }
308
309 /**
310 * Check if the field is locked on the edit profile page
311 * @return boolean
312 */
313 function is_locked() {
314 return (boolean)$this->field->locked;
315 }
316
62ebb000 317 /**
318 * Check if the field data should be unique
319 * @return boolean
320 */
321 function is_unique() {
501ca377 322 return (boolean)$this->field->forceunique;
62ebb000 323 }
324
325 /**
326 * Check if the field should appear on the signup page
327 * @return boolean
328 */
329 function is_signup_field() {
330 return (boolean)$this->field->signup;
331 }
aa6c1ced 332
62ebb000 333
6b64d3b3 334} /// End of class definition
335
9ac33264 336
337/***** General purpose functions for customisable user profiles *****/
338
bb6d3d34 339function profile_load_data(&$user) {
5d910388 340 global $CFG, $DB;
bb6d3d34 341
5d910388 342 if ($fields = $DB->get_records('user_info_field')) {
bb6d3d34 343 foreach ($fields as $field) {
344 require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
345 $newfield = 'profile_field_'.$field->datatype;
334415e9 346 $formfield = new $newfield($field->id, $user->id);
347 $formfield->edit_load_user_data($user);
bb6d3d34 348 }
349 }
350}
351
7240a6ea 352/**
353 * Print out the customisable categories and fields for a users profile
354 * @param object instance of the moodleform class
7240a6ea 355 */
a5d3b072 356function profile_definition(&$mform) {
5d910388 357 global $CFG, $DB;
7240a6ea 358
85576776 359 // if user is "admin" fields are displayed regardless
360 $update = has_capability('moodle/user:update', get_context_instance(CONTEXT_SYSTEM));
361
5d910388 362 if ($categories = $DB->get_records('user_info_category', null, 'sortorder ASC')) {
7240a6ea 363 foreach ($categories as $category) {
5d910388 364 if ($fields = $DB->get_records('user_info_field', array('categoryid'=>$category->id), 'sortorder ASC')) {
aa6c1ced 365
85576776 366 // check first if *any* fields will be displayed
367 $display = false;
7240a6ea 368 foreach ($fields as $field) {
85576776 369 if ($field->visible != PROFILE_VISIBLE_NONE) {
370 $display = true;
17a99a13 371 }
372 }
85576776 373
374 // display the header and the fields
375 if ($display or $update) {
17a99a13 376 $mform->addElement('header', 'category_'.$category->id, format_string($category->name));
85576776 377 foreach ($fields as $field) {
378 require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
379 $newfield = 'profile_field_'.$field->datatype;
380 $formfield = new $newfield($field->id);
381 $formfield->edit_field($mform);
382 }
7240a6ea 383 }
bb6d3d34 384 }
9ac33264 385 }
386 }
9ac33264 387}
388
63466095 389function profile_definition_after_data(&$mform, $userid) {
5d910388 390 global $CFG, $DB;
63466095 391
392 $userid = ($userid < 0) ? 0 : (int)$userid;
393
5d910388 394 if ($fields = $DB->get_records('user_info_field')) {
bb6d3d34 395 foreach ($fields as $field) {
396 require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
397 $newfield = 'profile_field_'.$field->datatype;
63466095 398 $formfield = new $newfield($field->id, $userid);
399 $formfield->edit_after_data($mform);
d052a813 400 }
63466095 401 }
d052a813 402}
403
a78890d5 404function profile_validation($usernew, $files) {
5d910388 405 global $CFG, $DB;
d052a813 406
bb6d3d34 407 $err = array();
5d910388 408 if ($fields = $DB->get_records('user_info_field')) {
bb6d3d34 409 foreach ($fields as $field) {
410 require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
411 $newfield = 'profile_field_'.$field->datatype;
334415e9 412 $formfield = new $newfield($field->id, $usernew->id);
a78890d5 413 $err += $formfield->edit_validate_field($usernew, $files);
d052a813 414 }
415 }
bb6d3d34 416 return $err;
d052a813 417}
418
bb6d3d34 419function profile_save_data($usernew) {
5d910388 420 global $CFG, $DB;
d052a813 421
5849cfe2 422 if ($fields = $DB->get_records('user_info_field')) {
bb6d3d34 423 foreach ($fields as $field) {
424 require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
425 $newfield = 'profile_field_'.$field->datatype;
334415e9 426 $formfield = new $newfield($field->id, $usernew->id);
427 $formfield->edit_save_data($usernew);
bb6d3d34 428 }
9ac33264 429 }
9ac33264 430}
431
334415e9 432function profile_display_fields($userid) {
5d910388 433 global $CFG, $USER, $DB;
d052a813 434
5d910388 435 if ($categories = $DB->get_records('user_info_category', null, 'sortorder ASC')) {
334415e9 436 foreach ($categories as $category) {
5d910388 437 if ($fields = $DB->get_records('user_info_field', array('categoryid'=>$category->id), 'sortorder ASC')) {
334415e9 438 foreach ($fields as $field) {
439 require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
440 $newfield = 'profile_field_'.$field->datatype;
441 $formfield = new $newfield($field->id, $userid);
4a7030e0 442 if ($formfield->is_visible() and !$formfield->is_empty()) {
dc45a43a 443 print_row(format_string($formfield->field->name.':'), $formfield->display_data());
334415e9 444 }
445 }
446 }
447 }
448 }
449}
d052a813 450
831d450e 451/**
452 * Adds code snippet to a moodle form object for custom profile fields that
453 * should appear on the signup page
454 * @param object moodle form object
455 */
456function profile_signup_fields(&$mform) {
5d910388 457 global $CFG, $DB;
416afdbb 458
459 //only retrieve required custom fields (with category information)
460 //results are sort by categories, then by fields
461 $sql = "SELECT uf.id as fieldid, ic.id as categoryid, ic.name as categoryname, uf.datatype
351d5250 462 FROM {user_info_field} uf
463 JOIN {user_info_category} ic
aa6c1ced 464 ON uf.categoryid = ic.id AND uf.signup = 1 AND uf.visible<>0
416afdbb 465 ORDER BY ic.sortorder ASC, uf.sortorder ASC";
466
467 if ( $fields = $DB->get_records_sql($sql)) {
831d450e 468 foreach ($fields as $field) {
416afdbb 469 //check if we change the categories
470 if (!isset($currentcat) || $currentcat != $field->categoryid) {
471 $currentcat = $field->categoryid;
472 $mform->addElement('header', 'category_'.$field->categoryid, format_string($field->categoryname));
473 }
831d450e 474 require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
475 $newfield = 'profile_field_'.$field->datatype;
416afdbb 476 $formfield = new $newfield($field->fieldid);
831d450e 477 $formfield->edit_field($mform);
478 }
479 }
480}
d052a813 481
66643c0a 482/**
483 * Returns an object with the custom profile fields set for the given user
484 * @param integer userid
485 * @return object
486 */
487function profile_user_record($userid) {
5d910388 488 global $CFG, $DB;
66643c0a 489
bf718f50 490 $usercustomfields = new stdClass();
66643c0a 491
5d910388 492 if ($fields = $DB->get_records('user_info_field')) {
66643c0a 493 foreach ($fields as $field) {
494 require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
495 $newfield = 'profile_field_'.$field->datatype;
496 $formfield = new $newfield($field->id, $userid);
a1248ca4
PS
497 if ($formfield->is_user_object_data()) {
498 $usercustomfields->{$field->shortname} = $formfield->data;
499 }
66643c0a 500 }
501 }
502
fb79269b 503 return $usercustomfields;
66643c0a 504}
505
a1248ca4
PS
506/**
507 * Load custom profile fields into user object
508 *
509 * Please note originally in 1.9 we were using the custom field names directly,
510 * but it was causing unexpected collisions when adding new fields to user table,
511 * so instead we now use 'profile_' prefix.
512 *
513 * @param object $user user object
514 * @return void $user object is modified
515 */
516function profile_load_custom_fields(&$user) {
517 $user->profile = (array)profile_user_record($user->id);
518}
66643c0a 519
aa6c1ced 520