#!/usr/bin/perl -w
#------------------------------------------------------------------------------
# User create CGI
#------------------------------------------------------------------------------

use strict;
use utf8;
use Encode qw(encode);
use CGI;
use HTML::Template;
use LWP::UserAgent;
use HTTP::Request;
use URI::Escape qw(uri_escape_utf8);
use TF::CommonUtils;
use TF::UserPermissions;
use TF::UserExpiration;

binmode(STDOUT, ":utf8");

my $BRAND         = $ENV{'TF_BRANDNAME'} || '';
my $LOCATION      = $ENV{'TF_LOCATION'} || '';
my $USERTYPE      = $ENV{'TF_USERTYPE'} || 'normal';
my $REMOTE_USER   = $ENV{'REMOTE_USER'} || '';
my $PASSWD        = $ENV{'TF_USERPLAINPASSWORD'} || '';
my $TF_TOKEN      = $ENV{'TF_TOKEN'} || '';
my $LANGPARAM     = TF::CommonUtils::getLangPriority();
my $TEMPLATE_FILE = './tmpl/usercreate.tmpl.' . $LANGPARAM;
my $BACKURI       = $LOCATION . '/.cgi-bin/user';
my ($SCHEMA, $SERVER_PORT, $URI_PREFIX) = TF::CommonUtils::getCgiConnectInfo();
my $USER_MGMT_URL = $URI_PREFIX . $LOCATION . '/.management/USER/';

my $ERRORTABLE_ja = {
	CGIError => 'CGIエラーです',
	invalidToken => '画面を再読み込みしてからやり直してください',
	required => '未入力の項目があります',
	passwordMismatch => 'パスワード確認が一致しません',
	invalidUserType => '選択できないユーザ種別です',
	invalidUserId => 'ユーザIDの形式が不正です',
	invalidMail => 'メールアドレスの形式が不正です',
	invalidQuota => '利用上限の入力値が不正です',
	invalidExpiration => '有効期限の日時を正しく入力してください',
	invalidPermissions => 'ユーザ種別に対するアクセス権限の設定が不正です',
	createFailed => 'ユーザ作成に失敗しました',
	alreadyExists => '同じユーザIDが既に存在します',
	forbidden => 'このユーザは作成できません',
};
my $ERRORTABLE_en = {
	CGIError => 'CGI error found.',
	invalidToken => 'Reload the page and try again.',
	required => 'Some required fields are missing.',
	passwordMismatch => 'Password confirmation does not match.',
	invalidUserType => 'The selected user type is not allowed.',
	invalidUserId => 'The user ID format is invalid.',
	invalidMail => 'The mail address format is invalid.',
	invalidQuota => 'The quota value is invalid.',
	invalidExpiration => 'Enter a valid expiration date and time.',
	invalidPermissions => 'The permissions are not valid for this user type.',
	createFailed => 'Failed to create the user.',
	alreadyExists => 'The same user ID already exists.',
	forbidden => 'This user type cannot be created.',
};
my $ERRORTABLE = ($LANGPARAM eq 'ja') ? $ERRORTABLE_ja : $ERRORTABLE_en;

if (!$LOCATION || !$USERTYPE || ($USERTYPE ne 'admin' && $USERTYPE ne 'groupleader')) {
	TF::CommonUtils::showAccessDeny();
	die '';
}

main();

sub main {
	my $cgi = CGI->new;
	if (!$cgi) {
		TF::CommonUtils::showErrorPage($ERRORTABLE->{CGIError}, $LANGPARAM);
		return;
	}
	if ($cgi->cgi_error) {
		TF::CommonUtils::showErrorPage($ERRORTABLE->{CGIError} . '(' . $cgi->cgi_error . ')', $LANGPARAM);
		return;
	}

	my %values = default_values();
	my $status = '';

	if (($cgi->request_method || 'GET') eq 'POST' && (($cgi->param('hidCmd') || '') eq 'set')) {
		%values = submitted_values($cgi);
		my $err = validate_csrf($cgi);
		if ($err) {
			$status = $err;
		}
		else {
			$err = validate_values(\%values);
			if ($err) {
				$status = $err;
			}
			else {
				my ($ok, $errmsg) = request_create_user(\%values, get_session_id());
				if ($ok) {
					TF::CommonUtils::redirectPage($BACKURI, $LANGPARAM);
					return;
				}
				$status = $errmsg;
			}
		}
		$values{password} = '';
		$values{password_confirm} = '';
	}

	showResponse($cgi, \%values, $status);
}

sub showResponse {
	my ($cgi, $values_ref, $status) = @_;
	my %values = %{$values_ref || {}};

	my @type_rows = ();
	push @type_rows, radio_row('normal', localize('typeNormal'), ($values{usertype} || '') eq 'normal');
	if ($USERTYPE eq 'admin') {
		push @type_rows, radio_row('groupleader', localize('typeGroupLeader'), ($values{usertype} || '') eq 'groupleader');
		push @type_rows, radio_row('admin', localize('typeAdmin'), ($values{usertype} || '') eq 'admin');
	}
	push @type_rows, radio_row('limited', localize('typeLimited'), ($values{usertype} || '') eq 'limited');

	if (open(my $tmplfh, "<", $TEMPLATE_FILE)) {
		binmode($tmplfh, ":utf8");
		my $htmltmpl = HTML::Template->new(
			filehandle => $tmplfh,
			case_sensitive => 1,
			die_on_bad_params => 0,
		);
		$htmltmpl->param(
			TF::UserPermissions::template_values(\%values, 0, ($ENV{TF_GROUPCONSTRAINTS} || '') eq 'on'),
			TF::UserExpiration::template_values(\%values, 0),
			brandname => $BRAND,
			title => localize('title'),
			subtitle => localize('subtitle'),
			backUri => $BACKURI,
			backLabel => localize('backLabel'),
			saveLabel => localize('saveLabel'),
			saveHelp => localize('saveHelp'),
			modeLabel => localize('modeLabel'),
			simpleModeLabel => localize('simpleModeLabel'),
			detailModeLabel => localize('detailModeLabel'),
			clientPasswordMismatch => localize('clientPasswordMismatch'),
			clientPasswordRequired => localize('clientPasswordRequired'),
			status => $status,
			hasStatus => $status ? 1 : 0,
			token => $TF_TOKEN,
			mode => esc($values{mode}),
			isSimpleMode => $values{mode} eq 'simple' ? 1 : 0,
			isDetailMode => $values{mode} eq 'detail' ? 1 : 0,
			sectionState => localize('sectionState'),
			sectionBasic => localize('sectionBasic'),
			sectionQuota => localize('sectionQuota'),
			userIdLabel => localize('userIdLabel'),
			userIdDesc => localize('userIdDesc'),
			userIdValue => esc($values{userid}),
			nameLabel => localize('nameLabel'),
			nameDesc => localize('nameDesc'),
			nameValue => esc($values{fullname}),
			mailLabel => localize('mailLabel'),
			mailDesc => localize('mailDesc'),
			mailValue => esc($values{mailaddr}),
			mailPlaceholder => localize('mailPlaceholder'),
			removeAddressLabel => localize('removeAddressLabel'),
			mailWarningTitle => localize('mailWarningTitle'),
			mailWarningBody => localize('mailWarningBody'),
			mailWarningConfirm => localize('mailWarningConfirm'),
			mailWarningCancel => localize('mailWarningCancel'),
			passwordLabel => localize('passwordLabel'),
			passwordDesc => localize('passwordDesc'),
			passwordValue => '',
			passwordToggleLabel => localize('passwordToggleLabel'),
			passwordHideLabel => localize('passwordHideLabel'),
			passwordConfirmLabel => localize('passwordConfirmLabel'),
			passwordConfirmDesc => localize('passwordConfirmDesc'),
			passwordConfirmValue => '',
			commentLabel => localize('commentLabel'),
			commentDesc => localize('commentDesc'),
			commentValue => esc($values{comment}),
			maxStorageLabel => localize('maxStorageLabel'),
			maxStorageDesc => localize('maxStorageDesc'),
			maxStorageValue => esc($values{maxstorage_display}),
			unlimitedStorageLabel => localize('unlimitedStorageLabel'),
			maxStorageUnlimitedChecked => $values{maxstorage_unlimited} ? 1 : 0,
			maxResourceLabel => localize('maxResourceLabel'),
			maxResourceDesc => localize('maxResourceDesc'),
			maxResourceValue => esc($values{maxresource_display}),
			unlimitedResourceLabel => localize('unlimitedResourceLabel'),
			maxResourceUnlimitedChecked => $values{maxresource_unlimited} ? 1 : 0,
			activeLabel => localize('activeLabel'),
			activeDesc => localize('activeDesc'),
			activeChecked => $values{active} eq 'on' ? 1 : 0,
			expirationLabel => localize('expirationLabel'),
			expirationDesc => localize('expirationDesc'),
			expirationUnlimitedLabel => localize('expirationUnlimitedLabel'),
			toggleOnLabel => localize('toggleOnLabel'),
			toggleOffLabel => localize('toggleOffLabel'),
			typeLabel => localize('typeLabel'),
			typeDesc => localize('typeDesc'),
			simpleTypeValue => localize('typeNormal'),
			typeRows => \@type_rows,
		);
		print $cgi->header(-charset => 'utf-8');
		print $htmltmpl->output;
		close($tmplfh);
	}
	else {
		TF::CommonUtils::showTemplateOpenErr($LANGPARAM);
	}
}

sub submitted_values {
	my $cgi = shift;
	my %values = default_values();

	$values{mode} = (($cgi->param('mode') || '') eq 'detail') ? 'detail' : 'simple';
	$values{active} = (($cgi->param('active') || '') eq 'on') ? 'on' : 'off';
	$values{usertype} = trim(TF::CommonUtils::decodeUTF8($cgi->param('usertype') || $values{usertype}));
	$values{userid} = trim(TF::CommonUtils::decodeUTF8($cgi->param('userid') || ''));
	$values{fullname} = trim(TF::CommonUtils::decodeUTF8($cgi->param('fullname') || ''));
	$values{mailaddr} = normalize_mail_list(TF::CommonUtils::decodeUTF8($cgi->param('mailaddr') || ''));
	$values{password} = TF::CommonUtils::decodeUTF8($cgi->param('new_user_password') || '');
	$values{password_confirm} = TF::CommonUtils::decodeUTF8($cgi->param('new_user_password_confirm') || '');
	$values{comment} = TF::CommonUtils::decodeUTF8($cgi->param('comment') || '');

	my $raw_maxstorage = trim(TF::CommonUtils::decodeUTF8($cgi->param('maxstorage') || '0'));
	my $raw_maxresource = trim(TF::CommonUtils::decodeUTF8($cgi->param('maxresource') || '0'));

	$values{maxstorage_unlimited} = (($cgi->param('maxstorage_unlimited') || '') eq 'on') ? 1 : 0;
	$values{maxresource_unlimited} = (($cgi->param('maxresource_unlimited') || '') eq 'on') ? 1 : 0;

	if ($values{maxstorage_unlimited}) {
		$values{maxstorage_display} = '0';
		$values{maxstorage} = '0';
	}
	else {
		$values{maxstorage_display} = $raw_maxstorage;
		$values{maxstorage} = expand_scaled_number($raw_maxstorage);
	}

	if ($values{maxresource_unlimited}) {
		$values{maxresource_display} = '0';
		$values{maxresource} = '0';
	}
	else {
		$values{maxresource_display} = $raw_maxresource;
		$values{maxresource} = expand_scaled_number($raw_maxresource);
	}

	if ($values{mode} eq 'simple') {
		$values{comment} = '';
		$values{usertype} = 'normal';
		$values{fullname} = $values{userid};
		$values{maxstorage_unlimited} = 1;
		$values{maxstorage_display} = '0';
		$values{maxstorage} = '0';
		$values{maxresource_unlimited} = 1;
		$values{maxresource_display} = '0';
		$values{maxresource} = '0';
	}

	%values = (%values, TF::UserPermissions::submitted($cgi, undef,
		$values{mode} eq 'simple', 0, ($ENV{TF_GROUPCONSTRAINTS} || '') eq 'on'));
	%values = (%values, TF::UserExpiration::submitted($cgi, undef, $values{mode} eq 'simple', 0));
	return %values;
}

sub default_values {
	return (
		TF::UserPermissions::defaults(),
		TF::UserExpiration::defaults(),
		mode => 'simple',
		active => 'on',
		usertype => 'normal',
		userid => '',
		fullname => '',
		mailaddr => '',
		password => '',
		password_confirm => '',
		comment => '',
		maxstorage => '0',
		maxstorage_display => '0',
		maxstorage_unlimited => 1,
		maxresource => '0',
		maxresource_display => '0',
		maxresource_unlimited => 1,
	);
}

sub validate_csrf {
	my $cgi = shift;
	return $ERRORTABLE->{invalidToken} if !$TF_TOKEN || (($cgi->param('hidcsrftoken') || '') ne $TF_TOKEN);
	return '';
}

sub validate_values {
	my $values = shift;
	if ($values->{mode} eq 'simple') {
		$values->{fullname} = $values->{userid};
		$values->{usertype} = 'normal';
		$values->{maxstorage} = '0';
		$values->{maxstorage_display} = '0';
		$values->{maxstorage_unlimited} = 1;
		$values->{maxresource} = '0';
		$values->{maxresource_display} = '0';
		$values->{maxresource_unlimited} = 1;
	}
	return $ERRORTABLE->{invalidPermissions} unless TF::UserPermissions::valid($values, ($ENV{TF_GROUPCONSTRAINTS} || '') eq 'on');
	return $ERRORTABLE->{invalidExpiration} unless TF::UserExpiration::valid($values);
	return $ERRORTABLE->{required} if !$values->{userid} || !$values->{fullname} || $values->{password} eq '' || $values->{password_confirm} eq '';
	return $ERRORTABLE->{passwordMismatch} if $values->{password} ne $values->{password_confirm};
	return $ERRORTABLE->{invalidUserType} if !is_allowed_usertype($values->{usertype});
	return $ERRORTABLE->{invalidUserId} if !is_valid_userid($values->{userid});
	return $ERRORTABLE->{invalidMail} if !is_valid_mail_list($values->{mailaddr});
	return $ERRORTABLE->{invalidQuota} if !defined $values->{maxstorage} || !defined $values->{maxresource};
	return '';
}

sub request_create_user {
	my ($values, $sid) = @_;

	my $ua  = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0 });
	my $req = HTTP::Request->new;

	$req->uri($USER_MGMT_URL . uri_escape_utf8($values->{userid}));
	$req->method('MKCOL');
	$req->protocol('HTTP/1.1');
	$req->authorization_basic($REMOTE_USER, $PASSWD);
	$req->header('x-tf-session' => $sid) if $sid;
	$req->header('TF-Client-Info' => '001101000');
	$req->header('Content-Type' => 'text/xml; charset=UTF-8');
	$req->content(encode('UTF-8', build_usercreate_xml($values)));
	$ua->agent(TF::CommonUtils::getCgiUserAgent());

	my $res = $ua->request($req);
	return (1, '') if $res->is_success;

	my $code = $res->code || 500;
	my $status = $res->status_line || '';
	print STDERR "usercreate: MKCOL failed ($code) $status\n";

	if ($code == 403) {
		return (0, $ERRORTABLE->{forbidden});
	}
	if ($code == 405 || $code == 409) {
		return (0, $ERRORTABLE->{alreadyExists});
	}
	if ($code == 400) {
		return (0, $ERRORTABLE->{invalidQuota});
	}
	return (0, $ERRORTABLE->{createFailed} . " ($code)");
}

sub build_usercreate_xml {
	my $values = shift;
	my $state_tag = ($values->{active} eq 'on') ? '<TF:active/>' : '<TF:inactive/>';
	my $usertype_tag = '<TF:normal/>';
	my $privilege_tag = TF::UserPermissions::build_xml($values);
	my $expiration_tag = TF::UserExpiration::build_xml($values);
	my $comment_tag = '<TF:comment>' . escape_xml($values->{comment} || '') . '</TF:comment>';
	my $accessdeny_tag = '<TF:accessdeny>MANAGEMENT DBLINK REPOSDBLINK</TF:accessdeny>';
	if ($values->{usertype} eq 'groupleader') {
		$usertype_tag = '<TF:groupleader/>';
	}
	elsif ($values->{usertype} eq 'admin') {
		$usertype_tag = '<TF:admin/>';
	}
	elsif ($values->{usertype} eq 'limited') {
		$usertype_tag = '<TF:limited/>';
	}

	my $mail_tag = '';
	if ($values->{mailaddr} ne '') {
		$mail_tag = '<TF:mailaddr>' . escape_xml($values->{mailaddr}) . '</TF:mailaddr>';
	}

	return '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>'
		. '<D:propertyupdate xmlns:D="DAV:" xmlns:TF="http://www.teamfile.com/DTD/TF/">'
		. '<D:set><D:prop><TF:userdiscovery><TF:userinfo>'
		. '<TF:userid>' . escape_xml($values->{userid}) . '</TF:userid>'
		. '<TF:name>' . escape_xml($values->{fullname}) . '</TF:name>'
		. $mail_tag
		. '<TF:password>' . escape_xml($values->{password}) . '</TF:password>'
		. '<TF:maxstorage>' . escape_xml($values->{maxstorage}) . '</TF:maxstorage>'
		. '<TF:maxresource>' . escape_xml($values->{maxresource}) . '</TF:maxresource>'
		. $accessdeny_tag
		. $comment_tag
		. '<TF:state>' . $state_tag . '</TF:state>'
		. $expiration_tag
		. $privilege_tag
		. '<TF:usertype>' . $usertype_tag . '</TF:usertype>'
		. '</TF:userinfo></TF:userdiscovery></D:prop></D:set></D:propertyupdate>';
}

sub get_session_id {
	my @pairs = split(/;\s*/, $ENV{'HTTP_COOKIE'} || '');
	foreach my $pair (@pairs) {
		my ($key, $val) = split(/=/, $pair, 2);
		if (defined $key && $key eq 'tf-session') {
			return $val || '';
		}
	}
	return '';
}

sub normalize_mail_list {
	my $raw = shift || '';
	my %seen;
	my @tokens;
	foreach my $token (split(/,/, $raw)) {
		$token = trim($token);
		next if $token eq '' || $seen{$token};
		$seen{$token} = 1;
		push @tokens, $token;
	}
	return join(', ', @tokens);
}

sub is_valid_mail_list {
	my $raw = shift || '';
	return 1 if $raw eq '';
	foreach my $token (split(/,\s*/, $raw)) {
		next if $token =~ /^[^@\s,]+@[^@\s,]+$/;
		return 0;
	}
	return 1;
}

sub is_valid_userid {
	my $userid = shift || '';
	return 0 if $userid eq '';
	return 0 if $userid =~ m{/};
	return 0 if $userid =~ /^\./;
	return 1;
}

sub is_allowed_usertype {
	my $type = shift || '';
	return 1 if $type eq 'normal' || $type eq 'limited';
	return 1 if $USERTYPE eq 'admin' && ($type eq 'groupleader' || $type eq 'admin');
	return 0;
}

sub expand_scaled_number {
	my $raw = shift;
	return undef if !defined $raw;
	$raw = trim($raw);
	return undef if $raw eq '';
	return undef if $raw !~ /^([0-9]+)([kKmMgGtT]?)$/;
	my ($num, $suffix) = ($1, uc($2 || ''));
	my %multi = (
		'' => 1,
		K => 1024,
		M => 1024 * 1024,
		G => 1024 * 1024 * 1024,
		T => 1024 * 1024 * 1024 * 1024,
	);
	return int($num) * $multi{$suffix};
}

sub escape_xml {
	my $str = shift;
	return '' if !defined $str;
	$str =~ s/&/&amp;/g;
	$str =~ s/</&lt;/g;
	$str =~ s/>/&gt;/g;
	$str =~ s/"/&quot;/g;
	$str =~ s/'/&apos;/g;
	return $str;
}

sub trim {
	my $str = shift;
	return '' if !defined $str;
	$str =~ s/^[\s\x{3000}]+//;
	$str =~ s/[\s\x{3000}]+$//;
	return $str;
}

sub esc {
	my $str = shift;
	return '' if !defined $str || $str eq '';
	my $escaped = TF::CommonUtils::escapeHTML($str, 0);
	return defined $escaped ? $escaped : '';
}

sub radio_row {
	my ($value, $label, $checked) = @_;
	return {
		value => $value,
		label => $label,
		checked => $checked ? 1 : 0,
	};
}

sub localize {
	my $key = shift || '';
	my %ja = (
		title => 'ユーザ作成',
		subtitle => '管理者またはグループリーダー配下のユーザを作成します。',
		backLabel => '一覧へ戻る',
		saveLabel => '保存',
		saveHelp => '入力内容を確認してから作成します。',
		modeLabel => '表示モード',
		simpleModeLabel => '簡易',
		detailModeLabel => '詳細',
		clientPasswordMismatch => 'パスワード確認が一致しません',
		clientPasswordRequired => 'パスワードを入力してください',
		sectionState => '状態と種別',
		sectionBasic => '基本情報',
		sectionQuota => '利用上限',
		userIdLabel => 'ユーザID',
		userIdDesc => 'ログインに使用する識別子',
		nameLabel => '表示名',
		nameDesc => '画面表示に使用する名称',
		mailLabel => 'メールアドレス',
		mailDesc => '通知先として利用するメールアドレス。カンマで区切ると複数登録できます。',
		mailPlaceholder => 'mail@example.com, second@example.com',
		removeAddressLabel => '削除',
		mailWarningTitle => 'メールアドレス未設定',
		mailWarningBody => 'メールアドレスが無いため、作成通知は送信されません。このまま作成しますか？',
		mailWarningConfirm => '作成する',
		mailWarningCancel => '戻る',
		passwordLabel => 'パスワード',
		passwordDesc => '初期パスワード',
		passwordToggleLabel => '表示',
		passwordHideLabel => '非表示',
		passwordConfirmLabel => 'パスワード確認',
		passwordConfirmDesc => '確認用の再入力',
		commentLabel => 'コメント',
		commentDesc => 'このユーザに関する管理用のメモ',
		maxStorageLabel => '容量上限',
		maxStorageDesc => '利用可能な最大容量。0 または KMGT 付きで入力できます。',
		unlimitedStorageLabel => '無制限',
		maxResourceLabel => 'ファイル数上限',
		maxResourceDesc => '登録可能な最大ファイル数。0 または KMGT 付きで入力できます。',
		unlimitedResourceLabel => '無制限',
		activeLabel => '有効状態',
		activeDesc => '作成直後に利用可能とするか',
		expirationLabel => '有効期限',
		expirationDesc => 'この日時を過ぎるとログインできなくなります',
		expirationUnlimitedLabel => '無期限',
		toggleOnLabel => 'ON',
		toggleOffLabel => 'OFF',
		typeLabel => 'ユーザ種別',
		typeDesc => '作成時に付与する基本種別',
		typeNormal => '一般ユーザ',
		typeLimited => '制限ユーザ',
		typeGroupLeader => 'グループリーダー',
		typeAdmin => '管理者',
	);
	my %en = (
		title => 'Create User',
		subtitle => 'Create a user under the current administrator or group leader scope.',
		backLabel => 'Back to list',
		saveLabel => 'Save',
		saveHelp => 'Create the user with the current values.',
		modeLabel => 'Display mode',
		simpleModeLabel => 'Simple',
		detailModeLabel => 'Detail',
		clientPasswordMismatch => 'Password confirmation does not match.',
		clientPasswordRequired => 'Please enter the password.',
		sectionState => 'State and type',
		sectionBasic => 'Basic information',
		sectionQuota => 'Quota',
		userIdLabel => 'User ID',
		userIdDesc => 'Identifier used for login',
		nameLabel => 'Display name',
		nameDesc => 'Name shown in the UI',
		mailLabel => 'Mail address',
		mailDesc => 'Address used for notification mail. Separate multiple addresses with commas.',
		mailPlaceholder => 'mail@example.com, second@example.com',
		removeAddressLabel => 'Remove',
		mailWarningTitle => 'No mail address',
		mailWarningBody => 'No creation notice will be sent because the mail address is empty. Do you want to continue?',
		mailWarningConfirm => 'Create',
		mailWarningCancel => 'Back',
		passwordLabel => 'Password',
		passwordDesc => 'Initial password',
		passwordToggleLabel => 'Show',
		passwordHideLabel => 'Hide',
		passwordConfirmLabel => 'Confirm password',
		passwordConfirmDesc => 'Re-enter for confirmation',
		commentLabel => 'Comment',
		commentDesc => 'Administrative notes about this user',
		maxStorageLabel => 'Max storage',
		maxStorageDesc => 'Maximum available storage. Enter 0 or use K/M/G/T suffixes.',
		unlimitedStorageLabel => 'Unlimited',
		maxResourceLabel => 'Max files',
		maxResourceDesc => 'Maximum number of files. Enter 0 or use K/M/G/T suffixes.',
		unlimitedResourceLabel => 'Unlimited',
		activeLabel => 'Active state',
		activeDesc => 'Whether the user is enabled after creation',
		expirationLabel => 'Expiration',
		expirationDesc => 'The user cannot sign in after this date and time.',
		expirationUnlimitedLabel => 'No expiration',
		toggleOnLabel => 'ON',
		toggleOffLabel => 'OFF',
		typeLabel => 'User type',
		typeDesc => 'Base type assigned at creation',
		typeNormal => 'Normal user',
		typeLimited => 'Limited user',
		typeGroupLeader => 'Group leader',
		typeAdmin => 'Administrator',
	);
	return ($LANGPARAM eq 'ja') ? $ja{$key} : $en{$key};
}
