query_select('f_topics', ['DISTINCT t_pt_from_category'], ['t_pt_from_member' => get_member()]); $filter_rows_b = $GLOBALS['FORUM_DB']->query_select('f_topics', ['DISTINCT t_pt_to_category'], ['t_pt_to_member' => get_member()]); $filter_cats = ['' => 1]; if (!$only_exists_now) { $filter_cats[do_lang('TRASH')] = 1; } if ($GLOBALS['FORUM_DB']->query_select_value('f_special_pt_access', 'COUNT(*)', ['s_member_id' => get_member()]) > 0) { $filter_cats[do_lang('INVITED_TO_PTS')] = 1; } foreach ($filter_rows_a as $filter_row) { $filter_cats[$filter_row['t_pt_from_category']] = 1; } foreach ($filter_rows_b as $filter_row) { $filter_cats[$filter_row['t_pt_to_category']] = 1; } return array_keys($filter_cats); } /** * Find whether a member of a certain username is bound to HTTP authentication (an exceptional situation, only for sites that use it). * * @param string $authusername The username * @return ?integer The member ID, if it is (null: not bound) */ function cns_authusername_is_bound_via_httpauth(string $authusername) : ?int { $ret = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_members', 'id', ['m_password_compat_scheme' => 'httpauth', 'm_pass_hash_salted' => $authusername]); if ($ret === null) { $ret = $GLOBALS['FORUM_DB']->query_value_if_there('SELECT id FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_members WHERE ' . db_string_not_equal_to('m_password_compat_scheme', '')/*LEGACY*/ . ' AND ' . db_string_not_equal_to('m_password_compat_scheme', 'bcrypt') . ' AND ' . db_string_equal_to('m_username', $authusername)); } return $ret; } /** * Find whether a member is bound to HTTP LDAP (an exceptional situation, only for sites that use it). * * @param MEMBER $member_id The member * @return boolean The answer */ function cns_is_ldap_member(int $member_id) : bool { global $LDAP_CONNECTION; if ($LDAP_CONNECTION === null) { return false; } $scheme = $GLOBALS['CNS_DRIVER']->get_member_row_field($member_id, 'm_password_compat_scheme'); return $scheme == 'ldap'; } /** * Find whether a member is bound to HTTP authentication (an exceptional situation, only for sites that use it). * * @param MEMBER $member_id The member * @return boolean The answer */ function cns_is_httpauth_member(int $member_id) : bool { $scheme = $GLOBALS['CNS_DRIVER']->get_member_row_field($member_id, 'm_password_compat_scheme'); return $scheme == 'httpauth'; } /** * Gets all the system custom fields that match certain parameters. * * @param ?array $groups That are applicable only to one of the usergroups in this list (empty: CPFs with no restriction) (null: disregard restriction) * @param ?BINARY $public_view That are publicly viewable (null: don't care) * @param ?BINARY $owner_view That are owner viewable (null: don't care) * @param ?BINARY $owner_set That are owner settable (null: don't care) * @param ?BINARY $required That are required (null: don't care) * @param ?BINARY $show_in_posts That are to be shown in posts (null: don't care) * @param ?BINARY $show_in_post_previews That are to be shown in post previews (null: don't care) * @param ?BINARY $special_start That start 'cms_' (null: don't care) * @param ?boolean $show_on_join_form That are to go on the join form (null: don't care) * @param array $adjusted_config_options A map of adjusted config options; actually it is field_=0|1 for the purposes of this function, and overrides cf_show_on_join_form * @return array A list of rows of such fields */ function cns_get_all_custom_fields_match(?array $groups = null, ?int $public_view = null, ?int $owner_view = null, ?int $owner_set = null, ?int $required = null, ?int $show_in_posts = null, ?int $show_in_post_previews = null, ?int $special_start = null, ?bool $show_on_join_form = null, array $adjusted_config_options = []) : array { global $CUSTOM_FIELD_CACHE; $x = serialize([$public_view, $owner_view, $owner_set, $required, $show_in_posts, $show_in_post_previews, $special_start, $show_on_join_form]); if (isset($CUSTOM_FIELD_CACHE[$x])) { $result = $CUSTOM_FIELD_CACHE[$x]; } else { // Load up filters $hooks = find_all_hook_obs('systems', 'cns_cpf_filter', 'Hook_cns_cpf_filter_'); $to_keep = []; foreach ($hooks as $ob) { $to_keep += $ob->to_enable(); } $where = ' WHERE 1=1 '; if ($public_view !== null) { $where .= ' AND cf_public_view=' . strval($public_view); } if ($owner_view !== null) { $where .= ' AND cf_owner_view=' . strval($owner_view); } if ($owner_set !== null) { $where .= ' AND cf_owner_set=' . strval($owner_set); } if ($required !== null) { $where .= ' AND cf_required=' . strval($required); } if ($show_in_posts !== null) { $where .= ' AND cf_show_in_posts=' . strval($show_in_posts); } if ($show_in_post_previews !== null) { $where .= ' AND cf_show_in_post_previews=' . strval($show_in_post_previews); } if ($special_start === 1) { $where .= ' AND ' . $GLOBALS['FORUM_DB']->translate_field_ref('cf_name') . ' LIKE \'' . db_encode_like('cms\_%') . '\''; } elseif ($special_start === 0) { $where .= ' AND ' . $GLOBALS['FORUM_DB']->translate_field_ref('cf_name') . ' NOT LIKE \'' . db_encode_like('cms\_%') . '\''; } if ($show_on_join_form !== null) { $where .= ' AND (cf_show_on_join_form=' . strval($show_on_join_form); foreach ($adjusted_config_options as $config_option => $config_value) { if ($config_value == '1') { if (preg_match('#^field_\d+$#', $config_option) != 0) { $where .= ' OR id=' . substr($config_option, strlen('field_')); } } } $where .= ')'; foreach ($adjusted_config_options as $config_option => $config_value) { if ($config_value == '0') { if (preg_match('#^field_\d+$#', $config_option) != 0) { $where .= ' AND id<>' . substr($config_option, strlen('field_')); } } } } $sql = 'SELECT f.* FROM ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_custom_fields f' . $where . ' ORDER BY cf_order,' . $GLOBALS['FORUM_DB']->translate_field_ref('cf_name'); $_result = $GLOBALS['FORUM_DB']->query($sql, null, 0, false, true, find_lang_fields('f_custom_fields', 'f')); $result = []; foreach ($_result as $row) { $row['trans_name'] = get_translated_text($row['cf_name'], $GLOBALS['FORUM_DB']); if ((substr($row['trans_name'], 0, 4) == 'cms_') && ($special_start !== 1)) { // See if it gets filtered if (!isset($to_keep[substr($row['trans_name'], 4)])) { continue; } require_lang('cns'); require_lang('cns_special_cpf'); $test = do_lang('SPECIAL_CPF__' . $row['trans_name'], null, null, null, null, false); if ($test !== null) { $row['trans_name'] = $test; } } $result[] = $row; } $CUSTOM_FIELD_CACHE[$x] = $result; if (function_exists('persistent_cache_set')) { persistent_cache_set('CUSTOM_FIELD_CACHE', $CUSTOM_FIELD_CACHE); } } $result2 = []; foreach ($result as $row) { if (($row['cf_only_group'] == '') || ($groups === null) || (!empty(array_intersect(explode(',', $row['cf_only_group']), $groups)))) { $result2[] = $row; } } return $result2; } /** * Gets all a member's custom fields that match certain parameters. * * @param MEMBER $member_id The member * @param ?BINARY $public_view That are publicly viewable (null: don't care) * @param ?BINARY $owner_view That are owner viewable (null: don't care) * @param ?BINARY $owner_set That are owner settable (null: don't care) * @param ?BINARY $encrypted That are encrypted (null: don't care) * @param ?BINARY $required That are required (null: don't care) * @param ?BINARY $show_in_posts That are to be shown in posts (null: don't care) * @param ?BINARY $show_in_post_previews That are to be shown in post previews (null: don't care) * @param ?BINARY $special_start That start 'cms_' (null: don't care) * @param ?boolean $show_on_join_form That are to go on the join form (null: don't care) * @return array A mapping of field title to a map of details: 'RAW' as the raw field value, 'RENDERED' as the rendered field value, 'FIELD_ID' to the field ID, 'EDITABILITY' defining if fractional editing can work on this */ function cns_get_all_custom_fields_match_member(int $member_id, ?int $public_view = null, ?int $owner_view = null, ?int $owner_set = null, ?int $encrypted = null, ?int $required = null, ?int $show_in_posts = null, ?int $show_in_post_previews = null, ?int $special_start = null, ?bool $show_on_join_form = null) : array { $fields_to_show = cns_get_all_custom_fields_match($GLOBALS['FORUM_DRIVER']->get_members_groups($member_id), $public_view, $owner_view, $owner_set, $required, $show_in_posts, $show_in_post_previews, $special_start, $show_on_join_form); $custom_fields = []; $member_mappings = cns_get_custom_field_mappings($member_id); $member_value = null; // Initialise type to mixed $all_cpf_permissions = ((get_member() == $member_id) || $GLOBALS['FORUM_DRIVER']->is_super_admin(get_member())) ? /*no restricts if you are the member or a super-admin*/[] : list_to_map('field_id', $GLOBALS['FORUM_DB']->query_select('f_member_cpf_perms', ['*'], ['member_id' => $member_id])); require_code('fields'); $editable_with_comcode = ['long_text' => 1, 'long_trans' => 1, 'short_trans' => 1]; $editable_without_comcode = ['list' => 1, 'short_text' => 1, 'codename' => 1, 'url' => 1, 'integer' => 1, 'float' => 1, 'email' => 1]; foreach ($fields_to_show as $i => $field_to_show) { $key = 'field_' . strval($field_to_show['id']); if (!array_key_exists($key, $member_mappings)) { continue; } $member_value = $member_mappings[$key]; if (!is_string($member_value)) { if (is_float($member_value)) { $member_value = float_to_raw_string($member_value, 30); } elseif ($member_value !== null) { $member_value = strval($member_value); } } // Load decryption context $decrypt = null; if (($member_value !== null) && ($member_value != '') && ($member_value != $field_to_show['cf_default'])) { require_code('encryption'); if (is_encryption_enabled()) { $decrypt = post_param_string('decrypt', null); } } $ob = get_fields_hook($field_to_show['cf_type']); list(, , $storage_type) = $ob->get_field_value_row_bits($field_to_show); if ($storage_type == 'short_trans' || $storage_type == 'long_trans') { if (($member_value === null) || ((multi_lang_content()) && ($member_value == '0'))) { $member_value_raw = ''; $member_value = ''; // This is meant to be '' for blank, not new Tempcode() } else { $member_value_raw = get_translated_text($member_mappings['field_' . strval($field_to_show['id'])], $GLOBALS['FORUM_DB']); if (($decrypt !== null) && (is_data_encrypted($member_value_raw))) { $member_value_raw = decrypt_data($member_value_raw, $decrypt); $member_value = comcode_to_tempcode($member_value_raw, $member_id); } else { $member_mappings_copy = db_map_restrict($member_mappings, ['mf_member_id', 'field_' . strval($field_to_show['id'])]); $member_value = get_translated_tempcode('f_member_custom_fields', $member_mappings_copy, 'field_' . strval($field_to_show['id']), $GLOBALS['FORUM_DB']); } if ((is_object($member_value)) && ($member_value->is_empty())) { $member_value = ''; } } } else { if ($member_value === null) { $member_value = ''; } else { if (($decrypt !== null) && (is_data_encrypted($member_value))) { $member_value = decrypt_data($member_value, $decrypt); } } $member_value_raw = $member_value; } // Get custom permissions for the current CPF $cpf_permissions = isset($all_cpf_permissions[$field_to_show['id']]) ? $all_cpf_permissions[$field_to_show['id']] : null; $display_cpf = true; // If there are custom permissions set and we are not showing to all if (($cpf_permissions !== null) && ($public_view !== null)) { $display_cpf = false; // Negative ones if ($cpf_permissions['guest_view'] == 1) { $display_cpf = true; } if (!is_guest()) { if ($cpf_permissions['member_view'] == 1) { $display_cpf = true; } } if (!$display_cpf) { // Guard this, as the code will take some time to run if ($cpf_permissions['friend_view'] == 1) { if (addon_installed('chat')) { if ($GLOBALS['SITE_DB']->query_select_value_if_there('chat_friends', 'member_liked', ['member_likes' => $member_id, 'member_liked' => get_member()]) !== null) { $display_cpf = true; } } } if (!is_guest()) { if ($cpf_permissions['group_view'] == 'all') { $display_cpf = true; } else { if (strlen($cpf_permissions['group_view']) > 0) { require_code('selectcode'); $real_group_list = $GLOBALS['FORUM_DRIVER']->get_members_groups(get_member()); if (!empty(array_intersect(selectcode_to_idlist_using_memory($cpf_permissions['group_view'], $GLOBALS['FORUM_DRIVER']->get_usergroup_list()), $real_group_list))) { $display_cpf = true; } } } } } } if ($display_cpf) { $rendered_value = $ob->render_field_value($field_to_show, $member_value, $i, null, 'f_member_custom_fields', $member_id, 'mf_member_id', null, 'field_' . strval($field_to_show['id']), $member_id, $member_value_raw); $editability = null; // If stays as null, not editable if (isset($editable_with_comcode[$field_to_show['cf_type']])) { $editability = true; // Editable: Supports Comcode } elseif (isset($editable_without_comcode[$field_to_show['cf_type']])) { $editability = false; // Editable: Does not support Comcode } $edit_type = 'line'; if ($field_to_show['cf_type'] == 'list') { $edit_type = $field_to_show['cf_default']; } elseif (($field_to_show['cf_type'] == 'long_text') || ($field_to_show['cf_type'] == 'long_trans')) { $edit_type = 'textarea'; } $bindings = [ 'NAME_FULL' => $field_to_show['trans_name'], 'NAME' => preg_replace('#^.*: #', '', $field_to_show['trans_name']), 'RAW' => $member_value_raw, // Always a string or NULL 'RENDERED' => $rendered_value, 'ICON' => $field_to_show['cf_icon'], 'SECTION' => $field_to_show['cf_section'], 'FIELD_ID' => strval($field_to_show['id']), 'FIELD_TYPE' => $field_to_show['cf_type'], 'EDITABILITY' => $editability, // null not editable. 1 for Comcode, 0 otherwise 'EDIT_TYPE' => $edit_type, ]; if ($field_to_show['cf_tempcode'] != '') { require_code('tempcode_compiler'); $_rendered_value = template_to_tempcode($field_to_show['cf_tempcode']); $rendered_value = $_rendered_value->bind($bindings, 'CPF'); $bindings['RENDERED'] = protect_from_escaping($rendered_value->evaluate()); } $custom_fields[$field_to_show['trans_name']] = $bindings; } } return $custom_fields; } /** * Get the ID for any CPF if we only know the title. Warning: Only use this with custom code, never core code! It assumes a single language and that fields aren't renamed. * * @param SHORT_TEXT $title The title * @return ?AUTO_LINK The ID (null: could not find) */ function find_cpf_field_id(string $title) : ?int { static $cache = []; if (array_key_exists($title, $cache)) { return $cache[$title]; } $fields_to_show = cns_get_all_custom_fields_match(); foreach ($fields_to_show as $field_to_show) { if ($field_to_show['trans_name'] == $title) { $cache[$title] = $field_to_show['id']; return $field_to_show['id']; } } $cache[$title] = null; return null; } /** * Get the ID for a special CPF if we only know the title. Warning: Only use this with custom code, never core code! It assumes a single language and that fields aren't renamed. * * @param SHORT_TEXT $title The title * @return ?AUTO_LINK The ID (null: could not find) */ function find_cms_cpf_field_id(string $title) : ?int { static $cache = []; if (array_key_exists($title, $cache)) { return $cache[$title]; } $fields_to_show = cns_get_all_custom_fields_match( null, // groups null, // public view null, // owner view null, // owner set null, // required null, // show in posts null, // show in post previews 1 // special start ); foreach ($fields_to_show as $field_to_show) { if ($field_to_show['trans_name'] == $title) { $cache[$title] = $field_to_show['id']; return $field_to_show['id']; } } $cache[$title] = null; return null; } /** * Returns a mapping of all raw field values for user. * Doesn't take account of translation, anything permissive, or data conversion. Therefore only use if you are sure of the data you're getting. * Automatically (re)creates missing data. * Use cns_get_all_custom_fields_match_member if you want something smarter (but more intensive); that function is a wrapper around this function. * * @param MEMBER $member_id The member * @return array The mapping, field_ to value */ function cns_get_custom_field_mappings(int $member_id) : array { require_code('fields'); global $MEMBER_CACHE_FIELD_MAPPINGS; if (!isset($MEMBER_CACHE_FIELD_MAPPINGS[$member_id])) { if (is_guest($member_id)) { $test = persistent_cache_get('cns_get_custom_field_mappings_guest'); if ($test !== null) { $MEMBER_CACHE_FIELD_MAPPINGS[$member_id] = $test; return $test; } } $row = ['mf_member_id' => $member_id]; $query = $GLOBALS['FORUM_DB']->query_select('f_members m LEFT JOIN ' . $GLOBALS['FORUM_DB']->get_table_prefix() . 'f_member_custom_fields c ON c.mf_member_id=m.id', ['*'], $row, '', 1); if (!isset($query[0]['mf_member_id'])) { // Repair $value = null; $row = []; $all_fields_regardless = $GLOBALS['FORUM_DB']->query_select('f_custom_fields', ['id', 'cf_type', 'cf_required', 'cf_default']); foreach ($all_fields_regardless as $field) { $ob = get_fields_hook($field['cf_type']); list(, $value, $storage_type) = $ob->get_field_value_row_bits($field, $field['cf_required'] == 1, '', $GLOBALS['FORUM_DB']); $row['field_' . strval($field['id'])] = $value; if (is_string($value)) { // Should not normally be needed, but the grabbing from cf_default further up is not converted yet switch ($storage_type) { case 'short_trans': case 'long_trans': if ($value !== null) { $row = insert_lang_comcode('field_' . strval($field['id']), $value, 3, $GLOBALS['FORUM_DB']) + $row; } else { $row['field_' . strval($field['id'])] = null; } break; case 'integer': $row['field_' . strval($field['id'])] = @intval($value); break; case 'float': $row['field_' . strval($field['id'])] = @floatval($value); break; } } } $row = ['mf_member_id' => $member_id] + $row; $GLOBALS['FORUM_DB']->query_insert('f_member_custom_fields', $row, false, true); if (!isset($query[0])) { $query[0] = []; } $query[0] += [$row]; } $MEMBER_CACHE_FIELD_MAPPINGS[$member_id] = $query[0]; if (is_guest($member_id)) { persistent_cache_set('cns_get_custom_field_mappings_guest', $MEMBER_CACHE_FIELD_MAPPINGS[$member_id]); } } return $MEMBER_CACHE_FIELD_MAPPINGS[$member_id]; } /** * Returns a mapping between field number and field value. Doesn't take translation into account. Doesn't take anything permissive into account. * * @param MEMBER $member_id The member * @return array The mapping */ function cns_get_custom_fields_member(int $member_id) : array { $row = cns_get_custom_field_mappings($member_id); $result = []; foreach ($row as $column => $val) { if (preg_match('#^field_\d+$#', $column) != 0) { $result[intval(substr($column, 6))] = $val; } } return $result; } /** * Get the primary of a member (supports consulting of LDAP). * * @param MEMBER $member_id The member * @return ?GROUP The primary group (null: member not found) */ function cns_get_member_primary_group(int $member_id) : ?int { global $PRIMARY_GROUP_MEMBERS_CACHE; if (isset($PRIMARY_GROUP_MEMBERS_CACHE[$member_id])) { return $PRIMARY_GROUP_MEMBERS_CACHE[$member_id]; } if (cns_is_ldap_member($member_id)) { cns_ldap_get_member_primary_group($member_id); } else { $PRIMARY_GROUP_MEMBERS_CACHE[$member_id] = $GLOBALS['CNS_DRIVER']->get_member_row_field($member_id, 'm_primary_group'); } return $PRIMARY_GROUP_MEMBERS_CACHE[$member_id]; }