Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * This file contains the profile_define_base class.
19
 *
20
 * @package core_user
21
 * @copyright  2007 onwards Shane Elliot {@link http://pukunui.com}
22
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
/**
26
 * Class profile_define_base
27
 *
28
 * @copyright  2007 onwards Shane Elliot {@link http://pukunui.com}
29
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30
 */
31
class profile_define_base {
32
 
33
    /**
34
     * Prints out the form snippet for creating or editing a profile field
35
     * @param MoodleQuickForm $form instance of the moodleform class
36
     */
37
    public function define_form(&$form) {
38
        $form->addElement('header', '_commonsettings', get_string('profilecommonsettings', 'admin'));
39
        $this->define_form_common($form);
40
 
41
        $form->addElement('header', '_specificsettings', get_string('profilespecificsettings', 'admin'));
42
        $this->define_form_specific($form);
43
    }
44
 
45
    /**
46
     * Prints out the form snippet for the part of creating or editing a profile field common to all data types.
47
     *
48
     * @param MoodleQuickForm $form instance of the moodleform class
49
     */
50
    public function define_form_common(&$form) {
51
 
52
        $strrequired = get_string('required');
53
 
54
        // Accepted values for 'shortname' would follow [a-zA-Z0-9_] pattern,
55
        // but we are accepting any PARAM_TEXT value here,
56
        // and checking [a-zA-Z0-9_] pattern in define_validate_common() function to throw an error when needed.
57
        $form->addElement('text', 'shortname', get_string('profileshortname', 'admin'), 'maxlength="100" size="25"');
58
        $form->addRule('shortname', $strrequired, 'required', null, 'client');
59
        $form->setType('shortname', PARAM_TEXT);
60
 
61
        $form->addElement('text', 'name', get_string('profilename', 'admin'), 'size="50"');
62
        $form->addRule('name', $strrequired, 'required', null, 'client');
63
        $form->setType('name', PARAM_TEXT);
64
 
65
        $form->addElement('editor', 'description', get_string('profiledescription', 'admin'), null, null);
66
 
67
        $form->addElement('selectyesno', 'required', get_string('profilerequired', 'admin'));
68
 
69
        $form->addElement('selectyesno', 'locked', get_string('profilelocked', 'admin'));
70
 
71
        $form->addElement('selectyesno', 'forceunique', get_string('profileforceunique', 'admin'));
72
 
73
        $form->addElement('selectyesno', 'signup', get_string('profilesignup', 'admin'));
74
 
75
        $choices = array();
76
        $choices[PROFILE_VISIBLE_NONE]    = get_string('profilevisiblenone', 'admin');
77
        $choices[PROFILE_VISIBLE_PRIVATE] = get_string('profilevisibleprivate', 'admin');
78
        $choices[PROFILE_VISIBLE_TEACHERS] = get_string('profilevisibleteachers', 'admin');
79
        $choices[PROFILE_VISIBLE_ALL]     = get_string('profilevisibleall', 'admin');
80
 
81
        $form->addElement('select', 'visible', get_string('profilevisible', 'admin'), $choices);
82
        $form->addHelpButton('visible', 'profilevisible', 'admin');
83
        $form->setDefault('visible', PROFILE_VISIBLE_ALL);
84
 
85
        $choices = profile_list_categories();
86
        $form->addElement('select', 'categoryid', get_string('profilecategory', 'admin'), $choices);
87
    }
88
 
89
    /**
90
     * Prints out the form snippet for the part of creating or editing a profile field specific to the current data type.
91
     * @param MoodleQuickForm $form instance of the moodleform class
92
     */
93
    public function define_form_specific($form) {
94
        // Do nothing - overwrite if necessary.
95
    }
96
 
97
    /**
98
     * Validate the data from the add/edit profile field form.
99
     *
100
     * Generally this method should not be overwritten by child classes.
101
     *
102
     * @param stdClass|array $data from the add/edit profile field form
103
     * @param array $files
104
     * @return array associative array of error messages
105
     */
106
    public function define_validate($data, $files) {
107
 
108
        $data = (object)$data;
109
        $err = array();
110
 
111
        $err += $this->define_validate_common($data, $files);
112
        $err += $this->define_validate_specific($data, $files);
113
 
114
        return $err;
115
    }
116
 
117
    /**
118
     * Validate the data from the add/edit profile field form that is common to all data types.
119
     *
120
     * Generally this method should not be overwritten by child classes.
121
     *
122
     * @param stdClass|array $data from the add/edit profile field form
123
     * @param array $files
124
     * @return  array    associative array of error messages
125
     */
126
    public function define_validate_common($data, $files) {
127
        global $DB;
128
 
129
        $err = array();
130
 
131
        // Check the shortname was not truncated by cleaning.
132
        if (empty($data->shortname)) {
133
            $err['shortname'] = get_string('required');
134
 
135
        } else {
136
            // Check allowed pattern (numbers, letters and underscore).
137
            if (!preg_match('/^[a-zA-Z0-9_]+$/', $data->shortname)) {
138
                $err['shortname'] = get_string('profileshortnameinvalid', 'admin');
139
            } else {
140
                // Fetch field-record from DB.
141
                $field = profile_get_custom_field_data_by_shortname($data->shortname);
142
                // Check the shortname is unique.
143
                if ($field and $field->id <> $data->id) {
144
                    $err['shortname'] = get_string('profileshortnamenotunique', 'admin');
145
                }
146
                // NOTE: since 2.0 the shortname may collide with existing fields in $USER because we load these fields into
147
                // $USER->profile array instead.
148
            }
149
        }
150
 
151
        // No further checks necessary as the form class will take care of it.
152
        return $err;
153
    }
154
 
155
    /**
156
     * Validate the data from the add/edit profile field form
157
     * that is specific to the current data type
158
     * @param array $data
159
     * @param array $files
160
     * @return  array    associative array of error messages
161
     */
162
    public function define_validate_specific($data, $files) {
163
        // Do nothing - overwrite if necessary.
164
        return array();
165
    }
166
 
167
    /**
168
     * Alter form based on submitted or existing data
169
     * @param MoodleQuickForm $mform
170
     */
171
    public function define_after_data(&$mform) {
172
        // Do nothing - overwrite if necessary.
173
    }
174
 
175
    /**
176
     * Add a new profile field or save changes to current field
177
     * @param array|stdClass $data from the add/edit profile field form
178
     */
179
    public function define_save($data) {
180
        global $DB;
181
 
182
        $data = $this->define_save_preprocess($data); // Hook for child classes.
183
 
184
        $old = false;
185
        if (!empty($data->id)) {
186
            $old = $DB->get_record('user_info_field', array('id' => (int)$data->id));
187
        }
188
 
189
        // Check to see if the category has changed.
190
        if (!$old or $old->categoryid != $data->categoryid) {
191
            $data->sortorder = $DB->count_records('user_info_field', array('categoryid' => $data->categoryid)) + 1;
192
        }
193
 
194
        if (empty($data->id)) {
195
            unset($data->id);
196
            $data->id = $DB->insert_record('user_info_field', $data);
197
        } else {
198
            $DB->update_record('user_info_field', $data);
199
        }
200
 
201
        $field = $DB->get_record('user_info_field', array('id' => $data->id));
202
        if ($old) {
203
            \core\event\user_info_field_updated::create_from_field($field)->trigger();
204
        } else {
205
            \core\event\user_info_field_created::create_from_field($field)->trigger();
206
        }
207
        profile_purge_user_fields_cache();
208
    }
209
 
210
    /**
211
     * Preprocess data from the add/edit profile field form before it is saved.
212
     *
213
     * This method is a hook for the child classes to overwrite.
214
     *
215
     * @param array|stdClass $data from the add/edit profile field form
216
     * @return array|stdClass processed data object
217
     */
218
    public function define_save_preprocess($data) {
219
        // Do nothing - overwrite if necessary.
220
        return $data;
221
    }
222
 
223
    /**
224
     * Provides a method by which we can allow the default data in profile_define_* to use an editor
225
     *
226
     * This should return an array of editor names (which will need to be formatted/cleaned)
227
     *
228
     * @return array
229
     */
230
    public function define_editors() {
231
        return array();
232
    }
233
}
234
 
235
 
236
 
237
/**
238
 * Reorder the profile fields within a given category starting at the field at the given startorder.
239
 */
240
function profile_reorder_fields() {
241
    global $DB;
242
 
243
    if ($categories = $DB->get_records('user_info_category')) {
244
        foreach ($categories as $category) {
245
            $i = 1;
246
            if ($fields = $DB->get_records('user_info_field', array('categoryid' => $category->id), 'sortorder ASC')) {
247
                foreach ($fields as $field) {
248
                    $f = new stdClass();
249
                    $f->id = $field->id;
250
                    $f->sortorder = $i++;
251
                    $DB->update_record('user_info_field', $f);
252
                }
253
            }
254
        }
255
        profile_purge_user_fields_cache();
256
    }
257
}
258
 
259
/**
260
 * Reorder the profile categoriess starting at the category at the given startorder.
261
 */
262
function profile_reorder_categories() {
263
    global $DB;
264
 
265
    $i = 1;
266
    if ($categories = $DB->get_records('user_info_category', null, 'sortorder ASC')) {
267
        foreach ($categories as $cat) {
268
            $c = new stdClass();
269
            $c->id = $cat->id;
270
            $c->sortorder = $i++;
271
            $DB->update_record('user_info_category', $c);
272
        }
273
        profile_purge_user_fields_cache();
274
    }
275
}
276
 
277
/**
278
 * Delete a profile category
279
 * @param int $id of the category to be deleted
280
 * @return bool success of operation
281
 */
282
function profile_delete_category($id) {
283
    global $DB;
284
 
285
    // Retrieve the category.
286
    if (!$category = $DB->get_record('user_info_category', array('id' => $id))) {
287
        throw new \moodle_exception('invalidcategoryid');
288
    }
289
 
290
    if (!$categories = $DB->get_records('user_info_category', null, 'sortorder ASC')) {
291
        throw new \moodle_exception('nocate', 'debug');
292
    }
293
 
294
    unset($categories[$category->id]);
295
 
296
    if (!count($categories)) {
297
        return false; // We can not delete the last category.
298
    }
299
 
300
    // Does the category contain any fields.
301
    if ($DB->count_records('user_info_field', array('categoryid' => $category->id))) {
302
        if (array_key_exists($category->sortorder - 1, $categories)) {
303
            $newcategory = $categories[$category->sortorder - 1];
304
        } else if (array_key_exists($category->sortorder + 1, $categories)) {
305
            $newcategory = $categories[$category->sortorder + 1];
306
        } else {
307
            $newcategory = reset($categories); // Get first category if sortorder broken.
308
        }
309
 
310
        $sortorder = $DB->count_records('user_info_field', array('categoryid' => $newcategory->id)) + 1;
311
 
312
        if ($fields = $DB->get_records('user_info_field', array('categoryid' => $category->id), 'sortorder ASC')) {
313
            foreach ($fields as $field) {
314
                $f = new stdClass();
315
                $f->id = $field->id;
316
                $f->sortorder = $sortorder++;
317
                $f->categoryid = $newcategory->id;
318
                if ($DB->update_record('user_info_field', $f)) {
319
                    $field->sortorder = $f->sortorder;
320
                    $field->categoryid = $f->categoryid;
321
                    \core\event\user_info_field_updated::create_from_field($field)->trigger();
322
                }
323
            }
324
        }
325
    }
326
 
327
    // Finally we get to delete the category.
328
    $DB->delete_records('user_info_category', array('id' => $category->id));
329
    profile_reorder_categories();
330
 
331
    \core\event\user_info_category_deleted::create_from_category($category)->trigger();
332
    profile_purge_user_fields_cache();
333
 
334
    return true;
335
}
336
 
337
/**
338
 * Deletes a profile field.
339
 * @param int $id
340
 */
341
function profile_delete_field($id) {
342
    global $DB;
343
 
344
    // Remove any user data associated with this field.
345
    if (!$DB->delete_records('user_info_data', array('fieldid' => $id))) {
346
        throw new \moodle_exception('cannotdeletecustomfield');
347
    }
348
 
349
    // Note: Any availability conditions that depend on this field will remain,
350
    // but show the field as missing until manually corrected to something else.
351
 
352
    // Need to rebuild course cache to update the info.
353
    rebuild_course_cache(0, true);
354
 
355
    // Prior to the delete, pull the record for the event.
356
    $field = $DB->get_record('user_info_field', array('id' => $id));
357
 
358
    // Try to remove the record from the database.
359
    $DB->delete_records('user_info_field', array('id' => $id));
360
 
361
    \core\event\user_info_field_deleted::create_from_field($field)->trigger();
362
    profile_purge_user_fields_cache();
363
 
364
    // Reorder the remaining fields in the same category.
365
    profile_reorder_fields();
366
}
367
 
368
/**
369
 * Change the sort order of a field
370
 *
371
 * @param int $id of the field
372
 * @param string $move direction of move
373
 * @return bool success of operation
374
 */
375
function profile_move_field($id, $move) {
376
    global $DB;
377
 
378
    // Get the field object.
379
    if (!$field = $DB->get_record('user_info_field', array('id' => $id))) {
380
        return false;
381
    }
382
    // Count the number of fields in this category.
383
    $fieldcount = $DB->count_records('user_info_field', array('categoryid' => $field->categoryid));
384
 
385
    // Calculate the new sortorder.
386
    if ( ($move == 'up') and ($field->sortorder > 1)) {
387
        $neworder = $field->sortorder - 1;
388
    } else if (($move == 'down') and ($field->sortorder < $fieldcount)) {
389
        $neworder = $field->sortorder + 1;
390
    } else {
391
        return false;
392
    }
393
 
394
    // Retrieve the field object that is currently residing in the new position.
395
    $params = array('categoryid' => $field->categoryid, 'sortorder' => $neworder);
396
    if ($swapfield = $DB->get_record('user_info_field', $params)) {
397
 
398
        // Swap the sortorders.
399
        $swapfield->sortorder = $field->sortorder;
400
        $field->sortorder     = $neworder;
401
 
402
        // Update the field records.
403
        $DB->update_record('user_info_field', $field);
404
        $DB->update_record('user_info_field', $swapfield);
405
 
406
        \core\event\user_info_field_updated::create_from_field($field)->trigger();
407
        \core\event\user_info_field_updated::create_from_field($swapfield)->trigger();
408
    }
409
 
410
    profile_reorder_fields();
411
    return true;
412
}
413
 
414
/**
415
 * Change the sort order of a category.
416
 *
417
 * @param int $id of the category
418
 * @param string $move direction of move
419
 * @return bool success of operation
420
 */
421
function profile_move_category($id, $move) {
422
    global $DB;
423
    // Get the category object.
424
    if (!($category = $DB->get_record('user_info_category', array('id' => $id)))) {
425
        return false;
426
    }
427
 
428
    // Count the number of categories.
429
    $categorycount = $DB->count_records('user_info_category');
430
 
431
    // Calculate the new sortorder.
432
    if (($move == 'up') and ($category->sortorder > 1)) {
433
        $neworder = $category->sortorder - 1;
434
    } else if (($move == 'down') and ($category->sortorder < $categorycount)) {
435
        $neworder = $category->sortorder + 1;
436
    } else {
437
        return false;
438
    }
439
 
440
    // Retrieve the category object that is currently residing in the new position.
441
    if ($swapcategory = $DB->get_record('user_info_category', array('sortorder' => $neworder))) {
442
 
443
        // Swap the sortorders.
444
        $swapcategory->sortorder = $category->sortorder;
445
        $category->sortorder     = $neworder;
446
 
447
        // Update the category records.
448
        $DB->update_record('user_info_category', $category);
449
        $DB->update_record('user_info_category', $swapcategory);
450
 
451
        \core\event\user_info_category_updated::create_from_category($category)->trigger();
452
        \core\event\user_info_category_updated::create_from_category($swapcategory)->trigger();
453
        profile_purge_user_fields_cache();
454
 
455
        return true;
456
    }
457
 
458
    return false;
459
}
460
 
461
/**
462
 * Retrieve a list of all the available data types
463
 * @return   array   a list of the datatypes suitable to use in a select statement
464
 */
465
function profile_list_datatypes() {
466
    $datatypes = array();
467
 
468
    $plugins = core_component::get_plugin_list('profilefield');
469
    foreach ($plugins as $type => $unused) {
470
        $datatypes[$type] = get_string('pluginname', 'profilefield_'.$type);
471
    }
472
    asort($datatypes);
473
 
474
    return $datatypes;
475
}
476
 
477
/**
478
 * Retrieve a list of categories and ids suitable for use in a form
479
 * @return   array
480
 */
481
function profile_list_categories() {
482
    global $DB;
483
    $categories = $DB->get_records_menu('user_info_category', null, 'sortorder ASC', 'id, name');
484
    return array_map('format_string', $categories);
485
}
486
 
487
/**
488
 * Create or update a profile category
489
 *
490
 * @param stdClass $data
491
 */
492
function profile_save_category(stdClass $data): void {
493
    global $DB;
494
 
495
    if (empty($data->id)) {
496
        unset($data->id);
497
        $data->sortorder = $DB->count_records('user_info_category') + 1;
498
        $data->id = $DB->insert_record('user_info_category', $data, true);
499
 
500
        $createdcategory = $DB->get_record('user_info_category', array('id' => $data->id));
501
        \core\event\user_info_category_created::create_from_category($createdcategory)->trigger();
502
    } else {
503
        $DB->update_record('user_info_category', $data);
504
 
505
        $updatedcateogry = $DB->get_record('user_info_category', array('id' => $data->id));
506
        \core\event\user_info_category_updated::create_from_category($updatedcateogry)->trigger();
507
    }
508
    profile_reorder_categories();
509
    profile_purge_user_fields_cache();
510
}
511
 
512
/**
513
 * Save updated field definition or create a new field
514
 *
515
 * @param stdClass $data data from the form profile_field_form
516
 * @param array $editors editors for this form field type
517
 */
518
function profile_save_field(stdClass $data, array $editors): void {
519
    global $CFG;
520
 
521
    require_once($CFG->dirroot.'/user/profile/field/'.$data->datatype.'/define.class.php');
522
    $newfield = 'profile_define_'.$data->datatype;
523
    /** @var profile_define_base $formfield */
524
    $formfield = new $newfield();
525
 
526
    // Collect the description and format back into the proper data structure from the editor.
527
    // Note: This field will ALWAYS be an editor.
528
    $data->descriptionformat = $data->description['format'];
529
    $data->description = $data->description['text'];
530
 
531
    // Check whether the default data is an editor, this is (currently) only the textarea field type.
532
    if (is_array($data->defaultdata) && array_key_exists('text', $data->defaultdata)) {
533
        // Collect the default data and format back into the proper data structure from the editor.
534
        $data->defaultdataformat = $data->defaultdata['format'];
535
        $data->defaultdata = $data->defaultdata['text'];
536
    }
537
 
538
    // Convert the data format for.
539
    if (is_array($editors)) {
540
        foreach ($editors as $editor) {
541
            if (isset($field->$editor)) {
542
                $field->{$editor.'format'} = $field->{$editor}['format'];
543
                $field->$editor = $field->{$editor}['text'];
544
            }
545
        }
546
    }
547
 
548
    $formfield->define_save($data);
549
    profile_reorder_fields();
550
    profile_reorder_categories();
551
}
552
 
553
/**
554
 * Purge the cache for the user profile fields
555
 */
556
function profile_purge_user_fields_cache() {
557
    $cache = \cache::make_from_params(cache_store::MODE_REQUEST, 'core_profile', 'customfields',
558
        [], ['simplekeys' => true, 'simpledata' => true]);
559
    $cache->purge();
560
}