Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
25.59% covered (danger)
25.59%
65 / 254
15.38% covered (danger)
15.38%
2 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
manager
25.59% covered (danger)
25.59%
65 / 254
15.38% covered (danger)
15.38%
2 / 13
1698.18
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
1
 generate_profile_fields
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
72
 build_cache
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 submit_cp_field
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
90
 update_profile_field_data
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 generate_profile_fields_template_headlines
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 grab_profile_fields_data
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
56
 generate_profile_fields_template_data
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 1
56
 build_insert_sql_array
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
 disable_profilefields
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 purge_profilefields
96.30% covered (success)
96.30%
26 / 27
0.00% covered (danger)
0.00%
0 / 1
6
 enable_profilefields
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
3.01
 list_profilefields
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 *
4 * This file is part of the phpBB Forum Software package.
5 *
6 * @copyright (c) phpBB Limited <https://www.phpbb.com>
7 * @license GNU General Public License, version 2 (GPL-2.0)
8 *
9 * For full copyright and license information, please see
10 * the docs/CREDITS.txt file.
11 *
12 */
13
14namespace phpbb\profilefields;
15
16/**
17 * Custom Profile Fields (CPF) manager.
18 */
19class manager
20{
21    /** @var \phpbb\auth\auth */
22    protected $auth;
23
24    /** @var \phpbb\config\db_text */
25    protected $config_text;
26
27    /** @var \phpbb\db\driver\driver_interface */
28    protected $db;
29
30    /** @var \phpbb\db\tools\tools_interface */
31    protected $db_tools;
32
33    /** @var \phpbb\event\dispatcher_interface */
34    protected $dispatcher;
35
36    /** @var \phpbb\language\language */
37    protected $language;
38
39    /** @var \phpbb\log\log */
40    protected $log;
41
42    /** @var \phpbb\template\template */
43    protected $template;
44
45    /** @var \phpbb\di\service_collection */
46    protected $type_collection;
47
48    /** @var \phpbb\user */
49    protected $user;
50
51    /** @var string Profile fields table */
52    protected $fields_table;
53
54    /** @var string Profile fields data table */
55    protected $fields_data_table;
56
57    /** @var string Profile fields data (options) table */
58    protected $fields_data_lang_table;
59
60    /** @var string Profile fields language table */
61    protected $fields_lang_table;
62
63    /** @var array Users custom profile fields cache */
64    protected $profile_cache = [];
65
66    /**
67     * Construct
68     *
69     * @param \phpbb\auth\auth                    $auth                    Auth object
70     * @param \phpbb\config\db_text                $config_text            Config_text object
71     * @param \phpbb\db\driver\driver_interface    $db                        Database object
72     * @param \phpbb\db\tools\tools_interface            $db_tools                Database tools object
73     * @param \phpbb\event\dispatcher_interface    $dispatcher                Event dispatcher object
74     * @param \phpbb\language\language            $language                Language object
75     * @param \phpbb\log\log                    $log                    Log object
76     * @param \phpbb\template\template            $template                Template object
77     * @param \phpbb\di\service_collection        $type_collection        CPF Type collection
78     * @param \phpbb\user                        $user                    User object
79     * @param string                            $fields_table            CPF Table
80     * @param string                            $fields_data_table        CPF Data table
81     * @param string                            $fields_data_lang_table    CPF Data language table
82     * @param string                            $fields_lang_table        CPF Language table
83     */
84    public function __construct(
85        \phpbb\auth\auth $auth,
86        \phpbb\config\db_text $config_text,
87        \phpbb\db\driver\driver_interface $db,
88        \phpbb\db\tools\tools_interface $db_tools,
89        \phpbb\event\dispatcher_interface $dispatcher,
90        \phpbb\language\language $language,
91        \phpbb\log\log $log,
92        \phpbb\template\template $template,
93        \phpbb\di\service_collection $type_collection,
94        \phpbb\user $user,
95        $fields_table,
96        $fields_data_table,
97        $fields_data_lang_table,
98        $fields_lang_table
99    )
100    {
101        $this->auth                = $auth;
102        $this->config_text        = $config_text;
103        $this->db                = $db;
104        $this->db_tools            = $db_tools;
105        $this->dispatcher        = $dispatcher;
106        $this->language            = $language;
107        $this->log                = $log;
108        $this->template            = $template;
109        $this->type_collection    = $type_collection;
110        $this->user                = $user;
111
112        $this->fields_table                = $fields_table;
113        $this->fields_data_table        = $fields_data_table;
114        $this->fields_data_lang_table    = $fields_data_lang_table;
115        $this->fields_lang_table        = $fields_lang_table;
116    }
117
118    /**
119     * Assign editable fields to template.
120     *
121     * Called by ucp_profile and ucp_register.
122     *
123     * @param string    $mode        The mode (profile|register)
124     * @param int        $lang_id    The language identifier
125     * @return void
126     */
127    public function generate_profile_fields($mode, $lang_id)
128    {
129        $sql_where = '';
130
131        switch ($mode)
132        {
133            case 'register':
134                // If the field is required we show it on the registration page
135                $sql_where .= ' AND f.field_show_on_reg = 1';
136            break;
137
138            case 'profile':
139                // Show hidden fields to moderators/admins
140                if (!$this->auth->acl_gets('a_', 'm_') && !$this->auth->acl_getf_global('m_'))
141                {
142                    $sql_where .= ' AND f.field_show_profile = 1';
143                }
144            break;
145
146            default:
147                trigger_error('NO_MODE', E_USER_ERROR);
148            break;
149        }
150
151        $has_required = false;
152
153        $sql = 'SELECT l.*, f.*
154            FROM ' . $this->fields_lang_table . ' l,
155                ' . $this->fields_table . ' f
156            WHERE l.field_id = f.field_id
157                AND f.field_active = 1
158                AND l.lang_id = ' . (int) $lang_id
159            . $sql_where . '
160            ORDER BY f.field_order ASC';
161        $result = $this->db->sql_query($sql);
162        while ($row = $this->db->sql_fetchrow($result))
163        {
164            /** @var \phpbb\profilefields\type\type_interface $profile_field */
165            $profile_field = $this->type_collection[$row['field_type']];
166
167            $has_required = $has_required || $row['field_required'];
168
169            $this->template->assign_block_vars('profile_fields', [
170                'FIELD'            => $profile_field->process_field_row('change', $row),
171                'FIELD_ID'        => $profile_field->get_field_ident($row),
172                'LANG_NAME'        => $this->language->lang($row['lang_name']),
173                'LANG_EXPLAIN'    => $this->language->lang($row['lang_explain']),
174                'S_REQUIRED'    => (bool) $row['field_required'],
175            ]);
176        }
177        $this->db->sql_freeresult($result);
178
179        $this->template->assign_var('PROFILE_FIELDS_REQUIRED', $has_required);
180    }
181
182    /**
183     * Build profile cache, used for display.
184     *
185     * @return void
186     */
187    protected function build_cache()
188    {
189        $this->profile_cache = [];
190
191        // Display hidden/no_view fields for admin/moderator
192        $sql_where = !$this->auth->acl_gets('a_', 'm_') && !$this->auth->acl_getf_global('m_') ? ' AND f.field_hide = 0' : '';
193
194        $sql = 'SELECT l.*, f.*
195            FROM ' . $this->fields_lang_table . ' l,
196                ' . $this->fields_table . ' f
197            WHERE l.field_id = f.field_id
198                AND f.field_active = 1
199                AND f.field_no_view = 0
200                AND l.lang_id = ' . $this->user->get_iso_lang_id()
201            . $sql_where . '
202            ORDER BY f.field_order ASC';
203        $result = $this->db->sql_query($sql);
204        while ($row = $this->db->sql_fetchrow($result))
205        {
206            $this->profile_cache[$row['field_ident']] = $row;
207        }
208        $this->db->sql_freeresult($result);
209    }
210
211    /**
212     * Submit profile field for validation.
213     *
214     * @param string    $mode        The mode (profile|register)
215     * @param int        $lang_id    The language identifier
216     * @param array        $cp_data    Custom profile field data
217     * @param array        $cp_error    Custom profile field errors
218     */
219    public function submit_cp_field($mode, $lang_id, &$cp_data, &$cp_error)
220    {
221        $sql_where = '';
222
223        switch ($mode)
224        {
225            case 'register':
226                // If the field is required we show it on the registration page
227                $sql_where .= ' AND f.field_show_on_reg = 1';
228            break;
229
230            case 'profile':
231                // Show hidden fields to moderators/admins
232                if (!$this->auth->acl_gets('a_', 'm_') && !$this->auth->acl_getf_global('m_'))
233                {
234                    $sql_where .= ' AND f.field_show_profile = 1';
235                }
236            break;
237
238            default:
239                trigger_error('NO_MODE', E_USER_ERROR);
240            break;
241        }
242
243        $sql = 'SELECT l.*, f.*
244            FROM ' . $this->fields_lang_table . ' l,
245                ' . $this->fields_table . ' f
246            WHERE l.field_id = f.field_id
247                AND f.field_active = 1
248                AND l.lang_id = ' . (int) $lang_id
249            . $sql_where . '
250            ORDER BY f.field_order';
251        $result = $this->db->sql_query($sql);
252        while ($row = $this->db->sql_fetchrow($result))
253        {
254            /** @var \phpbb\profilefields\type\type_interface $profile_field */
255            $profile_field = $this->type_collection[$row['field_type']];
256            $cp_data['pf_' . $row['field_ident']] = $profile_field->get_profile_field($row);
257
258            /**
259             * Replace Emoji and other 4bit UTF-8 chars not allowed by MySQL
260             * with their Numeric Character Reference's Hexadecimal notation.
261             */
262            if (is_string($cp_data['pf_' . $row['field_ident']]))
263            {
264                $cp_data['pf_' . $row['field_ident']] = utf8_encode_ucr($cp_data['pf_' . $row['field_ident']]);
265            }
266
267            $check_value = $cp_data['pf_' . $row['field_ident']];
268
269            if (($cp_result = $profile_field->validate_profile_field($check_value, $row)) !== false)
270            {
271                // If the result is not false, it's an error message
272                $cp_error[] = $cp_result;
273            }
274        }
275        $this->db->sql_freeresult($result);
276    }
277
278    /**
279     * Update profile field data directly.
280     *
281     * @param int        $user_id        The user identifier
282     * @param array        $cp_data        Custom profile field data
283     */
284    public function update_profile_field_data($user_id, $cp_data)
285    {
286        if (empty($cp_data))
287        {
288            return;
289        }
290
291        $sql = 'UPDATE ' . $this->fields_data_table . '
292            SET ' . $this->db->sql_build_array('UPDATE', $cp_data) . '
293            WHERE user_id = ' . (int) $user_id;
294        $this->db->sql_query($sql);
295
296        if (!$this->db->sql_affectedrows())
297        {
298            $cp_data = $this->build_insert_sql_array($cp_data);
299            $cp_data['user_id'] = (int) $user_id;
300
301            $sql = 'INSERT INTO ' . $this->fields_data_table . $this->db->sql_build_array('INSERT', $cp_data);
302            $this->db->sql_query($sql);
303        }
304    }
305
306    /**
307     * Generate the template arrays in order to display the column names.
308     *
309     * @param string    $restrict_option    Restrict the published fields to a certain profile field option
310     * @return array                        Returns an array with the template variables type,
311     *                                         name and explain for the fields to display
312     */
313    public function generate_profile_fields_template_headlines($restrict_option = '')
314    {
315        if (empty($this->profile_cache))
316        {
317            $this->build_cache();
318        }
319
320        $tpl_fields = [];
321
322        // Go through the fields in correct order
323        foreach ($this->profile_cache as $field_ident => $field_data)
324        {
325            if ($restrict_option && !$field_data[$restrict_option])
326            {
327                continue;
328            }
329
330            /** @var \phpbb\profilefields\type\type_interface $profile_field */
331            $profile_field = $this->type_collection[$field_data['field_type']];
332
333            $tpl_fields[] = [
334                'PROFILE_FIELD_IDENT'    => $field_ident,
335                'PROFILE_FIELD_TYPE'    => $field_data['field_type'],
336                'PROFILE_FIELD_NAME'    => $profile_field->get_field_name($field_data['lang_name']),
337                'PROFILE_FIELD_EXPLAIN'    => $this->language->lang($field_data['lang_explain']),
338            ];
339        }
340
341        $profile_cache = $this->profile_cache;
342
343        /**
344         * Event to modify template headlines of the generated profile fields
345         *
346         * @event core.generate_profile_fields_template_headlines
347         * @var string    restrict_option    Restrict the published fields to a certain profile field option
348         * @var array    tpl_fields        Array with template data fields
349         * @var array    profile_cache    A copy of the profile cache to make additional checks
350         * @since 3.1.6-RC1
351         */
352        $vars = ['restrict_option', 'tpl_fields', 'profile_cache'];
353        extract($this->dispatcher->trigger_event('core.generate_profile_fields_template_headlines', compact($vars)));
354        unset($profile_cache);
355
356        return $tpl_fields;
357    }
358
359    /**
360     * Grab the user specific profile fields data.
361     *
362     * @param int|array    $user_ids    Single user id or an array of ids
363     * @return array                Users profile fields data
364     */
365    public function grab_profile_fields_data($user_ids = 0)
366    {
367        if (empty($this->profile_cache))
368        {
369            $this->build_cache();
370        }
371
372        if (empty($user_ids))
373        {
374            return [];
375        }
376
377        $user_ids = (array) $user_ids;
378
379        $sql = 'SELECT *
380            FROM ' . $this->fields_data_table . '
381            WHERE ' . $this->db->sql_in_set('user_id', array_map('intval', $user_ids));
382        $result = $this->db->sql_query($sql);
383        $rowset = $this->db->sql_fetchrowset($result);
384        $this->db->sql_freeresult($result);
385
386        $field_data = array_column($rowset, null, 'user_id');
387
388        /**
389         * Event to modify profile fields data retrieved from the database
390         *
391         * @event core.grab_profile_fields_data
392         * @var array    user_ids        Single user id or an array of ids
393         * @var array    field_data        Array with profile fields data
394         * @since 3.1.0-b3
395         */
396        $vars = ['user_ids', 'field_data'];
397        extract($this->dispatcher->trigger_event('core.grab_profile_fields_data', compact($vars)));
398
399        $user_fields = [];
400
401        // Go through the fields in correct order
402        foreach (array_keys($this->profile_cache) as $used_ident)
403        {
404            foreach ($field_data as $user_id => $row)
405            {
406                $user_fields[$user_id][$used_ident]['value'] = $row['pf_' . $used_ident];
407                $user_fields[$user_id][$used_ident]['data'] = $this->profile_cache[$used_ident];
408            }
409
410            foreach ($user_ids as $user_id)
411            {
412                if (!isset($user_fields[$user_id][$used_ident]))
413                {
414                    $user_fields[$user_id][$used_ident]['value'] = '';
415                    $user_fields[$user_id][$used_ident]['data'] = $this->profile_cache[$used_ident];
416                }
417            }
418        }
419
420        return $user_fields;
421    }
422
423    /**
424     * Generate the user's profile fields data for the template.
425     *
426     * @param array        $profile_row        Array with users profile field data
427     * @param bool        $use_contact_fields    Should we display contact fields as such?
428     *                                         This requires special treatments:
429     *                                         (links should not be parsed in the values, and more)
430     * @return array                        The user's profile fields data
431     */
432    public function generate_profile_fields_template_data($profile_row, $use_contact_fields = true)
433    {
434        // $profile_row == $user_fields[$row['user_id']];
435        $tpl_fields = [
436            'row'        => [],
437            'blockrow'    => [],
438        ];
439
440        /**
441         * Event to modify data of the generated profile fields, before the template assignment loop
442         *
443         * @event core.generate_profile_fields_template_data_before
444         * @var array    profile_row            Array with users profile field data
445         * @var array    tpl_fields            Array with template data fields
446         * @var bool    use_contact_fields    Should we display contact fields as such?
447         * @since 3.1.0-b3
448         */
449        $vars = ['profile_row', 'tpl_fields', 'use_contact_fields'];
450        extract($this->dispatcher->trigger_event('core.generate_profile_fields_template_data_before', compact($vars)));
451
452        foreach ($profile_row as $ident => $ident_ary)
453        {
454            /** @var \phpbb\profilefields\type\type_interface $profile_field */
455            $profile_field = $this->type_collection[$ident_ary['data']['field_type']];
456
457            $value = $profile_field->get_profile_value($ident_ary['value'], $ident_ary['data']);
458            $value_raw = $profile_field->get_profile_value_raw($ident_ary['value'], $ident_ary['data']);
459
460            if ($value === null)
461            {
462                continue;
463            }
464
465            $field_desc = '';
466            $contact_url = '';
467            $ident_upper = strtoupper($ident);
468
469            if ($use_contact_fields && $ident_ary['data']['field_is_contact'])
470            {
471                $value = $profile_field->get_profile_contact_value($ident_ary['value'], $ident_ary['data']);
472                $field_desc = $this->language->lang($ident_ary['data']['field_contact_desc']);
473
474                if (strpos($field_desc, '%s') !== false)
475                {
476                    $field_desc = sprintf($field_desc, $value);
477                }
478
479                if (strpos($ident_ary['data']['field_contact_url'], '%s') !== false)
480                {
481                    $contact_url = sprintf($ident_ary['data']['field_contact_url'], $value);
482                }
483            }
484
485            $tpl_fields['row'] += [
486                "PROFILE_{$ident_upper}_IDENT"        => $ident,
487                "PROFILE_{$ident_upper}_VALUE"        => $value,
488                "PROFILE_{$ident_upper}_VALUE_RAW"    => $value_raw,
489                "PROFILE_{$ident_upper}_CONTACT"    => $contact_url,
490                "PROFILE_{$ident_upper}_DESC"        => $field_desc,
491                "PROFILE_{$ident_upper}_TYPE"        => $ident_ary['data']['field_type'],
492                "PROFILE_{$ident_upper}_NAME"        => $this->language->lang($ident_ary['data']['lang_name']),
493                "PROFILE_{$ident_upper}_EXPLAIN"    => $this->language->lang($ident_ary['data']['lang_explain']),
494
495                "S_PROFILE_{$ident_upper}_CONTACT"    => $ident_ary['data']['field_is_contact'],
496                "S_PROFILE_{$ident_upper}"            => true,
497            ];
498
499            $tpl_fields['blockrow'][] = [
500                'PROFILE_FIELD_IDENT'        => $ident,
501                'PROFILE_FIELD_VALUE'        => $value,
502                'PROFILE_FIELD_VALUE_RAW'    => $value_raw,
503                'PROFILE_FIELD_CONTACT'        => $contact_url,
504                'PROFILE_FIELD_DESC'        => $field_desc,
505                'PROFILE_FIELD_TYPE'        => $ident_ary['data']['field_type'],
506                'PROFILE_FIELD_NAME'        => $this->language->lang($ident_ary['data']['lang_name']),
507                'PROFILE_FIELD_EXPLAIN'        => $this->language->lang($ident_ary['data']['lang_explain']),
508
509                'S_PROFILE_CONTACT'            => $ident_ary['data']['field_is_contact'],
510                "S_PROFILE_{$ident_upper}"    => true,
511            ];
512        }
513
514        /**
515         * Event to modify template data of the generated profile fields
516         *
517         * @event core.generate_profile_fields_template_data
518         * @var array    profile_row        Array with users profile field data
519         * @var array    tpl_fields        Array with template data fields
520         * @var bool    use_contact_fields    Should we display contact fields as such?
521         * @since 3.1.0-b3
522         */
523        $vars = ['profile_row', 'tpl_fields', 'use_contact_fields'];
524        extract($this->dispatcher->trigger_event('core.generate_profile_fields_template_data', compact($vars)));
525
526        return $tpl_fields;
527    }
528
529    /**
530     * Build array for the custom profile fields table.
531     *
532     * @param array        $cp_data        Custom profile field data
533     * @return array                    Custom profile field data for SQL usage
534     */
535    public function build_insert_sql_array($cp_data)
536    {
537        $prefix = 'pf_';
538        $length = strlen($prefix);
539        $not_in = [];
540
541        foreach (array_keys($cp_data) as $key)
542        {
543            $not_in[] = strncmp($key, $prefix, $length) === 0 ? substr($key, $length) : $key;
544        }
545
546        $sql = 'SELECT f.field_type, f.field_ident, f.field_default_value, l.lang_default_value
547            FROM ' . $this->fields_lang_table . ' l,
548                ' . $this->fields_table . ' f
549            WHERE l.field_id = f.field_id
550                AND l.lang_id = ' . $this->user->get_iso_lang_id() .
551                (!empty($not_in) ? ' AND ' . $this->db->sql_in_set('f.field_ident', $not_in, true) : '');
552        $result = $this->db->sql_query($sql);
553        while ($row = $this->db->sql_fetchrow($result))
554        {
555            /** @var \phpbb\profilefields\type\type_interface $profile_field */
556            $profile_field = $this->type_collection[$row['field_type']];
557            $cp_data[$prefix . $row['field_ident']] = $profile_field->get_default_field_value($row);
558        }
559        $this->db->sql_freeresult($result);
560
561        return $cp_data;
562    }
563
564    /**
565     * Disable all profile fields of a certain type.
566     *
567     * This should be called when an extension which has profile field types is disabled
568     * so that all those profile fields are hidden and do not cause errors.
569     *
570     * @param string    $type_name        Type identifier of the profile fields
571     */
572    public function disable_profilefields($type_name)
573    {
574        // Get the list of active profile fields of this type
575        $profile_fields = $this->list_profilefields($type_name, true);
576
577        // If no profile fields affected, then nothing to do
578        if (empty($profile_fields))
579        {
580            return;
581        }
582
583        // Update the affected profile fields to "inactive"
584        $sql = 'UPDATE ' . $this->fields_table . '
585            SET field_active = 0
586            WHERE field_active = 1
587                AND ' . $this->db->sql_in_set('field_id', array_keys($profile_fields));
588        $this->db->sql_query($sql);
589
590        // Save modified information into a config_text field to recover on enable
591        $this->config_text->set($type_name . '.saved', json_encode($profile_fields));
592
593        // Log activity
594        foreach ($profile_fields as $field_ident)
595        {
596            $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_PROFILE_FIELD_DEACTIVATE', time(), [$field_ident]);
597        }
598    }
599
600    /**
601     * Purge all profile fields of a certain type.
602     *
603     * This should be called when an extension which has profile field types is purged
604     * so that all those profile fields are removed.
605     *
606     * @param string    $type_name        Type identifier of the profile fields
607     */
608    public function purge_profilefields($type_name)
609    {
610        // Remove the information saved on disable in a config_text field, not needed any longer
611        $this->config_text->delete($type_name . '.saved');
612
613        // Get the list of all profile fields of this type
614        $profile_fields = $this->list_profilefields($type_name);
615
616        // If no profile fields exist, then nothing to do
617        if (empty($profile_fields))
618        {
619            return;
620        }
621
622        $this->db->sql_transaction('begin');
623
624        // Delete entries from all profile field definition tables
625        $where = $this->db->sql_in_set('field_id', array_keys($profile_fields));
626        $this->db->sql_query('DELETE FROM ' . $this->fields_table . ' WHERE ' . $where);
627        $this->db->sql_query('DELETE FROM ' . $this->fields_data_lang_table . ' WHERE ' . $where);
628        $this->db->sql_query('DELETE FROM ' . $this->fields_lang_table . ' WHERE ' . $where);
629
630        // Drop columns from the Profile Fields data table
631        foreach ($profile_fields as $field_ident)
632        {
633            $this->db_tools->sql_column_remove($this->fields_data_table, 'pf_' . $field_ident);
634        }
635
636        // Reset the order of the remaining fields
637        $order = 0;
638
639        $sql = 'SELECT *
640            FROM ' . $this->fields_table . '
641            ORDER BY field_order';
642        $result = $this->db->sql_query($sql);
643        while ($row = $this->db->sql_fetchrow($result))
644        {
645            $order++;
646
647            if ($row['field_order'] != $order)
648            {
649                $sql = 'UPDATE ' . $this->fields_table . "
650                    SET field_order = $order
651                    WHERE field_id = {$row['field_id']}";
652                $this->db->sql_query($sql);
653            }
654        }
655        $this->db->sql_freeresult($result);
656
657        $this->db->sql_transaction('commit');
658
659        // Log activity
660        foreach ($profile_fields as $field_ident)
661        {
662            $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_PROFILE_FIELD_REMOVED', time(), [$field_ident]);
663        }
664    }
665
666    /**
667     * Enable the profile fields of a certain type.
668     *
669     * This should be called when an extension which has profile field types that was disabled is re-enabled
670     * so that all those profile fields that were disabled are enabled again.
671     *
672     * @param string    $type_name        Type identifier of the profile fields
673     */
674    public function enable_profilefields($type_name)
675    {
676        // Read the modified information saved on disable from a config_text field to recover values
677        $profile_fields = $this->config_text->get($type_name . '.saved');
678
679        // If nothing saved, then nothing to do
680        if (empty($profile_fields))
681        {
682            return;
683        }
684
685        $profile_fields = (array) json_decode($profile_fields, true);
686
687        // Restore the affected profile fields to "active"
688        $sql = 'UPDATE ' . $this->fields_table . '
689            SET field_active = 1
690            WHERE field_active = 0
691                AND ' . $this->db->sql_in_set('field_id', array_keys($profile_fields));
692        $this->db->sql_query($sql);
693
694        // Remove the information saved in the config_text field, not needed any longer
695        $this->config_text->delete($type_name . '.saved');
696
697        // Log activity
698        foreach ($profile_fields as $field_ident)
699        {
700            $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_PROFILE_FIELD_ACTIVATE', time(), [$field_ident]);
701        }
702    }
703
704    /**
705     * Get list of profile fields of a certain type, if any
706     *
707     * @param string    $type_name        Type identifier of the profile fields
708     * @param bool        $active            True to limit output to active profile fields, false for all
709     * @return array                    Array with profile field ids as keys and idents as values
710     */
711    private function list_profilefields($type_name, $active = false)
712    {
713        // Get list of profile fields affected by this operation, if any
714        $sql = 'SELECT field_id, field_ident
715            FROM ' . $this->fields_table . "
716            WHERE field_type = '" . $this->db->sql_escape($type_name) . "'" .
717            ($active ? ' AND field_active = 1' : '');
718        $result = $this->db->sql_query($sql);
719        $rowset = $this->db->sql_fetchrowset($result);
720        $this->db->sql_freeresult($result);
721
722        return array_column($rowset, 'field_ident', 'field_id');
723    }
724}