MediaWiki:Gadget-twinkleprotect.js

Bách khoa toàn thư mở Wikipedia
Chú ý: Sau khi lưu thay đổi trang, bạn phải xóa bộ nhớ đệm của trình duyệt để nhìn thấy các thay đổi. Google Chrome, Firefox, Internet ExplorerSafari: Giữ phím ⇧ Shift và nhấn nút Reload/Tải lại trên thanh công cụ của trình duyệt. Để biết chi tiết và hướng dẫn cho các trình duyệt khác, xem Trợ giúp:Xóa bộ nhớ đệm.
// <nowiki>


(function($) {


/*
 ****************************************
 *** twinkleprotect.js: Protect/RPP module
 ****************************************
 * Mode of invocation:     Tab ("Khóa"/"Yêu cầu khóa")
 * Active on:              Non-special, non-MediaWiki pages
 */

// Note: a lot of code in this module is re-used/called by batchprotect.

Twinkle.protect = function twinkleprotect() {
	if (mw.config.get('wgNamespaceNumber') < 0 || mw.config.get('wgNamespaceNumber') === 8) {
		return;
	}

	Twinkle.addPortletLink(Twinkle.protect.callback, (Morebits.userIsSysop || Morebits.userIsInGroup('eliminator')) ? 'Khóa trang' : 'Yêu cầu khóa trang', 'tw-rpp',
		(Morebits.userIsSysop || Morebits.userIsInGroup('eliminator')) ? 'Khóa trang' : 'Yêu cầu khóa trang');
};

Twinkle.protect.callback = function twinkleprotectCallback() {
	var Window = new Morebits.simpleWindow(620, 530);
	Window.setTitle((Morebits.userIsSysop || Morebits.userIsInGroup('eliminator')) ? 'Khóa trang, yêu cầu hoặc thêm bản mẫu khóa trang' : 'Yêu cầu hoặc thêm bản mẫu khóa trang');
	Window.setScriptName('Twinkle');
	Window.addFooterLink('Các bản mẫu khóa trang', 'Template:Protection templates');
	Window.addFooterLink('Quy định khóa trang', 'WP:PROT');
	Window.addFooterLink('Trợ giúp Twinkle', 'WP:TW/DOC#protect');

	var form = new Morebits.quickForm(Twinkle.protect.callback.evaluate);
	var actionfield = form.append({
		type: 'field',
		label: 'Loại tác vụ'
	});
	if (Morebits.userIsSysop || Morebits.userIsInGroup('eliminator')) {
		actionfield.append({
			type: 'radio',
			name: 'actiontype',
			event: Twinkle.protect.callback.changeAction,
			list: [
				{
					label: 'Khóa trang',
					value: 'protect',
					tooltip: 'Thiết lập khóa trang.',
					checked: true
				}
			]
		});
	}
	actionfield.append({
		type: 'radio',
		name: 'actiontype',
		event: Twinkle.protect.callback.changeAction,
		list: [
			{
				label: 'Yêu cầu khóa trang',
				value: 'request',
				tooltip: 'Nếu bạn muốn đề nghị khóa trang WP:Khóa trang' + ((Morebits.userIsSysop || Morebits.userIsInGroup('eliminator')) ? ' thay vì bạn có thể tự thực hiện.' : '.'),
				checked: !(Morebits.userIsSysop || Morebits.userIsInGroup('eliminator'))
			},
			{
				label: 'Thêm bản mẫu khóa trang',
				value: 'tag',
				tooltip: 'Nếu bảo quản viên quên thêm bản mẫu khóa trang, hoặc bạn vừa khóa một trang mà chưa thêm bản mẫu, bạn có thể sử dụng tag này ở những trang tương ứng.',
				disabled: mw.config.get('wgArticleId') === 0 || mw.config.get('wgPageContentModel') === 'Scribunto'
			}
		]
	});

	form.append({ type: 'field', label: 'Preset', name: 'field_preset' });
	form.append({ type: 'field', label: '1', name: 'field1' });
	form.append({ type: 'field', label: '2', name: 'field2' });

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

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

	// We must init the controls
	var evt = document.createEvent('Event');
	evt.initEvent('change', true, true);
	result.actiontype[0].dispatchEvent(evt);

	// get current protection level asynchronously
	Twinkle.protect.fetchProtectionLevel();
};


// A list of bots who may be the protecting sysop, for whom we shouldn't
// remind the user contact before requesting unprotection (evaluate)
Twinkle.protect.trustedBots = ['MusikBot II', 'TFA Protector Bot'];

// Customizable namespace and FlaggedRevs settings
// In theory it'd be nice to have restrictionlevels defined here,
// but those are only available via a siteinfo query

// mw.loader.getState('ext.flaggedRevs.review') returns null if the
// FlaggedRevs extension is not registered.  Previously, this was done with
// wgFlaggedRevsParams, but after 1.34-wmf4 it is no longer exported if empty
// (https://gerrit.wikimedia.org/r/c/mediawiki/extensions/FlaggedRevs/+/508427)
var hasFlaggedRevs = mw.loader.getState('ext.flaggedRevs.review') &&
// FlaggedRevs only valid in some namespaces, hardcoded until [[phab:T218479]]
(mw.config.get('wgNamespaceNumber') === 0 || mw.config.get('wgNamespaceNumber') === 4);
// Limit template editor; a Twinkle restriction, not a site setting
var isTemplate = mw.config.get('wgNamespaceNumber') === 10 || mw.config.get('wgNamespaceNumber') === 828;


// Contains the current protection level in an object
// Once filled, it will look something like:
// { edit: { level: "sysop", expiry: <some date>, cascade: true }, ... }
Twinkle.protect.currentProtectionLevels = {};

// returns a jQuery Deferred object, usage:
//   Twinkle.protect.fetchProtectingAdmin(apiObject, pageName, protect/stable).done(function(admin_username) { ...code... });
Twinkle.protect.fetchProtectingAdmin = function twinkleprotectFetchProtectingAdmin(api, pageName, protType, logIds) {
	logIds = logIds || [];

	return api.get({
		format: 'json',
		action: 'query',
		list: 'logevents',
		letitle: pageName,
		letype: protType
	}).then(function(data) {
		// don't check log entries that have already been checked (e.g. don't go into an infinite loop!)
		var event = data.query ? $.grep(data.query.logevents, function(le) {
			return $.inArray(le.logid, logIds);
		})[0] : null;
		if (!event) {
			// fail gracefully
			return null;
		} else if (event.action === 'move_prot' || event.action === 'move_stable') {
			return twinkleprotectFetchProtectingAdmin(api, protType === 'protect' ? event.params.oldtitle_title : event.params.oldtitle, protType, logIds.concat(event.logid));
		}
		return event.user;
	});
};

Twinkle.protect.fetchProtectionLevel = function twinkleprotectFetchProtectionLevel() {

	var api = new mw.Api();
	var protectDeferred = api.get({
		format: 'json',
		indexpageids: true,
		action: 'query',
		list: 'logevents',
		letype: 'protect',
		letitle: mw.config.get('wgPageName'),
		prop: hasFlaggedRevs ? 'info|flagged' : 'info',
		inprop: 'protection',
		titles: mw.config.get('wgPageName')
	});
	var stableDeferred = api.get({
		format: 'json',
		action: 'query',
		list: 'logevents',
		letype: 'stable',
		letitle: mw.config.get('wgPageName')
	});

	var earlyDecision = [protectDeferred];
	if (hasFlaggedRevs) {
		earlyDecision.push(stableDeferred);
	}

	$.when.apply($, earlyDecision).done(function(protectData, stableData) {
		// $.when.apply is supposed to take an unknown number of promises
		// via an array, which it does, but the type of data returned varies.
		// If there are two or more deferreds, it returns an array (of objects),
		// but if there's just one deferred, it retuns a simple object.
		// This is annoying.
		protectData = $(protectData).toArray();

		var pageid = protectData[0].query.pageids[0];
		var page = protectData[0].query.pages[pageid];
		var current = {}, adminEditDeferred;

		$.each(page.protection, function(index, protection) {
			if (protection.type !== 'aft') {
				current[protection.type] = {
					level: protection.level,
					expiry: protection.expiry,
					cascade: protection.cascade === ''
				};
				// logs report last admin who made changes to either edit/move/create protection, regardless if they only modified one of them
				if (!adminEditDeferred) {
					adminEditDeferred = Twinkle.protect.fetchProtectingAdmin(api, mw.config.get('wgPageName'), 'protect');
				}
			}
		});

		if (page.flagged) {
			current.stabilize = {
				level: page.flagged.protection_level,
				expiry: page.flagged.protection_expiry
			};
			adminEditDeferred = Twinkle.protect.fetchProtectingAdmin(api, mw.config.get('wgPageName'), 'stable');
		}

		// show the protection level and log info
		Twinkle.protect.hasProtectLog = !!protectData[0].query.logevents.length;
		Twinkle.protect.hasStableLog = hasFlaggedRevs ? !!stableData[0].query.logevents.length : false;
		Twinkle.protect.currentProtectionLevels = current;

		if (adminEditDeferred) {
			adminEditDeferred.done(function(admin) {
				if (admin) {
					$.each(['edit', 'move', 'create', 'stabilize'], function(i, type) {
						if (Twinkle.protect.currentProtectionLevels[type]) {
							Twinkle.protect.currentProtectionLevels[type].admin = admin;
						}
					});
				}
				Twinkle.protect.callback.showLogAndCurrentProtectInfo();
			});
		} else {
			Twinkle.protect.callback.showLogAndCurrentProtectInfo();
		}
	});
};

Twinkle.protect.callback.showLogAndCurrentProtectInfo = function twinkleprotectCallbackShowLogAndCurrentProtectInfo() {
	var currentlyProtected = !$.isEmptyObject(Twinkle.protect.currentProtectionLevels);

	if (Twinkle.protect.hasProtectLog || Twinkle.protect.hasStableLog) {
		var $linkMarkup = $('<span>');

		if (Twinkle.protect.hasProtectLog) {
			$linkMarkup.append(
				$('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: mw.config.get('wgPageName'), type: 'protect'}) + '">nhật trình khóa</a>'),
				Twinkle.protect.hasStableLog ? $('<span> &bull; </span>') : null
			);
		}

		if (Twinkle.protect.hasStableLog) {
			$linkMarkup.append($('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: mw.config.get('wgPageName'), type: 'stable'}) + '">pending changes log</a>)'));
		}

		Morebits.status.init($('div[name="hasprotectlog"] span')[0]);
		Morebits.status.warn(
			currentlyProtected ? 'Các mức khóa trước' : 'Trang này đã được khóa trong quá khứ',
			$linkMarkup[0]
		);
	}

	Morebits.status.init($('div[name="currentprot"] span')[0]);
	var protectionNode = [], statusLevel = 'info';

	if (currentlyProtected) {
		$.each(Twinkle.protect.currentProtectionLevels, function(type, settings) {
			var label = type === 'stabilize' ? 'Các thay đổi đang chờ xử lý' : Morebits.string.toUpperCaseFirstChar(type);
			protectionNode.push($('<b>' + label + ': ' + settings.level + '</b>')[0]);
			if (settings.expiry === 'infinity') {
				protectionNode.push(' (vô hạn) ');
			} else {
				protectionNode.push(' (hết hạn ' + new Date(settings.expiry).toUTCString() + ') ');
			}
			if (settings.cascade) {
				protectionNode.push('(theo tầng) ');
			}
			if (settings.admin) {
				var adminLink = '<a target="_blank" href="' + mw.util.getUrl('User talk:' + settings.admin) + '">' + settings.admin + '</a>';
				protectionNode.push($('<span>bởi ' + adminLink + '&nbsp;</span>')[0]);
			}
			protectionNode.push($('<span> \u2022 </span>')[0]);
		});
		protectionNode = protectionNode.slice(0, -1); // remove the trailing bullet
		statusLevel = 'warn';
	} else {
		protectionNode.push($('<b>không khóa</b>')[0]);
	}

	Morebits.status[statusLevel]('Mức độ khóa hiện tại', protectionNode);
};

Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAction(e) {
	var field_preset;
	var field1;
	var field2;

	switch (e.target.values) {
		case 'protect':
			field_preset = new Morebits.quickForm.element({ type: 'field', label: 'Thiết lập', name: 'field_preset' });
			field_preset.append({
				type: 'select',
				name: 'category',
				label: 'Chọn một thiết lập trước:',
				event: Twinkle.protect.callback.changePreset,
				list: mw.config.get('wgArticleId') ? Twinkle.protect.protectionTypes : Twinkle.protect.protectionTypesCreate
			});

			field2 = new Morebits.quickForm.element({ type: 'field', label: 'Các tùy chọn khóa', name: 'field2' });
			field2.append({ type: 'div', name: 'currentprot', label: ' ' });  // holds the current protection level, as filled out by the async callback
			field2.append({ type: 'div', name: 'hasprotectlog', label: ' ' });
			// for existing pages
			if (mw.config.get('wgArticleId')) {
				field2.append({
					type: 'checkbox',
					event: Twinkle.protect.formevents.editmodify,
					list: [
						{
							label: 'Thay đổi mức khóa sửa đổi',
							name: 'editmodify',
							tooltip: 'Nếu tắt tính năng này, mức khóa sửa đổi và thời gian hết hạn sẽ được giữ nguyên.',
							checked: true
						}
					]
				});
				field2.append({
					type: 'select',
					name: 'editlevel',
					label: 'Khóa sửa đổi:',
					event: Twinkle.protect.formevents.editlevel,
					list: Twinkle.protect.protectionLevels.filter(function(level) {
						// Filter TE outside of templates and modules
						return isTemplate || level.value !== 'templateeditor';
					})
				});
				field2.append({
					type: 'select',
					name: 'editexpiry',
					label: 'Thời gian hết hạn:',
					event: function(e) {
						if (e.target.value === 'custom') {
							Twinkle.protect.doCustomExpiry(e.target);
						}
					},
					// default expiry selection (2 days) is conditionally set in Twinkle.protect.callback.changePreset
					list: Twinkle.protect.protectionLengths
				});
				field2.append({
					type: 'checkbox',
					event: Twinkle.protect.formevents.movemodify,
					list: [
						{
							label: 'Thay đổi mức khóa di chuyển trang',
							name: 'movemodify',
							tooltip: 'Nếu tắt tính năng này, mức khoa di chuyển trang và thời gian hết hạn sẽ được giữ nguyên.',
							checked: true
						}
					]
				});
				field2.append({
					type: 'select',
					name: 'movelevel',
					label: 'Khóa di chuyển trang:',
					event: Twinkle.protect.formevents.movelevel,
					list: Twinkle.protect.protectionLevels.filter(function(level) {
						// Autoconfirmed is required for a move, redundant
						return level.value !== 'autoconfirmed' && (isTemplate || level.value !== 'templateeditor');
					})
				});
				field2.append({
					type: 'select',
					name: 'moveexpiry',
					label: 'Thời gian hết hạn:',
					event: function(e) {
						if (e.target.value === 'custom') {
							Twinkle.protect.doCustomExpiry(e.target);
						}
					},
					// default expiry selection (2 days) is conditionally set in Twinkle.protect.callback.changePreset
					list: Twinkle.protect.protectionLengths
				});
				if (hasFlaggedRevs) {
					field2.append({
						type: 'checkbox',
						event: Twinkle.protect.formevents.pcmodify,
						list: [
							{
								label: 'Chỉnh sửa mức khóa các thay đổi đang chờ xử lý',
								name: 'pcmodify',
								tooltip: 'Nếu tắt tính năng này, mức thay đổi đang chờ xử lý và thời gian hết hạn sẽ được giữ nguyên.',
								checked: true
							}
						]
					});
					field2.append({
						type: 'select',
						name: 'pclevel',
						label: 'Các thay đổi đang chờ xử lý:',
						event: Twinkle.protect.formevents.pclevel,
						list: [
							{ label: 'None', value: 'none' },
							{ label: 'Thay đổi đang chờ xử lý', value: 'autoconfirmed', selected: true }
						]
					});
					field2.append({
						type: 'select',
						name: 'pcexpiry',
						label: 'Thời gian hết hạn:',
						event: function(e) {
							if (e.target.value === 'custom') {
								Twinkle.protect.doCustomExpiry(e.target);
							}
						},
						// default expiry selection (1 month) is conditionally set in Twinkle.protect.callback.changePreset
						list: Twinkle.protect.protectionLengths
					});
				}
			} else {  // for non-existing pages
				field2.append({
					type: 'select',
					name: 'createlevel',
					label: 'Tạo khóa:',
					event: Twinkle.protect.formevents.createlevel,
					list: Twinkle.protect.protectionLevels.filter(function(level) {
						// Filter TE always, and autoconfirmed in mainspace, redundant since WP:ACPERM
						return level.value !== 'templateeditor' && (mw.config.get('wgNamespaceNumber') !== 0 || level.value !== 'autoconfirmed');
					})
				});
				field2.append({
					type: 'select',
					name: 'createexpiry',
					label: 'Thời gian hết hạn:',
					event: function(e) {
						if (e.target.value === 'custom') {
							Twinkle.protect.doCustomExpiry(e.target);
						}
					},
					// default expiry selection (indefinite) is conditionally set in Twinkle.protect.callback.changePreset
					list: Twinkle.protect.protectionLengths
				});
			}
			field2.append({
				type: 'textarea',
				name: 'protectReason',
				label: 'Lý do (cho nhật trình khóa):'
			});
			if (!mw.config.get('wgArticleId') || mw.config.get('wgPageContentModel') === 'Scribunto') {  // tagging isn't relevant for non-existing or module pages
				break;
			}
			/* falls through */
		case 'tag':
			field1 = new Morebits.quickForm.element({ type: 'field', label: 'Các tùy chọn gắn thẻ', name: 'field1' });
			field1.append({ type: 'div', name: 'currentprot', label: ' ' });  // holds the current protection level, as filled out by the async callback
			field1.append({ type: 'div', name: 'hasprotectlog', label: ' ' });
			field1.append({
				type: 'select',
				name: 'tagtype',
				label: 'Chọn bản mẫu khóa:',
				list: Twinkle.protect.protectionTags,
				event: Twinkle.protect.formevents.tagtype
			});
			field1.append({
				type: 'checkbox',
				list: [
					{
						name: 'small',
						label: 'Biểu tượng khóa (small=yes)',
						tooltip: 'Sẽ sử dụng tính năng |small=yes của bản mẫu, và chỉ hiển thị nó dưới dạng một keylock',
						checked: true
					},
					{
						name: 'noinclude',
						label: 'Bọc bản mẫu khóa lại với thẻ <noinclude>',
						tooltip: 'Sẽ bọc bản mẫu khóa bên trong thẻ &lt;noinclude&gt;, vì vậy bản mẫu khóa sẽ không được nhúng vào nội dung',
						checked: mw.config.get('wgNamespaceNumber') === 10
					}
				]
			});
			break;

		case 'request':
			field_preset = new Morebits.quickForm.element({ type: 'field', label: 'Loại khóa', name: 'field_preset' });
			field_preset.append({
				type: 'select',
				name: 'category',
				label: 'Loại và lý do:',
				event: Twinkle.protect.callback.changePreset,
				list: mw.config.get('wgArticleId') ? Twinkle.protect.protectionTypes : Twinkle.protect.protectionTypesCreate
			});

			field1 = new Morebits.quickForm.element({ type: 'field', label: 'Tùy chọn', name: 'field1' });
			field1.append({ type: 'div', name: 'currentprot', label: ' ' });  // holds the current protection level, as filled out by the async callback
			field1.append({ type: 'div', name: 'hasprotectlog', label: ' ' });
			field1.append({
				type: 'select',
				name: 'expiry',
				label: 'Thời gian: ',
				list: [
					{ label: '', selected: true, value: '' },
					{ label: 'Tạm thời', value: 'temporary' },
					{ label: 'Vô thời hạn', value: 'infinity' }
				]
			});
			field1.append({
				type: 'textarea',
				name: 'reason',
				label: 'Lý do: '
			});
			break;
		default:
			alert("Có gì đó đang xảy ra trong twinkleprotect");
			break;
	}

	var oldfield;

	if (field_preset) {
		oldfield = $(e.target.form).find('fieldset[name="field_preset"]')[0];
		oldfield.parentNode.replaceChild(field_preset.render(), oldfield);
	} else {
		$(e.target.form).find('fieldset[name="field_preset"]').css('display', 'none');
	}
	if (field1) {
		oldfield = $(e.target.form).find('fieldset[name="field1"]')[0];
		oldfield.parentNode.replaceChild(field1.render(), oldfield);
	} else {
		$(e.target.form).find('fieldset[name="field1"]').css('display', 'none');
	}
	if (field2) {
		oldfield = $(e.target.form).find('fieldset[name="field2"]')[0];
		oldfield.parentNode.replaceChild(field2.render(), oldfield);
	} else {
		$(e.target.form).find('fieldset[name="field2"]').css('display', 'none');
	}

	if (e.target.values === 'protect') {
		// fake a change event on the preset dropdown
		var evt = document.createEvent('Event');
		evt.initEvent('change', true, true);
		e.target.form.category.dispatchEvent(evt);

		// reduce vertical height of dialog
		$(e.target.form).find('fieldset[name="field2"] select').parent().css({ display: 'inline-block', marginRight: '0.5em' });
	}

	// re-add protection level and log info, if it's available
	Twinkle.protect.callback.showLogAndCurrentProtectInfo();
};

// NOTE: This function is used by batchprotect as well
Twinkle.protect.formevents = {
	editmodify: function twinkleprotectFormEditmodifyEvent(e) {
		e.target.form.editlevel.disabled = !e.target.checked;
		e.target.form.editexpiry.disabled = !e.target.checked || (e.target.form.editlevel.value === 'all');
		e.target.form.editlevel.style.color = e.target.form.editexpiry.style.color = e.target.checked ? '' : 'transparent';
	},
	editlevel: function twinkleprotectFormEditlevelEvent(e) {
		e.target.form.editexpiry.disabled = e.target.value === 'all';
	},
	movemodify: function twinkleprotectFormMovemodifyEvent(e) {
		// sync move settings with edit settings if applicable
		if (e.target.form.movelevel.disabled && !e.target.form.editlevel.disabled) {
			e.target.form.movelevel.value = e.target.form.editlevel.value;
			e.target.form.moveexpiry.value = e.target.form.editexpiry.value;
		} else if (e.target.form.editlevel.disabled) {
			e.target.form.movelevel.value = 'sysop';
			e.target.form.moveexpiry.value = 'infinity';
		}
		e.target.form.movelevel.disabled = !e.target.checked;
		e.target.form.moveexpiry.disabled = !e.target.checked || (e.target.form.movelevel.value === 'all');
		e.target.form.movelevel.style.color = e.target.form.moveexpiry.style.color = e.target.checked ? '' : 'transparent';
	},
	movelevel: function twinkleprotectFormMovelevelEvent(e) {
		e.target.form.moveexpiry.disabled = e.target.value === 'all';
	},
	pcmodify: function twinkleprotectFormPcmodifyEvent(e) {
		e.target.form.pclevel.disabled = !e.target.checked;
		e.target.form.pcexpiry.disabled = !e.target.checked || (e.target.form.pclevel.value === 'none');
		e.target.form.pclevel.style.color = e.target.form.pcexpiry.style.color = e.target.checked ? '' : 'transparent';
	},
	pclevel: function twinkleprotectFormPclevelEvent(e) {
		e.target.form.pcexpiry.disabled = e.target.value === 'none';
	},
	createlevel: function twinkleprotectFormCreatelevelEvent(e) {
		e.target.form.createexpiry.disabled = e.target.value === 'all';
	},
	tagtype: function twinkleprotectFormTagtypeEvent(e) {
		e.target.form.small.disabled = e.target.form.noinclude.disabled = (e.target.value === 'none') || (e.target.value === 'noop');
	}
};

Twinkle.protect.doCustomExpiry = function twinkleprotectDoCustomExpiry(target) {
	var custom = prompt('Nhập thời gian hết hạn có thể tùy chỉnh. \nBạn có thể sử dụng thời gian tương đối, chẳng hạn "1 minute" hay "19 days", hoặc nhãn thời gian tuyệt đối, "yyyymmddhhmm" (ví dụ "200602011405" là ngày 1 tháng 2 năm 2006, lúc 14:05 UTC).', '');
	if (custom) {
		var option = document.createElement('option');
		option.setAttribute('value', custom);
		option.textContent = custom;
		target.appendChild(option);
		target.value = custom;
	} else {
		target.selectedIndex = 0;
	}
};

// NOTE: This list is used by batchprotect as well
Twinkle.protect.protectionLevels = [
	{ label: 'Tất cả', value: 'all' },
	{ label: 'Thành viên tự xác nhận', value: 'autoconfirmed' },
	{ label: 'Thành viên xác nhận mở rộng', value: 'extendedconfirmed' },
	{ label: 'Kỹ thuật viên bản mẫu', value: 'templateeditor' },
	{ label: 'Bảo quản viên', value: 'sysop', selected: true }
];

// default expiry selection is conditionally set in Twinkle.protect.callback.changePreset
// NOTE: This list is used by batchprotect as well
Twinkle.protect.protectionLengths = [
	{ label: '1 giờ', value: '1 hour' },
	{ label: '2 giờ', value: '2 hours' },
	{ label: '3 giờ', value: '3 hours' },
	{ label: '6 giờ', value: '6 hours' },
	{ label: '12 giờ', value: '12 hours' },
	{ label: '1 ngày', value: '1 day' },
	{ label: '2 ngày', value: '2 days' },
	{ label: '3 ngày', value: '3 days' },
	{ label: '4 ngày', value: '4 days' },
	{ label: '1 tuần', value: '1 week' },
	{ label: '2 tuần', value: '2 weeks' },
	{ label: '1 tháng', value: '1 month' },
	{ label: '2 tháng', value: '2 months' },
	{ label: '3 tháng', value: '3 months' },
	{ label: '1 năm', value: '1 year' },
	{ label: 'Vô hạn', value: 'infinity' },
	{ label: 'Tùy chỉnh...', value: 'custom' }
];

Twinkle.protect.protectionTypes = [
	{ label: 'Không khóa', value: 'unprotect' },
	{
		label: 'Khóa hoàn toàn',
		list: [
			{ label: 'Chung (hoàn toàn)', value: 'pp-protected' },
			{ label: 'Tranh chấp nội dung/bút chiến (hoàn toàn)', value: 'pp-dispute' },
			{ label: 'Phá hoại dai dẳng (hoàn toàn)', value: 'pp-vandalism' },
			{ label: 'Trang Thảo luận Thành viên của thành viên bị cấm (hoàn toàn)', value: 'pp-usertalk' }
		]
	},
	{
		label: 'Khóa bản mẫu',
		list: [
			{ label: 'Bản mẫu nhiều người xem (TE)', value: 'pp-template' }
		]
	},
	{
		label: 'Khóa xác nhận mở rộng',
		list: [
			// { label: 'Tuân theo Ủy ban Trọng tài (ECP)', value: 'pp-30-500-arb' }, // viwiki không cần
			{ label: 'Phá hoại dai dẳng (ECP)', selected: true, value: 'pp-30-500-vandalism' },
			{ label: 'Sửa đổi phá hoại (ECP)', value: 'pp-30-500-disruptive' },
			{ label: 'Vi phạm chính sách BLP (tiểu sử người còn sống) (ECP)', value: 'pp-30-500-blp' },
			{ label: 'Con rối (ECP)', value: 'pp-30-500-sock' }
		]
	},
	{
		label: 'Bán khóa',
		list: [
			{ label: 'Chung (bán khóa)', value: 'pp-semi-protected' },
			{ label: 'Phá hoại dai dẳng (bán khóa)', selected: true, value: 'pp-semi-vandalism' },
			{ label: 'Sửa đổi phá hoại (bán khóa)', value: 'pp-semi-disruptive' },
			{ label: 'Thêm nội dung không nguồn (bán khóa)', value: 'pp-semi-unsourced' },
			{ label: 'Vi phạm chính sách BLP (tiểu sử người còn sống) (bán khóa)', value: 'pp-semi-blp' },
			{ label: 'Con rối (bán khóa)', value: 'pp-semi-sock' },
			{ label: 'Trang Thảo luận Thành viên của thành viên bị cấm (bán khóa)', value: 'pp-semi-usertalk' }
		]
	},
	// {
	// 	label: 'Thay đổi đang chờ xử lý',
	// 	list: [
	// 		{ label: 'Chung (PC)', value: 'pp-pc-protected' },
	// 		{ label: 'Phá hoại dai dẳng (PC)', value: 'pp-pc-vandalism' },
	// 		{ label: 'Sửa đổi phá hoại (PC)', value: 'pp-pc-disruptive' },
	// 		{ label: 'Thêm nội dung không nguồn (PC)', value: 'pp-pc-unsourced' },
	// 		{ label: 'Vi phạm chính sách BLP (tiểu sử người còn sống) (PC)', value: 'pp-pc-blp' }
	// 	]
	// },
	{
		label: 'Khóa di chuyển trang',
		list: [
			{ label: 'Chung (di chuyển)', value: 'pp-move' },
			{ label: 'Tranh chấp di chuyển trang (di chuyển)', value: 'pp-move-dispute' },
			{ label: 'Phá hoại di chuyển trang (di chuyển)', value: 'pp-move-vandalism' },
			{ label: 'Trang nhiều người xem (di chuyển)', value: 'pp-move-indef' }
		]
	}
].filter(function(type) {
	// Filter for templates and flaggedrevs
	return (isTemplate || type.label !== 'Template protection') && (hasFlaggedRevs || type.label !== 'Pending changes');
});

Twinkle.protect.protectionTypesCreate = [
	{ label: 'Không khóa', value: 'unprotect' },
	{
		label: 'Tạo khóa',
		list: [
			{ label: 'Chung ({{pp-create}})', value: 'pp-create' },
			{ label: 'Tên xúc phạm', value: 'pp-create-offensive' },
			{ label: 'Tạo lại nội dung nhiều lần', selected: true, value: 'pp-create-salt' },
			{ label: 'Tiểu sử người còn sống (BLP) đã xóa gần đây', value: 'pp-create-blp' }
		]
	}
];

// A page with both regular and PC protection will be assigned its regular
// protection weight plus 2
Twinkle.protect.protectionWeight = {
	sysop: 40,
	templateeditor: 30,
	extendedconfirmed: 20,
	autoconfirmed: 10,
	flaggedrevs_autoconfirmed: 5,  // Pending Changes protection alone
	all: 0,
	flaggedrevs_none: 0  // just in case
};

// NOTICE: keep this synched with [[MediaWiki:Protect-dropdown]]
// Also note: stabilize = Pending Changes level
// expiry will override any defaults
Twinkle.protect.protectionPresetsInfo = {
	'pp-protected': {
		edit: 'sysop',
		move: 'sysop',
		reason: null
	},
	'pp-dispute': {
		edit: 'sysop',
		move: 'sysop',
		reason: '[[Wikipedia:Quy định khóa trang|Bút chiến / Nội dung tranh cãi]]'
	},
	'pp-vandalism': {
		edit: 'sysop',
		move: 'sysop',
		reason: '[[WP:Vandalism|Phá hoại]] thường xuyên'
	},
	'pp-usertalk': {
		edit: 'sysop',
		move: 'sysop',
		expiry: 'infinity',
		reason: '[[Wikipedia:Quy định khóa trang|Sử dụng trang thảo luận của người dùng không phù hợp khi bị khóa]]'
	},
	'pp-template': {
		edit: 'templateeditor',
		move: 'templateeditor',
		expiry: 'infinity',
		reason: '[[WP:High-risk templates|Bản mẫu nhiều người xem]]'
	},
	'pp-30-500-arb': {
		edit: 'extendedconfirmed',
		move: 'extendedconfirmed',
		expiry: 'infinity',
		reason: '[[WP:30/500|Tuân theo Ủy ban Trọng tài]]',
		template: 'pp-30-500'
	},
	'pp-30-500-vandalism': {
		edit: 'extendedconfirmed',
		move: 'extendedconfirmed',
		reason: '[[WP:Vandalism|Phá hoại]] thường xuyên từ tài khoản được xác nhận (tự động)',
		template: 'pp-30-500'
	},
	'pp-30-500-disruptive': {
		edit: 'extendedconfirmed',
		move: 'extendedconfirmed',
		reason: '[[WP:Disruptive editing|Sửa đổi phá hoại]] thường xuyên từ tài khoản được xác nhận (tự động)',
		template: 'pp-30-500'
	},
	'pp-30-500-blp': {
		edit: 'extendedconfirmed',
		move: 'extendedconfirmed',
		reason: 'Liên tục vi phạm [[Wikipedia:Tiểu sử người đang sống]] từ tài khoản được xác nhận (tự động)',
		template: 'pp-30-500'
	},
	'pp-30-500-sock': {
		edit: 'extendedconfirmed',
		move: 'extendedconfirmed',
		reason: '[[Wikipedia:Tài khoản con rối]] thường xuyên',
		template: 'pp-30-500'
	},
	'pp-semi-vandalism': {
		edit: 'autoconfirmed',
		reason: '[[WP:Vandalism|Phá hoại]] thường xuyên',
		template: 'pp-vandalism'
	},
	'pp-semi-disruptive': {
		edit: 'autoconfirmed',
		reason: '[[WP:Disruptive editing|Sửa đổi phá hoại]] thường xuyên',
		template: 'pp-protected'
	},
	'pp-semi-unsourced': {
		edit: 'autoconfirmed',
		reason: 'Thêm thường xuyên [[WP:INTREF|nội dung không nguồn hoặc nguồn yếu]]',
		template: 'pp-protected'
	},
	'pp-semi-blp': {
		edit: 'autoconfirmed',
		reason: 'Vi phạm chính sách [[Wikipedia:Tiểu sử người đang sống]]',
		template: 'pp-blp'
	},
	'pp-semi-usertalk': {
		edit: 'autoconfirmed',
		move: 'autoconfirmed',
		expiry: 'infinity',
		reason: '[[Wikipedia:Quy định khóa trang|Sử dụng trang thảo luận của người dùng không phù hợp khi bị cấm]]',
		template: 'pp-usertalk'
	},
	'pp-semi-template': {  // removed for now
		edit: 'autoconfirmed',
		move: 'autoconfirmed',
		expiry: 'infinity',
		reason: '[[WP:High-risk templates|Bản mẫu nhiều người xem]]',
		template: 'pp-template'
	},
	'pp-semi-sock': {
		edit: 'autoconfirmed',
		reason: '[[Wikipedia:Tài khoản con rối]] thường xuyên',
		template: 'pp-sock'
	},
	'pp-semi-protected': {
		edit: 'autoconfirmed',
		reason: null,
		template: 'pp-protected'
	},
	'pp-pc-vandalism': {
		stabilize: 'autoconfirmed',  // stabilize = Pending Changes
		reason: '[[WP:Vandalism|Phá hoại]] thường xuyên',
		template: 'pp-pc'
	},
	'pp-pc-disruptive': {
		stabilize: 'autoconfirmed',
		reason: '[[WP:Disruptive editing|Sửa đổi phá hoại]] thường xuyên',
		template: 'pp-pc'
	},
	'pp-pc-unsourced': {
		stabilize: 'autoconfirmed',
		reason: 'Thêm thường xuyên [[WP:INTREF|nội dung không nguồn hoặc nguồn yếu]]',
		template: 'pp-pc'
	},
	'pp-pc-blp': {
		stabilize: 'autoconfirmed',
		reason: 'Vi phạm chính sách [[Wikipedia:Tiểu sử người đang sống]]',
		template: 'pp-pc'
	},
	'pp-pc-protected': {
		stabilize: 'autoconfirmed',
		reason: null,
		template: 'pp-pc'
	},
	'pp-move': {
		move: 'sysop',
		reason: null
	},
	'pp-move-dispute': {
		move: 'sysop',
		reason: '[[WP:MOVP|Tranh chấp di chuyển trang]]'
	},
	'pp-move-vandalism': {
		move: 'sysop',
		reason: '[[WP:MOVP|Phá hoại di chuyển trang]'
	},
	'pp-move-indef': {
		move: 'sysop',
		expiry: 'infinity',
		reason: '[[WP:MOVP|Trang nhiều người xem]]'
	},
	'unprotect': {
		edit: 'all',
		move: 'all',
		stabilize: 'none',
		create: 'all',
		reason: null,
		template: 'none'
	},
	'pp-create-offensive': {
		create: 'sysop',
		reason: '[[WP:SALT|Tên xúc phạm]]'
	},
	'pp-create-salt': {
		create: 'extendedconfirmed',
		reason: '[[WP:SALT|Tạo lại nội dung nhiều lần]]'
	},
	'pp-create-blp': {
		create: 'extendedconfirmed',
		reason: '[[WP:BLPDEL|Tiểu sử người còn sống (BLP) đã xóa gần đây]]'
	},
	'pp-create': {
		create: 'extendedconfirmed',
		reason: '{{pp-create}}'
	}
};

Twinkle.protect.protectionTags = [
	{
		label: 'Không có bản mẫu (xóa các bản mẫu khóa hiện có)',
		value: 'none'
	},
	{
		label: 'Không có bản mẫu (không xóa các bản mẫu khóa hiện có)',
		value: 'noop'
	},
	{
		label: 'Chỉnh sửa các bản mẫu khóa',
		list: [
			{ label: '{{pp-vandalism}}: phá hoại', value: 'pp-vandalism' },
			{ label: '{{pp-dispute}}: tranh cãi/bút chiến', value: 'pp-dispute' },
			{ label: '{{pp-blp}}: Vi phạm BLP', value: 'pp-blp' },
			{ label: '{{pp-sock}}: con rối', value: 'pp-sock' },
			{ label: '{{pp-template}}: bản mẫu có nguy cơ cao bị phá hoại', value: 'pp-template' },
			{ label: '{{pp-usertalk}}: Thảo luận người dùng bị cấm', value: 'pp-usertalk' },
			{ label: '{{pp-protected}}: khóa chung', value: 'pp-protected' },
			{ label: '{{pp-semi-indef}}: bán khóa chung dài hạn', value: 'pp-semi-indef' },
			{ label: '{{pp-30-500}}: khóa xác nhận mở rộng', value: 'pp-30-500' }
		]
	},
	{
		label: 'Các bản mẫu thay đổi đang chờ xử lý',
		list: [
			{ label: '{{pp-pc}}: các thay đổi đang chờ xử lý', value: 'pp-pc' }
		]
	},
	{
		label: 'Các bản mẫu khóa di chuyển trang',
		list: [
			{ label: '{{pp-move-dispute}}: tranh chấp di chuyển trang', value: 'pp-move-dispute' },
			{ label: '{{pp-move-vandalism}}: phá hoại di chuyển trang', value: 'pp-move-vandalism' },
			{ label: '{{pp-move-indef}}: dài hạn chung', value: 'pp-move-indef' },
			{ label: '{{pp-move}}: khác', value: 'pp-move' }
		]
	}
].filter(function(type) {
	// Filter FlaggedRevs
	return hasFlaggedRevs || type.label !== 'Các bản mẫu thay đổi đang chờ xử lý';
});

Twinkle.protect.callback.changePreset = function twinkleprotectCallbackChangePreset(e) {
	var form = e.target.form;

	var actiontypes = form.actiontype;
	var actiontype;
	for (var i = 0; i < actiontypes.length; i++) {
		if (!actiontypes[i].checked) {
			continue;
		}
		actiontype = actiontypes[i].values;
		break;
	}

	if (actiontype === 'protect') {  // actually protecting the page
		var item = Twinkle.protect.protectionPresetsInfo[form.category.value];

		if (mw.config.get('wgArticleId')) {
			if (item.edit) {
				form.editmodify.checked = true;
				Twinkle.protect.formevents.editmodify({ target: form.editmodify });
				form.editlevel.value = item.edit;
				Twinkle.protect.formevents.editlevel({ target: form.editlevel });
			} else {
				form.editmodify.checked = false;
				Twinkle.protect.formevents.editmodify({ target: form.editmodify });
			}

			if (item.move) {
				form.movemodify.checked = true;
				Twinkle.protect.formevents.movemodify({ target: form.movemodify });
				form.movelevel.value = item.move;
				Twinkle.protect.formevents.movelevel({ target: form.movelevel });
			} else {
				form.movemodify.checked = false;
				Twinkle.protect.formevents.movemodify({ target: form.movemodify });
			}

			form.editexpiry.value = form.moveexpiry.value = item.expiry || '2 days';


			if (form.pcmodify) {
				if (item.stabilize) {
					form.pcmodify.checked = true;
					Twinkle.protect.formevents.pcmodify({ target: form.pcmodify });
					form.pclevel.value = item.stabilize;
					Twinkle.protect.formevents.pclevel({ target: form.pclevel });
				} else {
					form.pcmodify.checked = false;
					Twinkle.protect.formevents.pcmodify({ target: form.pcmodify });
				}
				form.pcexpiry.value = item.expiry || '1 month';
			}
		} else {
			if (item.create) {
				form.createlevel.value = item.create;
				Twinkle.protect.formevents.createlevel({ target: form.createlevel });
			}
			form.createexpiry.value = item.expiry || 'infinity';
		}

		var reasonField = actiontype === 'protect' ? form.protectReason : form.reason;
		if (item.reason) {
			reasonField.value = item.reason;
		} else {
			reasonField.value = '';
		}

		// sort out tagging options, disabled if nonexistent or lua
		if (mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') !== 'Scribunto') {
			if (form.category.value === 'unprotect') {
				form.tagtype.value = 'none';
			} else {
				form.tagtype.value = item.template ? item.template : form.category.value;
			}
			Twinkle.protect.formevents.tagtype({ target: form.tagtype });

			// We only have one TE template at the moment, so this
			// should be expanded if more are added (e.g. pp-semi-template)
			if (form.category.value === 'pp-template') {
				form.noinclude.checked = true;
			} else if (mw.config.get('wgNamespaceNumber') !== 10) {
				form.noinclude.checked = false;
			}
		}

	} else {  // RPP request
		if (form.category.value === 'unprotect') {
			form.expiry.value = '';
			form.expiry.disabled = true;
		} else {
			form.expiry.value = '';
			form.expiry.disabled = false;
		}
	}
};

Twinkle.protect.callback.evaluate = function twinkleprotectCallbackEvaluate(e) {
	var form = e.target;
	var input = Morebits.quickForm.getInputData(form);

	var tagparams;
	if (input.actiontype === 'tag' || (input.actiontype === 'protect' && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') !== 'Scribunto')) {
		tagparams = {
			tag: input.tagtype,
			reason: (input.tagtype === 'pp-protected' || input.tagtype === 'pp-semi-protected' || input.tagtype === 'pp-move') && input.protectReason,
			small: input.small,
			noinclude: input.noinclude
		};
	}

	switch (input.actiontype) {
		case 'protect':
			// protect the page
			Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
			Morebits.wiki.actionCompleted.notice = 'Khóa thành công';

			var statusInited = false;
			var thispage;

			var allDone = function twinkleprotectCallbackAllDone() {
				if (thispage) {
					thispage.getStatusElement().info('xong');
				}
				if (tagparams) {
					Twinkle.protect.callbacks.taggingPageInitial(tagparams);
				}
			};

			var protectIt = function twinkleprotectCallbackProtectIt(next) {
				thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Khóa trang');
				if (mw.config.get('wgArticleId')) {
					if (input.editmodify) {
						thispage.setEditProtection(input.editlevel, input.editexpiry);
					}
					if (input.movemodify) {
						// Ensure a level has actually been chosen
						if (input.movelevel) {
							thispage.setMoveProtection(input.movelevel, input.moveexpiry);
						} else {
							alert('Bạn phải chọn một mức khóa di chuyển!');
							return;
						}
					}
				} else {
					thispage.setCreateProtection(input.createlevel, input.createexpiry);
					thispage.setWatchlist(false);
				}

				if (input.protectReason) {
					thispage.setEditSummary(input.protectReason);
				} else {
					alert('Bạn phải nhập lý do khóa, lý do này sẽ được ghi vào nhật trình khóa.');
					return;
				}

				if (!statusInited) {
					Morebits.simpleWindow.setButtonsEnabled(false);
					Morebits.status.init(form);
					statusInited = true;
				}

				thispage.setChangeTags(Twinkle.changeTags);
				thispage.protect(next);
			};

			var stabilizeIt = function twinkleprotectCallbackStabilizeIt() {
				if (thispage) {
					thispage.getStatusElement().info('xong');
				}

				thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Áp dụng biện pháp khóa thay đổi đang chờ xử lý');
				thispage.setFlaggedRevs(input.pclevel, input.pcexpiry);

				if (input.protectReason) {
					thispage.setEditSummary(input.protectReason + Twinkle.summaryAd); // flaggedrevs tag support: [[phab:T247721]]
				} else {
					alert('Bạn phải nhập lý do khóa, lý do này sẽ được ghi vào nhật trình khóa.');
					return;
				}

				if (!statusInited) {
					Morebits.simpleWindow.setButtonsEnabled(false);
					Morebits.status.init(form);
					statusInited = true;
				}

				thispage.stabilize(allDone, function(error) {
					if (error.errorCode === 'stabilize_denied') { // [[phab:T234743]]
						thispage.getStatusElement().error('Không thể sửa đổi cài đặt thay đổi đang chờ xử lý, có thể do lỗi mediawiki. Các tác vụ khác (gắn thẻ hoặc khóa thường xuyên) có thể đã diễn ra. Hãy tải lại trang và thử lại.');
					}
				});
			};

			if (input.editmodify || input.movemodify || !mw.config.get('wgArticleId')) {
				if (input.pcmodify) {
					protectIt(stabilizeIt);
				} else {
					protectIt(allDone);
				}
			} else if (input.pcmodify) {
				stabilizeIt();
			} else {
				alert("Làm ơn cho Twinkle làm gì đó! \nNếu bạn chỉ muốn gắn thẻ trang, bạn có thể chọn các tùy chọn 'Gắn thẻ trang với bản mẫu bảo vệ' ở trên cùng.");
			}

			break;

		case 'tag':
			// apply a protection template

			Morebits.simpleWindow.setButtonsEnabled(false);
			Morebits.status.init(form);

			Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
			Morebits.wiki.actionCompleted.followRedirect = false;
			Morebits.wiki.actionCompleted.notice = 'Gắn thẻ hoàn thành';

			Twinkle.protect.callbacks.taggingPageInitial(tagparams);
			break;

		case 'request':
			// file request at RFPP
			var typename, typereason;
			switch (input.category) {
				case 'pp-dispute':
				case 'pp-vandalism':
				case 'pp-usertalk':
				case 'pp-protected':
					typename = 'khóa hoàn toàn';
					break;
				case 'pp-template':
					typename = 'khóa bản mẫu';
					break;
				case 'pp-30-500-arb':
				case 'pp-30-500-vandalism':
				case 'pp-30-500-disruptive':
				case 'pp-30-500-blp':
				case 'pp-30-500-sock':
					typename = 'khóa xác nhận mở rộng';
					break;
				case 'pp-semi-vandalism':
				case 'pp-semi-disruptive':
				case 'pp-semi-unsourced':
				case 'pp-semi-usertalk':
				case 'pp-semi-sock':
				case 'pp-semi-blp':
				case 'pp-semi-protected':
					typename = 'bán khóa';
					break;
				case 'pp-pc-vandalism':
				case 'pp-pc-blp':
				case 'pp-pc-protected':
				case 'pp-pc-unsourced':
				case 'pp-pc-disruptive':
					typename = 'các thay đổi đang chờ xử lý';
					break;
				case 'pp-move':
				case 'pp-move-dispute':
				case 'pp-move-indef':
				case 'pp-move-vandalism':
					typename = 'khóa di chuyển trang';
					break;
				case 'pp-create':
				case 'pp-create-offensive':
				case 'pp-create-blp':
				case 'pp-create-salt':
					typename = 'khóa khởi tạo';
					break;
				case 'unprotect':
					var admins = $.map(Twinkle.protect.currentProtectionLevels, function(pl) {
						if (!pl.admin || Twinkle.protect.trustedBots.indexOf(pl.admin) !== -1) {
							return null;
						}
						return 'User:' + pl.admin;
					});
					if (admins.length && !confirm('Bạn đã cố gắng liên hệ với quản trị viên bảo vệ chưa (' + Morebits.array.uniq(admins).join(', ') + ') first?')) {
						return false;
					}
					// otherwise falls through
				default:
					typename = 'không khóa';
					break;
			}
			switch (input.category) {
				case 'pp-dispute':
					typereason = 'Tranh chấp nội dung / bút chiến';
					break;
				case 'pp-vandalism':
				case 'pp-semi-vandalism':
				case 'pp-pc-vandalism':
				case 'pp-30-500-vandalism':
					typereason = '[[WP:VAND|Phá hoại]] thường xuyên';
					break;
				case 'pp-semi-disruptive':
				case 'pp-pc-disruptive':
				case 'pp-30-500-disruptive':
					typereason = '[[Wikipedia:Sửa đổi gây hại]] thường xuyên';
					break;
				case 'pp-semi-unsourced':
				case 'pp-pc-unsourced':
					typereason = 'Thêm thường xuyên [[WP:INTREF|nội dung không nguồn hoặc nguồn yếu]]';
					break;
				case 'pp-template':
					typereason = '[[WP:HIGHRISK|Bản mẫu có nguy cơ bị phá hoại cao]]';
					break;
				case 'pp-30-500-arb':
					typereason = '[[WP:30/500|Tuân theo Ủy ban Trọng tài]]';
					break;
				case 'pp-usertalk':
				case 'pp-semi-usertalk':
					typereason = 'Sử dụng trang thảo luận của người dùng không phù hợp khi bị cấm';
					break;
				case 'pp-semi-sock':
				case 'pp-30-500-sock':
					typereason = '[[Wikipedia:Tài khoản con rối]] thường xuyên';
					break;
				case 'pp-semi-blp':
				case 'pp-pc-blp':
				case 'pp-30-500-blp':
					typereason = 'Vi phạm chính sách [[Wikipedia:Tiểu sử người đang sống]]';
					break;
				case 'pp-move-dispute':
					typereason = 'Tranh chấp tên trang / di chuyển trang';
					break;
				case 'pp-move-vandalism':
					typereason = 'Phá hoại di chuyển trang';
					break;
				case 'pp-move-indef':
					typereason = 'Trang có nhiều người xem';
					break;
				case 'pp-create-offensive':
					typereason = 'Tên xúc phạm';
					break;
				case 'pp-create-blp':
					typereason = '[[Wikipedia:Tiểu sử người đang sống]] đã xóa gần đây';
					break;
				case 'pp-create-salt':
					typereason = 'Tạo lại nội dung nhiều lần';
					break;
				default:
					typereason = '';
					break;
			}

			var reason = typereason;
			if (input.reason !== '') {
				if (typereason !== '') {
					reason += '\u00A0\u2013 ';  // U+00A0 NO-BREAK SPACE; U+2013 EN RULE
				}
				reason += input.reason;
			}
			if (reason !== '' && reason.charAt(reason.length - 1) !== '.') {
				reason += '.';
			}

			var rppparams = {
				reason: reason,
				typename: typename,
				category: input.category,
				expiry: input.expiry
			};

			Morebits.simpleWindow.setButtonsEnabled(false);
			Morebits.status.init(form);

			var rppName = 'Wikipedia:Yêu cầu khóa hay mở khóa trang';

			// Updating data for the action completed event
			Morebits.wiki.actionCompleted.redirect = rppName;
			Morebits.wiki.actionCompleted.notice = 'Tác vụ đã hoàn thành, hiện đang chuyển hướng đến trang thảo luận';

			var rppPage = new Morebits.wiki.page(rppName, 'Yêu cầu khóa trang');
			rppPage.setFollowRedirect(true);
			rppPage.setCallbackParameters(rppparams);
			rppPage.load(Twinkle.protect.callbacks.fileRequest);
			break;
		default:
			alert('twinkleprotect: unknown kind of action');
			break;
	}
};

Twinkle.protect.callbacks = {
	taggingPageInitial: function(tagparams) {
		if (tagparams.tag === 'noop') {
			Morebits.status.info('Áp dụng bản mẫu khóa', 'nothing to do');
			return;
		}

		var protectedPage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Đang gắn thẻ trang');
		protectedPage.setCallbackParameters(tagparams);
		protectedPage.load(Twinkle.protect.callbacks.taggingPage);
	},
	taggingPage: function(protectedPage) {
		var params = protectedPage.getCallbackParameters();
		var text = protectedPage.getPageText();
		var tag, summary;

		var oldtag_re = /\s*(?:<noinclude>)?\s*\{\{\s*(pp-[^{}]*?|protected|(?:t|v|s|p-|usertalk-v|usertalk-s|sb|move)protected(?:2)?|protected template|privacy protection)\s*?\}\}\s*(?:<\/noinclude>)?\s*/gi;
		var re_result = oldtag_re.exec(text);
		if (re_result) {
			if (params.tag === 'none' || confirm('{{' + re_result[1] + '}} đã được tìm thấy trên trang. \nNhấp vào OK để xóa hoặc nhấp vào Hủy để lại đó.')) {
				text = text.replace(oldtag_re, '');
			}
		}

		if (params.tag === 'none') {
			summary = 'Removing protection template';
		} else {
			tag = params.tag;
			if (params.reason) {
				tag += '|reason=' + params.reason;
			}
			if (params.small) {
				tag += '|small=yes';
			}

			if (/^\s*#redirect/i.test(text)) { // redirect page
				// Only tag if no {{rcat shell}} is found
				if (!text.match(/{{(?:redr|this is a redirect|r(?:edirect)?(?:.?cat.*)?[ _]?sh)/i)) {
					text = text.replace(/#REDIRECT ?(\[\[.*?\]\])(.*)/i, '#REDIRECT $1$2\n\n{{' + tag + '}}');
				} else {
					Morebits.status.info('Chuyển hướng lớp vỏ thể loại hiện tại', 'nothing to do');
					return;
				}
			} else {
				if (params.noinclude) {
					tag = '<noinclude>{{' + tag + '}}</noinclude>';
				} else {
					tag = '{{' + tag + '}}\n';
				}

				// Insert tag after short description or any hatnotes
				var wikipage = new Morebits.wikitext.page(text);
				text = wikipage.insertAfterTemplates(tag, Twinkle.hatnoteRegex).getText();
			}
			summary = 'Đang thêm {{' + params.tag + '}}';
		}

		protectedPage.setEditSummary(summary);
		protectedPage.setChangeTags(Twinkle.changeTags);
		protectedPage.setPageText(text);
		protectedPage.setCreateOption('nocreate');
		protectedPage.suppressProtectWarning(); // no need to let admins know they are editing through protection
		protectedPage.save();
	},

	fileRequest: function(rppPage) {

		var params = rppPage.getCallbackParameters();
		var text = rppPage.getPageText();
		var statusElement = rppPage.getStatusElement();

		var rppRe = new RegExp('===\\s*(\\[\\[)?\\s*:?\\s*' + Morebits.string.escapeRegExp(Morebits.pageNameNorm) + '\\s*(\\]\\])?\\s*===', 'm');
		var tag = rppRe.exec(text);

		var rppLink = document.createElement('a');
		rppLink.setAttribute('href', mw.util.getUrl(rppPage.getPageName()));
		rppLink.appendChild(document.createTextNode(rppPage.getPageName()));

		if (tag) {
			statusElement.error([ 'Đã có yêu cầu khóa cho trang này tại ', rppLink, ', bị hủy bỏ.' ]);
			return;
		}

		var newtag = '=== [[:' + Morebits.pageNameNorm + ']] ===\n';
		if (new RegExp('^' + mw.util.escapeRegExp(newtag).replace(/\s+/g, '\\s*'), 'm').test(text)) {
			statusElement.error([ 'Đã có yêu cầu khóa cho trang này tại ', rppLink, ', bị hủy bỏ.' ]);
			return;
		}
		newtag += '* {{pagelinks|1=' + Morebits.pageNameNorm + '}}\n* {{Tình trạng khóa|1=' + Morebits.pageNameNorm + '}}\n\n';

		var words;
		switch (params.expiry) {
			case 'temporary':
				words = 'Tạm thời ';
				break;
			case 'infinity':
				words = 'Vô hạn ';
				break;
			default:
				words = '';
				break;
		}

		words += params.typename;

		newtag += "'''" + Morebits.string.toUpperCaseFirstChar(words) + (params.reason !== '' ? ":''' " +
			Morebits.string.formatReasonText(params.reason) : ".'''") + ' ~~~~';

		// If either protection type results in a increased status, then post it under increase
		// else we post it under decrease
		var increase = false;
		var protInfo = Twinkle.protect.protectionPresetsInfo[params.category];

		// function to compute protection weights (see comment at Twinkle.protect.protectionWeight)
		var computeWeight = function(mainLevel, stabilizeLevel) {
			var result = Twinkle.protect.protectionWeight[mainLevel || 'all'];
			if (stabilizeLevel) {
				if (result) {
					if (stabilizeLevel.level === 'autoconfirmed') {
						result += 2;
					}
				} else {
					result = Twinkle.protect.protectionWeight['flaggedrevs_' + stabilizeLevel];
				}
			}
			return result;
		};

		// compare the page's current protection weights with the protection we are requesting
		var editWeight = computeWeight(Twinkle.protect.currentProtectionLevels.edit &&
			Twinkle.protect.currentProtectionLevels.edit.level,
		Twinkle.protect.currentProtectionLevels.stabilize &&
			Twinkle.protect.currentProtectionLevels.stabilize.level);
		if (computeWeight(protInfo.edit, protInfo.stabilize) > editWeight ||
			computeWeight(protInfo.move) > computeWeight(Twinkle.protect.currentProtectionLevels.move &&
			Twinkle.protect.currentProtectionLevels.move.level) ||
			computeWeight(protInfo.create) > computeWeight(Twinkle.protect.currentProtectionLevels.create &&
			Twinkle.protect.currentProtectionLevels.create.level)) {
			increase = true;
		}

		var reg;
		if (increase) {
			reg = /(\n==\s*Các yêu cầu đã giải quyết\s*==)/;
		} else {
			reg = /(\n==\s*Các yêu cầu đã giải quyết\s*==)/;
		}

		var originalTextLength = text.length;
		// text = text.replace(reg, '\n' + newtag + '\n$1'); // Tạm không dùng (17/12/2021)
		text += '\n\n' + newtag; // Thêm yêu cầu khóa trang vào cuối trang (17/12/2021)
		if (text.length === originalTextLength) {
			var linknode = document.createElement('a');
			linknode.setAttribute('href', mw.util.getUrl('Wikipedia:Twinkle/Sửa RPP'));
			linknode.appendChild(document.createTextNode('Cách sửa RPP'));
			statusElement.error([ 'Không thể tìm thấy tiêu đề có liên quan tại WP:RPP. Để khắc phục sự cố này, vui lòng xem ', linknode, '.' ]);
			return;
		}
		statusElement.status('Thêm yêu cầu mới...');
		rppPage.setEditSummary('/* ' + Morebits.pageNameNorm + ' */ Đang yêu cầu ' + params.typename + (params.typename === 'pending changes' ? ' [[:' : ' [[:') +
			Morebits.pageNameNorm + ']].');
		rppPage.setChangeTags(Twinkle.changeTags);
		rppPage.setPageText(text);
		rppPage.setCreateOption('recreate');
		rppPage.save();
	}
};

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


// </nowiki>