<?php
/*========================================================================*\
|| ###################################################################### ||
|| # 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   # ||
|| ###################################################################### ||
\*========================================================================*/

// ######################## SET PHP ENVIRONMENT ###########################

// ##################### DEFINE IMPORTANT CONSTANTS #######################
define('CVS_REVISION', '$RCSfile$ - $Revision: 111008 $');

// #################### PRE-CACHE TEMPLATES AND DATA ######################
global $phrasegroups, $specialtemplates, $vbphrase, $vbulletin;
$phrasegroups = ['cpglobal', 'cphome', 'cppermission','cpuser', 'user', ];
$specialtemplates = [];

// ########################## REQUIRE BACK-END ############################
require_once(dirname(__FILE__) . '/global.php');

// ######################## CHECK ADMIN PERMISSIONS #######################
// TODO: Which perms? canadminusers might make sense since we're making user changes in a way.
// Don't forget to sync with the perm set on cpnav_vbulletin
if (!can_administer('canadminusers') OR !can_administer('canadminforums'))
{
	print_cp_no_permission();
}


// ############################# LOG ACTION ###############################
log_admin_action();


// ########################################################################
// ######################### START MAIN SCRIPT ############################
// ########################################################################

print_cp_header($vbphrase['channel_subscriptions'], [
	get_admincp_script_tag('vbulletin_paginate.js'),
	// This is only used in the user search -> add subscriptions flows, but it's easier to just include it here.
	get_admincp_script_tag('vbulletin_user.js'),
]);


if (empty($_REQUEST['do']))
{
	$_REQUEST['do'] = 'list_usergroupsubscriptions';
	//$_REQUEST['do'] = 'searchusers';
}

/** @var vB_Api_Follow */
$followApi = vB_Api::instanceInternal('follow');
$channelApi = vB_Api::instanceInternal('content_channel');
$userApi = vB_Api::instanceInternal('user');
$assertor = vB::getDbAssertor();


// print_form_header2() always appends '.php?do'...
$thispagelink = 'admincp/usergroupsubscription';

// ###################### Start List #######################
if ($_REQUEST['do'] == 'list_usergroupsubscriptions')
{
	// Future features:
	// list by channel tree...?

	printQuickLinks($vbphrase);

	$vbulletin->input->clean_array_gpc('r', [
		'page'	  => vB_Cleaner::TYPE_INT,
		'perpage' => vB_Cleaner::TYPE_INT,
	]);
	['p' => $page, 'pp' => $perpage] = getPageAndPerPage($vbulletin->GPC);

	// maybe we don't even need or want pagination for this?
	// if we do a tree view unclear what a "page" would mean...

	$totalcount = $assertor->getField('vBForum:usergroup_channel_subscription', [
		vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SUMMARY,
		vB_dB_Query::COLUMNS_KEY => ['COUNT(*)'],
	]);
	// allow perpage = 0 to be show all
	$conditions = [];
	if ($perpage > 0)
	{
		$conditions = [
			vB_dB_Query::PARAM_LIMIT => $perpage,
			vB_dB_Query::PARAM_LIMITSTART => ($page - 1) * $perpage,
		];
	}
	$sort = [
		// 'field' => ['month', 'day'],
		// 'direction' => [vB_dB_Query::SORT_ASC, vB_dB_Query::SORT_ASC],
		// probably more useful to sort by channelid, then usergroupid or something.
		'field' => ['subscriptionid'],
		'direction' => [vB_dB_Query::SORT_ASC],
	];
	$subscriptions = $assertor->getRows('vBForum:usergroup_channel_subscription', $conditions, $sort);

	[
		'usergroupTitles' => $usergroupTitles,
		'channelTitles' => $channelTitles,
	] = getUsergroupAndChannelTitles($subscriptions);

	$paginationParams = [
		'page' => $page,
		'perpage' => $perpage,
	];
	print_pagination_form($thispagelink, 'list_usergroupsubscriptions', $paginationParams, $totalcount);

	$checkall = "
		<label class=\"h-pointer h-flex-left\">
			<span class=\"h-checkbox-label h-align-middle \">
				<input class=\"h-pointer js-checkbox-master\" data-child=\"js-checkbox-child-top\" type=\"checkbox\" name=\"checkall\" value=\"1\">
			</span>
			{$vbphrase['all']}
		</label>";
	$headers = [
		$checkall,
		"<span class=\"h-center-content\">" . $vbphrase['usergroup'] . "</span>",
		"<span class=\"h-center-content\">" . $vbphrase['channel'] . "</span>",
		"<span class=\"h-center-content\">" . $vbphrase['description'] . "</span>",
		// Edit link etc
		'',
	];
	$colSpan = count($headers);

	// We want the left column left-aligned, everything else center aligned. We may want to push this to the print_cells_row2 function if this config
	// turns out to be common.
	$aligns = array_fill(0, $colSpan, 'vbcenter');
	$aligns[0] = 'vbleft';

	print_form_header2($thispagelink, 'delete_usergroupsubscriptions');
	print_table_start();
	print_table_header($vbphrase['view_auto_subscriptions'], $colSpan);
	$bgClasses = array_fill(0, $colSpan, 'thead thead--v-centered');
	$bgClasses[0] = 'thead thead--v-centered h-fixed-width-32';
	print_cells_row2($headers, $bgClasses, $aligns);

	construct_hidden_code('page', $page);
	construct_hidden_code('perpage', $perpage);

	//print_cells_row2($firstRow, [], $aligns);
	// Hack to pre-increment the global counter for our fetch_prev_row_bgclass() use below.
	fetch_row_bgclass();
	$strUtil = vB::getString();
	if (count($subscriptions) == 0)
	{
		print_description_row($vbphrase['no_auto_subscriptions_defined'], false, $colSpan);
	}
	else
	{
		foreach ($subscriptions AS $__row)
		{
			$__id = $__row['subscriptionid'];
			$__checkboxid = 'ug_c_sub_' . $__id;
			// Controls
			$__url = get_admincp_url('usergroupsubscription', ['do' => 'edit_usergroupsubscription', 'subscriptionid' => $__id]);
			$__editLink = construct_link_code2($vbphrase['edit'], $__url);
			$__url = get_admincp_url('usergroupsubscription', ['do' => 'delete_usergroupsubscriptions', 'subscriptionids[]' => $__id]);
			$__delLink = construct_link_code2($vbphrase['delete'], $__url);


			$__desc = $strUtil->htmlSpecialChars($__row['description'] ?? '');
			// don't wrap title (e.g. icons etc), calendar view normally shouldn't wrap it.
			$__channeltitle = $channelTitles[$__row['nodeid']] ?? 'Unknown Channel';
			$__ugTitle = $usergroupTitles[$__row['usergroupid']] ?? 'Unknown Group';
			// put the checkbox next to the title (inside the label).
			$__checkbox =  "<label class=\"h-pointer h-flex-left h-gap-4\">
					<span class=\"h-checkbox-label\" title=\"Subscriptionid: {$__id}\">
						<input id=\"$__checkboxid\" class=\"h-pointer js-checkbox-child-top\" type=\"checkbox\" name=\"subscriptionids[]\" value=\"$__id\">
					</span>
				</label>";
			$rowCells = [
				//"<span class=\"nowrap\" title=\"{$__desc}\">{$__row['title']}</span>",
				$__checkbox,
				// "<label for=\"$__checkboxid\" title=\"Usergroupid: {$__row['usergroupid']}\">{$__ugTitle}</label>",
				// "<label for=\"$__checkboxid\" title=\"Nodeid: {$__row['nodeid']}\">{$__channeltitle}</label>",
				// "<label for=\"$__checkboxid\">{$__desc}</label>",
				"<span title=\"Usergroupid: {$__row['usergroupid']}\">{$__ugTitle}</span>",
				"<span title=\"Nodeid: {$__row['nodeid']}\">{$__channeltitle}</span>",
				"<span>{$__desc}</span>",
				'<div class="h-center-content h-gap-4">' . $__editLink . $__delLink . '</div>',
			];

			// print_cells_row2() will always call fetch_row_bgclass() which increments the global counter. fetch_prev..() does not increment the counter.
			$__defaultBgClass = fetch_prev_row_bgclass();
			// vertically align every column (i.e. checkboxes vs text labels) to look a bit neater.
			$__bgClasses = array_fill(0, $colSpan, $__defaultBgClass . ' h-align-middle--only');
			print_cells_row2($rowCells, $__bgClasses, $aligns);
		}
	}

	$args = [
		'do' => 'edit_usergroupsubscription',
		'page' => $page,
		'perpage' => $perpage,
	];
	$editUrl = get_admincp_url('usergroupsubscription', $args);
	$buttons = [
		// note, the bulk delete action is a form submit because we need the subscriptionids checkboxes.
		['submit', $vbphrase['delete_usergroup_subscriptions']],
		construct_link_button($vbphrase['add_new_usergroup_subscription'] , $editUrl),
	];


	print_table_button_footer($buttons, $colSpan);

	print_pagination_form($thispagelink, 'list_usergroupsubscriptions', $paginationParams, $totalcount);
}

// ###################### Start Delete #######################
if ($_REQUEST['do'] == 'delete_usergroupsubscriptions')
{
	// todo: test this
	$vbulletin->input->clean_array_gpc('r', [
		'subscriptionids' => vB_Cleaner::TYPE_ARRAY_INT,
		// preserve for redirect
		'page' => vB_Cleaner::TYPE_INT,
		'perpage' => vB_Cleaner::TYPE_INT,
	]);
	$subscriptionids = $vbulletin->GPC['subscriptionids'];

	$records = $assertor->getRows('vBForum:usergroup_channel_subscription', ['subscriptionid' => $subscriptionids]);
	if (count($records) == 0)
	{
		print_stop_message('please_complete_required_fields');
	}

	print_form_header2($thispagelink, 'kill_usergroupsubscriptions');

	[
		'usergroupTitles' => $usergroupTitles,
		'channelTitles' => $channelTitles,
	] = getUsergroupAndChannelTitles($records);

	// $labels = [];
	$strUtil = vB::getString();
	// foreach ($records AS $__row)
	// {
	// 	construct_hidden_code("subscriptionids[{$__row['subscriptionid']}]", $__row['subscriptionid']);
	// 	$__desc = $strUtil->htmlSpecialChars($__row['description'] ?? '');
	// 	// TODO: get channel title & usergroup title and show them here.
	// 	$__ugTitle = $usergroupTitles[$__row['usergroupid']] ?? 'Unknown Group';
	// 	$labels[] = "<li><span title=\"$__desc\">{$__ugTitle}</span></li>";
	// }
	// $labels = '<ul>' . implode('<br />', $labels) . '</ul>';

	// Trying to keep the hidden inputs outside the table, though not sure if it matters.
	foreach ($records AS $__row)
	{
		construct_hidden_code("subscriptionids[{$__row['subscriptionid']}]", $__row['subscriptionid']);
	}
	construct_hidden_code('page', $vbulletin->GPC['page']);
	construct_hidden_code('perpage', $vbulletin->GPC['perpage']);

	print_table_start();
	$headers = [
		"<span class=\"h-center-content\">" . $vbphrase['usergroup'] . "</span>",
		"<span class=\"h-center-content\">" . $vbphrase['channel'] . "</span>",
		"<span class=\"h-center-content\">" . $vbphrase['description'] . "</span>",
	];
	$colSpan = count($headers);
	print_table_header($vbphrase['confirm_deletion_gcpglobal'], $colSpan);
	print_description_row($vbphrase['are_you_sure_delete_auto_sub'], false, $colSpan);

	$aligns = 'vbcenter';
	print_cells_row2($headers, 'thead', $aligns);

	foreach ($records AS $__row)
	{
		$__subDescription = $strUtil->htmlSpecialChars($__row['description'] ?? '');
		$__ugTitle = $usergroupTitles[$__row['usergroupid']] ?? 'Unknown Group';
		$__channelTitle = $channelTitles[$__row['nodeid']] ?? 'Unknown Channel';

		$cells = [
			$__ugTitle,
			$__channelTitle,
			$__subDescription,
		];
		print_cells_row2($cells, '', $aligns);
	}

	print_table_button_footer([['submit', $vbphrase['delete']], ['goback', $vbphrase['cancel']]], $colSpan);
}

if ($_REQUEST['do'] == 'kill_usergroupsubscriptions')
{
	// todo: test this
	$vbulletin->input->clean_array_gpc('r', [
		'subscriptionids' => vB_Cleaner::TYPE_ARRAY_INT,
		// preserve for redirect
		'page' => vB_Cleaner::TYPE_INT,
		'perpage' => vB_Cleaner::TYPE_INT,
	]);
	$subscriptionids = $vbulletin->GPC['subscriptionids'];
	if ($subscriptionids)
	{
		$assertor->delete('vBForum:usergroup_channel_subscription', ['subscriptionid' => $subscriptionids]);
	}

	$args = [
		'do' => 'list_usergroupsubscriptions',
		'page' => $vbulletin->GPC['page'],
		'perpage' => $vbulletin->GPC['perpage'],
	];
	$url = get_admincp_url('usergroupsubscription', $args);
	print_cp_redirect($url, 2);
}

// ###################### Start Save #######################
if ($_REQUEST['do'] == 'edit_usergroupsubscription')
{
	$vbulletin->input->clean_array_gpc('r', [
		'subscriptionid' => vB_Cleaner::TYPE_INT,
		// preserve for redirect
		'page' => vB_Cleaner::TYPE_INT,
		'perpage' => vB_Cleaner::TYPE_INT,
	]);
	$subscriptionid = $vbulletin->GPC['subscriptionid'];
	if ($subscriptionid)
	{
		$subscription = $assertor->getRow('vBForum:usergroup_channel_subscription', ['subscriptionid' => $subscriptionid]);
	}
	else
	{
		$subscription = [
			'subscriptionid' => 0,
			'usergroupid' => '',
			'nodeid' => '',
			'description' => '',
			'enabled' => 1,
		];
	}


	print_form_header2($thispagelink, 'save_usergroupsubscription');
	construct_hidden_code('page', $vbulletin->GPC['page']);
	construct_hidden_code('perpage', $vbulletin->GPC['perpage']);
	if ($subscriptionid)
	{
		construct_hidden_code('subscriptionid', $subscriptionid);
	}
	print_table_start();
	print_table_header($vbphrase['edit_usergroup_channel_subscription']);
	// usergroup selector
	/** @var vB_Api_UserGroup */
	$groupApi = vB_Api::instanceInternal('usergroup');
	$groups = $groupApi->fetchUsergroupList();
	$groups = array_column($groups, 'title', 'usergroupid');
	print_select_row($vbphrase['usergroup'], 'usergroupid', $groups, $subscription['usergroupid']);

	// forum selector
	$options = construct_forum_chooser_options();
	print_select_row($vbphrase['channel'], 'nodeid', $options, $subscription['nodeid']);
	print_textarea_row2($vbphrase['description'], 'description', $subscription['description'] ?? '');
	// Not sure yet if we need this or not
	//print_yes_no_row($vbphrase['enabled'], 'enabled', $subscription['enabled'] ?? 1);

	$addByUsergroupLink = get_admincp_url('usergroupsubscription', ['do' => 'add_subscription_by_usergroup']);
	$searchUsersLink = get_admincp_url('usergroupsubscription', ['do' => 'search_users']);
	$message = construct_phrase($vbphrase['usergroup_channel_autosub_first_x'], $addByUsergroupLink, $searchUsersLink);
	print_description_row($message, false, 2, 'warning');

	print_table_default_footer($vbphrase['save']);
}

// ###################### Start Save #######################
if ($_REQUEST['do'] == 'save_usergroupsubscription')
{
	$vbulletin->input->clean_array_gpc('p', [
		'subscriptionid' => vB_Cleaner::TYPE_INT,
		'description' => vB_Cleaner::TYPE_STR,
		'usergroupid' => vB_Cleaner::TYPE_INT,
		'nodeid' => vB_Cleaner::TYPE_INT,
		//'enabled' => vB_Cleaner::TYPE_BOOL,
		// preserve for redirect
		'page' => vB_Cleaner::TYPE_INT,
		'perpage' => vB_Cleaner::TYPE_INT,
	]);

	$subscriptionid = $vbulletin->GPC['subscriptionid'];
	$subscription = [
		'usergroupid' => $vbulletin->GPC['usergroupid'],
		'nodeid' => $vbulletin->GPC['nodeid'],
		'description' => $vbulletin->GPC['description'],
		//'enabled' => $vbulletin->GPC['enabled'],
	];

	if ($subscriptionid)
	{
		$assertor->update('vBForum:usergroup_channel_subscription', $subscription, ['subscriptionid' => $subscriptionid]);
	}
	else
	{
		$assertor->insert('vBForum:usergroup_channel_subscription', $subscription);
	}

	$args = [
		'do' => 'list_usergroupsubscriptions',
		'page' => $vbulletin->GPC['page'],
		'perpage' => $vbulletin->GPC['perpage'],
	];
	$url = get_admincp_url('usergroupsubscription', $args);
	print_cp_redirect($url, 2);
}


// ###################### Search Users With Subscriptions #######################
if ($_REQUEST['do'] == 'search_users')
{
	// for various print_user_search...() functions
	require_once(DIR . '/includes/adminfunctions_user.php');
	$skipDisplay = [
		'options',
	];

	printQuickLinks($vbphrase);

	print_form_header2($thispagelink, 'do_search_users');
	print_table_start2();
	print_table_header($vbphrase['search_users']);
	print_description_row($vbphrase['if_you_leave_a_field_blank_it_will_be_ignored']);
	print_description_row('', 0, 2, 'thead');
	print_user_search_rows();
	print_table_break();

	print_table_header($vbphrase['display_options']);
	print_user_search_display_rows($vbphrase, $skipDisplay);
	print_middle_submit_row($vbphrase['find']);

	print_table_header($vbphrase['user_profile_field_options']);
	print_user_search_display_profilefield_rows($vbphrase);
	print_middle_submit_row($vbphrase['find']);
	print_table_break();

	print_table_header($vbphrase['sorting_options']);
	print_user_search_sort_rows($vbphrase);
	print_input_row($vbphrase['page'], 'page', 1);
	$perpageoptions = getUserSearchPerpageOptions();
	print_select_row($vbphrase['per_page'], 'perpage', $perpageoptions, 50);
	print_table_default_footer($vbphrase['find']);
}

if ($_REQUEST['do'] == 'do_search_users')
{
	// for get_user_search_result_headers(), print_user_search_results() functions
	require_once(DIR . '/includes/adminfunctions_user.php');

	// Initially copied from user.php do == find & do == find2

	$vbulletin->input->clean_array_gpc('r', [
		'user'              => vB_Cleaner::TYPE_ARRAY,
		'profile'           => vB_Cleaner::TYPE_ARRAY,
		'display'           => vB_Cleaner::TYPE_ARRAY_BOOL,
		'orderby'           => vB_Cleaner::TYPE_STR,
		'page'              => vB_Cleaner::TYPE_UINT,
		'perpage'           => vB_Cleaner::TYPE_UINT,
		'direction'         => vB_Cleaner::TYPE_STR,
		'serializedprofile' => vB_Cleaner::TYPE_STR,
		'serializeduser'    => vB_Cleaner::TYPE_STR,
		'serializeddisplay' => vB_Cleaner::TYPE_STR,
	]);

	if (!empty($vbulletin->GPC['serializeduser']))
	{
		$vbulletin->GPC['user']    = json_decode(verify_client_string($vbulletin->GPC['serializeduser']), true);
		$vbulletin->GPC['profile'] = json_decode(verify_client_string($vbulletin->GPC['serializedprofile']), true);
	}

	if (!empty($vbulletin->GPC['serializeddisplay']))
	{
		$vbulletin->GPC['display'] = json_decode(verify_client_string($vbulletin->GPC['serializeddisplay']), true);
	}
	$serializedUser = sign_client_string(json_encode($vbulletin->GPC['user']));
	$serializedProfile = sign_client_string(json_encode($vbulletin->GPC['profile']));
	$serializedDisplay = sign_client_string(json_encode($vbulletin->GPC['display']));

	// If admin accidentally turned off showing every column, default to some useful columns.
	if (array_sum($vbulletin->GPC['display']) == 0)
	{
		$vbulletin->GPC['display'] = [
			'username' => 1,
			'displayname' => !empty($vboptions['enabledisplayname']),
			// these options are for the user.php links, we don't want to show them here even by default.
			//'options' => 1,
			'email' => 1,
			'joindate' => 1,
			'lastactivity' => 1,
			'posts' => 1,
		];
	}

	// the find function will default to 25 and we don't want the values to get out of sync
	['p' => $page, 'pp' => $perpage] = getPageAndPerPage($vbulletin->GPC, 25);

	// user search is 0-indexed
	$limitstart = ($page - 1) * $perpage;
	/** @var vB_Api_User */
	$userApi = vB_Api::instance('user');
	$users = $userApi->find(
		$vbulletin->GPC['user'],
		$vbulletin->GPC['profile'],
		$vbulletin->GPC['orderby'],
		$vbulletin->GPC['direction'],
		$limitstart,
		$perpage
	);
	print_stop_message_on_api_error($users);

	if (empty($users['users']) OR $users['count'] == 0)
	{
		print_stop_message('no_users_matched_your_query');
	}
	$countusers = $users['count'];

	$paginationParams = [
		'page' => $page,
		'perpage' => $perpage,
		'serializedprofile' => $serializedProfile,
		'serializeduser'    => $serializedUser,
		'serializeddisplay' => $serializedDisplay,
		'orderby'           => $vbulletin->GPC['orderby'],
		'direction'         => $vbulletin->GPC['direction'],
	];
	$perpageoptions = getUserSearchPerpageOptions();
	print_pagination_form($thispagelink, 'do_search_users', $paginationParams, $countusers, '', false, $perpageoptions);

	// end copy from user.php do == find


	// start copy from user.php do == find2

	$userLib = vB_Library::instance('user');

	// carries on from do == find at top of script
	$display = $vbulletin->GPC['display'];
	$limitfinish = $limitstart + $perpage;

	$profilefields = vB::getDbAssertor()->assertQuery('fetchProfileFields');

	// display the column headings
	$header = get_user_search_result_headers($vbphrase, $display, $profilefields);
	$checkAllOnPage = "
		<label class=\"h-pointer h-flex-left\">
			<span class=\"h-checkbox-label h-align-middle \">
				<input class=\"h-pointer js-checkbox-master\" data-child=\"js-checkbox-child-top\" type=\"checkbox\" name=\"checkall\" value=\"1\">
			</span>
			{$vbphrase['all']}
		</label>";
	array_unshift($header, $checkAllOnPage);

	// get number of cells for use in 'colspan=' attributes
	$colSpan = sizeof($header);
	print_form_header2($thispagelink, 'add_user_subscription');
	// Hook for vbulletin_user.js's serializeFormData().
	// We need this because we could hypothetically have thousands of userid[] inputs, and that can
	// cause problems with PHP's max_input_vars ini setting
	echo '<div class="hide js-serialize-form-data" data-source="userid"></div>';

	print_table_start();
	print_table_header($vbphrase['select_channel'], $colSpan);
	$options = construct_forum_chooser_options();
	print_select_row($vbphrase['channel'], 'nodeid', $options, '', false, 0, false, [1, $colSpan - 1]);
	// $echoform = false here, we need the userid inputs below for the next steps.
	print_table_footer($colSpan, '', '', false);

	print_table_start();
	print_table_header(
		construct_phrase(
			$vbphrase['showing_users_x_to_y_of_z'],
			$limitstart + 1,
			min($countusers, $limitfinish),
			$countusers
		),
		$colSpan
	);
	print_cells_row2($header, 'thead');

	// now display the results
	print_user_search_results($vbphrase, $display, $profilefields, $users['users'], callbackUserCellPre(...));

	$buttons = [
		// next action is to choose a channel to add the user(s) to.
		['submit', $vbphrase['add_channel_subscriptions']],
	];
	print_table_button_footer($buttons, $colSpan);

	print_pagination_form($thispagelink, 'do_search_users', $paginationParams, $countusers, '', false, $perpageoptions);
}

if ($_REQUEST['do'] == 'add_user_subscription')
{
	$vbulletin->input->clean_array_gpc('r', [
		'userid'             => vB_Cleaner::TYPE_ARRAY_INT,
		'userid_csv'         => vB_Cleaner::TYPE_STR,
		'nodeid'             => vB_Cleaner::TYPE_INT,
	]);

	$userids = $vbulletin->GPC['userid'];
	if (!empty($vbulletin->GPC['userid_csv']))
	{
		$userids = explode(',', $vbulletin->GPC['userid_csv']);
		$userids = array_map('intval', $userids);
	}
	$useridsCSV = implode(',', $userids);
	$othersCount = count($userids) - 1;
	$firstUserid = reset($userids);

	$assertor = vB::getDbAssertor();
	$firstUser = $assertor->getRow('user',	[
		'userid' => $firstUserid,
		vB_dB_Query::COLUMNS_KEY => ['username'],
	]);
	$firstUser = $firstUser['username'] ?? 'Unkonwn User';

	if (empty($userids))
	{
		print_stop_message('please_select_at_least_one_user');
	}

	print_form_header2($thispagelink, 'do_add_user_subscription');
	construct_hidden_code('nodeid', $vbulletin->GPC['nodeid']);
	construct_hidden_code('userid_csv', $useridsCSV);

	print_table_start();
	print_table_header($vbphrase['confirm_add_subscriptions']);
	print_description_row($vbphrase['please_confirm_your_selection']);
	if ($othersCount == 0)
	{
		print_label_row($vbphrase['user'], $firstUser);
	}
	else
	{
		$message = construct_phrase(
			$vbphrase['x_and_y_other_users'],
			$firstUser,
			$othersCount
		);
		print_label_row($vbphrase['users'], $message);
	}
	// This not only works as a choice confirmation, but it also allows admins one more chance to check/change
	// the channel, in case they missed it on the previous page, as our forms are not the neatest UI.
	$options = construct_forum_chooser_options();
	print_select_row($vbphrase['channel'], 'nodeid', $options, $vbulletin->GPC['nodeid']);

	$buttonphrase = ($othersCount > 0) ? 'add_channel_subscriptions' : 'add_channel_subscription';
	$buttons = [
		['submit', $vbphrase[$buttonphrase]],
	];
	print_table_button_footer($buttons);
}

if ($_REQUEST['do'] == 'do_add_user_subscription')
{
	$vbulletin->input->clean_array_gpc('r', [
		'userid_csv'         => vB_Cleaner::TYPE_STR,
		'nodeid'             => vB_Cleaner::TYPE_INT,
	]);

	$userids = explode(',', $vbulletin->GPC['userid_csv']);
	$userids = array_map('intval', $userids);
	$nodeid = $vbulletin->GPC['nodeid'];

	bulkCreateChannelSubscriptionsForUsers($userids, $nodeid);

	print_cp_redirect(get_admincp_url('usergroupsubscription', ['do' => 'list_usergroupsubscriptions']), 2, $vbphrase['done']);
}



if ($_REQUEST['do'] == 'add_subscription_by_usergroup')
{
	// These params are currently used for redirecting back to this page after finishing the batchdo_add_subscription_by_usergroup processes.
	$vbulletin->input->clean_array_gpc('r', [
		'group_type'         => vB_Cleaner::TYPE_STR,
		'usergroupid'        => vB_Cleaner::TYPE_INT,
		'nodeid'             => vB_Cleaner::TYPE_INT,
		'startat'            => vB_Cleaner::TYPE_INT,
		'batchsize'          => vB_Cleaner::TYPE_UINT,
		'count'              => vB_Cleaner::TYPE_UINT,
	]);
	$defaultGroupid = $vbulletin->GPC['usergroupid'] ? $vbulletin->GPC['usergroupid'] : '';
	$defaultGrouptype = $vbulletin->GPC['group_type'] ? $vbulletin->GPC['group_type'] : 'primary';
	$defaultNodeid = $vbulletin->GPC['nodeid'] ? $vbulletin->GPC['nodeid'] : '';

	printQuickLinks($vbphrase);

	print_form_header2($thispagelink, 'do_add_subscription_by_usergroup');
	print_table_start();
	print_table_header($vbphrase['add_subscription_by_usergroup']);

	// usergroup selector
	/** @var vB_Api_UserGroup */
	$groupApi = vB_Api::instanceInternal('usergroup');
	$groups = $groupApi->fetchUsergroupList();
	$groups = array_column($groups, 'title', 'usergroupid');
	print_select_row($vbphrase['usergroup'], 'usergroupid', $groups, $defaultGroupid);

	print_radio_row(
		$vbphrase['usergroup_membership_type'],
		'group_type',
		[
			'primary' => $vbphrase['primary_usergroup'],
			'secondary' => $vbphrase['additional_usergroups'],
			'both' => $vbphrase['either'],
		],
		$defaultGrouptype
	);

	// forum selector
	$options = construct_forum_chooser_options();
	print_select_row($vbphrase['channel'], 'nodeid', $options, $defaultNodeid);

	print_description_row($vbphrase['slow_process_warning'], false, 2, 'warning');

	print_table_default_footer($vbphrase['add_channel_subscriptions']);
}

if ($_REQUEST['do'] == 'do_add_subscription_by_usergroup')
{
	$vbulletin->input->clean_array_gpc('r', [
		'group_type'         => vB_Cleaner::TYPE_STR,
		'usergroupid'        => vB_Cleaner::TYPE_INT,
		'nodeid'             => vB_Cleaner::TYPE_INT,
		'startat'            => vB_Cleaner::TYPE_INT,
		'batchsize'          => vB_Cleaner::TYPE_UINT,
		'count'              => vB_Cleaner::TYPE_UINT,
		'max'                => vB_Cleaner::TYPE_UINT,
	]);

	$type = $vbulletin->GPC['group_type'];
	$usergroupid = $vbulletin->GPC['usergroupid'];
	$nodeid = $vbulletin->GPC['nodeid'];
	$startat = $vbulletin->GPC['startat'];
	$batchsize = $vbulletin->GPC['batchsize'];
	if (!$batchsize)
	{
		$batchsize = 1000;
	}
	$count = $vbulletin->GPC['count'];
	$max = $vbulletin->GPC['max'];

	$assertor = vB::getDbAssertor();

	// Do some validation, but only for the start of the batch.
	if ($startat == 0)
	{
		$check1 = $assertor->getRow('usergroup', ['usergroupid' => $usergroupid]);
		$check2 = $assertor->getRow('vBForum:node', ['nodeid' => $nodeid]);
		if (empty($check1) OR empty($check2))
		{
			print_stop_message('invalid_data');
		}

		// limit subscriptions to channels & topics for now
		if (!(empty($check2['starter']) OR $check2['nodeid'] == $check2['starter']))
		{
			print_stop_message('invalid_data');
		}
	}
	$conditions = [
		'usergroupid' => $usergroupid,
		'membership_type' => $type,
		'startat' => $startat,
		'batchsize' => $batchsize,
	];

	if (!$max)
	{
		$max = $assertor->getColumn('vBAdmincp:getMaxUseridInUsergroup', 'maxid', $conditions);
		$max = reset($max);
	}

	$nextid = $assertor->getColumn('vBAdmincp:getNextUseridInUsergroup', 'userid', $conditions);
	$nextid = reset($nextid);
	// if we don't get a nextid, it's probably because we don't have >= $batchsize users to process.
	if (!$nextid)
	{
		$nextid = $max + 1;
	}

	$conditions = [
		'usergroupid' => $usergroupid,
		'membership_type' => $type,
		'startat' => $startat,
		'nextid' => $nextid,
	];
	$userids = $assertor->getColumn('vBAdmincp:getUsersInUsergroup', 'userid', $conditions);
	if (empty($userids))
	{
		$params = [
			'do' => 'add_subscription_by_usergroup',
			'group_type' => $type,
			'usergroupid' => $usergroupid,
			'nodeid' => $nodeid,
		];
		print_stop_message(['done_adding_user_subscriptions_x', $count], get_admincp_url('usergroupsubscription', $params));
	}

	bulkCreateChannelSubscriptionsForUsers($userids, $nodeid);
	$count += count($userids);

	// prettify the counts: startat is 0 for first iteration, and nextid is exclusive.
	echo construct_phrase($vbphrase['x_to_y_of_z'], ($startat ? $startat : 1), ($nextid - 1), $max) . "<br />\n";

	$startat = $nextid;
	$args = [
		'do' => 'do_add_subscription_by_usergroup',
		'group_type' => $type,
		'usergroupid' => $usergroupid,
		'nodeid' => $nodeid,
		'startat' => $startat,
		'batchsize' => $batchsize,
		'count' => $count,
		'max' => $max,
	];
	print_cp_redirect(get_admincp_url('usergroupsubscription', $args), 2);

}

print_cp_footer();

// These functions should be considered private to this script.

function bulkCreateChannelSubscriptionsForUsers(array $userids, int $nodeid) : void
{
	if (!empty($userids) AND !empty($nodeid))
	{
		$assertor = vB::getDbAssertor();
		$existing = $assertor->getColumn('vBForum:subscribediscussion', 'userid', ['discussionid' => $nodeid]);
		$userids = array_diff($userids, $existing);

		$chunked = array_chunk($userids, 1000);
		foreach ($chunked AS $__userids)
		{
			$inserts = [];
			foreach ($__userids AS $__userid)
			{
				$inserts[] = [
					$__userid,
					$nodeid,
				];
			}
			// This GREPMARK is to try to keep track of the different places we do node subscription inserts.
			// My hope is that eventually we'll have a couple of follow library functions to wrap the various
			// dedupe / batching logics, but not right now as they each have slightly different logics and
			// I'm not sure how to generalize them yet.
			// GREPMARK: ADD NODE SUBSCRIPTION
			// This one does potentially multiple users per one node
			if (count($inserts))
			{
				$assertor->insertMultiple('vBForum:subscribediscussion',
					['userid', 'discussionid'],
					$inserts
				);
			}
		}
	}
}

function printQuickLinks(array $vbphrase) : void
{
	$thispagelink = 'admincp/usergroupsubscription';
	$quickLinks = [
		"$thispagelink.php?do=list_usergroupsubscriptions" => $vbphrase['view_auto_subscriptions'],
		"$thispagelink.php?do=search_users" => $vbphrase['search_users'],
		"$thispagelink.php?do=add_subscription_by_usergroup" => $vbphrase['add_subscription_by_usergroup'],
	];

	// Since we're sharing the quicklinks on more than one page, and I'd rather not remove or re-order links
	// as that can mess with muscle memory, let's mark when the current page is one of the links.
	foreach ($quickLinks AS $__link => &$__phrase)
	{
		$__query = parse_url($__link, PHP_URL_QUERY);
		parse_str($__query, $__params);
		if ($__params['do'] == $_REQUEST['do'])
		{
			$__phrase .= " <span class=\"h-link-accent\">({$vbphrase['current_page']})</span>";
		}
	}

	print_form_header2('admincp/', '');
	print_table_start();
	print_table_header($vbphrase['quick_links']);
	print_link_list_row($quickLinks);
	print_table_footer();
}

function callbackUserCellPre(array $vbphrase, array $display, array $user, array &$cell) : void
{
	$__checkbox =  "<label class=\"h-pointer h-flex-left h-gap-4\">
			<span class=\"h-checkbox-label\" title=\"Userid: {$user['userid']}\">
				<input class=\"h-pointer js-checkbox-child-top\" type=\"checkbox\" name=\"userid[]\" value=\"{$user['userid']}\">
			</span>
		</label>";
	$cell[] = $__checkbox;
}

function getUserSearchPerpageOptions() : array
{
	return [
		10 => 10,
		25 => 25,
		50 => 50,
		100 => 100,
		200 => 200,
		500 => 500,
		5000 => 5000,
		//50k's start hitting memory issues, so we'll cap it at 5k for now.
		// user find does not support "show all".
		//0 => $vbphrase['all'],
	];
}

/**
 * @param array $records `usergroup_channel_subscription` records
 *
 * @return array [
 *             'usergroupTitles' => [ usergroupid => title, ... ],
 *             'channelTitles' => [ nodeid => title, ... ],
 * ]
 */
function getUsergroupAndChannelTitles(array $records) : array
{
	$assertor = vB::getDbAssertor();

	$usergroupids = array_column($records, 'usergroupid');
	$usergroupids = array_unique($usergroupids);
	$usergroupTitles = $assertor->getColumn('usergroup', 'title', ['usergroupid' => $usergroupids], false, 'usergroupid');

	$channelids = array_column($records, 'nodeid');
	$channelids = array_unique($channelids);
	$channelTitles = $assertor->getColumn('vBForum:node', 'title', ['nodeid' => $channelids], false, 'nodeid');

	return [
		'usergroupTitles' => $usergroupTitles,
		'channelTitles' => $channelTitles,
	];
}

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