<?php if (!defined('VB_ENTRY')) die('Access denied.');
/*========================================================================*\
|| ###################################################################### ||
|| # vBulletin 6.2.3 - Licence Number L3138846
|| # ------------------------------------------------------------------ # ||
|| # Copyright 2000-2026 MH Sub I, LLC dba vBulletin. All Rights Reserved.  # ||
|| # This file may not be redistributed in whole or significant part.   # ||
|| # ----------------- VBULLETIN IS NOT FREE SOFTWARE ----------------- # ||
|| # http://www.vbulletin.com | http://www.vbulletin.com/license.html   # ||
|| ###################################################################### ||
\*========================================================================*/

// this used to switch between admincp/modcp usage.  We should clean up the links that no longer
// need it and remove this function
function get_area_data()
{
	$data = [
		'userscript' => 'usertools.php',
		'base' => 'admincp',
		'useraction' => 'edit',
	];
	return $data;
}


// ###################### Start doipaddress #######################
function construct_ip_usage_table($ipaddress, $prevuserid, $depth = 1)
{
	return construct_ip_table_internal($ipaddress, $prevuserid, $depth, 'postipusers');
}

// ###################### Start construct_ip_register_table #######################
function construct_ip_register_table($ipaddress, $prevuserid, $depth = 1)
{
	return construct_ip_table_internal($ipaddress, $prevuserid, $depth, 'regipusers');
}

function construct_ip_table_internal($ipaddress, $prevuserid, $depth, $key)
{
	global $vbphrase;

	$depth--;

	$data = get_area_data();
	$userscript = $data['userscript'];
	$base = $data['base'];
	$useraction = $data['useraction'];

	$users = vB_Api::instanceInternal('user')->searchUsersByIP($ipaddress, $depth);

	//this isn't right but let's go with it for now.  We probably need to fix searchUsersByIp
	$users = current($users[$key]);
	if ($users)
	{
		$retdata = '';
		foreach ($users AS $user)
		{
			$viewuserurl = htmlspecialchars($base . '/user.php?do=' . $useraction . '&u=' . $user['userid']);
			$resolveaddressurl = htmlspecialchars("$base/$userscript?do=gethost&ip=$user[ipaddress]");
			$usersearchurl = htmlspecialchars(vB5_Route::buildUrl('search|fullurl', [], ['searchJSON' => json_encode(['authorid' => $user['userid']])]));
			$otheripurl =  htmlspecialchars("$base/$userscript?do=doips&u=$user[userid]&hash=" . CP_SESSIONHASH);

			$retdata .= '<li>' .
				construct_link_code('<b>' . $user['username']. '</b>', $viewuserurl, false, '', false, false) . '&nbsp; ' .
				construct_link_code($user['ipaddress'], $resolveaddressurl, false, $vbphrase['resolve_address'], false, false) . '&nbsp; ' .
				construct_link_code($vbphrase['find_posts_by_user'], $usersearchurl, true, '', false, false) .
				construct_link_code($vbphrase['view_other_ip_addresses_for_this_user'], $otheripurl, false, '', false, false) .
			"</li>\n";

			if ($depth > 0)
			{
				$retdata .= construct_user_ip_table($user['userid'], $user['ipaddress'], $depth);
			}
		}
	}

	if (empty($retdata))
	{
		return '';
	}
	else
	{
		return '<ul>' . $retdata . '</ul>';
	}
}

// ###################### Start douseridip #######################
function construct_user_ip_table($userid, $previpaddress, $depth = 2)
{
	global $vbphrase;

	$data = get_area_data();
	$userscript = $data['userscript'];
	$base = $data['base'];

	$depth --;
	$ips = vB_Api::instanceInternal('user')->searchIP($userid, $depth);

	$ips = current($ips['postips']);
	if ($ips)
	{
		$retdata = '';
		foreach ($ips AS $ip)
		{
			$ipurl = htmlspecialchars("$base/$userscript?do=gethost&ip=$ip[ipaddress]");
			$moreusersurl = htmlspecialchars("$base/$userscript?do=doips&ipaddress=$ip[ipaddress]&hash=" . CP_SESSIONHASH);

			$retdata .= '<li>' .
				construct_link_code($ip['ipaddress'], $ipurl, false, $vbphrase['resolve_address'], false, false) . '&nbsp; ' .
				construct_link_code($vbphrase['find_more_users_with_this_ip_address'], $moreusersurl, false, '', false, false) .
			"</li>\n";

			if ($depth > 0)
			{
				$retdata .= construct_ip_usage_table($ip['ipaddress'], $userid, $depth);
			}
		}
	}

	if (empty($retdata))
	{
		return '';
	}
	else
	{
		return '<ul>' . $retdata . '</ul>';
	}
}

// ###################### Start finduserhtml #######################
function print_user_search_rows(bool $email = false, ?string $actionlabel = null) : void
{
	global $vbphrase;

	$controls = construct_input('text', 'user[username]', ['size' => 35]) .
		construct_input('submit', 'user[exact]]', ['class' => 'button', 'value' => $vbphrase['exact_match']]);
	print_label_row($vbphrase['username'], $controls, '', 'top', 'user[username]');

	if ($email)
	{
		$extra = [
			'class' => 'bginput js-checkbox-master',
			'data-child' => 'js-checkbox-all-child',
		];
		print_checkbox_row($vbphrase['all_usergroups'], 'usergroup_all', 0, -1, $vbphrase['all_usergroups'], $extra);

		$extra = [
			'class' => 'bginput js-checkbox-master',
			'data-child' => 'js-checkbox-canview-child',
		];
		print_checkbox_row($vbphrase['can_view_forum'], 'usergroup_canview', 0, -1, $vbphrase['can_view_forum'], $extra);

		$groups = vB_Library_Usergroup::instance()->getGroupsWithPerm('forumpermissions', 'canview');
		$extraCallback = function($group) use ($groups) {
			$class = 'bginput js-checkbox-all-child';
			if (in_array($group['usergroupid'], $groups))
			{
				$class .= ' js-checkbox-canview-child';
			}

			return ['class' => $class];
		};
		print_membergroup_row($vbphrase['primary_usergroup'], 'user[usergroupid]', 2, [], $extraCallback);

		print_membergroup_row($vbphrase['additional_usergroups'], 'user[membergroup]', 2);
		print_yes_no_row($vbphrase['include_users_that_have_declined_email'], 'user[adminemail]', 0);

		$actionlabel ??=  $vbphrase['submit'];
	}
	else
	{
		print_select_row($vbphrase['primary_usergroup'], 'user[usergroupid]', get_usergroups_for_select('-- ' . $vbphrase['all_usergroups'] . ' --'));
		print_membergroup_row($vbphrase['additional_usergroups'], 'user[membergroup]', 2);

		$actionlabel ??=  $vbphrase['find'];
	}

	print_middle_submit_row($actionlabel);
	print_input_row($vbphrase['email'], 'user[email]');
	print_input_row($vbphrase['parent_email_address'], 'user[parentemail]');
	print_yes_no_other_row($vbphrase['coppa_user'], 'user[coppauser]', $vbphrase['either'], -1);
	print_yes_no_other_row($vbphrase['custom_avatar_user'], 'user[customavatar]', $vbphrase['either'], -1);

	$subscriptionOptions = [-1 => '-- ' . $vbphrase['admincp_user_search_all_paid_subscriptions'] . ' --'];
	foreach (vB::getDbAssertor()->getRows('vBForum:subscription', [], false, 'subscriptionid') AS $sub)
	{
		$subId = (int) $sub['subscriptionid'];
		$subscriptionOptions[$subId] = $vbphrase['sub' . $subId . '_title'] ?? ('Subscription #' . $subId);
	}
	print_select_row(
		$vbphrase['admincp_user_search_paid_subscription'],
		'user[subscriptionid]',
		$subscriptionOptions,
		-1,
		true
	);

	$subscriptionStatusOptions = [
		-1 => $vbphrase['either'],
		1 => $vbphrase['active_gsubscription'],
		0 => $vbphrase['disabled'],
	];
	print_select_row(
		$vbphrase['admincp_user_search_subscription_status'],
		'user[subscriptionstatus]',
		$subscriptionStatusOptions,
		-1,
		false
	);

	print_input_row($vbphrase['home_page_guser'], 'user[homepage]');
	print_yes_no_other_row($vbphrase['facebook_connected'], 'user[facebook]', $vbphrase['either'], -1);
	print_input_row($vbphrase['signature'], 'user[signature]');
	print_input_row($vbphrase['user_title_guser'], 'user[usertitle]');
	print_input_row($vbphrase['join_date_is_after'] . $vbphrase['user_search_date_format_hint'], 'user[joindateafter]');
	print_input_row($vbphrase['join_date_is_before'] . $vbphrase['user_search_date_format_hint'], 'user[joindatebefore]');
	print_input_row($vbphrase['last_activity_is_after'] . $vbphrase['user_search_date_time_format_hint'], 'user[lastactivityafter]');
	print_input_row($vbphrase['last_activity_is_before'] . $vbphrase['user_search_date_time_format_hint'], 'user[lastactivitybefore]');
	print_input_row($vbphrase['last_post_is_after'] . $vbphrase['user_search_date_time_format_hint'], 'user[lastpostafter]');
	print_input_row($vbphrase['last_post_is_before'] . $vbphrase['user_search_date_time_format_hint'], 'user[lastpostbefore]');
	print_input_row($vbphrase['birthday_is_after'] . $vbphrase['user_search_date_format_hint'], 'user[birthdayafter]');
	print_input_row($vbphrase['birthday_is_before'] . $vbphrase['user_search_date_format_hint'], 'user[birthdaybefore]');
	print_input_row($vbphrase['posts_are_greater_than'], 'user[postslower]', '', 1, 7);
	print_input_row($vbphrase['posts_are_less_than'], 'user[postsupper]', '', 1, 7);
	print_input_row($vbphrase['reputation_is_greater_than'], 'user[reputationlower]', '', 1, 7);
	print_input_row($vbphrase['reputation_is_less_than'], 'user[reputationupper]', '', 1, 7);
	print_input_row($vbphrase['warnings_are_greater_than'], 'user[warningslower]', '', 1, 7);
	print_input_row($vbphrase['warnings_are_less_than'], 'user[warningsupper]', '', 1, 7);
	print_input_row($vbphrase['infractions_are_greater_than'], 'user[infractionslower]', '', 1, 7);
	print_input_row($vbphrase['infractions_are_less_than'], 'user[infractionsupper]', '', 1, 7);
	print_input_row($vbphrase['infraction_points_are_greater_than'], 'user[pointslower]', '', 1, 7);
	print_input_row($vbphrase['infraction_points_are_less_than'], 'user[pointsupper]', '', 1, 7);
	print_input_row($vbphrase['userid_is_greater_than'], 'user[useridlower]', '', 1, 7);
	print_input_row($vbphrase['userid_is_less_than'], 'user[useridupper]', '', 1, 7);
	print_input_row($vbphrase['registration_ip_address'], 'user[ipaddress]');

	// privacy consent search fields
	print_yes_no_other_row($vbphrase['admincp_privacyconsent_required_label'], 'user[eustatus_check]', $vbphrase['either'], -1);
	print_radio_row(
		$vbphrase['admincp_privacyconsent_status_label'],
		'user[privacyconsent]',
		[
			'1' => $vbphrase['admincp_privacyconsent_provided'],
			'-1' => $vbphrase['admincp_privacyconsent_withdrawn'],
			'0' => $vbphrase['admincp_privacyconsent_unknown'],
			'any' => $vbphrase['admincp_privacyconsent_any'],
		],
		'any'
	);
	print_input_row($vbphrase['admincp_privacyconsentupdated_after'] . $vbphrase['user_search_date_format_hint'], 'user[privacyconsentupdatedafter]');
	print_input_row($vbphrase['admincp_privacyconsentupdated_before'] . $vbphrase['user_search_date_format_hint'], 'user[privacyconsentupdatedbefore]');
	print_middle_submit_row($actionlabel);

	print_table_header($vbphrase['user_profile_fields']);

	$profilefields = vB::getDbAssertor()->assertQuery('fetchProfileFields');
	require_once(DIR . '/includes/adminfunctions_profilefield.php');
	foreach ($profilefields AS $profilefield)
	{
		print_profilefield_row('profile', $profilefield);
	}
	print_middle_submit_row($actionlabel);
}

/**
 * Print standard admincp user search column display options.
 *
 * @param array $vbphrase
 * @param array $skipOrShow    Array of fields to skip or show depending on $modeIsSkip.
 * @param bool  $modeIsSkip    Default true. If true, will skip fields specified in $skipOrShow. If false, will only show fields
 *                             specified in $skipOrShow.
 *
 * @return void
 */
function print_user_search_display_rows(array $vbphrase, array $skipOrShow = [], bool $modeIsSkip = true) : void
{
	$enabledisplayname = vB::getDatastore()->getOption('enabledisplayname');

	// input name => [
	// 	phrasetitle => will default to "display_{input name}".
	// 	default => default value for the input.
	// ]
	$data = [
		'username' => [
			'default' => 1,
		],
		'displayname' => [
			// We may want to revisit this, but QoL to just hide displaynames in search result columns by default when
			// enabledisplayname is off. Note that they can manually turn it on/off in either case, and they will always be able
			// to search by & edit displaynames regardless of the enabledisplayname setting.
			'default' => !empty($enabledisplayname),
		],
		'options' => [
			'default' => 1,
		],
		'usergroup' => [
			'default' => 0,
		],
		'email' => [
			'phrasetitle' => 'display_email_gcpuser',
			'default' => 1,
		],
		'parentemail' => [
			'phrasetitle' => 'display_parent_email_address',
			'default' => 0,
		],
		'coppauser' => [
			'phrasetitle' => 'display_coppa_user',
			'default' => 0,
		],
		'homepage' => [
			'phrasetitle' => 'display_home_page',
			'default' => 0,
		],
		'signature' => [
			'default' => 0,
		],
		'usertitle' => [
			'phrasetitle' => 'display_user_title',
			'default' => 0,
		],
		'joindate' => [
			'phrasetitle' => 'display_join_date',
			'default' => 1,
		],
		'lastactivity' => [
			'phrasetitle' => 'display_last_activity',
			'default' => 1,
		],
		'lastpost' => [
			'phrasetitle' => 'display_last_post',
			'default' => 0,
		],
		'posts' => [
			'phrasetitle' => 'display_post_count',
			'default' => 1,
		],
		'reputation' => [
			'phrasetitle' => 'display_reputation_gcpuser',
			'default' => 0,
		],
		'warnings' => [
			'default' => 0,
		],
		'infractions' => [
			'phrasetitle' => 'display_infractions_gcpuser',
			'default' => 0,
		],
		'ipoints' => [
			'phrasetitle' => 'display_infraction_points',
			'default' => 0,
		],
		'ipaddress' => [
			'phrasetitle' => 'display_ip_address',
			'default' => 0,
		],
		'birthday' => [
			'default' => 0,
		],
		'privacyrequired' => [
			'phrasetitle' => 'display_eustatus',
			'default' => 0,
		],
		'privacyconsent' => [
			'default' => 0,
		],
		'privacyconsentupdated' => [
			'default' => 0,
		],
	];

	if ($modeIsSkip)
	{
		// skip specified.
		$skip = $skipOrShow;
	}
	else
	{
		// show only the specified
		$skip = array_keys($data);
		$skip = array_diff($skip, $skipOrShow);
	}
	$skip = array_combine($skip, $skip);

	foreach ($data as $__key => $__value) {
		if (isset($skip[$__key])) {
			continue;
		}

		$__phrasetitle = $__value['phrasetitle'] ?? 'display_' . $__key;
		print_yes_no_row($vbphrase[$__phrasetitle], 'display[' . $__key . ']', $__value['default']);
	}

}

function print_user_search_display_profilefield_rows(array $vbphrase) : void
{
	$profilefields = vB::getDbAssertor()->assertQuery('fetchProfileFields');
	foreach ($profilefields AS $profilefield)
	{
		$key = 'field' . $profilefield['profilefieldid'];
		$titlekey = $key . '_title';
		$__title = construct_phrase($vbphrase['display_x'], htmlspecialchars_uni($vbphrase[$titlekey] ?? $titlekey));
		print_yes_no_row($__title, "display[$key]", 0);
	}
}

function print_user_search_sort_rows(array $vbphrase) : void
{
	print_label_row($vbphrase['order_by_gcpglobal'], '
		<select name="orderby" tabindex="1" class="bginput">
			<option value="username" selected="selected">' . 	$vbphrase['username'] . '</option>
			<option value="email">' . $vbphrase['email'] . '</option>
			<option value="joindate">' . $vbphrase['join_date'] . '</option>
			<option value="lastactivity">' . $vbphrase['last_activity'] . '</option>
			<option value="lastpost">' . $vbphrase['last_post'] . '</option>
			<option value="posts">' . $vbphrase['post_count'] . '</option>
			<option value="birthday_search">' . $vbphrase['birthday_guser'] . '</option>
			<option value="reputation">' . $vbphrase['reputation'] . '</option>
			<option value="warnings">' . $vbphrase['warnings_gcpuser'] . '</option>
			<option value="infractions">' . $vbphrase['infractions'] . '</option>
			<option value="ipoints">' . $vbphrase['infraction_points'] . '</option>
		</select>
		<select name="direction" tabindex="1" class="bginput">
			<option value="">' . $vbphrase['ascending'] . '</option>
			<option value="DESC">' . $vbphrase['descending'] . '</option>
		</select>
	', '', 'top', 'orderby');
}

function get_user_search_result_fieldmap() : array
{
	//these are array of ['phrasename', 'displaytype', 'columnoverride']
	//phrasename -- phrase system id to show the for the column header
	//displaytype -- switch to determine how to do the formatting
	//columnoverride -- the user array column name for the value.  If not
	//	provided then will use the field name (key for this array)
	$fieldmap = [
		'username'              => ['username',                              'userlink',         ''],
		'displayname'           => ['displayname',                           'displayname_link', ''],
		'usergroup'             => ['usergroup',                             'usergroup',        'usergroupid'],
		'email'                 => ['email',                                 'email',            ''],
		'parentemail'           => ['parent_email_address',                  'email',            ''],
		'coppauser'             => ['coppa_user',                            'yesno',            ''],
		'homepage'              => ['personal_home_page',                    'url',              ''],
		'signature'             => ['signature',                             'signature',        ''],
		'usertitle'             => ['user_title_guser',                      'string',           ''],
		'joindate'              => ['join_date',                             'date',             ''],
		'lastactivity'          => ['last_activity',                         'date',             ''],
		'lastpost'              => ['last_post',                             'date',             ''],
		'posts'                 => ['post_count',                            'number',           ''],
		'reputation'            => ['reputation',                            'number',           ''],
		'warnings'              => ['warnings_gcpuser',                      'number',           ''],
		'infractions'           => ['infractions',                           'number',           ''],
		'ipoints'               => ['infraction_points',                     'number',           ''],
		'ipaddress'             => ['ip_address',                            'string',           ''],
		'birthday'              => ['birthday_guser',                        'string',           'birthday_search'],
		'privacyrequired'       => ['admincp_privacyconsent_required_label', 'privacyrequired',  'location'],
		'privacyconsent'        => ['admincp_privacyconsent_status_label',   'privacyconsent',   ''],
		'privacyconsentupdated' => ['admincp_privacyconsentupdated_label', 'date', ''],
	];

	return $fieldmap;
}

function get_user_search_result_headers(array $vbphrase, array $display, Iterable $profilefields) : array
{
	$fieldmap = get_user_search_result_fieldmap();

	$header = [];

	foreach ($fieldmap AS $field => $fieldinfo)
	{
		if (!empty($display[$field]))
		{
			$header[] = $vbphrase[$fieldinfo[0]];
		}
	}

	foreach ($profilefields AS $profilefield)
	{
		$key = 'field' . $profilefield['profilefieldid'];
		if (!empty($display[$key]))
		{
			$titlekey = $key . '_title';
			$header[] = htmlspecialchars_uni($vbphrase[$titlekey] ?? $titlekey);
		}
	}

	if (!empty($display['options']))
	{
		$header[] = $vbphrase['options'];
	}

	return $header;
}

function print_user_search_results(array $vbphrase, array $display, Iterable $profilefields, array $users, ?Callable $cellPre = null, ?Callable $cellPost = null) : void
{
	$privacyConsentCache = [
		1 => $vbphrase['admincp_privacyconsent_provided'],
		-1 => $vbphrase['admincp_privacyconsent_withdrawn'],
		0 => $vbphrase['admincp_privacyconsent_unknown'],
	];

	$groupcache = vB_Api::instanceInternal('usergroup')->fetchUsergroupList();
	$usergroupTitleCache = array_column($groupcache, 'title', 'usergroupid');


	$fieldmap = get_user_search_result_fieldmap();

	$datastore = vB::getDatastore();
	$dateformat = $datastore->getOption('dateformat');

	/** @var vB_Library_User */
	$userLib = vB_Library::instance('user');

	foreach ($users AS $user)
	{
		$cell = [];

		if (!is_null($cellPre) AND is_callable($cellPre))
		{
			$cellPre($vbphrase, $display, $user, $cell);
		}

		foreach ($fieldmap AS $field => $fieldinfo)
		{
			if (!empty($display[$field]))
			{
				//this gets a trifle convoluted because so may of the types are one-off custom forms.
				//however we don't wnat to stick with the field by field approach that this is replacing because
				//a) The master list aids in reordering columns without having to make sure that the header/body
				//	order stays in sync
				//b) Some columns share logic and we'd like to avoid repeating it.
				//c) We want to skip the repeated if (display) then show column logic because with so many columns
				//	it clutters the code.  The alternative to break this mess up further would be to set a format
				//	function (perhaps anonymous for one offs) in the master list and just call it here.
				//	that might be overkill.

				//default to the field name as the user column but a couple of field don't match.
				$usercolumn = ($fieldinfo[2] ? $fieldinfo[2] : $field);
				$fieldval = $user[$usercolumn];

				switch ($fieldinfo[1])
				{
					case 'userlink':
						$url = 'admincp/user.php?do=edit&u=' . $user['userid'];
						$cell[] = '<a href="' . htmlspecialchars($url) . '"><b>' . $fieldval . '</b></a>&nbsp;';
						break;
					// same as above, but displaynames are not HTML safe.
					case 'displayname_link':
						$url = 'admincp/user.php?do=edit&u=' . $user['userid'];
						$cell[] = '<a href="' . htmlspecialchars($url) . '"><b>' . vB_String::htmlSpecialCharsUni($fieldval) . '</b></a>&nbsp;';
						break;

					case 'usergroup':
						$cell[] = $usergroupTitleCache[$fieldval];
						break;

					case 'email':
						$cell[] = '<a href="mailto:' . $fieldval . '">' . $fieldval . '</a>';
						break;

					case 'yesno':
						$cell[] = ($user[$field] ? $vbphrase['yes'] : $vbphrase['no']);
						break;

					case 'url':
						$url = htmlspecialchars($fieldval);
						if ($url)
						{
							$cell[] = '<a href="' . $url . '" target="_blank">' . $url . '</a>';
						}
						else
						{
							$cell[] = '';
						}
						break;

					//this might be more genrically a multiline
					case 'signature':
						$cell[] = nl2br(htmlspecialchars_uni($fieldval));
						break;

					case 'date':
						if ($fieldval)
						{
							$cell[] = '<span class="smallfont">' . vbdate($dateformat, $fieldval) . '</span>';
						}
						else
						{
							$cell[] = '<i>' . $vbphrase['never'] . '</i>';
						}
						break;

					case 'number':
						$cell[] = vb_number_format($fieldval);
						break;

					case 'ipaddress':
						if ($fieldval)
						{
							$host = @gethostbyaddr($fieldval);
							$cell[] = $fieldval . ' (' . $host . ')';
						}
						else
						{
							$cell[] = '&nbsp;';
						}
						break;

					case 'privacyrequired':
						$required = $userLib->checkPrivacyOption('enable_privacy_registered', $fieldval);
						$required	=  ($required ? $vbphrase['yes'] : $vbphrase['no']);
						$cell[] = '<span title="location: ' . htmlentities($fieldval) . '">'. $required . '</span>';
						break;

					case 'privacyconsent':
						$cell[] = $privacyConsentCache[$fieldval] ?? $privacyConsentCache[0];
						break;

					case 'string':
					default:
						$cell[] = $fieldval;
						break;
				}
			}
		}

		foreach ($profilefields AS $profilefield)
		{
			$profilefieldname = 'field' . $profilefield['profilefieldid'];
			if (!empty($display[$profilefieldname]))
			{
				if ($profilefield['type'] == 'checkbox' OR $profilefield['type'] == 'select_multiple')
				{
					$output = '';
					$data = vb_unserialize($profilefield['data']);
					foreach ($data AS $index => $value)
					{
						if (intval($user[$profilefieldname]) & pow(2, $index))
						{
							if (!empty($output))
							{
								$output .= '<b>,</b> ';
							}
							$output .= $value;
						}
					}
					$cell[] = $output;
				}
				else
				{
					$cell[] = $user[$profilefieldname];
				}
			}
		}

		if (!is_null($cellPost) AND is_callable($cellPost))
		{
			$cellPost($vbphrase, $display, $user, $cell);
		}

		print_cells_row2($cell);
	}
}

// can we better formalize this somehow?
function callback_show_user_search_result_options(array $vbphrase, array $display, array $user, array &$cell) : void
{
	if (!empty($display['options']))
	{
		$options = [];
		$options['edit'] = $vbphrase['view'] . " / " . $vbphrase['edit_user'];

		if (!empty($user['email']))
		{
			$options[unhtmlspecialchars($user['email'])] = $vbphrase['send_password_to_user'];
		}

		$options['kill'] = $vbphrase['delete_user'];
		$cell[] = construct_jump_control('ufind', $user['userid'],  $vbphrase['go'], $options);
	}

}

/**
 *	Prints a submit button in the middle of the form to avoid forcing a scroll down on long forms.
 *
 *	Unlike print_table_default_footer, it does not close the form/table
 */
//this is potentially more general but for the moment I'm only seeing the idiom in the user code
//and it's not entirely clear if all uses are going to be identical (they should be consolidated
//and regularlized where possible).  Leaving it with the user functions until I'm sure it
//generalizes better.
function print_middle_submit_row($submitname, $colspan = 2)
{
	$logicalright = vB_Template_Runtime::fetchStyleVar('right');
	$submitdescription = '<div align="' . $logicalright .'"><input type="submit" class="button" value=" ' . $submitname . ' " tabindex="1" /></div>';
	print_description_row($submitdescription, false, $colspan);
}

/*=========================================================================*\
|| #######################################################################
|| # Downloaded: 13:27, Sun Aug 30th 2026
|| # CVS: $RCSfile$ - $Revision: 122726 $
|| #######################################################################
\*=========================================================================*/
