05e465533d285bffce52767ad6965af0577c955f
[moodle.git] / user / profile / lib.php
1 <?php
3 /// Some constants
5 define ('PROFILE_VISIBLE_ALL',     '2'); // only visible for users with moodle/user:update capability
6 define ('PROFILE_VISIBLE_PRIVATE', '1'); // either we are viewing our own profile or we have moodle/user:update capability
7 define ('PROFILE_VISIBLE_NONE',    '0'); // only visible for moodle/user:update capability
11 /**
12  * Base class for the customisable profile fields.
13  */
14 class profile_field_base {
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;
21     var $field;
22     var $inputname;
23     var $data;
24     var $dataformat;
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      */
31     function profile_field_base($fieldid=0, $userid=0) {
32         global $USER;
34         $this->set_fieldid($fieldid);
35         $this->set_userid($userid);
36         $this->load_data();
37     }
40 /***** The following methods must be overwritten by child classes *****/
42     /**
43      * Abstract method: Adds the profile field to the moodle form class
44      * @param  form  instance of the moodleform class
45      */
46     function edit_field_add(&$mform) {
47         print_error('mustbeoveride', 'debug', '', 'edit_field_add');
48     }
51 /***** The following methods may be overwritten by child classes *****/
53     /**
54      * Display the data for this field
55      */
56     function display_data() {
57         $options = new stdClass();
58         $options->para = false;
59         return format_text($this->data, FORMAT_MOODLE, $options);
60     }
62     /**
63      * Print out the form field in the edit profile page
64      * @param   object   instance of the moodleform class
65      * $return  boolean
66      */
67     function edit_field(&$mform) {
69         if ($this->field->visible != PROFILE_VISIBLE_NONE
70           or has_capability('moodle/user:update', get_context_instance(CONTEXT_SYSTEM))) {
72             $this->edit_field_add($mform);
73             $this->edit_field_set_default($mform);
74             $this->edit_field_set_required($mform);
75             return true;
76         }
77         return false;
78     }
80     /**
81      * Tweaks the edit form
82      * @param   object   instance of the moodleform class
83      * $return  boolean
84      */
85     function edit_after_data(&$mform) {
87         if ($this->field->visible != PROFILE_VISIBLE_NONE
88           or has_capability('moodle/user:update', get_context_instance(CONTEXT_SYSTEM))) {
89             $this->edit_field_set_locked($mform);
90             return true;
91         }
92         return false;
93     }
95     /**
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
99      */
100     function edit_save_data($usernew) {
101         global $DB;
103         if (!isset($usernew->{$this->inputname})) {
104             // field not present in form, probably locked and invisible - skip it
105             return;
106         }
108         $data = new stdClass();
110         $usernew->{$this->inputname} = $this->edit_save_data_preprocess($usernew->{$this->inputname}, $data);
112         $data->userid  = $usernew->id;
113         $data->fieldid = $this->field->id;
114         $data->data    = $usernew->{$this->inputname};
116         if ($dataid = $DB->get_field('user_info_data', 'id', array('userid'=>$data->userid, 'fieldid'=>$data->fieldid))) {
117             $data->id = $dataid;
118             $DB->update_record('user_info_data', $data);
119         } else {
120             $DB->insert_record('user_info_data', $data);
121         }
122     }
124     /**
125      * Validate the form field from profile page
126      * @return  string  contains error message otherwise NULL
127      **/
128     function edit_validate_field($usernew) {
129         global $DB;
131         $errors = array();
132         /// Check for uniqueness of data if required
133         if ($this->is_unique()) {
134             $userid = $DB->get_field_sql('
135                     SELECT userid
136                       FROM {user_info_data}
137                      WHERE fieldid = ?
138                        AND ' . $DB->sql_compare_text('data') . ' = ?',
139                     array($this->field->id, $usernew->{$this->inputname}));
140             if ($userid && $userid != $usernew->id) {
141                 $errors[$this->inputname] = get_string('valuealreadyused');
142             }
143         }
144         return $errors;
145     }
147     /**
148      * Sets the default data for the field in the form object
149      * @param   object   instance of the moodleform class
150      */
151     function edit_field_set_default(&$mform) {
152         if (!empty($default)) {
153             $mform->setDefault($this->inputname, $this->field->defaultdata);
154         }
155     }
157     /**
158      * Sets the required flag for the field in the form object
159      * @param   object   instance of the moodleform class
160      */
161     function edit_field_set_required(&$mform) {
162         if ($this->is_required() and !has_capability('moodle/user:update', get_context_instance(CONTEXT_SYSTEM))) {
163             $mform->addRule($this->inputname, get_string('required'), 'required', null, 'client');
164         }
165     }
167     /**
168      * HardFreeze the field if locked.
169      * @param   object   instance of the moodleform class
170      */
171     function edit_field_set_locked(&$mform) {
172         if (!$mform->elementExists($this->inputname)) {
173             return;
174         }
175         if ($this->is_locked() and !has_capability('moodle/user:update', get_context_instance(CONTEXT_SYSTEM))) {
176             $mform->hardFreeze($this->inputname);
177             $mform->setConstant($this->inputname, $this->data);
178         }
179     }
181     /**
182      * Hook for child classess to process the data before it gets saved in database
183      * @param   mixed
184      * @param   stdClass The object that will be used to save the record
185      * @return  mixed
186      */
187     function edit_save_data_preprocess($data, &$datarecord) {
188         return $data;
189     }
191     /**
192      * Loads a user object with data for this field ready for the edit profile
193      * form
194      * @param   object   a user object
195      */
196     function edit_load_user_data(&$user) {
197         if ($this->data !== NULL) {
198             $user->{$this->inputname} = $this->data;
199         }
200     }
202     /**
203      * Check if the field data should be loaded into the user object
204      * By default it is, but for field types where the data may be potentially
205      * large, the child class should override this and return false
206      * @return boolean
207      */
208     function is_user_object_data() {
209         return true;
210     }
213 /***** The following methods generally should not be overwritten by child classes *****/
215     /**
216      * Accessor method: set the userid for this instance
217      * @param   integer   id from the user table
218      */
219     function set_userid($userid) {
220         $this->userid = $userid;
221     }
223     /**
224      * Accessor method: set the fieldid for this instance
225      * @param   integer   id from the user_info_field table
226      */
227     function set_fieldid($fieldid) {
228         $this->fieldid = $fieldid;
229     }
231     /**
232      * Accessor method: Load the field record and user data associated with the
233      * object's fieldid and userid
234      */
235     function load_data() {
236         global $DB;
238         /// Load the field object
239         if (($this->fieldid == 0) or (!($field = $DB->get_record('user_info_field', array('id'=>$this->fieldid))))) {
240             $this->field = NULL;
241             $this->inputname = '';
242         } else {
243             $this->field = $field;
244             $this->inputname = 'profile_field_'.$field->shortname;
245         }
247         if (!empty($this->field)) {
248             if ($data = $DB->get_record('user_info_data', array('userid'=>$this->userid, 'fieldid'=>$this->fieldid), 'data, dataformat')) {
249                 $this->data = $data->data;
250                 $this->dataformat = $data->dataformat;
251             } else {
252                 $this->data = $this->field->defaultdata;
253                 $this->dataformat = FORMAT_HTML;
254             }
255         } else {
256             $this->data = NULL;
257         }
258     }
260     /**
261      * Check if the field data is visible to the current user
262      * @return  boolean
263      */
264     function is_visible() {
265         global $USER;
267         switch ($this->field->visible) {
268             case PROFILE_VISIBLE_ALL:
269                 return true;
270             case PROFILE_VISIBLE_PRIVATE:
271                 if ($this->userid == $USER->id) {
272                     return true;
273                 } else {
274                     return has_capability('moodle/user:viewalldetails',
275                             get_context_instance(CONTEXT_USER, $this->userid));
276                 }
277             default:
278                 return has_capability('moodle/user:viewalldetails',
279                         get_context_instance(CONTEXT_USER, $this->userid));
280         }
281     }
283     /**
284      * Check if the field data is considered empty
285      * return boolean
286      */
287     function is_empty() {
288         return ( ($this->data != '0') and empty($this->data));
289     }
291     /**
292      * Check if the field is required on the edit profile page
293      * @return   boolean
294      */
295     function is_required() {
296         return (boolean)$this->field->required;
297     }
299     /**
300      * Check if the field is locked on the edit profile page
301      * @return   boolean
302      */
303     function is_locked() {
304         return (boolean)$this->field->locked;
305     }
307     /**
308      * Check if the field data should be unique
309      * @return   boolean
310      */
311     function is_unique() {
312         return (boolean)$this->field->forceunique;
313     }
315     /**
316      * Check if the field should appear on the signup page
317      * @return   boolean
318      */
319     function is_signup_field() {
320         return (boolean)$this->field->signup;
321     }
324 } /// End of class definition
327 /***** General purpose functions for customisable user profiles *****/
329 function profile_load_data(&$user) {
330     global $CFG, $DB;
332     if ($fields = $DB->get_records('user_info_field')) {
333         foreach ($fields as $field) {
334             require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
335             $newfield = 'profile_field_'.$field->datatype;
336             $formfield = new $newfield($field->id, $user->id);
337             $formfield->edit_load_user_data($user);
338         }
339     }
342 /**
343  * Print out the customisable categories and fields for a users profile
344  * @param  object   instance of the moodleform class
345  */
346 function profile_definition(&$mform) {
347     global $CFG, $DB;
349     // if user is "admin" fields are displayed regardless
350     $update = has_capability('moodle/user:update', get_context_instance(CONTEXT_SYSTEM));
352     if ($categories = $DB->get_records('user_info_category', null, 'sortorder ASC')) {
353         foreach ($categories as $category) {
354             if ($fields = $DB->get_records('user_info_field', array('categoryid'=>$category->id), 'sortorder ASC')) {
356                 // check first if *any* fields will be displayed
357                 $display = false;
358                 foreach ($fields as $field) {
359                     if ($field->visible != PROFILE_VISIBLE_NONE) {
360                         $display = true;
361                     }
362                 }
364                 // display the header and the fields
365                 if ($display or $update) {
366                     $mform->addElement('header', 'category_'.$category->id, format_string($category->name));
367                     foreach ($fields as $field) {
368                         require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
369                         $newfield = 'profile_field_'.$field->datatype;
370                         $formfield = new $newfield($field->id);
371                         $formfield->edit_field($mform);
372                     }
373                 }
374             }
375         }
376     }
379 function profile_definition_after_data(&$mform, $userid) {
380     global $CFG, $DB;
382     $userid = ($userid < 0) ? 0 : (int)$userid;
384     if ($fields = $DB->get_records('user_info_field')) {
385         foreach ($fields as $field) {
386             require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
387             $newfield = 'profile_field_'.$field->datatype;
388             $formfield = new $newfield($field->id, $userid);
389             $formfield->edit_after_data($mform);
390         }
391     }
394 function profile_validation($usernew, $files) {
395     global $CFG, $DB;
397     $err = array();
398     if ($fields = $DB->get_records('user_info_field')) {
399         foreach ($fields as $field) {
400             require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
401             $newfield = 'profile_field_'.$field->datatype;
402             $formfield = new $newfield($field->id, $usernew->id);
403             $err += $formfield->edit_validate_field($usernew, $files);
404         }
405     }
406     return $err;
409 function profile_save_data($usernew) {
410     global $CFG, $DB;
412     if ($fields = $DB->get_records('user_info_field')) {
413         foreach ($fields as $field) {
414             require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
415             $newfield = 'profile_field_'.$field->datatype;
416             $formfield = new $newfield($field->id, $usernew->id);
417             $formfield->edit_save_data($usernew);
418         }
419     }
422 function profile_display_fields($userid) {
423     global $CFG, $USER, $DB;
425     if ($categories = $DB->get_records('user_info_category', null, 'sortorder ASC')) {
426         foreach ($categories as $category) {
427             if ($fields = $DB->get_records('user_info_field', array('categoryid'=>$category->id), 'sortorder ASC')) {
428                 foreach ($fields as $field) {
429                     require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
430                     $newfield = 'profile_field_'.$field->datatype;
431                     $formfield = new $newfield($field->id, $userid);
432                     if ($formfield->is_visible() and !$formfield->is_empty()) {
433                         print_row(format_string($formfield->field->name.':'), $formfield->display_data());
434                     }
435                 }
436             }
437         }
438     }
441 /**
442  * Adds code snippet to a moodle form object for custom profile fields that
443  * should appear on the signup page
444  * @param  object  moodle form object
445  */
446 function profile_signup_fields(&$mform) {
447     global $CFG, $DB;
449      //only retrieve required custom fields (with category information)
450     //results are sort by categories, then by fields
451     $sql = "SELECT uf.id as fieldid, ic.id as categoryid, ic.name as categoryname, uf.datatype
452                 FROM {user_info_field} uf
453                 JOIN {user_info_category} ic
454                 ON uf.categoryid = ic.id AND uf.signup = 1 AND uf.visible<>0
455                 ORDER BY ic.sortorder ASC, uf.sortorder ASC";
457     if ( $fields = $DB->get_records_sql($sql)) {
458         foreach ($fields as $field) {
459             //check if we change the categories
460             if (!isset($currentcat) || $currentcat != $field->categoryid) {
461                  $currentcat = $field->categoryid;
462                  $mform->addElement('header', 'category_'.$field->categoryid, format_string($field->categoryname));
463             }
464             require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
465             $newfield = 'profile_field_'.$field->datatype;
466             $formfield = new $newfield($field->fieldid);
467             $formfield->edit_field($mform);
468         }
469     }
472 /**
473  * Returns an object with the custom profile fields set for the given user
474  * @param  integer  userid
475  * @return  object
476  */
477 function profile_user_record($userid) {
478     global $CFG, $DB;
480     $usercustomfields = new stdClass();
482     if ($fields = $DB->get_records('user_info_field')) {
483         foreach ($fields as $field) {
484             require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php');
485             $newfield = 'profile_field_'.$field->datatype;
486             $formfield = new $newfield($field->id, $userid);
487             if ($formfield->is_user_object_data()) {
488                 $usercustomfields->{$field->shortname} = $formfield->data;
489             }
490         }
491     }
493     return $usercustomfields;
496 /**
497  * Load custom profile fields into user object
498  *
499  * Please note originally in 1.9 we were using the custom field names directly,
500  * but it was causing unexpected collisions when adding new fields to user table,
501  * so instead we now use 'profile_' prefix.
502  *
503  * @param object $user user object
504  * @return void $user object is modified
505  */
506 function profile_load_custom_fields(&$user) {
507     $user->profile = (array)profile_user_record($user->id);