Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | 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
namespace core_user;
18
 
19
use core_text;
1441 ariadna 20
use core_user;
1 efrain 21
 
22
/**
23
 * Class for retrieving information about user fields that are needed for displaying user identity.
24
 *
25
 * @package core_user
26
 */
27
class fields {
28
    /** @var string Prefix used to identify custom profile fields */
29
    const PROFILE_FIELD_PREFIX = 'profile_field_';
30
    /** @var string Regular expression used to match a field name against the prefix */
31
    const PROFILE_FIELD_REGEX = '~^' . self::PROFILE_FIELD_PREFIX . '(.*)$~';
32
 
33
    /** @var int All fields required to display user's identity, based on server configuration */
34
    const PURPOSE_IDENTITY = 0;
35
    /** @var int All fields required to display a user picture */
36
    const PURPOSE_USERPIC = 1;
37
    /** @var int All fields required for somebody's name */
38
    const PURPOSE_NAME = 2;
39
    /** @var int Field required by custom include list */
40
    const CUSTOM_INCLUDE = 3;
41
 
42
    /** @var \context|null Context in use */
43
    protected $context;
44
 
45
    /** @var bool True to allow custom user fields */
46
    protected $allowcustom;
47
 
48
    /** @var bool[] Array of purposes (from PURPOSE_xx to true/false) */
49
    protected $purposes;
50
 
51
    /** @var string[] List of extra fields to include */
52
    protected $include;
53
 
54
    /** @var string[] List of fields to exclude */
55
    protected $exclude;
56
 
57
    /** @var int Unique identifier for different queries generated in same request */
58
    protected static $uniqueidentifier = 1;
59
 
60
    /** @var array|null Associative array from field => array of purposes it was used for => true */
61
    protected $fields = null;
62
 
63
    /**
64
     * Protected constructor - use one of the for_xx methods to create an object.
65
     *
66
     * @param int $purpose Initial purpose for object or -1 for none
67
     */
68
    protected function __construct(int $purpose = -1) {
69
        $this->purposes = [
70
            self::PURPOSE_IDENTITY => false,
71
            self::PURPOSE_USERPIC => false,
72
            self::PURPOSE_NAME => false,
73
        ];
74
        if ($purpose != -1) {
75
            $this->purposes[$purpose] = true;
76
        }
77
        $this->include = [];
78
        $this->exclude = [];
79
        $this->context = null;
80
        $this->allowcustom = true;
81
    }
82
 
83
    /**
84
     * Constructs an empty user fields object to get arbitrary user fields.
85
     *
86
     * You can add fields to retrieve with the including() function.
87
     *
88
     * @return fields User fields object ready for use
89
     */
90
    public static function empty(): fields {
91
        return new fields();
92
    }
93
 
94
    /**
95
     * Constructs a user fields object to get identity information for display.
96
     *
97
     * The function does all the required capability checks to see if the current user is allowed
98
     * to see them in the specified context. You can pass context null to get all the fields without
99
     * checking permissions.
100
     *
101
     * If the code can only handle fields in the main user table, and not custom profile fields,
102
     * then set $allowcustom to false.
103
     *
104
     * Note: After constructing the object you can use the ->with_xx, ->including, and ->excluding
105
     * functions to control the required fields in more detail. For example:
106
     *
107
     * $fields = fields::for_identity($context)->with_userpic()->excluding('email');
108
     *
109
     * @param \context|null $context Context; if supplied, includes only fields the current user should see
110
     * @param bool $allowcustom If true, custom profile fields may be included
111
     * @return fields User fields object ready for use
112
     */
113
    public static function for_identity(?\context $context, bool $allowcustom = true): fields {
114
        $fields = new fields(self::PURPOSE_IDENTITY);
115
        $fields->context = $context;
116
        $fields->allowcustom = $allowcustom;
117
        return $fields;
118
    }
119
 
120
    /**
121
     * Constructs a user fields object to get information required for displaying a user picture.
122
     *
123
     * Note: After constructing the object you can use the ->with_xx, ->including, and ->excluding
124
     * functions to control the required fields in more detail. For example:
125
     *
126
     * $fields = fields::for_userpic()->with_name()->excluding('email');
127
     *
128
     * @return fields User fields object ready for use
129
     */
130
    public static function for_userpic(): fields {
131
        return new fields(self::PURPOSE_USERPIC);
132
    }
133
 
134
    /**
135
     * Constructs a user fields object to get information required for displaying a user full name.
136
     *
137
     * Note: After constructing the object you can use the ->with_xx, ->including, and ->excluding
138
     * functions to control the required fields in more detail. For example:
139
     *
140
     * $fields = fields::for_name()->with_userpic()->excluding('email');
141
     *
142
     * @return fields User fields object ready for use
143
     */
144
    public static function for_name(): fields {
145
        return new fields(self::PURPOSE_NAME);
146
    }
147
 
148
    /**
149
     * On an existing fields object, adds the fields required for displaying user pictures.
150
     *
151
     * @return $this Same object for chaining function calls
152
     */
153
    public function with_userpic(): fields {
154
        $this->purposes[self::PURPOSE_USERPIC] = true;
155
        return $this;
156
    }
157
 
158
    /**
159
     * On an existing fields object, adds the fields required for displaying user full names.
160
     *
161
     * @return $this Same object for chaining function calls
162
     */
163
    public function with_name(): fields {
164
        $this->purposes[self::PURPOSE_NAME] = true;
165
        return $this;
166
    }
167
 
168
    /**
169
     * On an existing fields object, adds the fields required for displaying user identity.
170
     *
171
     * The function does all the required capability checks to see if the current user is allowed
172
     * to see them in the specified context. You can pass context null to get all the fields without
173
     * checking permissions.
174
     *
175
     * If the code can only handle fields in the main user table, and not custom profile fields,
176
     * then set $allowcustom to false.
177
     *
178
     * @param \context|null Context; if supplied, includes only fields the current user should see
179
     * @param bool $allowcustom If true, custom profile fields may be included
180
     * @return $this Same object for chaining function calls
181
     */
182
    public function with_identity(?\context $context, bool $allowcustom = true): fields {
183
        $this->context = $context;
184
        $this->allowcustom = $allowcustom;
185
        $this->purposes[self::PURPOSE_IDENTITY] = true;
186
        return $this;
187
    }
188
 
189
    /**
190
     * On an existing fields object, adds extra fields to be retrieved. You can specify either
191
     * fields from the user table e.g. 'email', or profile fields e.g. 'profile_field_height'.
192
     *
193
     * @param string ...$include One or more fields to add
194
     * @return $this Same object for chaining function calls
195
     */
196
    public function including(string ...$include): fields {
197
        $this->include = array_merge($this->include, $include);
198
        return $this;
199
    }
200
 
201
    /**
202
     * On an existing fields object, excludes fields from retrieval. You can specify either
203
     * fields from the user table e.g. 'email', or profile fields e.g. 'profile_field_height'.
204
     *
205
     * This is useful when constructing queries where your query already explicitly references
206
     * certain fields, so you don't want to retrieve them twice.
207
     *
208
     * @param string ...$exclude One or more fields to exclude
209
     * @return $this Same object for chaining function calls
210
     */
211
    public function excluding(...$exclude): fields {
212
        $this->exclude = array_merge($this->exclude, $exclude);
213
        return $this;
214
    }
215
 
216
    /**
217
     * Gets an array of all fields that are required for the specified purposes, also taking
218
     * into account the $includes and $excludes settings.
219
     *
220
     * The results may include basic field names (columns from the 'user' database table) and,
221
     * unless turned off, custom profile field names in the format 'profile_field_myfield'.
222
     *
223
     * You should not rely on the order of fields, with one exception: if there is an id field
224
     * it will be returned first. This is in case it is used with get_records calls.
225
     *
226
     * The $limitpurposes parameter is useful if you want to get a different set of fields than the
227
     * purposes in the constructor. For example, if you want to get SQL for identity + user picture
228
     * fields, but you then want to only get the identity fields as a list. (You can only specify
229
     * purposes that were also passed to the constructor i.e. it can only be used to restrict the
230
     * list, not add to it.)
231
     *
232
     * @param array $limitpurposes If specified, gets fields only for these purposes
233
     * @return string[] Array of required fields
234
     * @throws \coding_exception If any unknown purpose is listed
235
     */
236
    public function get_required_fields(array $limitpurposes = []): array {
237
        // The first time this is called, actually work out the list. There is no way to 'un-cache'
238
        // it, but these objects are designed to be short-lived so it doesn't need one.
239
        if ($this->fields === null) {
240
            // Add all the fields as array keys so that there are no duplicates.
241
            $this->fields = [];
242
            if ($this->purposes[self::PURPOSE_IDENTITY]) {
243
                foreach (self::get_identity_fields($this->context, $this->allowcustom) as $field) {
244
                    $this->fields[$field] = [self::PURPOSE_IDENTITY => true];
245
                }
246
            }
247
            if ($this->purposes[self::PURPOSE_USERPIC]) {
248
                foreach (self::get_picture_fields() as $field) {
249
                    if (!array_key_exists($field, $this->fields)) {
250
                        $this->fields[$field] = [];
251
                    }
252
                    $this->fields[$field][self::PURPOSE_USERPIC] = true;
253
                }
254
            }
255
            if ($this->purposes[self::PURPOSE_NAME]) {
256
                foreach (self::get_name_fields() as $field) {
257
                    if (!array_key_exists($field, $this->fields)) {
258
                        $this->fields[$field] = [];
259
                    }
260
                    $this->fields[$field][self::PURPOSE_NAME] = true;
261
                }
262
            }
263
            foreach ($this->include as $field) {
264
                if ($this->allowcustom || !preg_match(self::PROFILE_FIELD_REGEX, $field)) {
265
                    if (!array_key_exists($field, $this->fields)) {
266
                        $this->fields[$field] = [];
267
                    }
268
                    $this->fields[$field][self::CUSTOM_INCLUDE] = true;
269
                }
270
            }
271
            foreach ($this->exclude as $field) {
272
                unset($this->fields[$field]);
273
            }
274
 
275
            // If the id field is included, make sure it's first in the list.
276
            if (array_key_exists('id', $this->fields)) {
277
                $newfields = ['id' => $this->fields['id']];
278
                foreach ($this->fields as $field => $purposes) {
279
                    if ($field !== 'id') {
280
                        $newfields[$field] = $purposes;
281
                    }
282
                }
283
                $this->fields = $newfields;
284
            }
285
        }
286
 
287
        if ($limitpurposes) {
288
            // Check the value was legitimate.
289
            foreach ($limitpurposes as $purpose) {
290
                if ($purpose != self::CUSTOM_INCLUDE && empty($this->purposes[$purpose])) {
291
                    throw new \coding_exception('$limitpurposes can only include purposes defined in object');
292
                }
293
            }
294
 
295
            // Filter the fields to include only those matching the purposes.
296
            $result = [];
297
            foreach ($this->fields as $key => $purposes) {
298
                foreach ($limitpurposes as $purpose) {
299
                    if (array_key_exists($purpose, $purposes)) {
300
                        $result[] = $key;
301
                        break;
302
                    }
303
                }
304
            }
305
            return $result;
306
        } else {
307
            return array_keys($this->fields);
308
        }
309
    }
310
 
311
    /**
312
     * Gets fields required for user pictures.
313
     *
314
     * The results include only basic field names (columns from the 'user' database table).
315
     *
316
     * @return string[] All fields required for user pictures
317
     */
318
    public static function get_picture_fields(): array {
319
        return ['id', 'picture', 'firstname', 'lastname', 'firstnamephonetic', 'lastnamephonetic',
320
                'middlename', 'alternatename', 'imagealt', 'email'];
321
    }
322
 
323
    /**
324
     * Gets fields required for user names.
325
     *
326
     * The results include only basic field names (columns from the 'user' database table).
327
     *
328
     * Fields are usually returned in a specific order, which the fullname() function depends on.
329
     * If you specify 'true' to the $strangeorder flag, then the firstname and lastname fields
330
     * are moved to the front; this is useful in a few places in existing code. New code should
331
     * avoid requiring a particular order.
332
     *
333
     * @param bool $differentorder In a few places, a different order of fields is required
334
     * @return string[] All fields used to display user names
335
     */
336
    public static function get_name_fields(bool $differentorder = false): array {
337
        $fields = ['firstnamephonetic', 'lastnamephonetic', 'middlename', 'alternatename',
338
                'firstname', 'lastname'];
339
        if ($differentorder) {
340
            return array_merge(array_slice($fields, -2), array_slice($fields, 0, -2));
341
        } else {
342
            return $fields;
343
        }
344
    }
345
 
346
    /**
347
     * Gets all fields required for user identity. These fields should be included in tables
348
     * showing lists of users (in addition to the user's name which is included as standard).
349
     *
350
     * The results include basic field names (columns from the 'user' database table) and, unless
351
     * turned off, custom profile field names in the format 'profile_field_myfield', note these
352
     * fields will always be returned lower cased to match how they are returned by the DML library.
353
     *
354
     * This function does all the required capability checks to see if the current user is allowed
355
     * to see them in the specified context. You can pass context null to get all the fields
356
     * without checking permissions.
357
     *
358
     * @param \context|null $context Context; if not supplied, all fields will be included without checks
359
     * @param bool $allowcustom If true, custom profile fields will be included
360
     * @return string[] Array of required fields
361
     * @throws \coding_exception
362
     */
363
    public static function get_identity_fields(?\context $context, bool $allowcustom = true): array {
364
        global $CFG;
365
 
366
        // Only users with permission get the extra fields.
367
        if ($context && !has_capability('moodle/site:viewuseridentity', $context)) {
368
            return [];
369
        }
370
 
371
        // Split showuseridentity on comma (filter needed in case the showuseridentity is empty).
372
        $extra = array_filter(explode(',', $CFG->showuseridentity));
373
 
374
        // If there are any custom fields, remove them if necessary (either if allowcustom is false,
375
        // or if the user doesn't have access to see them).
376
        foreach ($extra as $key => $field) {
377
            if (preg_match(self::PROFILE_FIELD_REGEX, $field, $matches)) {
378
                $allowed = false;
379
                if ($allowcustom) {
380
                    require_once($CFG->dirroot . '/user/profile/lib.php');
381
 
382
                    // Ensure the field exists (it may have been deleted since user identity was configured).
383
                    $field = profile_get_custom_field_data_by_shortname($matches[1], false);
384
                    if ($field !== null) {
385
                        $fieldinstance = profile_get_user_field($field->datatype, $field->id, 0, $field);
386
                        $allowed = $fieldinstance->is_visible($context);
387
                    }
388
                }
389
                if (!$allowed) {
390
                    unset($extra[$key]);
391
                }
392
            }
393
        }
394
 
395
        // For standard user fields, access is controlled by the hiddenuserfields option and
396
        // some different capabilities. Check and remove these if the user can't access them.
397
        $hiddenfields = array_filter(explode(',', $CFG->hiddenuserfields));
398
        $hiddenidentifiers = array_intersect($extra, $hiddenfields);
399
 
400
        if ($hiddenidentifiers) {
401
            if (!$context) {
402
                $canviewhiddenuserfields = true;
403
            } else if ($context->get_course_context(false)) {
404
                // We are somewhere inside a course.
405
                $canviewhiddenuserfields = has_capability('moodle/course:viewhiddenuserfields', $context);
406
            } else {
407
                // We are not inside a course.
408
                $canviewhiddenuserfields = has_capability('moodle/user:viewhiddendetails', $context);
409
            }
410
 
411
            if (!$canviewhiddenuserfields) {
412
                // Remove hidden identifiers from the list.
413
                $extra = array_diff($extra, $hiddenidentifiers);
414
            }
415
        }
416
 
417
        // Re-index the entries and return.
418
        $extra = array_values($extra);
419
        return array_map([core_text::class, 'strtolower'], $extra);
420
    }
421
 
422
    /**
423
     * Gets SQL that can be used in a query to get the necessary fields.
424
     *
425
     * The result of this function is an object with fields 'selects', 'joins', 'params', and
426
     * 'mappings'.
427
     *
428
     * If not empty, the list of selects will begin with a comma and the list of joins will begin
429
     * and end with a space. You can include the result in your existing query like this:
430
     *
431
     * SELECT (your existing fields)
432
     *        $selects
433
     *   FROM {user} u
434
     *   JOIN (your existing joins)
435
     *        $joins
436
     *
437
     * When there are no custom fields then the 'joins' result will always be an empty string, and
438
     * 'params' will be an empty array.
439
     *
440
     * The $fieldmappings value is often not needed. It is an associative array from each field
441
     * name to an SQL expression for the value of that field, e.g.:
442
     *   'profile_field_frog' => 'uf1d_3.data'
443
     *   'city' => 'u.city'
444
     * This is helpful if you want to use the profile fields in a WHERE clause, becuase you can't
445
     * refer to the aliases used in the SELECT list there.
446
     *
447
     * The leading comma is included because this makes it work in the pattern above even if there
448
     * are no fields from the get_sql() data (which can happen if doing identity fields and none
449
     * are selected). If you want the result without a leading comma, set $leadingcomma to false.
450
     *
451
     * If the 'id' field is included then it will always be first in the list. Otherwise, you
452
     * should not rely on the field order.
453
     *
454
     * For identity fields, the function does all the required capability checks to see if the
455
     * current user is allowed to see them in the specified context. You can pass context null
456
     * to get all the fields without checking permissions.
457
     *
458
     * If your code for any reason cannot cope with custom fields then you can turn them off.
459
     *
460
     * You can have either named or ? params. If you use named params, they are of the form
461
     * uf1s_2; the first number increments in each call using a static variable in this class and
462
     * the second number refers to the field being queried. A similar pattern is used to make
463
     * join aliases unique.
464
     *
465
     * If your query refers to the user table by an alias e.g. 'u' then specify this in the $alias
466
     * parameter; otherwise it will use {user} (if there are any joins for custom profile fields)
467
     * or simply refer to the field by name only (if there aren't).
468
     *
469
     * If you need to use a prefix on the field names (for example in case they might coincide with
470
     * existing result columns from your query, or if you want a convenient way to split out all
471
     * the user data into a separate object) then you can specify one here. For example, if you
472
     * include name fields and the prefix is 'u_' then the results will include 'u_firstname'.
473
     *
474
     * If you don't want to prefix all the field names but only change the id field name, use
475
     * the $renameid parameter. (When you use this parameter, it takes precedence over any prefix;
476
     * the id field will not be prefixed, while all others will.)
477
     *
478
     * @param string $alias Optional (but recommended) alias for user table in query, e.g. 'u'
479
     * @param bool $namedparams If true, uses named :parameters instead of indexed ? parameters
480
     * @param string $prefix Optional prefix for all field names in result, e.g. 'u_'
481
     * @param string $renameid Renames the 'id' field if specified, e.g. 'userid'
482
     * @param bool $leadingcomma If true the 'selects' list will start with a comma
483
     * @return \stdClass Object with necessary SQL components
484
     */
485
    public function get_sql(string $alias = '', bool $namedparams = false, string $prefix = '',
486
            string $renameid = '', bool $leadingcomma = true): \stdClass {
487
        global $DB;
488
 
489
        $fields = $this->get_required_fields();
490
 
491
        $selects = '';
492
        $joins = '';
493
        $params = [];
494
        $mappings = [];
495
 
496
        $unique = self::$uniqueidentifier++;
497
        $fieldcount = 0;
498
 
499
        if ($alias) {
500
            $usertable = $alias . '.';
501
        } else {
502
            // If there is no alias, we still need to use {user} to identify the table when there
503
            // are joins with other tables. When there are no customfields then there are no joins
504
            // so we can refer to the fields by name alone.
505
            $gotcustomfields = false;
506
            foreach ($fields as $field) {
507
                if (preg_match(self::PROFILE_FIELD_REGEX, $field, $matches)) {
508
                    $gotcustomfields = true;
509
                    break;
510
                }
511
            }
512
            if ($gotcustomfields) {
513
                $usertable = '{user}.';
514
            } else {
515
                $usertable = '';
516
            }
517
        }
518
 
519
        foreach ($fields as $field) {
520
            if (preg_match(self::PROFILE_FIELD_REGEX, $field, $matches)) {
521
                // Custom profile field.
522
                $shortname = $matches[1];
523
 
524
                $fieldcount++;
525
 
526
                $fieldalias = 'uf' . $unique . 'f_' . $fieldcount;
527
                $dataalias = 'uf' . $unique . 'd_' . $fieldcount;
528
                if ($namedparams) {
529
                    $withoutcolon = 'uf' . $unique . 's' . $fieldcount;
530
                    $placeholder = ':' . $withoutcolon;
531
                    $params[$withoutcolon] = $shortname;
532
                } else {
533
                    $placeholder = '?';
534
                    $params[] = $shortname;
535
                }
536
                $joins .= " JOIN {user_info_field} $fieldalias ON " .
537
                                 $DB->sql_equal($fieldalias . '.shortname', $placeholder, false) . "
538
                       LEFT JOIN {user_info_data} $dataalias ON $dataalias.fieldid = $fieldalias.id
539
                                 AND $dataalias.userid = {$usertable}id";
1441 ariadna 540
                // For sqlsrv we need to convert the field into a usable format.
1 efrain 541
                $fieldsql = $DB->sql_compare_text($dataalias . '.data', 255);
542
                $selects .= ", $fieldsql AS $prefix$field";
543
                $mappings[$field] = $fieldsql;
544
            } else {
545
                // Standard user table field.
546
                $selects .= ", $usertable$field";
547
                if ($field === 'id' && $renameid && $renameid !== 'id') {
548
                    $selects .= " AS $renameid";
549
                } else if ($prefix) {
550
                    $selects .= " AS $prefix$field";
551
                }
552
                $mappings[$field] = "$usertable$field";
553
            }
554
        }
555
 
556
        // Add a space to the end of the joins list; this means it can be appended directly into
557
        // any existing query without worrying about whether the developer has remembered to add
558
        // whitespace after it.
559
        if ($joins) {
560
            $joins .= ' ';
561
        }
562
 
563
        // Optionally remove the leading comma.
564
        if (!$leadingcomma) {
565
            $selects = ltrim($selects, ' ,');
566
        }
567
 
568
        return (object)['selects' => $selects, 'joins' => $joins, 'params' => $params,
569
                'mappings' => $mappings];
570
    }
571
 
572
    /**
573
     * Similar to {@see \moodle_database::sql_fullname} except it returns all user name fields as defined by site config, in a
574
     * single select statement suitable for inclusion in a query/filter for a users fullname, e.g.
575
     *
576
     * [$select, $params] = fields::get_sql_fullname('u');
577
     * $users = $DB->get_records_sql_menu("SELECT u.id, {$select} FROM {user} u", $params);
578
     *
579
     * @param string|null $tablealias User table alias, if set elsewhere in the query, null if not required
580
     * @param bool $override If true then the alternativefullnameformat format rather than fullnamedisplay format will be used
581
     * @return array SQL select snippet and parameters
582
     */
583
    public static function get_sql_fullname(?string $tablealias = 'u', bool $override = false): array {
584
        global $DB;
585
 
586
        $unique = self::$uniqueidentifier++;
587
 
588
        $namefields = self::get_name_fields();
1441 ariadna 589
        $dummyfullname = core_user::get_dummy_fullname(null, ['override' => $override]);
1 efrain 590
 
591
        // Extract any name fields from the fullname format in the order that they appear.
592
        $matchednames = array_values(order_in_string($namefields, $dummyfullname));
593
        $namelookup = $namepattern = $elements = $params = [];
594
 
595
        foreach ($namefields as $index => $namefield) {
596
            $namefieldwithalias = $tablealias ? "{$tablealias}.{$namefield}" : $namefield;
597
 
598
            // Coalesce the name fields to ensure we don't return null.
599
            $emptyparam = "uf{$unique}ep_{$index}";
600
            $namelookup[$namefield] = "COALESCE({$namefieldwithalias}, :{$emptyparam})";
601
            $params[$emptyparam] = '';
602
 
603
            $namepattern[] = '\b' . preg_quote($namefield) . '\b';
604
        }
605
 
606
        // Grab any content between the name fields, inserting them after each name field.
607
        $chunks = preg_split('/(' . implode('|', $namepattern) . ')/', $dummyfullname);
608
        foreach ($chunks as $index => $chunk) {
609
            if ($index > 0) {
610
                $elements[] = $namelookup[$matchednames[$index - 1]];
611
            }
612
 
613
            if (core_text::strlen($chunk) > 0) {
1441 ariadna 614
                // If content is just whitespace, add it directly to elements to handle it appropriately.
1 efrain 615
                if (preg_match('/^\s+$/', $chunk)) {
616
                    $elements[] = "'$chunk'";
617
                } else {
618
                    $elementparam = "uf{$unique}fp_{$index}";
619
                    $elements[] = ":{$elementparam}";
620
                    $params[$elementparam] = $chunk;
621
                }
622
            }
623
        }
624
 
625
        return [$DB->sql_concat(...$elements), $params];
626
    }
627
 
628
    /**
629
     * Gets the display name of a given user field.
630
     *
631
     * Supports field names from the 'user' database table, and custom profile fields supplied in
632
     * the format 'profile_field_xx'.
633
     *
634
     * @param string $field Field name in database
635
     * @return string Field name for display to user
636
     * @throws \coding_exception
637
     */
638
    public static function get_display_name(string $field): string {
639
        global $CFG;
640
 
641
        // Custom fields have special handling.
642
        if (preg_match(self::PROFILE_FIELD_REGEX, $field, $matches)) {
643
            require_once($CFG->dirroot . '/user/profile/lib.php');
644
            $fieldinfo = profile_get_custom_field_data_by_shortname($matches[1], false);
645
            // Use format_string so it can be translated with multilang filter if necessary.
646
            return $fieldinfo ? format_string($fieldinfo->name) : $field;
647
        }
648
 
649
        // Some fields have language strings which are not the same as field name.
650
        switch ($field) {
651
            case 'picture' : {
652
                return get_string('pictureofuser');
653
            }
654
        }
655
        // Otherwise just use the same lang string.
656
        return get_string($field);
657
    }
658
 
659
    /**
660
     * Resets the unique identifier used to ensure that multiple SQL fragments generated in the
661
     * same request will have different identifiers for parameters and table aliases.
662
     *
663
     * This is intended only for use in unit testing.
664
     */
665
    public static function reset_unique_identifier() {
666
        self::$uniqueidentifier = 1;
667
    }
668
 
669
    /**
670
     * Checks if a field name looks like a custom profile field i.e. it begins with profile_field_
671
     * (does not check if that profile field actually exists).
672
     *
673
     * @param string $fieldname Field name
674
     * @return string Empty string if not a profile field, or profile field name (without profile_field_)
675
     */
676
    public static function match_custom_field(string $fieldname): string {
677
        if (preg_match(self::PROFILE_FIELD_REGEX, $fieldname, $matches)) {
678
            return $matches[1];
679
        } else {
680
            return '';
681
        }
682
    }
683
}