پیش‌نویس:Gadget-twinkleblock-2022.js

از ویکی‌پدیا، دانشنامهٔ آزاد
// <nowiki>


(function($) {

var api = new mw.Api(), relevantUserName, blockedUserName;
var menuFormattedNamespaces = $.extend({}, mw.config.get('wgFormattedNamespaces'));
menuFormattedNamespaces[0] = '(مقاله)';

/*
 ****************************************
 *** twinkleblock.js: Block module
 ****************************************
 * Mode of invocation:     Tab ("بستن")
 * Active on:              Any page with relevant user name (userspace, contribs, etc.)
 */

Twinkle.block = function twinkleblock() {
	relevantUserName = mw.config.get('wgRelevantUserName');
	// should show on Contributions or Block pages, anywhere there's a relevant user
	// Ignore ranges wider than the CIDR limit
	if (Morebits.userIsSysop && relevantUserName && (!Morebits.ip.isRange(relevantUserName) || Morebits.ip.validCIDR(relevantUserName))) {
		Twinkle.addPortletLink(Twinkle.block.callback, 'بستن', 'tw-block', 'قطه دسترسی کاربر مرتبط');
	}
};

Twinkle.block.callback = function twinkleblockCallback() {
	if (relevantUserName === mw.config.get('wgUserName') &&
			!confirm('شما در حال قطع دسترسی خودتان هستید! آیا مطمئنید که می‌خواهید ادامه دهید؟')) {
		return;
	}

	Twinkle.block.currentBlockInfo = undefined;
	Twinkle.block.field_block_options = {};
	Twinkle.block.field_template_options = {};

	var Window = new Morebits.simpleWindow(650, 530);
	// need to be verbose about who we're blocking
	Window.setTitle('قطع دسترسی یا افزودن الگوی قطع دسترسی برای ' + relevantUserName);
	Window.setScriptName('توینکل');
	Window.addFooterLink('الگوهای قطع دسترسی', 'الگو:Uw-block/doc/Block_templates');
	Window.addFooterLink('سیاست قطع دسترسی', 'وپ:بستن');
	Window.addFooterLink('ترجیحات قطع دسترسی', 'وپ:توینکل/ترجیحات#twinkle-config-section-2');
	Window.addFooterLink('راهنمای توینکل', 'en:WP:TW/DOC#block');
	Window.addFooterLink('ارائهٔ بازخورد', 'بوپ:توینکل');

	// Always added, hidden later if actual user not blocked
	Window.addFooterLink('باز کردن دسترسی این کاربر', 'ویژه:باز_کردن/' + relevantUserName, true);

	var form = new Morebits.quickForm(Twinkle.block.callback.evaluate);
	var actionfield = form.append({
		type: 'field',
		label: 'نوع عمل'
	});
	actionfield.append({
		type: 'checkbox',
		name: 'actiontype',
		event: Twinkle.block.callback.change_action,
		list: [
			{
				label: 'بستن کاربر',
				value: 'block',
				tooltip: 'قطع دسترسی کاربر مرتبط با تنظیمات انجام‌شده. اگر گزینهٔ قطع دسترسی مورد غیرفعال شود، این قطع دسترسی به‌صورت کلی اعمال خواهد شد.',
				checked: true
			},
			{
				label: 'قطع دسترسی موردی',
				value: 'partial',
				tooltip: 'فعال‌سازی قطع دسترسی‌های موردی و الگوهای قطع دسترسی موردی.',
				checked: Twinkle.getPref('defaultToPartialBlocks') // Overridden if already blocked
			},
			{
				label: 'افزودن الگوی قطع دسترسی به صفحهٔ بحث کاربر',
				value: 'template',
				tooltip: 'اگر مدیر قطع دسترسی کننده فراموش کرده‌است که الگوی قطع دسترسی را در بحث کاربر قرار دهد، یا این که شما دسترسی کاربر را قطع کرده‌اید و او را با افزودن الگو به بحثش آگاه نکرده‌اید، می‌توانید از این گزینه برای درج الگوی متناسب در بحث کاربر استفاه کنید. برای دیدن الگوهای قطع دسترسی موردی، گزینهٔ قطع دسترسی موردی را فعال کنید.',
				// Disallow when viewing the block dialog on an IP range
				checked: !Morebits.ip.isRange(relevantUserName),
				disabled: Morebits.ip.isRange(relevantUserName)
			}
		]
	});

	/*
	  Add option for IPv6 ranges smaller than /64 to upgrade to the 64
	  CIDR ([[WP:/64]]).  This is one of the few places where we want
	  wgRelevantUserName since this depends entirely on the original user.
	  In theory, we shouldn't use Morebits.ip.get64 here since since we want
	  to exclude functionally-equivalent /64s.  That'd be:
	  // if (mw.util.isIPv6Address(mw.config.get('wgRelevantUserName'), true) &&
	  // (mw.util.isIPv6Address(mw.config.get('wgRelevantUserName')) || parseInt(mw.config.get('wgRelevantUserName').replace(/^(.+?)\/?(\d{1,3})?$/, '$2'), 10) > 64)) {
	  In practice, though, since functionally-equivalent ranges are
	  (mis)treated as separate by MediaWiki's logging ([[phab:T146628]]),
	  using Morebits.ip.get64 provides a modicum of relief in thise case.
	*/
	var sixtyFour = Morebits.ip.get64(mw.config.get('wgRelevantUserName'));
	if (sixtyFour && sixtyFour !== mw.config.get('wgRelevantUserName')) {
		var block64field = form.append({
			type: 'field',
			label: 'تبدیل به قطع دسترسی بازهٔ /۶۴',
			name: 'field_64'
		});
		block64field.append({
			type: 'div',
			style: 'margin-bottom: 0.5em',
			label: ['معمولاً مشکلی در ', $.parseHTML('<a target="_blank" href="' + mw.util.getUrl(':en:WP:/64') + '">قطع دسترسی فقط بازهٔ /۶۴</a>')[0], ' (',
				$.parseHTML('<a target="_blank" href="' + mw.util.getUrl('ویژه:مشارکت‌ها/' + sixtyFour) + '">' + sixtyFour + '</a>)')[0], ') وجود ندارد.']
		});
		block64field.append({
			type: 'checkbox',
			name: 'block64',
			event: Twinkle.block.callback.change_block64,
			list: [{
				checked: Twinkle.getPref('defaultToBlock64'),
				label: 'قطع دسترسی بازهٔ /۶۴',
				value: 'block64',
				tooltip: Morebits.ip.isRange(mw.config.get('wgRelevantUserName')) ? 'از درج الگو اجتناب خواهد شد.' : 'هر الگوی تعیین‌شده به صفحهٔ بحث این آی‌پی افزوده خواهد شد: ' + mw.config.get('wgRelevantUserName')
			}]
		});
	}

	form.append({ type: 'field', label: 'از پیش تعیین‌شده', name: 'field_preset' });
	form.append({ type: 'field', label: 'گزینه‌های الگو', name: 'field_template_options' });
	form.append({ type: 'field', label: 'گزینه‌های قطع دسترسی', name: 'field_block_options' });

	form.append({ type: 'submit' });

	var result = form.render();
	Window.setContent(result);
	Window.display();
	result.root = result;

	Twinkle.block.fetchUserInfo(function() {
		// Toggle initial partial state depending on prior block type,
		// will override the defaultToPartialBlocks pref
		if (blockedUserName === relevantUserName) {
			$(result).find('[name=actiontype][value=partial]').prop('checked', Twinkle.block.currentBlockInfo.partial === '');
		}

		// clean up preset data (defaults, etc.), done exactly once, must be before Twinkle.block.callback.change_action is called
		Twinkle.block.transformBlockPresets();

		// init the controls after user and block info have been fetched
		var evt = document.createEvent('Event');
		evt.initEvent('change', true, true);

		if (result.block64 && result.block64.checked) {
			// Calls the same change_action event once finished
			result.block64.dispatchEvent(evt);
		} else {
			result.actiontype[0].dispatchEvent(evt);
		}
	});
};

// Store fetched user data, only relevant if switching IPv6 to a /64
Twinkle.block.fetchedData = {};
// Processes the data from a a query response, separated from
// Twinkle.block.fetchUserInfo to allow reprocessing of already-fetched data
Twinkle.block.processUserInfo = function twinkleblockProcessUserInfo(data, fn) {
	var blockinfo = data.query.blocks[0],
		userinfo = data.query.users[0];
	// If an IP is blocked *and* rangeblocked, the above finds
	// whichever block is more recent, not necessarily correct.
	// Three seems... unlikely
	if (data.query.blocks.length > 1 && blockinfo.user !== relevantUserName) {
		blockinfo = data.query.blocks[1];
	}
	// Cache response, used when toggling /64 blocks
	Twinkle.block.fetchedData[userinfo.name] = data;

	Twinkle.block.isRegistered = !!userinfo.userid;
	if (Twinkle.block.isRegistered) {
		Twinkle.block.userIsBot = !!userinfo.groupmemberships && userinfo.groupmemberships.map(function(e) {
			return e.group;
		}).indexOf('bot') !== -1;
	} else {
		Twinkle.block.userIsBot = false;
	}

	if (blockinfo) {
		// handle frustrating system of inverted boolean values
		blockinfo.disabletalk = blockinfo.allowusertalk === undefined;
		blockinfo.hardblock = blockinfo.anononly === undefined;
	}
	// will undefine if no blocks present
	Twinkle.block.currentBlockInfo = blockinfo;
	blockedUserName = Twinkle.block.currentBlockInfo && Twinkle.block.currentBlockInfo.user;

	// Toggle unblock link if not the user in question; always first
	var unblockLink = document.querySelector('.morebits-dialog-footerlinks a');
	if (blockedUserName !== relevantUserName) {
		unblockLink.hidden = true, unblockLink.nextSibling.hidden = true; // link+trailing bullet
	} else {
		unblockLink.hidden = false, unblockLink.nextSibling.hidden = false; // link+trailing bullet
	}

	// Semi-busted on ranges, see [[phab:T270737]] and [[phab:T146628]].
	// Basically, logevents doesn't treat functionally-equivalent ranges
	// as equivalent, meaning any functionally-equivalent IP range is
	// misinterpreted by the log throughout.  Without logevents
	// redirecting (like Special:Block does) we would need a function to
	// parse ranges, which is a pain.  IPUtils has the code, but it'd be a
	// lot of cruft for one purpose.
	Twinkle.block.hasBlockLog = !!data.query.logevents.length;
	Twinkle.block.blockLog = Twinkle.block.hasBlockLog && data.query.logevents;
	// Used later to check if block status changed while filling out the form
	Twinkle.block.blockLogId = Twinkle.block.hasBlockLog ? data.query.logevents[0].logid : false;

	if (typeof fn === 'function') {
		return fn();
	}
};

Twinkle.block.fetchUserInfo = function twinkleblockFetchUserInfo(fn) {
	var query = {
		format: 'json',
		action: 'query',
		list: 'blocks|users|logevents',
		letype: 'block',
		lelimit: 1,
		letitle: 'کاربر:' + relevantUserName,
		bkprop: 'expiry|reason|flags|restrictions|range|user',
		ususers: relevantUserName
	};

	// bkusers doesn't catch single IPs blocked as part of a range block
	if (mw.util.isIPAddress(relevantUserName, true)) {
		query.bkip = relevantUserName;
	} else {
		query.bkusers = relevantUserName;
		// groupmemberships only relevant for registered users
		query.usprop = 'groupmemberships';
	}

	api.get(query).then(function(data) {
		Twinkle.block.processUserInfo(data, fn);
	}, function(msg) {
		Morebits.status.init($('div[name="currentblock"] span').last()[0]);
		Morebits.status.warn('خطا در واکشی اطلاعات کاربر', msg);
	});
};

Twinkle.block.callback.saveFieldset = function twinkleblockCallbacksaveFieldset(fieldset) {
	Twinkle.block[$(fieldset).prop('name')] = {};
	$(fieldset).serializeArray().forEach(function(el) {
		// namespaces and pages for partial blocks are overwritten
		// here, but we're handling them elsewhere so that's fine
		Twinkle.block[$(fieldset).prop('name')][el.name] = el.value;
	});
};

Twinkle.block.callback.change_block64 = function twinkleblockCallbackChangeBlock64(e) {
	var $form = $(e.target.form), $block64 = $form.find('[name=block64]');

	// Show/hide block64 button
	// Single IPv6, or IPv6 range smaller than a /64
	var priorName = relevantUserName;
	if ($block64.is(':checked')) {
		relevantUserName = Morebits.ip.get64(mw.config.get('wgRelevantUserName'));
	} else {
		relevantUserName = mw.config.get('wgRelevantUserName');
	}
	// No templates for ranges, but if the original user is a single IP, offer the option
	// (done separately in Twinkle.block.callback.issue_template)
	var originalIsRange = Morebits.ip.isRange(mw.config.get('wgRelevantUserName'));
	$form.find('[name=actiontype][value=template]').prop('disabled', originalIsRange).prop('checked', !originalIsRange);

	// Refetch/reprocess user info then regenerate the main content
	var regenerateForm = function() {
		// Tweak titlebar text.  In theory, we could save the dialog
		// at initialization and then use `.setTitle` or
		// `dialog('option', 'title')`, but in practice that swallows
		// the scriptName and requires `.display`ing, which jumps the
		// window.  It's just a line of text, so this is fine.
		var titleBar = document.querySelector('.ui-dialog-title').firstChild.nextSibling;
		titleBar.nodeValue = titleBar.nodeValue.replace(priorName, relevantUserName);
		// Tweak unblock link
		var unblockLink = document.querySelector('.morebits-dialog-footerlinks a');
		unblockLink.href = unblockLink.href.replace(priorName, relevantUserName);
		unblockLink.title = unblockLink.title.replace(priorName, relevantUserName);

		// Correct partial state
		$form.find('[name=actiontype][value=partial]').prop('checked', Twinkle.getPref('defaultToPartialBlocks'));
		if (blockedUserName === relevantUserName) {
			$form.find('[name=actiontype][value=partial]').prop('checked', Twinkle.block.currentBlockInfo.partial === '');
		}

		// Set content appropriately
		Twinkle.block.callback.change_action(e);
	};

	if (Twinkle.block.fetchedData[relevantUserName]) {
		Twinkle.block.processUserInfo(Twinkle.block.fetchedData[relevantUserName], regenerateForm);
	} else {
		Twinkle.block.fetchUserInfo(regenerateForm);
	}
};

Twinkle.block.callback.change_action = function twinkleblockCallbackChangeAction(e) {
	var field_preset, field_template_options, field_block_options, $form = $(e.target.form);
	// Make ifs shorter
	var blockBox = $form.find('[name=actiontype][value=block]').is(':checked');
	var templateBox = $form.find('[name=actiontype][value=template]').is(':checked');
	var $partial = $form.find('[name=actiontype][value=partial]');
	var partialBox = $partial.is(':checked');
	var blockGroup = partialBox ? Twinkle.block.blockGroupsPartial : Twinkle.block.blockGroups;

	$partial.prop('disabled', !blockBox && !templateBox);

	// Add current block parameters as default preset
	var prior = { label: 'قطع دسترسی قبلی' };
	if (blockedUserName === relevantUserName) {
		Twinkle.block.blockPresetsInfo.prior = Twinkle.block.currentBlockInfo;
		// value not a valid template selection, chosen below by setting templateName
		prior.list = [{ label: 'تنظیمات قطع دسترسی قبلی', value: 'prior', selected: true }];

		// Arrays of objects are annoying to check
		if (!blockGroup.some(function(bg) {
			return bg.label === prior.label;
		})) {
			blockGroup.push(prior);
		}

		// Always ensure proper template exists/is selected when switching modes
		if (partialBox) {
			Twinkle.block.blockPresetsInfo.prior.templateName = Morebits.string.isInfinity(Twinkle.block.currentBlockInfo.expiry) ? 'هبک-بستن موردی بی‌پایان' : 'هبک-بستن موردی';
		} else {
			if (!Twinkle.block.isRegistered) {
				Twinkle.block.blockPresetsInfo.prior.templateName = 'هبک-قطع دسترسی ناشناس';
			} else {
				Twinkle.block.blockPresetsInfo.prior.templateName = Morebits.string.isInfinity(Twinkle.block.currentBlockInfo.expiry) ? 'هبک-قطع دسترسی بی‌پایان' : 'هبک-قطع دسترسی';
			}
		}
	} else {
		// But first remove any prior prior
		blockGroup = blockGroup.filter(function(bg) {
			return bg.label !== prior.label;
		});
	}

	// Can be in preset or template field, so the old one in the template
	// field will linger. No need to keep the old value around, so just
	// remove it; saves trouble when hiding/evaluating
	$form.find('[name=dstopic]').parent().remove();

	Twinkle.block.callback.saveFieldset($('[name=field_block_options]'));
	Twinkle.block.callback.saveFieldset($('[name=field_template_options]'));

	if (blockBox) {
		field_preset = new Morebits.quickForm.element({ type: 'field', label: 'از پیش تعیین‌شده', name: 'field_preset' });
		field_preset.append({
			type: 'select',
			name: 'preset',
			label: 'یک مورد را انتخاب کنید:',
			event: Twinkle.block.callback.change_preset,
			list: Twinkle.block.callback.filtered_block_groups(blockGroup)
		});

		field_block_options = new Morebits.quickForm.element({ type: 'field', label: 'گزینه‌های قطع دسترسی', name: 'field_block_options' });
		field_block_options.append({ type: 'div', name: 'currentblock', label: ' ' });
		field_block_options.append({ type: 'div', name: 'hasblocklog', label: ' ' });
		field_block_options.append({
			type: 'select',
			name: 'expiry_preset',
			label: 'منقضی شدن:',
			event: Twinkle.block.callback.change_expiry,
			list: [
				{ label: 'سفارشی', value: 'custom', selected: true },
				{ label: 'بی‌پایان', value: 'infinity' },
				{ label: '۳ ساعت', value: '3 hours' },
				{ label: '۱۲ ساعت', value: '12 hours' },
				{ label: '۲۴ ساعت', value: '24 hours' },
				{ label: '۳۱ ساعت', value: '31 hours' },
				{ label: '۳۶ ساعت', value: '36 hours' },
				{ label: '۴۸ ساعت', value: '48 hours' },
				{ label: '۶۰ ساعت', value: '60 hours' },
				{ label: '۷۲ ساعت', value: '72 hours' },
				{ label: '۱ هفته', value: '1 week' },
				{ label: '۲ هفته', value: '2 weeks' },
				{ label: '۱ ماه', value: '1 month' },
				{ label: '۳ ماه', value: '3 months' },
				{ label: '۶ ماه', value: '6 months' },
				{ label: '۱ سال', value: '1 year' },
				{ label: '۲ سال', value: '2 years' },
				{ label: '۳ سال', value: '3 years' }
			]
		});
		field_block_options.append({
			type: 'input',
			name: 'expiry',
			label: 'زمان منقضی‌شدن سفارشی',
			tooltip: 'می‌توانید از زمان‌های نسبی مانند «1 minute» یا «19 days» (حتماً باید به انگلیسی وارد شوند)، یا برچسب‌های زمانی مطلق نظیر «yyyymmddhhmm» (مثلاً «200602011405» با ارقام انگلیسی برابر با ۱ فوریهٔ ۲۰۰۶، ساعت ۱۴:۰۵ (UTC) است) استفاده کنید.',
			value: Twinkle.block.field_block_options.expiry || Twinkle.block.field_template_options.template_expiry
		});

		if (partialBox) { // Partial block
			field_block_options.append({
				type: 'select',
				multiple: true,
				name: 'pagerestrictions',
				label: 'صفحه‌های خاص برای بستن دسترسی ویرایشی',
				value: '',
				tooltip: 'حداکثر ۱۰ صفحه.'
			});
			var ns = field_block_options.append({
				type: 'select',
				multiple: true,
				name: 'namespacerestrictions',
				label: 'قطع دسترسی از فضاهای نام',
				value: '',
				tooltip: 'قطع دسترسی ویرایشی در این فضاهای نام.'
			});
			$.each(menuFormattedNamespaces, function(number, name) {
				// Ignore -1: Special; -2: Media; and 2300-2303: Gadget (talk) and Gadget definition (talk)
				if (number >= 0 && number < 830) {
					ns.append({ type: 'option', label: name, value: number });
				}
			});
		}

		var blockoptions = [
			{
				checked: Twinkle.block.field_block_options.nocreate,
				label: 'بستن قابلیت ایجاد حساب',
				name: 'nocreate',
				value: '1'
			},
			{
				checked: Twinkle.block.field_block_options.noemail,
				label: 'قطع دسترسی کاربر به ویژگی ارسال ایمیل',
				name: 'noemail',
				value: '1'
			},
			{
				checked: Twinkle.block.field_block_options.disabletalk,
				label: 'جلوگیری از ویرایش بحث کاربر توسط خودش در زمان بسته بودن',
				name: 'disabletalk',
				value: '1',
				tooltip: partialBox ? 'اگر در حال اعمال قطع دسترسی موردی هستید، این مورد باید غیرفعال بماند؛ مگر آنکه در حال جلوگیری از ویرایش فضای نام «بحث کاربر» توسط کاربر باشید' : ''
			}
		];

		if (Twinkle.block.isRegistered) {
			blockoptions.push({
				checked: Twinkle.block.field_block_options.autoblock,
				label: 'قطع دسترسی خودکار نشانی‌های آی‌پی مورد استفادهٔ کاربر (قطع دسترسی سخت)',
				name: 'autoblock',
				value: '1'
			});
		} else {
			blockoptions.push({
				checked: Twinkle.block.field_block_options.hardblock,
				label: 'قطع دسترسی ویرایشی کاربران ثبت نام کرده که از این نشانی آی‌پی استفاده می‌کنند (قطع دسترسی سخت)',
				name: 'hardblock',
				value: '1'
			});
		}

		blockoptions.push({
			checked: Twinkle.block.field_block_options.watchuser,
			label: 'پی‌گیری صفحه‌های کاربری و بحث کاربری',
			name: 'watchuser',
			value: '1'
		});

		field_block_options.append({
			type: 'checkbox',
			name: 'blockoptions',
			list: blockoptions
		});
		field_block_options.append({
			type: 'textarea',
			label: 'دلیل (برای درج در سیاههٔ حذف):',
			name: 'reason',
			tooltip: 'لطفاً جزئیاتی مفید را به پیام پیش‌فرض سامانه اضافه کنید.',
			value: Twinkle.block.field_block_options.reason
		});

		field_block_options.append({
			type: 'div',
			name: 'filerlog_label',
			label: 'جستارهای وابسته:',
			style: 'display:inline-block;font-style:normal !important',
			tooltip: 'افزودن یک پیام «جستارهای وابسته» برای اشاره به این که آیا مورد در سیاههٔ پالایه یا مشارکت‌های حذف‌شدهٔ کاربر تأثیری بر این تصمیم برای قطع دسترسی داشته‌است یا خیر.'
		});
		field_block_options.append({
			type: 'checkbox',
			name: 'filter_see_also',
			event: Twinkle.block.callback.toggle_see_alsos,
			style: 'display:inline-block; margin-left:5px',
			list: [
				{
					label: 'سیاههٔ پالایه',
					checked: false,
					value: 'filter log'
				}
			]
		});
		field_block_options.append({
			type: 'checkbox',
			name: 'deleted_see_also',
			event: Twinkle.block.callback.toggle_see_alsos,
			style: 'display:inline-block',
			list: [
				{
					label: 'مشارکت‌های حذف‌شده',
					checked: false,
					value: 'deleted contribs'
				}
			]
		});

		// Yet-another-logevents-doesn't-handle-ranges-well
		if (blockedUserName === relevantUserName) {
			field_block_options.append({ type: 'hidden', name: 'reblock', value: '1' });
		}
	}

	// DS selection visible in either the template field set or preset,
	// joint settings saved here
	var dsSelectSettings = {
		type: 'select',
		name: 'dstopic',
		label: 'موضوع تحریم',
		value: '',
		tooltip: 'در صورت انتخاب این گزینه، به الگو افزوده می‌شود و ممکن است به پیام قطع دسترسی نیز افزوده شود',
		event: Twinkle.block.callback.toggle_ds_reason,
		list: $.map(Twinkle.block.dsinfo, function(info, label) {
			return {label: label, value: info.code};
		})
	};
	if (templateBox) {
		field_template_options = new Morebits.quickForm.element({ type: 'field', label: 'گزینه‌های الگو', name: 'field_template_options' });
		field_template_options.append({
			type: 'select',
			name: 'template',
			label: 'انتخاب الگوی صفحهٔ بحث:',
			event: Twinkle.block.callback.change_template,
			list: Twinkle.block.callback.filtered_block_groups(blockGroup, true),
			value: Twinkle.block.field_template_options.template
		});

		// Only visible for aeblock and aepblock, toggled in change_template
		field_template_options.append(dsSelectSettings);

		field_template_options.append({
			type: 'input',
			name: 'article',
			label: 'صفحه برای پیونددهی',
			value: '',
			tooltip: 'پیوندی به یک صفحه، که احتمالاً هدف اصلی اخلالگری کاربر بوده‌است را می‌توان در متن اعلان پیوند داد. برای جلوگیری از درج پیوند، این جعبه را خالی بگذارید.'
		});

		// Only visible if partial and not blocking
		field_template_options.append({
			type: 'input',
			name: 'area',
			label: 'فضای هدف قطع دسترسی',
			value: '',
			tooltip: 'توضیح اختیاری پیرامون صفحه‌ها یا فضاهای نامی که دسترسی ویرایشی کاربر در آن‌ها بسته شده‌است.'
		});

		if (!blockBox) {
			field_template_options.append({
				type: 'input',
				name: 'template_expiry',
				label: 'مدت‌زمان قطع دسترسی:',
				value: '',
				tooltip: 'مدت‌زمانی که قطع دسترسی برقرار خواهد ماند؛ برای مثال، ۲۴ ساعت، ۲ هفته، بی‌پایان و غیره'
			});
		}
		field_template_options.append({
			type: 'input',
			name: 'block_reason',
			label: '«دسترسی ویرایشی شما به‌دلیل ... بسته شده‌است»',
			tooltip: 'یک دلیل اختیاری، برای جایگزینی با دلیل عمومی پیش‌فرض. تنها برای الگوهای قطع دسترسی عمومی در دسترس است.',
			value: Twinkle.block.field_template_options.block_reason
		});

		if (blockBox) {
			field_template_options.append({
				type: 'checkbox',
				name: 'blank_duration',
				list: [
					{
						label: 'زمان منقضی‌شدن قطع دسترسی در الگو درج نشود',
						checked: Twinkle.block.field_template_options.blank_duration,
						tooltip: 'به‌جای درج مدت‌زمان قطع دسترسی، متن الگوی اعلان قطع دسترسی به «دسترسی ویرایشی شما به‌طور موقت بسته شده‌است...» تغییر می‌یابد'
					}
				]
			});
		} else {
			field_template_options.append({
				type: 'checkbox',
				list: [
					{
						label: 'دسترسی به صفحهٔ بحث قطع شده‌است',
						name: 'notalk',
						checked: Twinkle.block.field_template_options.notalk,
						tooltip: 'تنظیم الگوی اعلان قطع دسترسی به‌گونه‌ای که نشان دهد دسترسی کاربر به صفحهٔ بحثش بسته شده‌است'
					},
					{
						label: 'دسترسی کاربر به ویژگی ارسال ایمیل بسته شده‌است',
						name: 'noemail_template',
						checked: Twinkle.block.field_template_options.noemail_template,
						tooltip: 'اگر فضای مورد نظر مشخص نشده‌است، الگوی اعلان قطع دسترسی به‌گونه‌ای تنظیم می‌شود که نشان دهد دسترسی کاربر برای ارسال ایمیل بسته شده‌است'
					},
					{
						label: 'قابلیت ایجاد حساب توسط کاربر بسته شده‌است',
						name: 'nocreate_template',
						checked: Twinkle.block.field_template_options.nocreate_template,
						tooltip: 'اگر فضای مورد نظر مشخص نشده‌است، الگوی اعلان قطع دسترسی به‌گونه‌ای تنظیم می‌شود که نشان دهد قابلیت ایجاد حساب برای کاربر غیرفعال شده‌است'
					}
				]
			});
		}

		var $previewlink = $('<a id="twinkleblock-preview-link">پیش‌نمایش</a>');
		$previewlink.off('click').on('click', function() {
			Twinkle.block.callback.preview($form[0]);
		});
		$previewlink.css({cursor: 'pointer'});
		field_template_options.append({ type: 'div', id: 'blockpreview', label: [ $previewlink[0] ] });
		field_template_options.append({ type: 'div', id: 'twinkleblock-previewbox', style: 'display: none' });
	} else if (field_preset) {
		// Only visible for arbitration enforcement, toggled in change_preset
		field_preset.append(dsSelectSettings);
	}

	var oldfield;
	if (field_preset) {
		oldfield = $form.find('fieldset[name="field_preset"]')[0];
		oldfield.parentNode.replaceChild(field_preset.render(), oldfield);
	} else {
		$form.find('fieldset[name="field_preset"]').hide();
	}
	if (field_block_options) {
		oldfield = $form.find('fieldset[name="field_block_options"]')[0];
		oldfield.parentNode.replaceChild(field_block_options.render(), oldfield);
		$form.find('fieldset[name="field_64"]').show();


		$form.find('[name=pagerestrictions]').select2({
			width: '100%',
			placeholder: 'انتخاب صفحه برای بستن دسترسی ویرایشی کاربر',
			language: {
				errorLoading: function() {
					return 'عبارت جستجوی ناقص یا نامعتبر';
				}
			},
			maximumSelectionLength: 10, // Software limitation [[phab:T202776]]
			minimumInputLength: 1, // prevent ajax call when empty
			ajax: {
				url: mw.util.wikiScript('api'),
				dataType: 'json',
				delay: 100,
				data: function(params) {
					var title = mw.Title.newFromText(params.term);
					if (!title) {
						return;
					}
					return {
						action: 'query',
						format: 'json',
						list: 'allpages',
						apfrom: title.title,
						apnamespace: title.namespace,
						aplimit: '10'
					};
				},
				processResults: function(data) {
					return {
						results: data.query.allpages.map(function(page) {
							var title = mw.Title.newFromText(page.title, page.ns).toText();
							return {
								id: title,
								text: title
							};
						})
					};
				}
			},
			templateSelection: function(choice) {
				return $('<a>').text(choice.text).attr({
					href: mw.util.getUrl(choice.text),
					target: '_blank'
				});
			}
		});

		$form.find('[name=namespacerestrictions]').select2({
			width: '100%',
			matcher: Morebits.select2.matchers.wordBeginning,
			language: {
				searching: Morebits.select2.queryInterceptor
			},
			templateResult: Morebits.select2.highlightSearchMatches,
			placeholder: 'انتخاب فضای نام برای بستن دسترسی ویرایشی کاربر'
		});

		mw.util.addCSS(
			// Reduce padding
			'.select2-results .select2-results__option { padding-top: 1px; padding-bottom: 1px; }' +
			// Adjust font size
			'.select2-container .select2-dropdown .select2-results { font-size: 13px; }' +
			'.select2-container .selection .select2-selection__rendered { font-size: 13px; }' +
			// Remove black border
			'.select2-container--default.select2-container--focus .select2-selection--multiple { border: 1px solid #aaa; }' +
			// Make the tiny cross larger
			'.select2-selection__choice__remove { font-size: 130%; }'
		);
	} else {
		$form.find('fieldset[name="field_block_options"]').hide();
		$form.find('fieldset[name="field_64"]').hide();
		// Clear select2 options
		$form.find('[name=pagerestrictions]').val(null).trigger('change');
		$form.find('[name=namespacerestrictions]').val(null).trigger('change');
	}

	if (field_template_options) {
		oldfield = $form.find('fieldset[name="field_template_options"]')[0];
		oldfield.parentNode.replaceChild(field_template_options.render(), oldfield);
		e.target.form.root.previewer = new Morebits.wiki.preview($(e.target.form.root).find('#twinkleblock-previewbox').last()[0]);
	} else {
		$form.find('fieldset[name="field_template_options"]').hide();
	}

	// Any block, including ranges
	if (Twinkle.block.currentBlockInfo) {
		// false for an ip covered by a range or a smaller range within a larger range;
		// true for a user, single ip block, or the exact range for a range block
		var sameUser = blockedUserName === relevantUserName;

		Morebits.status.init($('div[name="currentblock"] span').last()[0]);
		var statusStr = 'دسترسی ویرایشی ' + relevantUserName + ' ' + (Twinkle.block.currentBlockInfo.partial === '' ? 'به‌صورت موردی' : 'به‌صورت کلی');

		// Range blocked
		if (Twinkle.block.currentBlockInfo.rangestart !== Twinkle.block.currentBlockInfo.rangeend) {
			if (sameUser) {
				statusStr += ' در قالب قطع دسترسی بازهٔ آی‌پی';
			} else {
				statusStr += ' درون یک قطع دسترسی بازه' + (Morebits.ip.get64(relevantUserName) === blockedUserName ? ' /۶۴' : '');
				// Link to the full range
				var $rangeblockloglink = $('<span>').append($('<a target="_blank" href="' + mw.util.getUrl('ویژه:سیاهه‌ها', {action: 'view', page: blockedUserName, type: 'block'}) + '">' + blockedUserName + '</a>)'));
				statusStr += ' (' + $rangeblockloglink.html() + ')';
			}
		}
		statusStr += ' بسته شده‌است'

		if (Twinkle.block.currentBlockInfo.expiry === 'infinity') {
			statusStr += ' (بی‌پایان)';
		} else if (new Morebits.date(Twinkle.block.currentBlockInfo.expiry).isValid()) {
			statusStr += ' (در ' + new Morebits.date(Twinkle.block.currentBlockInfo.expiry).calendar('utc') + ' منقضی می‌شود)';
		}


		var infoStr = 'این فرم';
		if (sameUser) {
			infoStr += ' آن قطع دسترسی را تغییر می‌دهد';
			if (Twinkle.block.currentBlockInfo.partial === undefined && partialBox) {
				infoStr += ' و آن را به یک قطع دسترسی موردی تبدیل می‌کند';
			} else if (Twinkle.block.currentBlockInfo.partial === '' && !partialBox) {
				infoStr += ' و آن را به یک قطع دسترسی کلی تبدیل می‌کند';
			}
			infoStr += '.';
		} else {
			infoStr += ' یک قطع دسترسی ' + (partialBox ? 'موردی ' : '') + 'دیگر نیز به آن می‌افزاید.';
		}

		Morebits.status.warn(statusStr, infoStr);

		// Default to the current block conditions on intial form generation
		Twinkle.block.callback.update_form(e, Twinkle.block.currentBlockInfo);
	}

	// This is where T146628 really comes into play: a rangeblock will
	// only return the correct block log if wgRelevantUserName is the
	// exact range, not merely a funtional equivalent
	if (Twinkle.block.hasBlockLog) {
		var $blockloglink = $('<span>').append($('<a target="_blank" href="' + mw.util.getUrl('ویژه:سیاهه‌ها', {action: 'view', page: relevantUserName, type: 'block'}) + '">سیاههٔ قطع دسترسی</a>)'));
		if (!Twinkle.block.currentBlockInfo) {
			var lastBlockAction = Twinkle.block.blockLog[0];
			if (lastBlockAction.action === 'unblock') {
				$blockloglink.append(' (در ' + new Morebits.date(lastBlockAction.timestamp).calendar('utc') + ' باز شده‌است)');
			} else { // block or reblock
				$blockloglink.append(' (' + lastBlockAction.params.duration + '، منقضی‌شده در ' + new Morebits.date(lastBlockAction.params.expiry).calendar('utc') + ')');
			}
		}

		Morebits.status.init($('div[name="hasblocklog"] span').last()[0]);
		Morebits.status.warn(Twinkle.block.currentBlockInfo ? 'قطع دسترسی‌های قبلی' : 'دسترسی این ' + (Morebits.ip.isRange(relevantUserName) ? 'بازه' : 'کاربر') + ' در گذشته بسته شده‌است', $blockloglink[0]);
	}

	// Make sure all the fields are correct based on initial defaults
	if (blockBox) {
		Twinkle.block.callback.change_preset(e);
	} else if (templateBox) {
		Twinkle.block.callback.change_template(e);
	}
};

/*
 * Keep alphabetized by key name, Twinkle.block.blockGroups establishes
 *    the order they will appear in the interface
 *
 * Block preset format, all keys accept only 'true' (omit for false) except where noted:
 * <title of block template> : {
 *   autoblock: <autoblock any IP addresses used (for registered users only)>
 *   disabletalk: <disable user from editing their own talk page while blocked>
 *   expiry: <string - expiry timestamp, can include relative times like "5 months", "2 weeks" etc>
 *   forAnonOnly: <show block option in the interface only if the relevant user is an IP>
 *   forRegisteredOnly: <show block option in the interface only if the relevant user is registered>
 *   label: <string - label for the option of the dropdown in the interface (keep brief)>
 *   noemail: prevent the user from sending email through Special:Emailuser
 *   pageParam: <set if the associated block template accepts a page parameter>
 *   prependReason: <string - prepends the value of 'reason' to the end of the existing reason, namely for when revoking talk page access>
 *   nocreate: <block account creation from the user's IP (for anonymous users only)>
 *   nonstandard: <template does not conform to stewardship of WikiProject User Warnings and may not accept standard parameters>
 *   reason: <string - block rationale, as would appear in the block log,
 *            and the edit summary for when adding block template, unless 'summary' is set>
 *   reasonParam: <set if the associated block template accepts a reason parameter>
 *   sig: <string - set to ~~~~ if block template does not accept "true" as the value, or set null to omit sig param altogether>
 *   summary: <string - edit summary for when adding block template to user's talk page, if not set, 'reason' is used>
 *   suppressArticleInSummary: <set to suppress showing the article name in the edit summary, as with attack pages>
 *   templateName: <string - name of template to use (instead of key name), entry will be omitted from the Templates list.
 *                  (e.g. use another template but with different block options)>
 *   useInitialOptions: <when preset is chosen, only change given block options, leave others as they were>
 *
 * WARNING: 'anononly' and 'allowusertalk' are enabled by default.
 *   To disable, set 'hardblock' and 'disabletalk', respectively
 */
Twinkle.block.blockPresetsInfo = {
	'anonblock': {
		expiry: '31 hours',
		forAnonOnly: true,
		nocreate: true,
		nonstandard: true,
		reason: '{{قطع دسترسی ناشناس}}',
		sig: '~~~~'
	},
	'anonblock - school': {
		expiry: '36 hours',
		forAnonOnly: true,
		nocreate: true,
		nonstandard: true,
		reason: '{{قطع دسترسی ناشناس}} <!-- مشکوک به دانشجویی بر پایه شواهد رفتاری -->',
		templateName: 'anonblock',
		sig: '~~~~'
	},
	'پروکسی باز': {
		expiry: '1 year',
		forAnonOnly: true,
		nocreate: true,
		nonstandard: true,
		hardblock: true,
		reason: '{{پروکسی باز}}',
		sig: null
	},
	'قطع دسترسی بازرسی کاربر': {
		expiry: '1 week',
		forAnonOnly: true,
		nocreate: true,
		nonstandard: true,
		reason: '{{قطع دسترسی بازرسی کاربر}}',
		sig: '~~~~'
	},
	'بندایش بازرسی کاربر-حساب کاربری': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		nonstandard: true,
		reason: '{{بندایش بازرسی کاربر-حساب کاربری}}',
		sig: '~~~~'
	},
	'قطع دسترسی بازرسی کاربر-کلی': {
		forAnonOnly: true,
		nocreate: true,
		nonstandard: true,
		reason: '{{قطع دسترسی بازرسی کاربر-کلی}}',
		sig: '~~~~'
	},
	'میزبانی‌وب‌سرور': {
		expiry: '1 year',
		forAnonOnly: true,
		nonstandard: true,
		reason: '{{میزبانی‌وب‌سرور}}',
		sig: null
	},
	'هبک-قطع دسترسی پنهان‌گر': {
		autoblock: true,
		expiry: 'infinity',
		nocreate: true,
		nonstandard: true,
		reason: '{{هبک-قطع دسترسی پنهان‌گر}}',
		sig: '~~~~'
	},
	'school block': {
		forAnonOnly: true,
		nocreate: true,
		nonstandard: true,
		reason: '{{قطع دسترسی مدرسه}}',
		sig: '~~~~'
	},
	'هبک-بستن فهرست سیاه': {
		forAnonOnly: true,
		expiry: '1 month',
		disabletalk: true,
		nocreate: true,
		reason: '{{هبک-بستن فهرست سیاه}} <!-- ویرایشگر تنها اقدام به افزودن پیوندهای هرز می‌کند؛ [[ویژه:سیاهه‌ها/spamblacklist]] را ببینید -->'
	},
	'قطع دسترسی بازه آی‌پی': {
		reason: '{{قطع دسترسی بازه آی‌پی}}',
		nocreate: true,
		nonstandard: true,
		forAnonOnly: true,
		sig: '~~~~'
	},
	'تور': {
		expiry: '1 year',
		forAnonOnly: true,
		nonstandard: true,
		reason: '{{تور}}',
		sig: null
	},
	'میزبان وب بسته‌شده': {
		expiry: '1 year',
		forAnonOnly: true,
		nonstandard: true,
		reason: '{{میزبان وب بسته‌شده}}',
		sig: null
	},
	// uw-prefixed
	'هبک-قطع دسترسی ۳ب': {
		autoblock: true,
		expiry: '24 hours',
		nocreate: true,
		pageParam: true,
		reason: 'نقض [[وپ:۳ب|قاعدهٔ سه‌برگردان]]',
		summary: 'دسترسی ویرایشی شما به‌دلیل نقض [[وپ:۳ب|قاعدهٔ سه‌برگردان]] بسته شده‌است'
	},
	'هبک-قطع دسترسی ناشناس': {
		autoblock: true,
		expiry: '31 hours',
		forAnonOnly: true,
		nocreate: true,
		pageParam: true,
		reasonParam: true,
		summary: 'دسترسی ویرایشی نشانی آی‌پی شما قطع شده‌است',
		suppressArticleInSummary: true
	},
	'هبک-قطع دسترسی هرزنگاری': {
		autoblock: true,
		nocreate: true,
		pageParam: true,
		reason: 'استفاده از ویکی‌پدیا [[وپ:هرز|هرزنگاری]] یا مقاصد [[وپ:تبلیغ مکنید|تبلیغاتی]] purposes',
		summary: 'دسترسی ویرایشی شما به‌دلیل [[وپ:تبلیغ|هرزنگاری یا تبلیغات]] بسته شده‌است'
	},
	
	/* remove; not usable on fawiki
	'uw-aeblock': {
		autoblock: true,
		nocreate: true,
		pageParam: true,
		reason: '[[WP:Arbitration enforcement|Arbitration enforcement]]',
		reasonParam: true,
		summary: 'You have been blocked from editing for violating an [[WP:Arbitration|arbitration decision]]'
	}, */
	
	'هبک-قطع دسترسی زندگان': {
		autoblock: true,
		nocreate: true,
		pageParam: true,
		reason: 'نقض مکرر [[وپ:زنده|سیاست زندگی‌نامهٔ افراد زنده]]',
		summary: 'دسترسی ویرایشی شما به‌دلیل نقض مکرر [[وپ:زنده|سیاست زندگی‌نامهٔ افراد زنده]] بسته شده‌است'
	},
	'هبک-قطع دسترسی': {
		autoblock: true,
		expiry: '24 hours',
		forRegisteredOnly: true,
		nocreate: true,
		pageParam: true,
		reasonParam: true,
		summary: 'دسترسی ویرایش شما قطع شده‌است',
		suppressArticleInSummary: true
	},
	'هبک-قطع دسترسی بی‌پایان': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		pageParam: true,
		reasonParam: true,
		summary: 'دسترسی ویرایشی شما برای مدت نامعین قطع شده‌است',
		suppressArticleInSummary: true
	},
	'هبک-قطع دسترسی بحث': {
		disabletalk: true,
		pageParam: true,
		reasonParam: true,
		summary: 'دسترسی ویرایشی شما قطع شده و دسترسی شما برای ویرایش صفحهٔ بحث خودتان نیز گرفته شده‌است',
		suppressArticleInSummary: true
	},
	'هبک-قطع دسترسی ربات': {
		forRegisteredOnly: true,
		pageParam: true,
		reason: 'اجرای یک [[وپ:ربات|اسکریپت رباتیک]] بدون [[وپ:پرچم|مجوز]]',
		summary: 'دسترسی ویرایشی شما قطع شده‌است؛ زیرا به‌نظر می‌رسد که در حال اجرای یک [[وپ:ربات|اسکریپت رباتیک]] بدون [[وپ:پرچم|مجوز]] هستید'
	},
	'هبک-بستن نام کاربری ربات': {
		expiry: 'infinity',
		forRegisteredOnly: true,
		reason: '{{هبک-بستن نام کاربری ربات}} <!-- نام کاربری بر ربات بودن حساب کاربری دلالت دارد؛ قطع دسترسی نرم -->',
		summary: 'دسترسی ویرایشی شما برای مدت نامعین بسته شده‌است؛ زیرا [[وپ:نام|نام کاربری]] شما نشان‌دهندهٔ این است که این حساب، یک حساب [[وپ:ربات|ربات]] است که در حال حاضر مجاز به فعالیت نیست'
	},
	'هبک-بستن سخت نام ربات ': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		reason: '{{هبک-بستن سخت نام ربات }} <!-- نام کاربری بر ربات بودن حساب دلالت دارد؛ قطع دسترسی سخت -->',
		summary: 'دسترسی ویرایشی شما برای مدت نامعین بسته شده‌است؛ زیرا نام کاربری شما نقض واضح [[وپ:نام|سیاست نام کاربری]] است.'
	},
	'هبک-قطع دسترسی جنبش ': {
		expiry: 'infinity',
		forRegisteredOnly: true,
		reason: '{{هبک-قطع دسترسی جنبش }} <!-- نام کاربری نمایندهٔ سازمان ناسودبر است؛ قطع دسترسی نرم -->',
		summary: 'دسترسی ویرایشی شما برای مدت نامعین قطع شده‌است؛ زیرا [[وپ:نام|نام کاربری]] شما این‌طور نشان می‌دهد که این حساب کاربری نمایندهٔ یک شرکت، سازمان یا وبگاه است'
	},
	'هبک-بستن حساب به‌خطر افتاده': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		reason: 'حساب کاربری به‌خطر افتاده',
		summary: 'دسترسی ویرایشی شما برای مدت نامعین قطع شده‌است؛ زیرا به‌نظر می‌رسد که [[وپ:امنیت|حساب شما به‌خطر افتاده است]]'
	},
	'هبک-قطع دسترسی حق تکثیر': {
		autoblock: true,
		expiry: 'infinity',
		nocreate: true,
		pageParam: true,
		reason: '[[وپ:حت|نقض حق تکثیر]]',
		summary: 'دسترسی ویرایشی شما به‌دلیل نقض ادامه‌دار [[وپ:حت|قوانین حق تکثیر]] قطع شده‌است'
	},
	'هبک-قطع دسترسی خرابکاری': {
		autoblock: true,
		nocreate: true,
		reason: 'حذف ادامه‌دار محتوا',
		pageParam: true,
		summary: 'دسترسی ویرایشی شما به‌دلیل [[وپ:خ|حذف ادامه‌دار محتوا]] قطع شده‌است'
	},
	'هبک-قطع دسترسی اخلالگری': {
		autoblock: true,
		nocreate: true,
		reason: '[[وپ:ویرایش اخلالگرانه|ویرایش‌های اخلالگرانه]]',
		summary: 'دسترسی ویرایشی شما به‌دلیل [[وپ:اخلال|ویرایش‌های اخلالگرانه]] قطع شده‌است'
	},
	'هبک-قطع دسترسی پالایه ویرایش': {
		autoblock: true,
		nocreate: true,
		reason: 'فعال کردن مکرر [[وپ:پالایه ویرایش|پالایهٔ ویرایش]]',
		summary: 'دسترسی ویرایشی شما به‌دلیل ویرایش‌های اخلالگرانه‌ای که به‌طور مکرر [[وپ:پالایه|پالایهٔ ویرایش]] را فعال کرده‌اند، قطع شده‌است'
	},
	'هبک-قطع دسترسی جنگ ویرایشی': {
		autoblock: true,
		expiry: '24 hours',
		nocreate: true,
		pageParam: true,
		reason: '[[وپ:جنگ|جنگ ویرایشی]]',
		summary: 'دسترسی ویرایشی شما برای جلوگیری از [[وپ:اخلال|اخلالگری]] بیشتر، که به‌واسطهٔ درگیر شدن شما در [[وپ:جنگ|جنگ ویرایشی]] ایجاد شده، قطع شده‌است'
	},
	'هبک-قطع دسترسی آزار': {
		autoblock: true,
		nocreate: true,
		pageParam: true,
		reason: '[[وپ:حمله شخصی ممنوع|حمله‌های شخصی]] یا [[وپ:آزار و اذیت|آزار و اذیت]]',
		summary: 'دسترسی ویرایشی شما به‌دلیل تلاش برای [[وپ:آزار|آزار و اذیت]] کاربران دیگر قطع شده‌است'
	},
	'هبک-بستن دور زدن قطع دسترسی': {
		forAnonOnly: true,
		nocreate: true,
		reason: '[[ویکی‌پدیا:قطع دسترسی#دور زدن و اجرا|دور زدن قطع دسترسی]]',
		summary: 'دسترسی ویرایشی نشانی آی‌پی شما قطع شده‌است؛ زیرا برای [[وپ:دور زدن|دور زدن قطع دسترسی]] مورد استفاده قرار گرفته‌است'
	},
	'هبک-قطع دسترسی تهدید': {
		autoblock: true,
		expiry: 'infinity',
		nocreate: true,
		reason: '[[وپ:تهدید قانونی ممنوع|تهدید قانونی]]',
		summary: 'دسترسی ویرایشی شما به‌دلیل [[وپ:تهدید قانونی|تهدید قانونی یا اقدام‌های قانونی]]'
	},
	'هبک-قطع دسترسی اینجا نه': {
		autoblock: true,
		expiry: 'infinity',
		nocreate: true,
		reason: 'مشخص است که [[وپ:اینجا نه|برای کمک به ساخت دانشنامه اینجا نیست]]',
		forRegisteredOnly: true,
		summary: 'دسترسی ویرایشی‌شما برای مدت نامعین قطع شده‌است؛ زیرا به‌نظر می‌رسد که [[وپ:اینجا نه|برای کمک به ساخت دانشنامه اینجا نیستید]]'
	},
	'هبک-قطع دسترسی بی‌معنی': {
		autoblock: true,
		nocreate: true,
		pageParam: true,
		reason: 'ایجاد [[وپ:بی‌معنی|صفحه‌های نامفهوم]] یا سایر صفحه‌های نامناسب',
		summary: 'دسترسی ویرایشی شما به‌دلیل ایجاد [[وپ:بی‌معنی|صفحه‌های نامفهوم]] قطع شده‌است'
	},
	'هبک-قطع دسترسی حمله شخصی ': {
		autoblock: true,
		expiry: '31 hours',
		nocreate: true,
		reason: '[[وپ:حمله شخصی ممنوع|حمله‌های شخصی]] یا [[وپ:آزار و اذیت|آزار و اذیت]]',
		summary: 'دسترسی ویرایشی شما به‌دلیل [[وپ:حمله|حمله‌های شخصی]] به کاربران دیگر قطع شده‌است'
	},
	'هبک-قطع دسترسی اسپم': {
		autoblock: true,
		nocreate: true,
		reason: 'استفاده از ویکی‌پدیا برای [[وپ:اسپم|هرزنگاری]]',
		summary: 'دسترسی ویرایش شما به‌دلیل استفاده از ویکی‌پدیا برای [[وپ:اسپم|هرزنگاری]] قطع شده‌است'
	},
	'uw-soablock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		pageParam: true,
		reason: '[[WP:Spam|Spam]] / [[WP:NOTADVERTISING|advertising]]-only account',
		summary: 'You have been indefinitely blocked from editing because your account is being used only for [[WP:SPAM|spam, advertising, or promotion]]'
	},
	'uw-socialmediablock': {
		autoblock: true,
		nocreate: true,
		pageParam: true,
		reason: 'Using Wikipedia as a [[WP:NOTMYSPACE|blog, web host, social networking site or forum]]',
		summary: 'You have been blocked from editing for using user and/or article pages as a [[WP:NOTMYSPACE|blog, web host, social networking site or forum]]'
	},
	'uw-sockblock': {
		autoblock: true,
		forRegisteredOnly: true,
		nocreate: true,
		reason: 'Abusing [[WP:Sock puppetry|multiple accounts]]',
		summary: 'You have been blocked from editing for abusing [[WP:SOCK|multiple accounts]]'
	},
	'uw-softerblock': {
		expiry: 'infinity',
		forRegisteredOnly: true,
		reason: '{{uw-softerblock}} <!-- Promotional username, soft block -->',
		summary: 'You have been indefinitely blocked from editing because your [[WP:U|username]] gives the impression that the account represents a group, organization or website'
	},
	'uw-spamublock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		reason: '{{uw-spamublock}} <!-- Promotional username, promotional edits -->',
		summary: 'You have been indefinitely blocked from editing because your account is being used only for [[WP:SPAM|spam or advertising]] and your username is a violation of the [[WP:U|username policy]]'
	},
	'uw-spoablock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		reason: '[[WP:SOCK|Sock puppetry]]',
		summary: 'This account has been blocked as a [[WP:SOCK|sock puppet]] created to violate Wikipedia policy'
	},
	'uw-talkrevoked': {
		disabletalk: true,
		reason: 'Revoking talk page access: inappropriate use of user talk page while blocked',
		prependReason: true,
		summary: 'Your user talk page access has been disabled',
		useInitialOptions: true
	},
	'uw-ublock': {
		expiry: 'infinity',
		forRegisteredOnly: true,
		reason: '{{uw-ublock}} <!-- Username violation, soft block -->',
		reasonParam: true,
		summary: 'You have been indefinitely blocked from editing because your username is a violation of the [[WP:U|username policy]]'
	},
	'uw-ublock-double': {
		expiry: 'infinity',
		forRegisteredOnly: true,
		reason: '{{uw-ublock-double}} <!-- Username closely resembles another user, soft block -->',
		summary: 'You have been indefinitely blocked from editing because your [[WP:U|username]] is too similar to the username of another Wikipedia user'
	},
	'uw-ucblock': {
		autoblock: true,
		expiry: '31 hours',
		nocreate: true,
		pageParam: true,
		reason: 'Persistent addition of [[WP:INTREF|unsourced content]]',
		summary: 'You have been blocked from editing for persistent addition of [[WP:INTREF|unsourced content]]'
	},
	'uw-uhblock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		reason: '{{uw-uhblock}} <!-- Username violation, hard block -->',
		reasonParam: true,
		summary: 'You have been indefinitely blocked from editing because your username is a blatant violation of the [[WP:U|username policy]]'
	},
	'uw-ublock-wellknown': {
		expiry: 'infinity',
		forRegisteredOnly: true,
		reason: '{{uw-ublock-wellknown}} <!-- Username represents a well-known person, soft block -->',
		summary: 'You have been indefinitely blocked from editing because your [[WP:U|username]] matches the name of a well-known living individual'
	},
	'uw-uhblock-double': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		reason: '{{uw-uhblock-double}} <!-- Attempted impersonation of another user, hard block -->',
		summary: 'You have been indefinitely blocked from editing because your [[WP:U|username]] appears to impersonate another established Wikipedia user'
	},
	'uw-upeblock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		pageParam: true,
		reason: '[[WP:PAID|Undisclosed paid editing]] in violation of the WMF [[WP:TOU|Terms of Use]]',
		summary: 'You have been indefinitely blocked from editing because your account is being used in violation of [[WP:PAID|Wikipedia policy on undisclosed paid advocacy]]'
	},
	'uw-vaublock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		pageParam: true,
		reason: '{{uw-vaublock}} <!-- Username violation, vandalism-only account -->',
		summary: 'You have been indefinitely blocked from editing because your account is being [[WP:VOA|used only for vandalism]] and your username is a blatant violation of the [[WP:U|username policy]]'
	},
	'uw-vblock': {
		autoblock: true,
		expiry: '31 hours',
		nocreate: true,
		pageParam: true,
		reason: '[[WP:Vandalism|Vandalism]]',
		summary: 'You have been blocked from editing to prevent further [[WP:VAND|vandalism]]'
	},
	'uw-voablock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		pageParam: true,
		reason: '[[WP:Vandalism-only account|Vandalism-only account]]',
		summary: 'You have been indefinitely blocked from editing because your account is being [[WP:VOA|used only for vandalism]]'
	},
	'زامبی پراکسی': {
		expiry: '1 month',
		forAnonOnly: true,
		nocreate: true,
		nonstandard: true,
		reason: '{{زامبی پراکسی}}',
		sig: null
	},

	// Begin partial block templates, accessed in Twinkle.block.blockGroupsPartial
	'uw-acpblock': {
		autoblock: true,
		expiry: '48 hours',
		nocreate: true,
		pageParam: false,
		reasonParam: true,
		reason: 'Misusing [[WP:Sock puppetry|multiple accounts]]',
		summary: 'You have been [[WP:PB|blocked from creating accounts]] for misusing [[WP:SOCK|multiple accounts]]'
	},
	'uw-acpblockindef': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		pageParam: false,
		reasonParam: true,
		reason: 'Misusing [[WP:Sock puppetry|multiple accounts]]',
		summary: 'You have been indefinitely [[WP:PB|blocked from creating accounts]] for misusing [[WP:SOCK|multiple accounts]]'
	},
	'uw-aepblock': {
		autoblock: true,
		nocreate: false,
		pageParam: false,
		reason: '[[WP:Arbitration enforcement|Arbitration enforcement]]',
		reasonParam: true,
		summary: 'You have been [[WP:PB|partially blocked]] from editing for violating an [[WP:Arbitration|arbitration decision]]'
	},
	'uw-epblock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: false,
		noemail: true,
		pageParam: false,
		reasonParam: true,
		reason: 'Email [[WP:Harassment|harassment]]',
		summary: 'You have been [[WP:PB|blocked from emailing]] other editors for [[WP:Harassment|harassment]]'
	},
	'uw-ewpblock': {
		autoblock: true,
		expiry: '24 hours',
		nocreate: false,
		pageParam: false,
		reasonParam: true,
		reason: '[[WP:Edit warring|Edit warring]]',
		summary: 'You have been [[WP:PB|partially blocked]] from editing certain areas of the encyclopedia to prevent further [[WP:DE|disruption]] due to [[WP:EW|edit warring]]'
	},
	'هبک-بستن موردی': {
		autoblock: true,
		expiry: '24 hours',
		nocreate: false,
		pageParam: false,
		reasonParam: true,
		summary: 'دسترسی ویرایشی شما در برخی فضاهای خاص دانشنامه [[وپ:موردی|به‌صورت موردی بسته شده‌است]]'
	},
	'هبک-بستن موردی بی‌پایان': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: false,
		pageParam: false,
		reasonParam: true,
		summary: 'دسترسی ویرایشی شما در برخی فضاهای خاص دانشنامه برای مدت نامعین و [[وپ:موردی|به‌صورت موردی بسته شده‌است]]'
	}
};

// Codes and links for Discretionary Sanctions, see [[Template:Ds/topics]]
// Used for uw-ae(p)block
Twinkle.block.dsinfo = {
	'': {
		code: ''
	},
	'Abortion': {
		code: 'ab',
		page: 'Wikipedia:Arbitration/Requests/Case/Abortion'
	},
	'American politics post-1992': {
		code: 'ap',
		page: 'Wikipedia:Arbitration/Requests/Case/American politics 2'
	},
	'Ancient Egyptian race controversy': {
		code: 'aerc',
		page: 'Wikipedia:Requests for arbitration/Ancient Egyptian race controversy'
	},
	'Arab-Israeli conflict': {
		code: 'a-i',
		page: 'Wikipedia:Arbitration/Index/Palestine-Israel articles'
	},
	'Armenia, Azerbaijan, or related conflicts': {
		code: 'a-a',
		page: 'Wikipedia:Requests for arbitration/Armenia-Azerbaijan 2'
	},
	'Biographies of Living Persons (BLPs)': {
		code: 'blp',
		page: 'Wikipedia:Requests for arbitration/Editing of Biographies of Living Persons'
	},
	'Climate change': {
		code: 'cc',
		page: 'Wikipedia:Arbitration/Requests/Case/Climate change'
	},
	'Complementary and alternative medicine': {
		code: 'com',
		page: 'Wikipedia:Arbitration/Requests/Case/Acupuncture'
	},
	'Eastern Europe or the Balkans': {
		code: 'e-e',
		page: 'Wikipedia:Requests for arbitration/Eastern Europe'
	},
	'Electronic cigarettes': {
		code: 'ecig',
		page: 'Wikipedia:Arbitration/Requests/Case/Editor conduct in e-cigs articles'
	},
	'Falun Gong': {
		code: 'fg',
		page: 'Wikipedia:Requests for arbitration/Falun Gong'
	},
	'Gender-related dispute or controversy and associated people (includes GamerGate)': {
		code: 'gas',
		page: 'Wikipedia:Arbitration/Requests/Case/Gender and sexuality'
	},
	'Genetically modified organisms (GMO)': {
		code: 'gmo',
		page: 'Wikipedia:Arbitration/Requests/Case/Genetically modified organisms'
	},
	'Gun control': {
		code: 'gc',
		page: 'Wikipedia:Arbitration/Requests/Case/Gun control'
	},
	'Horn of Africa (Ethiopia, Somalia, Eritrea, Djibouti)': {
		code: 'horn',
		page: 'Wikipedia:Arbitration/Requests/Case/Horn of Africa'
	},
	'India, Pakistan, and Afghanistan': {
		code: 'ipa',
		page: 'Wikipedia:Requests for arbitration/India-Pakistan'
	},
	'Infoboxes': {
		code: 'cid',
		page: 'Wikipedia:Arbitration/Requests/Case/Civility in infobox discussions'
	},
	'Kurds and Kurdistan': {
		code: 'kurd',
		page: 'Wikipedia:Arbitration/Requests/Case/Kurds and Kurdistan'
	},
	'Landmark Worldwide': {
		code: 'lw',
		page: 'Wikipedia:Arbitration/Requests/Case/Landmark Worldwide'
	},
	'Liancourt Rocks': {
		code: 'lr',
		page: 'Wikipedia:Requests for arbitration/Liancourt Rocks'
	},
	'Manual of Style and article titles': {
		code: 'mos',
		page: 'Wikipedia:Arbitration/Requests/Case/Article titles and capitalisation'
	},
	'Muhammad': {
		code: 'muh-im',
		page: 'Wikipedia:Arbitration/Requests/Case/Muhammad images'
	},
	'Pharmaceutical drug prices (medicine)': {
		code: 'med',
		page: 'Wikipedia:Arbitration/Requests/Case/Medicine'
	},
	'Prem Rawat': {
		code: 'pr',
		page: 'Wikipedia:Requests for arbitration/Prem Rawat'
	},
	'Pseudoscience and fringe science': {
		code: 'ps',
		page: 'Wikipedia:Requests for arbitration/Pseudoscience'
	},
	'Race/ethnicity and human abilities, behaviour, and intelligence': {
		code: 'r-i',
		page: 'Wikipedia:Arbitration/Requests/Case/Race and intelligence'
	},
	'Scientology': {
		code: 'sci',
		page: 'Wikipedia:Requests for arbitration/Scientology'
	},
	'Senkaku Islands dispute': {
		code: 'sen',
		page: 'Wikipedia:Arbitration/Requests/Case/Senkaku Islands'
	},
	'September 11 attacks': {
		code: '9/11',
		page: 'Wikipedia:Requests for arbitration/September 11 conspiracy theories'
	},
	'Shakespeare authorship question': {
		code: 'saq',
		page: 'Wikipedia:Arbitration/Requests/Case/Shakespeare authorship question'
	},
	'Transcendental Meditation movement': {
		code: 'tm',
		page: 'Wikipedia:Arbitration/Requests/Case/Transcendental Meditation movement'
	},
	'The Troubles': {
		code: 'tt',
		page: 'Wikipedia:Requests for arbitration/The Troubles'
	},
	'Waldorf education': {
		code: 'we',
		page: 'Wikipedia:Requests for arbitration/Waldorf education'
	}
};

Twinkle.block.transformBlockPresets = function twinkleblockTransformBlockPresets() {
	// supply sensible defaults
	$.each(Twinkle.block.blockPresetsInfo, function(preset, settings) {
		settings.summary = settings.summary || settings.reason;
		settings.sig = settings.sig !== undefined ? settings.sig : 'yes';
		settings.indefinite = settings.indefinite || Morebits.string.isInfinity(settings.expiry);

		if (!Twinkle.block.isRegistered && settings.indefinite) {
			settings.expiry = '31 hours';
		} else {
			settings.expiry = settings.expiry || '31 hours';
		}

		Twinkle.block.blockPresetsInfo[preset] = settings;
	});
};

// These are the groups of presets and defines the order in which they appear. For each list item:
//   label: <string, the description that will be visible in the dropdown>
//   value: <string, the key of a preset in blockPresetsInfo>
Twinkle.block.blockGroups = [
	{
		label: 'دلایل مرسوم برای قطع دسترسی',
		list: [
			{ label: 'قطع دسترسی ناشناس', value: 'anonblock' },
			{ label: 'قطع دسترسی ناشناس - مشکوک به مدرسه', value: 'anonblock - school' },
			{ label: 'قطع دسترسی مدرسه', value: 'school block' },
			{ label: 'قطع دسترسی عمومی (دلیل سفارشی)', value: 'uw-block' }, // ends up being default for registered users
			{ label: 'قطع دسترسی عمومی (دلیل سفارشی) - آی‌پی', value: 'uw-ablock', selected: true }, // set only when blocking IP
			{ label: 'قطع دسترسی عمومی (دلیل سفارشی) - بی پایان', value: 'uw-blockindef' },
			{ label: 'ویرایش اخلالگرانه', value: 'uw-disruptblock' },
			{ label: 'استفادهٔ نادرست از صفحهٔ بحث در زمان بسته‌بودن', value: 'uw-talkrevoked' },
			{ label: 'برای کمک به دانشنامه اینجا نیست', value: 'uw-nothereblock' },
			{ label: 'محتوای بدون منبع', value: 'uw-ucblock' },
			{ label: 'خرابکاری', value: 'uw-vblock' },
			{ label: 'حساب ایجادشده برای خرابکاری', value: 'uw-voablock' }
		]
	},
	{
		label: 'دلایل بیشتر',
		list: [
			{ label: 'تبلیغات', value: 'uw-adblock' },
			// { label: 'تصمیم هیئت حکمیت', value: 'uw-aeblock' },
			{ label: 'دور زدن قطع دسترسی - آی‌پی', value: 'uw-ipevadeblock' },
			{ label: 'نقض سیاست زندگی‌نامه زندگان', value: 'uw-bioblock' },
			{ label: 'نقض حق تکثیر', value: 'uw-copyrightblock' },
			{ label: 'ایجاد صفحه‌های نامفهوم و بی‌معنی', value: 'uw-npblock' },
			{ label: 'مرتبط با پالایهٔ ویرایش', value: 'uw-efblock' },
			{ label: 'جنگ ویرایشی', value: 'uw-ewblock' },
			{ label: 'قطع دسترسی عمومی به‌همراه گرفتن دسترسی صفحهٔ بحث', value: 'uw-blocknotalk' },
			{ label: 'آزار و اذیت', value: 'uw-hblock' },
			{ label: 'تهدید قانونی', value: 'هبک-قطع دسترسی تهدید' },
			{ label: 'حملهٔ شخصی و آزار و اذیت', value: 'uw-pablock' },
			{ label: 'حساب کاربری که احتمالاً به‌خطر افتاده', value: 'uw-compblock' },
			{ label: 'حذف محتوا', value: 'uw-dblock' },
			{ label: 'زاپاس‌بازی (حساب اصلی)', value: 'uw-sockblock' },
			{ label: 'زاپاس‌بازی (حساب زاپاس)', value: 'uw-spoablock' },
			{ label: 'استفاده از ویکی‌پدیا مانند شبکه‌های اجتماعی', value: 'uw-socialmediablock' },
			{ label: 'هرزنگاری', value: 'uw-sblock' },
			{ label: 'حساب ایجادشده برای هرزنگاری/تبلیغات', value: 'uw-soablock' },
			{ label: 'ربات بدون مجوز', value: 'uw-botblock' },
			{ label: 'ویرایش در برابر دستمزد فاش‌نشده', value: 'uw-upeblock' },
			{ label: 'نقض قاعدهٔ سه‌برگردان', value: 'uw-3block' }
		]
	},
	{
		label: 'مشکلات نام کاربری',
		list: [
			{ label: 'نام کاربری ربات، قطع دسترسی نرم', value: 'uw-botublock' },
			{ label: 'نام کاربری ربات، قطع دسترسی سخت', value: 'uw-botuhblock' },
			{ label: 'نام کاربری تبلیغاتی، قطع دسترسی سخت', value: 'uw-spamublock' },
			{ label: 'نام کاربری تبلیغاتی، قطع دسترسی نرم', value: 'uw-softerblock' },
			{ label: 'نام مشابه، قطع دسترسی نرم', value: 'uw-ublock-double' },
			{ label: 'نقض سیاست نام کاربری، قطع دسترسی نرم', value: 'uw-ublock' },
			{ label: 'نقض سیاست نام کاربری، قطع دسترسی سخت', value: 'uw-uhblock' },
			{ label: 'جعل هویت با نام کاربری، قطع دسترسی سخت', value: 'uw-uhblock-double' },
			{ label: 'نام کاربری مشابه نام شخص شناخته‌شده است، قطع دسترسی نرم', value: 'uw-ublock-wellknown' },
			{ label: 'نام کاربری مشابه نام شخص شناخته‌شده است، قطع دسترسی سخت', value: 'uw-causeblock' },
			{ label: 'نقض سیاست نام کاربری، حساب ایجادشده برای خرابکاری', value: 'uw-vaublock' }
		]
	},
	{
		label: 'دلایل حاوی الگو',
		list: [
			{ label: 'پروکسی باز', value: 'پروکسی باز' },
			{ label: 'قطع دسترسی بازرسی کاربر', value: 'قطع دسترسی بازرسی کاربر' },
			{ label: 'قطع دسترسی بازرسی کاربر-حساب کاربری', value: 'بندایش بازرسی کاربر-حساب کاربری' },
			{ label: 'قطع دسترسی بازرسی کاربر-کلی', value: 'قطع دسترسی بازرسی کاربر-کلی' },
			{ label: 'میزبانی‌وب‌سرور', value: 'میزبانی‌وب‌سرور' },
			{ label: 'قطع دسترسی پنهان‌گر', value: 'هبک-قطع دسترسی پنهان‌گر' },
			{ label: 'قطع دسترسی بازه آی‌پی', value: 'قطع دسترسی بازه آی‌پی' }, // Only for IP ranges, selected for non-/64 ranges in filtered_block_groups
			{ label: 'بستن فهرست سیاه', value: 'هبک-بستن فهرست سیاه' },
			{ label: 'تور', value: 'تور' },
			{ label: 'میزبان وب بسته‌شده', value: 'میزبان وب بسته‌شده' },
			{ label: 'زامبی پراکسی', value: 'زامبی پراکسی' }
		]
	}
];

Twinkle.block.blockGroupsPartial = [
	{
		label: 'دلایل مرسوم برای قطع دسترسی موردی',
		list: [
			{ label: 'قطع دسترسی موردی عمومی (دلیل سفارشی)', value: 'هبک-بستن موردی', selected: true },
			{ label: 'قطع دسترسی موردی عمومی (دلیل سفارشی) - بی‌پایان', value: 'هبک-بستن موردی بی‌پایان' },
			{ label: 'جنگ ویرایشی', value: 'uw-ewpblock' }
		]
	},
	{
		label: 'دلایل بیشتر برای قطع دسترسی موردی',
		list: [
			{ label: 'تصمیم هیئت حکمیت', value: 'uw-aepblock' },
			{ label: 'آزار و اذیت از طریق ایمیل', value: 'uw-epblock' },
			{ label: 'سوءاستفاده از چند حساب کاربری', value: 'uw-acpblock' },
			{ label: 'سوءاستفاده از چند حساب کاربری - بی‌پایان', value: 'uw-acpblockindef' }
		]
	}
];


Twinkle.block.callback.filtered_block_groups = function twinkleblockCallbackFilteredBlockGroups(group, show_template) {
	return $.map(group, function(blockGroup) {
		var list = $.map(blockGroup.list, function(blockPreset) {
			switch (blockPreset.value) {
				case 'uw-talkrevoked':
					if (blockedUserName !== relevantUserName) {
						return;
					}
					break;
				case 'قطع دسترسی بازه آی‌پی':
					if (!Morebits.ip.isRange(relevantUserName)) {
						return;
					}
					blockPreset.selected = !Morebits.ip.get64(relevantUserName);
					break;
				case 'قطع دسترسی بازرسی کاربر':
				case 'بندایش بازرسی کاربر-حساب کاربری':
				case 'قطع دسترسی بازرسی کاربر-کلی':
					if (!Morebits.userIsInGroup('checkuser')) {
						return;
					}
					break;
				case 'هبک-قطع دسترسی پنهان‌گر':
					if (!Morebits.userIsInGroup('oversight')) {
						return;
					}
					break;
				default:
					break;
			}

			var blockSettings = Twinkle.block.blockPresetsInfo[blockPreset.value];
			var registrationRestrict = blockSettings.forRegisteredOnly ? Twinkle.block.isRegistered : blockSettings.forAnonOnly ? !Twinkle.block.isRegistered : true;
			if (!(blockSettings.templateName && show_template) && registrationRestrict) {
				var templateName = blockSettings.templateName || blockPreset.value;
				return {
					label: (show_template ? '{{' + templateName + '}}: ' : '') + blockPreset.label,
					value: blockPreset.value,
					data: [{
						name: 'template-name',
						value: templateName
					}],
					selected: !!blockPreset.selected,
					disabled: !!blockPreset.disabled
				};
			}
		});
		if (list.length) {
			return {
				label: blockGroup.label,
				list: list
			};
		}
	});
};

Twinkle.block.callback.change_preset = function twinkleblockCallbackChangePreset(e) {
	var form = e.target.form, key = form.preset.value;
	if (!key) {
		return;
	}

	Twinkle.block.callback.update_form(e, Twinkle.block.blockPresetsInfo[key]);
	if (form.template) {
		form.template.value = Twinkle.block.blockPresetsInfo[key].templateName || key;
		Twinkle.block.callback.change_template(e);
	} else {
		Morebits.quickForm.setElementVisibility(form.dstopic.parentNode, key === 'uw-aeblock' || key === 'uw-aepblock');
	}
};

Twinkle.block.callback.change_expiry = function twinkleblockCallbackChangeExpiry(e) {
	var expiry = e.target.form.expiry;
	if (e.target.value === 'custom') {
		Morebits.quickForm.setElementVisibility(expiry.parentNode, true);
	} else {
		Morebits.quickForm.setElementVisibility(expiry.parentNode, false);
		expiry.value = e.target.value;
	}
};

Twinkle.block.seeAlsos = [];
Twinkle.block.callback.toggle_see_alsos = function twinkleblockCallbackToggleSeeAlso() {
	var reason = this.form.reason.value.replace(
		new RegExp('( <!--|;) ' + 'همچنین نگاه کنید به ' + Twinkle.block.seeAlsos.join(' و ') + '( -->)?'), ''
	);

	Twinkle.block.seeAlsos = Twinkle.block.seeAlsos.filter(function(el) {
		return el !== this.value;
	}.bind(this));

	if (this.checked) {
		Twinkle.block.seeAlsos.push(this.value);
	}
	var seeAlsoMessage = Twinkle.block.seeAlsos.join(' و ');

	if (!Twinkle.block.seeAlsos.length) {
		this.form.reason.value = reason;
	} else if (reason.indexOf('{{') !== -1) {
		this.form.reason.value = reason + ' <!-- همچنین نگاه کنید به ' + seeAlsoMessage + ' -->';
	} else {
		this.form.reason.value = reason + '; همچنین نگاه کنید به ' + seeAlsoMessage;
	}
};

Twinkle.block.dsReason = '';
Twinkle.block.callback.toggle_ds_reason = function twinkleblockCallbackToggleDSReason() {
	var reason = this.form.reason.value.replace(
		new RegExp(' ?\\(\\[\\[' + Twinkle.block.dsReason + '\\]\\]\\)'), ''
	);

	Twinkle.block.dsReason = Twinkle.block.dsinfo[this.options[this.selectedIndex].label].page;
	if (!this.value) {
		this.form.reason.value = reason;
	} else {
		this.form.reason.value = reason + ' ([[' + Twinkle.block.dsReason + ']])';
	}
};

Twinkle.block.callback.update_form = function twinkleblockCallbackUpdateForm(e, data) {
	var form = e.target.form, expiry = data.expiry;

	// don't override original expiry if useInitialOptions is set
	if (!data.useInitialOptions) {
		if (Date.parse(expiry)) {
			expiry = new Date(expiry).toGMTString();
			form.expiry_preset.value = 'custom';
		} else {
			form.expiry_preset.value = data.expiry || 'custom';
		}

		form.expiry.value = expiry;
		if (form.expiry_preset.value === 'custom') {
			Morebits.quickForm.setElementVisibility(form.expiry.parentNode, true);
		} else {
			Morebits.quickForm.setElementVisibility(form.expiry.parentNode, false);
		}
	}

	// boolean-flipped options, more at [[mw:API:Block]]
	data.disabletalk = data.disabletalk !== undefined ? data.disabletalk : false;
	data.hardblock = data.hardblock !== undefined ? data.hardblock : false;

	// disable autoblock if blocking a bot
	if (Twinkle.block.userIsBot || /(bot\b|بات)/i.test(relevantUserName)) {
		data.autoblock = false;
	}

	$(form).find('[name=field_block_options]').find(':checkbox').each(function(i, el) {
		// don't override original options if useInitialOptions is set
		if (data.useInitialOptions && data[el.name] === undefined) {
			return;
		}

		var check = data[el.name] === '' || !!data[el.name];
		$(el).prop('checked', check);
	});

	if (data.prependReason && data.reason) {
		form.reason.value = data.reason + '؛ ' + form.reason.value;
	} else {
		form.reason.value = data.reason || '';
	}

	// Clear and/or set any partial page or namespace restrictions
	if (form.pagerestrictions) {
		var $pageSelect = $(form).find('[name=pagerestrictions]');
		var $namespaceSelect = $(form).find('[name=namespacerestrictions]');

		// Respect useInitialOptions by clearing data when switching presets
		// In practice, this will always clear, since no partial presets use it
		if (!data.useInitialOptions) {
			$pageSelect.val(null).trigger('change');
			$namespaceSelect.val(null).trigger('change');
		}

		// Add any preset options; in practice, just used for prior block settings
		if (data.restrictions) {
			if (data.restrictions.pages && !$pageSelect.val().length) {
				var pages = data.restrictions.pages.map(function(pr) {
					return pr.title;
				});
				// since page restrictions use an ajax source, we
				// short-circuit that and just add a new option
				pages.forEach(function(page) {
					if (!$pageSelect.find("option[value='" + $.escapeSelector(page) + "']").length) {
						var newOption = new Option(page, page, true, true);
						$pageSelect.append(newOption);
					}
				});
				$pageSelect.val($pageSelect.val().concat(pages)).trigger('change');
			}
			if (data.restrictions.namespaces) {
				$namespaceSelect.val($namespaceSelect.val().concat(data.restrictions.namespaces)).trigger('change');
			}
		}
	}
};

Twinkle.block.callback.change_template = function twinkleblockcallbackChangeTemplate(e) {
	var form = e.target.form, value = form.template.value, settings = Twinkle.block.blockPresetsInfo[value];

	var blockBox = $(form).find('[name=actiontype][value=block]').is(':checked');
	var partialBox = $(form).find('[name=actiontype][value=partial]').is(':checked');
	var templateBox = $(form).find('[name=actiontype][value=template]').is(':checked');

	// Block form is not present
	if (!blockBox) {
		if (settings.indefinite || settings.nonstandard) {
			if (Twinkle.block.prev_template_expiry === null) {
				Twinkle.block.prev_template_expiry = form.template_expiry.value || '';
			}
			form.template_expiry.parentNode.style.display = 'none';
			form.template_expiry.value = 'infinity';
		} else if (form.template_expiry.parentNode.style.display === 'none') {
			if (Twinkle.block.prev_template_expiry !== null) {
				form.template_expiry.value = Twinkle.block.prev_template_expiry;
				Twinkle.block.prev_template_expiry = null;
			}
			form.template_expiry.parentNode.style.display = 'block';
		}
		if (Twinkle.block.prev_template_expiry) {
			form.expiry.value = Twinkle.block.prev_template_expiry;
		}
		Morebits.quickForm.setElementVisibility(form.notalk.parentNode, !settings.nonstandard);
		// Partial
		Morebits.quickForm.setElementVisibility(form.noemail_template.parentNode, partialBox);
		Morebits.quickForm.setElementVisibility(form.nocreate_template.parentNode, partialBox);
	} else if (templateBox) { // Only present if block && template forms both visible
		Morebits.quickForm.setElementVisibility(
			form.blank_duration.parentNode,
			!settings.indefinite && !settings.nonstandard
		);
	}

	Morebits.quickForm.setElementVisibility(form.dstopic.parentNode, value === 'uw-aeblock' || value === 'uw-aepblock');

	// Only particularly relevant if template form is present
	Morebits.quickForm.setElementVisibility(form.article.parentNode, settings && !!settings.pageParam);
	Morebits.quickForm.setElementVisibility(form.block_reason.parentNode, settings && !!settings.reasonParam);

	// Partial block
	Morebits.quickForm.setElementVisibility(form.area.parentNode, partialBox && !blockBox);

	form.root.previewer.closePreview();
};
Twinkle.block.prev_template_expiry = null;

Twinkle.block.callback.preview = function twinkleblockcallbackPreview(form) {
	var params = {
		article: form.article.value,
		blank_duration: form.blank_duration ? form.blank_duration.checked : false,
		disabletalk: form.disabletalk.checked || (form.notalk ? form.notalk.checked : false),
		expiry: form.template_expiry ? form.template_expiry.value : form.expiry.value,
		hardblock: Twinkle.block.isRegistered ? form.autoblock.checked : form.hardblock.checked,
		indefinite: Morebits.string.isInfinity(form.template_expiry ? form.template_expiry.value : form.expiry.value),
		reason: form.block_reason.value,
		template: form.template.value,
		dstopic: form.dstopic.value,
		partial: $(form).find('[name=actiontype][value=partial]').is(':checked'),
		pagerestrictions: $(form.pagerestrictions).val() || [],
		namespacerestrictions: $(form.namespacerestrictions).val() || [],
		noemail: form.noemail.checked || (form.noemail_template ? form.noemail_template.checked : false),
		nocreate: form.nocreate.checked || (form.nocreate_template ? form.nocreate_template.checked : false),
		area: form.area.value
	};

	var templateText = Twinkle.block.callback.getBlockNoticeWikitext(params);

	form.previewer.beginRender(templateText, 'بحث_کاربر:' + relevantUserName); // Force wikitext/correct username
};

Twinkle.block.callback.evaluate = function twinkleblockCallbackEvaluate(e) {
	var $form = $(e.target),
		toBlock = $form.find('[name=actiontype][value=block]').is(':checked'),
		toWarn = $form.find('[name=actiontype][value=template]').is(':checked'),
		toPartial = $form.find('[name=actiontype][value=partial]').is(':checked'),
		blockoptions = {}, templateoptions = {};

	Twinkle.block.callback.saveFieldset($form.find('[name=field_block_options]'));
	Twinkle.block.callback.saveFieldset($form.find('[name=field_template_options]'));

	blockoptions = Twinkle.block.field_block_options;

	templateoptions = Twinkle.block.field_template_options;

	templateoptions.disabletalk = !!(templateoptions.disabletalk || blockoptions.disabletalk);
	templateoptions.hardblock = !!blockoptions.hardblock;

	delete blockoptions.expiry_preset; // remove extraneous

	// Partial API requires this to be gone, not false or 0
	if (toPartial) {
		blockoptions.partial = templateoptions.partial = true;
	}
	templateoptions.pagerestrictions = $form.find('[name=pagerestrictions]').val() || [];
	templateoptions.namespacerestrictions = $form.find('[name=namespacerestrictions]').val() || [];
	// Format for API here rather than in saveFieldset
	blockoptions.pagerestrictions = templateoptions.pagerestrictions.join('|');
	blockoptions.namespacerestrictions = templateoptions.namespacerestrictions.join('|');

	// use block settings as warn options where not supplied
	templateoptions.summary = templateoptions.summary || blockoptions.reason;
	templateoptions.expiry = templateoptions.template_expiry || blockoptions.expiry;

	if (toBlock) {
		if (blockoptions.partial) {
			if (blockoptions.disabletalk && blockoptions.namespacerestrictions.indexOf('3') === -1) {
				return alert('قطع دسترسی موردی نمی‌تواند برای جلوگیری از دسترسی به صفحه بحث به‌کار رود؛ مگر آن که دسترسی کاربر به فضای نام بحث کاربر نیز گرفته شود!');
			}
			if (!blockoptions.namespacerestrictions && !blockoptions.pagerestrictions) {
				if (!blockoptions.noemail && !blockoptions.nocreate) { // Blank entries technically allowed [[phab:T208645]]
					return alert('هیچ صفحه یا فضای نامی انتخاب نشده‌است و هیچ محدودیتی برای ارسال ایمیل یا ایجاد حساب اعمال نشده‌است؛ برای اعمال قطع دسترسی موردی، لطفاً دست کم یک گزینه را انتخاب کنید!');
				} else if ((templateoptions.template !== 'uw-epblock' || $form.find('select[name="preset"]').val() !== 'uw-epblock') &&
					// Don't require confirmation if email harassment defaults are set
					!confirm('شما در حال اعمال قطع دسترسی بدون محدودیت در ویرایش صفحه یا فضای نام هستید؛ آیا مطمئنید که می‌خواهید ادامه دهید؟')) {
					return;
				}
			}
		}
		if (!blockoptions.expiry) {
			return alert('لطفاً زمان پایان قطع دسترسی را مشخص کنید!');
		} else if (Morebits.string.isInfinity(blockoptions.expiry) && !Twinkle.block.isRegistered) {
			return alert("قطع دسترسی بی‌پایان نشانی‌ّهای آی‌پی مقدور نیست!");
		}
		if (!blockoptions.reason) {
			return alert('لطفاً دلیلی برای قطع دسترسی ارائه دهید!');
		}

		Morebits.simpleWindow.setButtonsEnabled(false);
		Morebits.status.init(e.target);
		var statusElement = new Morebits.status('در حال اعمال قطع دسترسی');
		blockoptions.action = 'block';

		blockoptions.user = relevantUserName;

		// boolean-flipped options
		blockoptions.anononly = blockoptions.hardblock ? undefined : true;
		blockoptions.allowusertalk = blockoptions.disabletalk ? undefined : true;

		/*
		  Check if block status changed while processing the form.

		  There's a lot to consider here. list=blocks provides the
		  current block status, but there are at least two issues with
		  relying on it. First, the id doesn't update on a reblock,
		  meaning the individual parameters need to be compared. This
		  can be done roughly with JSON.stringify - we can thankfully
		  rely on order from the server, although sorting would be
		  fine if not - but falsey values are problematic and is
		  non-ideal. More importantly, list=blocks won't indicate if a
		  non-blocked user is blocked then unblocked. This should be
		  exceedingy rare, but regardless, we thus need to check
		  list=logevents, which has a nicely updating logid
		  parameter. We can't rely just on that, though, since it
		  doesn't account for blocks that have expired on their own.

		  As such, we use both. Using some ternaries, the logid
		  variables are false if there's no logevents, so if they
		  aren't equal we defintely have a changed entry (send
		  confirmation). If they are equal, then either the user was
		  never blocked (the block statuses will be equal, no
		  confirmation) or there's no new block, in which case either
		  a block expired (different statuses, confirmation) or the
		  same block is still active (same status, no confirmation).
		*/
		var query = {
			format: 'json',
			action: 'query',
			list: 'blocks|logevents',
			letype: 'block',
			lelimit: 1,
			letitle: 'کاربر:' + blockoptions.user
		};
		// bkusers doesn't catch single IPs blocked as part of a range block
		if (mw.util.isIPAddress(blockoptions.user, true)) {
			query.bkip = blockoptions.user;
		} else {
			query.bkusers = blockoptions.user;
		}
		api.get(query).then(function(data) {
			var block = data.query.blocks[0];
			// As with the initial data fetch, if an IP is blocked
			// *and* rangeblocked, this would only grab whichever
			// block is more recent, which would likely mean a
			// mismatch.  However, if the rangeblock is updated
			// while filling out the form, this won't detect that,
			// but that's probably fine.
			if (data.query.blocks.length > 1 && block.user !== relevantUserName) {
				block = data.query.blocks[1];
			}
			var logevents = data.query.logevents[0];
			var logid = data.query.logevents.length ? logevents.logid : false;

			if (logid !== Twinkle.block.blockLogId || !!block !== !!Twinkle.block.currentBlockInfo) {
				var message = 'وضعیت قطع دسترسی ' + blockoptions.user + ' تغییر کرده‌است. ';
				if (block) {
					message += 'وضعیت جدید: ';
				} else {
					message += 'آخرین مدخل: ';
				}

				var logExpiry = '';
				if (logevents.params.duration) {
					if (logevents.params.duration === 'infinity') {
						logExpiry = 'بی‌پایان';
					} else {
						var expiryDate = new Morebits.date(logevents.params.expiry);
						logExpiry += (expiryDate.isBefore(new Date()) ? '، منقضی‌شده ' : ' تا ') + expiryDate.calendar();
					}
				} else { // no duration, action=unblock, just show timestamp
					logExpiry = ' ' + new Morebits.date(logevents.timestamp).calendar();
				}
				message += Morebits.string.toUpperCaseFirstChar(logevents.action) + '‌شده توسط ' + logevents.user + logExpiry +
					' به‌دلیل «' + logevents.comment + '». آیا می‌خواهید تنظیمات خود را جایگزین کنید؟';

				if (!confirm(message)) {
					Morebits.status.info('در حال اعمال قطع دسترسی', 'از سوی کاربر لغو شد');
					return;
				}
				blockoptions.reblock = 1; // Writing over a block will fail otherwise
			}

			// execute block
			blockoptions.tags = Twinkle.changeTags;
			blockoptions.token = mw.user.tokens.get('csrfToken');
			var mbApi = new Morebits.wiki.api('در حال اعمال قطع دسترسی', blockoptions, function() {
				statusElement.info('کامل شد');
				if (toWarn) {
					Twinkle.block.callback.issue_template(templateoptions);
				}
			});
			mbApi.post();
		});
	} else if (toWarn) {
		Morebits.simpleWindow.setButtonsEnabled(false);

		Morebits.status.init(e.target);
		Twinkle.block.callback.issue_template(templateoptions);
	} else {
		return alert('لطفاً کاری را برای انجام توسط توینکل مشخص کنید!');
	}
};

Twinkle.block.callback.issue_template = function twinkleblockCallbackIssueTemplate(formData) {
	// Use wgRelevantUserName to ensure the block template goes to a single IP and not to the
	// "talk page" of an IP range (which does not exist)
	var userTalkPage = 'بحث_کاربر:' + mw.config.get('wgRelevantUserName');

	var params = $.extend(formData, {
		messageData: Twinkle.block.blockPresetsInfo[formData.template],
		reason: Twinkle.block.field_template_options.block_reason,
		disabletalk: Twinkle.block.field_template_options.notalk,
		noemail: Twinkle.block.field_template_options.noemail_template,
		nocreate: Twinkle.block.field_template_options.nocreate_template
	});

	Morebits.wiki.actionCompleted.redirect = userTalkPage;
	Morebits.wiki.actionCompleted.notice = 'کنش‌های کامل شدند، بارگیری صفحهٔ بحث کاربر تا چند ثانیهٔ دیگر';

	var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'ویرایش صفحهٔ بحث کاربر');
	wikipedia_page.setCallbackParameters(params);
	wikipedia_page.load(Twinkle.block.callback.main);
};

Twinkle.block.callback.getBlockNoticeWikitext = function(params) {
	var text = '{{', settings = Twinkle.block.blockPresetsInfo[params.template];
	if (!settings.nonstandard) {
		text += 'جا:' + params.template;
		if (params.article && settings.pageParam) {
			text += '|page=' + params.article;
		}
		if (params.dstopic) {
			text += '|topic=' + params.dstopic;
		}

		if (!/te?mp|^\s*$|min/.exec(params.expiry)) {
			if (params.indefinite) {
				text += '|indef=yes';
			} else if (!params.blank_duration && !new Morebits.date(params.expiry).isValid()) {
				// Block template wants a duration, not date
				text += '|time=' + params.expiry;
			}
		}

		if (!Twinkle.block.isRegistered && !params.hardblock) {
			text += '|anon=yes';
		}

		if (params.reason) {
			text += '|reason=' + params.reason;
		}
		if (params.disabletalk) {
			text += '|notalk=yes';
		}

		// Currently, all partial block templates are "standard"
		// Building the template, however, takes a fair bit of logic
		if (params.partial) {
			if (params.pagerestrictions.length || params.namespacerestrictions.length) {
				var makeSentence = function (array) {
					if (array.length < 3) {
						return array.join(' و ');
					}
					var last = array.pop();
					return array.join(', ') + ' و ' + last;

				};
				text += '|area=' + (params.indefinite ? '' : 'از ');
				if (params.pagerestrictions.length) {
					text += 'صفحه‌هایی خاص (' + makeSentence(params.pagerestrictions.map(function(p) {
						return '[[:' + p + ']]';
					}));
					text += params.namespacerestrictions.length ? ') و ' : ')';
				}
				if (params.namespacerestrictions.length) {
					// 1 => Talk, 2 => User, etc.
					var namespaceNames = params.namespacerestrictions.map(function(id) {
						return menuFormattedNamespaces[id];
					});
					text += '[[ویکی‌پدیا:فضای نام|فضاهای نام]] خاص (' + makeSentence(namespaceNames) + ')';
				}
			} else if (params.area) {
				text += '|area=' + params.area;
			} else {
				if (params.noemail) {
					text += '|email=yes';
				}
				if (params.nocreate) {
					text += '|accountcreate=yes';
				}
			}
		}
	} else {
		text += params.template;
	}

	if (settings.sig) {
		text += '|sig=' + settings.sig;
	}
	return text + '}}';
};

Twinkle.block.callback.main = function twinkleblockcallbackMain(pageobj) {
	var params = pageobj.getCallbackParameters(),
		date = new Morebits.date(pageobj.getLoadTime()),
		messageData = params.messageData,
		text;

	params.indefinite = Morebits.string.isInfinity(params.expiry);

	if (Twinkle.getPref('blankTalkpageOnIndefBlock') && params.template !== 'هبک-قطع دسترسی تهدید' && params.indefinite) {
		Morebits.status.info('اطلاعات', 'خالی‌کردن صفحهٔ بحث بر پایه ترجیحات و ایجاد بخش جدید در صفحهٔ بحث برای ماه جاری');
		text = date.monthHeader() + '\n';
	} else {
		text = pageobj.getPageText();

		var dateHeaderRegex = date.monthHeaderRegex(), dateHeaderRegexLast, dateHeaderRegexResult;
		while ((dateHeaderRegexLast = dateHeaderRegex.exec(text)) !== null) {
			dateHeaderRegexResult = dateHeaderRegexLast;
		}
		// If dateHeaderRegexResult is null then lastHeaderIndex is never checked. If it is not null but
		// \n== is not found, then the date header must be at the very start of the page. lastIndexOf
		// returns -1 in this case, so lastHeaderIndex gets set to 0 as desired.
		var lastHeaderIndex = text.lastIndexOf('\n==') + 1;

		if (text.length > 0) {
			text += '\n\n';
		}

		if (!dateHeaderRegexResult || dateHeaderRegexResult.index !== lastHeaderIndex) {
			Morebits.status.info('اطلاعات', 'یک بخش جدید برای ماه جاری ایجاد خواهد شد؛ چرا که بخشی برای این ماه یافت نشد');
			text += date.monthHeader() + '\n';
		}
	}

	params.expiry = typeof params.template_expiry !== 'undefined' ? params.template_expiry : params.expiry;

	text += Twinkle.block.callback.getBlockNoticeWikitext(params);

	// build the edit summary
	var summary = messageData.summary;
	if (messageData.suppressArticleInSummary !== true && params.article) {
		summary += ' در [[:' + params.article + ']]';
	}
	summary += '.';

	pageobj.setPageText(text);
	pageobj.setEditSummary(summary);
	pageobj.setChangeTags(Twinkle.changeTags);
	pageobj.setWatchlist(Twinkle.getPref('watchWarnings'));
	pageobj.save();
};

Twinkle.addInitCallback(Twinkle.block, 'block');
})(jQuery);


// </nowiki>