<?php
/*

  This code is part of LDAP Account Manager (http://www.sourceforge.net/projects/lam)
  Copyright (C) 2009 - 2012  Pavel Pozdnyak
                2009 - 2026  Roland Gruber

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/


/**
 * Manages the Asterisk extension of user accounts.
 *
 * @package modules
 *
 * @author Pavel Pozdnyak
 * @author Roland Gruber
 */

use LAM\TYPES\ConfiguredType;

/**
 * Manages the Asterisk extension of user accounts.
 *
 * @package modules
 */
class asteriskAccount extends baseModule implements passwordService {

	private const ASTERISK_DEFAULT_REALM = "asterisk";

	/**
	 * These attributes will be ignored by default if a new account is copied from an existing one.
	 */
	private const ATTRIBUTES_TO_IGNORE_ON_COPY = ['AstAccountCallerID', 'AstAccountRealmedPassword',
		'AstAccountFullContact', 'AstAccountMailbox', 'AstAccountIPAddress'];

	/**
	 * Creates a new asteriskAccount object.
	 *
	 * @param string $scope account type (user, group, host)
	 */
	function __construct($scope) {
		// call parent constructor
		parent::__construct($scope);
		$this->autoAddObjectClasses = false;
	}

	/**
	 * {@inheritDoc}
	 */
	public function can_manage(): bool {
		return $this->get_scope() === 'user';
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_metaData(): array {
		$return = [];
		$return["is_base"] = false;
		// alias name
		$return["alias"] = _("Asterisk");
		// module dependencies
		$return['dependencies'] = ['depends' => [], 'conflicts' => []];
		// managed object classes
		$return['objectClasses'] = ['AsteriskSIPUser'];
		// managed attributes
		$return['attributes'] = ['AstAccountCallerID', 'AstAccountHost',
			'AstAccountRealmedPassword', 'AstAccountContext', 'AstAccountType',
			'AstAccountUserAgent', 'AstAccountAMAFlags', 'AstAccountCallGroup',
			'AstAccountDTMFMode', 'AstAccountFromUser', 'AstAccountFromDomain',
			'AstAccountFullContact', 'AstAccountInsecure', 'AstAccountMailbox',
			'AstAccountNAT', 'AstAccountDeny', 'AstAccountPermit',
			'AstAccountPickupGroup', 'AstAccountPort', 'AstAccountQualify',
			'AstAccountRestrictCID', 'AstAccountRTPTimeout', 'AstAccountRTPHoldTimeout',
			'AstAccountDisallowedCodec', 'AstAccountAllowedCodec', 'AstAccountMusicOnHold',
			'AstAccountExpirationTimestamp', 'AstAccountRegistrationContext',
			'AstAccountRegistrationExten', 'AstAccountCanCallForward', 'AstAccountIPAddress',
			'AstAccountDefaultUser', 'AstAccountRegistrationServer', 'AstAccountLastQualifyMilliseconds',
		];
		// icon
		$return['icon'] = 'asterisk.png';
		// self-service
		$return['selfServiceFieldSettings'] = [
			'syncAsteriskPassword' => _('Sync Asterisk password with Unix password'),
		];
		// self-service: fields that cannot be relabeled
		$return['selfServiceNoRelabelFields'] = ['syncAsteriskPassword'];
		// help
		$return['help'] = [
			'AstAccountCallerID' => [
				"Headline" => _("Caller ID"), 'attr' => 'AstAccountCallerID',
				"Text" => _("This is the ID of the user in the Asterisk database. It may contain digits and letters (e.g. user1 or 200134).")
			],
			'AstAccountHost' => [
				"Headline" => _("Host"), 'attr' => 'AstAccountHost',
				"Text" => _("This is the machine id (e.g. IP address or host name) from which the user can call/receive calls.")
			],
			'AstAccountContext' => [
				"Headline" => _("Account context"), 'attr' => 'AstAccountContext',
				"Text" => _("The account context stores information about the dial plan.")
			],
			'AstAccountRealmedPassword' => [
				"Headline" => _("Password"), 'attr' => 'AstAccountRealmedPassword',
				"Text" => _("Please enter the password which you want to set for this account.")
			],
			'AstAccountType' => [
				"Headline" => _("Account type"), 'attr' => 'AstAccountType',
				"Text" => _("Please enter the account's type (e.g. \"friend\").")
			],
			'AsteriskRealm' => [
				"Headline" => _("Asterisk realm"),
				"Text" => _("Authentication realm for Asterisk server (default: asterisk). This value set in sip.conf (option: \"realm\").")
			],
			'AstAccountUserAgent' => [
				"Headline" => _("User agent"), 'attr' => 'AstAccountUserAgent',
				"Text" => _("SIP user agent identification.")
			],
			'AstAccountAMAFlags' => [
				"Headline" => _("AMA flags"), 'attr' => 'AstAccountAMAFlags',
				"Text" => _("Asterisk AMA (Automated Message Accounting) flags.")
			],
			'AstAccountCallGroup' => [
				"Headline" => _("Call groups"), 'attr' => 'AstAccountCallGroup',
				"Text" => _("The user's call groups.")
			],
			'AstAccountDTMFMode' => [
				"Headline" => _("DTMF mode"), 'attr' => 'AstAccountDTMFMode',
				"Text" => _("DTMF mode for SIP client configuration.")
			],
			'AstAccountFromUser' => [
				"Headline" => _("From user"), 'attr' => 'AstAccountFromUser',
				"Text" => _("From user setting for this account.")
			],
			'AstAccountFromDomain' => [
				"Headline" => _("From domain"), 'attr' => 'AstAccountFromDomain',
				"Text" => _("From domain setting for this account.")
			],
			'AstAccountFullContact' => [
				"Headline" => _("Full contact"), 'attr' => 'AstAccountFullContact',
				"Text" => _("SIP URI for a realtime peer.")
			],
			'AstAccountInsecure' => [
				"Headline" => _("Insecure"), 'attr' => 'AstAccountInsecure',
				"Text" => _("This is typically used to allow incoming calls (e.g. from FWD) while having a type=friend entry defined with username and password.")
			],
			'AstAccountMailbox' => [
				"Headline" => _("Mailbox"), 'attr' => 'AstAccountMailbox',
				"Text" => _("Defines the mailbox to check for message waiting indication (MWI) for this peer.")
			],
			'AstAccountNAT' => [
				"Headline" => _("NAT"), 'attr' => 'AstAccountNAT',
				"Text" => _("NAT setting for this account.")
			],
			'AstAccountDeny' => [
				"Headline" => _("Deny"), 'attr' => 'AstAccountDeny',
				"Text" => _("Used to limit SIP traffic to and from this peer to a certain IP or network.")
			],
			'AstAccountPermit' => [
				"Headline" => _("Permit"), 'attr' => 'AstAccountPermit',
				"Text" => _("Used to limit SIP traffic to and from this peer to a certain IP or network.")
			],
			'AstAccountPickupGroup' => [
				"Headline" => _("Pickup group"), 'attr' => 'AstAccountPickupGroup',
				"Text" => _("Specifies the user's pickup group.")
			],
			'AstAccountPort' => [
				"Headline" => _("Port"), 'attr' => 'AstAccountPort',
				"Text" => _("Port number.")
			],
			'AstAccountQualify' => [
				"Headline" => _("Qualify"), 'attr' => 'AstAccountQualify',
				"Text" => _("Used to regularly check that a device is still online.")
			],
			'AstAccountRestrictCID' => [
				"Headline" => _("Restrict caller ID"), 'attr' => 'AstAccountRestrictCID',
				"Text" => _("Use this to hide the caller ID.")
			],
			'AstAccountRTPTimeout' => [
				"Headline" => _("RTP timeout"), 'attr' => 'AstAccountRTPTimeout',
				"Text" => _("Used to automatically hangup the call if no RTP traffic is received.")
			],
			'AstAccountRTPHoldTimeout' => [
				"Headline" => _("RTP hold timeout"), 'attr' => 'AstAccountRTPHoldTimeout',
				"Text" => _("Maximum number of seconds of inactivity before terminating a call on hold.")
			],
			'AstAccountDisallowedCodec' => [
				"Headline" => _("Disallowed codec"), 'attr' => 'AstAccountDisallowedCodec',
				"Text" => _("List of disallowed codecs.")
			],
			'AstAccountAllowedCodec' => [
				"Headline" => _("Allowed codec"), 'attr' => 'AstAccountAllowedCodec',
				"Text" => _("List of allowed codecs.")
			],
			'AstAccountMusicOnHold' => [
				"Headline" => _("Music on hold"), 'attr' => 'AstAccountMusicOnHold',
				"Text" => _("Music to play on hold.")
			],
			'AstAccountExpirationTimestamp' => [
				"Headline" => _("Expiration timestamp"), 'attr' => 'AstAccountExpirationTimestamp',
				"Text" => _("Expiration timestamp (\"regseconds\" option).")
			],
			'AstAccountRegistrationContext' => [
				"Headline" => _("Registration context"), 'attr' => 'AstAccountRegistrationContext',
				"Text" => _("If registration context is specified, Asterisk will dynamically create and destroy a NoOp priority 1 extension for a given peer who registers or unregisters with Asterisk.")
			],
			'AstAccountRegistrationExten' => [
				"Headline" => _("Registration extension"), 'attr' => 'AstAccountRegistrationExten',
				"Text" => _("Used for registration context.")
			],
			'AstAccountCanCallForward' => [
				"Headline" => _("Can call forward"), 'attr' => 'AstAccountCanCallForward',
				"Text" => _("Specifies if the user can call forward.")
			],
			'AstAccountIPAddress' => [
				"Headline" => _("IP address"), 'attr' => 'AstAccountIPAddress',
				"Text" => _("IP address of the peer. Valid only for realtime peers.")
			],
			'AstAccountDefaultUser' => [
				"Headline" => _("Default user"), 'attr' => 'AstAccountDefaultUser',
				"Text" => _("Authentication user for outbound proxies.")
			],
			'AstAccountRegistrationServer' => [
				"Headline" => _("Registration server"), 'attr' => 'AstAccountRegistrationServer',
				"Text" => _("IP address or domain name of the registration server.")
			],
			'AstAccountLastQualifyMilliseconds' => [
				"Headline" => _("Last qualify milliseconds"), 'attr' => 'AstAccountLastQualifyMilliseconds',
				"Text" => _("The number of milliseconds for the last qualify.")
			],
			'hiddenOptions' => [
				"Headline" => _("Hidden options"),
				"Text" => _("The selected options will not be managed inside LAM. You can use this to reduce the number of displayed input fields.")
			],
		];
		// self-service options
		$selfServiceContainer = new htmlResponsiveRow();
		$selfServiceContainer->add(new htmlResponsiveInputField(_('Asterisk realm'), 'asteriskAccount_AsteriskRealm', null, ['AsteriskRealm', static::class]));
		$return['selfServiceSettings'] = $selfServiceContainer;
		// profile options
		$profileContainer = new htmlResponsiveRow();
		$profileContainer->add(new htmlResponsiveInputField(_('Host'), 'asteriskAccount_AstAccountHost', null, 'AstAccountHost'));
		$profileContainer->add(new htmlResponsiveInputField(_('Account context'), 'asteriskAccount_AstAccountContext', null, 'AstAccountContext'));
		$profileContainer->add(new htmlResponsiveInputField(_('Account type'), 'asteriskAccount_AstAccountType', null, 'AstAccountType'));
		$return['profile_options'] = $profileContainer;
		// profile mappings
		$return['profile_mappings'] = [
			'asteriskAccount_AstAccountHost' => 'AstAccountHost',
			'asteriskAccount_AstAccountContext' => 'AstAccountContext',
			'asteriskAccount_AstAccountType' => 'AstAccountType',
		];
		// available PDF fields
		$return['PDF_fields'] = [
			'AstAccountCallerID' => _('Caller ID'),
			'AstAccountContext' => _('Account context'),
			'AstAccountHost' => _('Host'),
			'AstAccountType' => _('Account type'),
			'AstAccountUserAgent' => _('User agent'),
			'AstAccountAMAFlags' => _('AMA flags'),
			'AstAccountCallGroup' => _('Call groups'),
			'AstAccountDTMFMode' => _('DTFM flags'),
			'AstAccountFromUser' => _('From user'),
			'AstAccountFromDomain' => _('From domain'),
			'AstAccountFullContact' => _('Full contact'),
			'AstAccountInsecure' => _('Insecure'),
			'AstAccountMailbox' => _('Mailbox'),
			'AstAccountNAT' => _('NAT'),
			'AstAccountDeny' => _('Deny'),
			'AstAccountPermit' => _('Permit'),
			'AstAccountPickupGroup' => _('Pickup group'),
			'AstAccountPort' => _('Port'),
			'AstAccountQualify' => _('Qualify'),
			'AstAccountRestrictCID' => _('Restrict caller ID'),
			'AstAccountRTPTimeout' => _('RTP timeout'),
			'AstAccountRTPHoldTimeout' => _('RTP hold timeout'),
			'AstAccountDisallowedCodec' => _('Disallowed codec'),
			'AstAccountAllowedCodec' => _('Allowed codec'),
			'AstAccountMusicOnHold' => _('Music on hold'),
			'AstAccountExpirationTimestamp' => _('Expiration timestamp'),
			'AstAccountRegistrationContext' => _('Registration context'),
			'AstAccountRegistrationExten' => _('Registration extension'),
			'AstAccountCanCallForward' => _('Can call forward'),
			'AstAccountIPAddress' => _('IP address'),
			'AstAccountDefaultUser' => _('Default user'),
			'AstAccountRegistrationServer' => _('Registration server'),
			'AstAccountLastQualifyMilliseconds' => _('Last qualify milliseconds'),
		];
		// upload fields
		$return['upload_columns'] = [
			[
				'name' => 'asteriskAccount_AstAccountCallerID',
				'description' => _('Caller ID'),
				'help' => 'AstAccountCallerID',
				'example' => '12345',
				'required' => true
			],
			[
				'name' => 'asteriskAccount_AstAccountContext',
				'description' => _('Account context'),
				'help' => 'AstAccountContext',
				'example' => 'default',
				'required' => true
			],
			[
				'name' => 'asteriskAccount_AstAccountHost',
				'description' => _('Host'),
				'help' => 'AstAccountHost',
				'example' => 'dynamic',
				'default' => 'dynamic',
			],
			[
				'name' => 'asteriskAccount_AstAccountRealmedPassword',
				'description' => _('Password'),
				'help' => 'AstAccountRealmedPassword',
				'example' => _('secret'),
			],
			[
				'name' => 'asteriskAccount_AstAccountType',
				'description' => _('Account type'),
				'help' => 'AstAccountType',
				'example' => 'friend',
				'required' => true
			],
		];
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountUserAgent')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountUserAgent',
				'description' => _('User agent'),
				'help' => 'AstAccountUserAgent',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountAMAFlags')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountAMAFlags',
				'description' => _('AMA flags'),
				'help' => 'AstAccountAMAFlags',
				'example' => 'billing',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountCallGroup')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountCallGroup',
				'description' => _('Call groups'),
				'help' => 'AstAccountCallGroup',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountDTMFMode')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountDTMFMode',
				'description' => _('DTFM flags'),
				'help' => 'AstAccountDTMFMode',
				'example' => 'auto',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountFromUser')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountFromUser',
				'description' => _('From user'),
				'help' => 'AstAccountFromUser',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountFromDomain')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountFromDomain',
				'description' => _('From domain'),
				'help' => 'AstAccountFromDomain',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountFullContact')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountFullContact',
				'description' => _('Full contact'),
				'help' => 'AstAccountFullContact',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountInsecure')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountInsecure',
				'description' => _('Insecure'),
				'help' => 'AstAccountInsecure',
				'example' => 'invite',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountMailbox')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountMailbox',
				'description' => _('Mailbox'),
				'help' => 'AstAccountMailbox',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountNAT')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountNAT',
				'description' => _('NAT'),
				'help' => 'AstAccountNAT',
				'example' => 'never',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountDeny')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountDeny',
				'description' => _('Deny'),
				'help' => 'AstAccountDeny',
			];
		}

		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountPermit')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountPermit',
				'description' => _('Permit'),
				'help' => 'AstAccountPermit',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountPickupGroup')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountPickupGroup',
				'description' => _('Pickup group'),
				'help' => 'AstAccountPickupGroup',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountPort')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountPort',
				'description' => _('Port'),
				'help' => 'AstAccountPort',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountQualify')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountQualify',
				'description' => _('Qualify'),
				'help' => 'AstAccountQualify',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRestrictCID')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountRestrictCID',
				'description' => _('Restrict caller ID'),
				'help' => 'AstAccountRestrictCID',
			];
		}

		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRTPTimeout')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountRTPTimeout',
				'description' => _('RTP timeout'),
				'help' => 'AstAccountRTPTimeout',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRTPHoldTimeout')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountRTPHoldTimeout',
				'description' => _('RTP hold timeout'),
				'help' => 'AstAccountRTPHoldTimeout',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountDisallowedCodec')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountDisallowedCodec',
				'description' => _('Disallowed codec'),
				'help' => 'AstAccountDisallowedCodec',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountAllowedCodec')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountAllowedCodec',
				'description' => _('Allowed codec'),
				'help' => 'AstAccountAllowedCodec',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountMusicOnHold')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountMusicOnHold',
				'description' => _('Music on hold'),
				'help' => 'AstAccountMusicOnHold',
			];
		}

		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountExpirationTimestamp')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountExpirationTimestamp',
				'description' => _('Expiration timestamp'),
				'help' => 'AstAccountExpirationTimestamp',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRegistrationContext')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountRegistrationContext',
				'description' => _('Registration context'),
				'help' => 'AstAccountRegistrationContext',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRegistrationExten')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountRegistrationExten',
				'description' => _('Registration extension'),
				'help' => 'AstAccountRegistrationExten',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountCanCallForward')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountCanCallForward',
				'description' => _('Can call forward'),
				'help' => 'AstAccountCanCallForward',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountIPAddress')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountIPAddress',
				'description' => _('IP address'),
				'help' => 'AstAccountIPAddress',
			];
		}

		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountDefaultUser')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountDefaultUser',
				'description' => _('Default user'),
				'help' => 'AstAccountDefaultUser',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRegistrationServer')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountRegistrationServer',
				'description' => _('Registration server'),
				'help' => 'AstAccountRegistrationServer',
			];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountLastQualifyMilliseconds')) {
			$return['upload_columns'][] = [
				'name' => 'asteriskAccount_AstAccountLastQualifyMilliseconds',
				'description' => _('Last qualify milliseconds'),
				'help' => 'AstAccountLastQualifyMilliseconds',
			];
		}
		return $return;
	}

	/**
	 * {@inheritDoc}
	 */
	protected function load_Messages(): void {
		$this->messages['AstAccountCallerID'][0] = ['ERROR', _('Please enter a caller ID.')];
		$this->messages['AstAccountCallerID'][1] = ['ERROR', _('The caller ID format is invalid.')];
		$this->messages['AstAccountCallerID'][2] = ['ERROR', _('There is already another user with this caller ID.')];
		$this->messages['AstAccountCallerID'][3] = ['ERROR', _('Account %s:') . ' asteriskAccount_AstAccountCallerID', _('The caller ID format is invalid.')];
		$this->messages['AstAccountContext'][0] = ['ERROR', _('Please enter the extension context.')];
		$this->messages['AstAccountContext'][1] = ['ERROR', _('The extension context is invalid.')];
		$this->messages['AstAccountContext'][2] = ['ERROR', _('Account %s:') . ' asteriskAccount_AstAccountContext', _('The extension context is invalid.')];
		$this->messages['AstAccountHost'][0] = ['ERROR', _('Please enter the host name.')];
		$this->messages['AstAccountHost'][1] = ['ERROR', _('The host name is invalid.')];
		$this->messages['AstAccountHost'][2] = ['ERROR', _('Account %s:') . ' asteriskAccount_AstAccountHost', _('The host name is invalid.')];
		$this->messages['AstAccountType'][0] = ['ERROR', _('Please enter the account type.')];
		$this->messages['AstAccountType'][1] = ['ERROR', _('The account type is invalid.')];
		$this->messages['AstAccountType'][2] = ['ERROR', _('Account %s:') . ' asteriskAccount_AstAccountType', _('The account type is invalid.')];
		$this->messages['AstAccountFromUser'][0] = ['ERROR', _('Please enter a valid from user.')];
		$this->messages['AstAccountFromUser'][1] = ['ERROR', _('Account %s:') . ' asteriskAccount_AstAccountFromUser', _('Please enter a valid from user.')];
		$this->messages['AstAccountFromDomain'][0] = ['ERROR', _('Please enter a valid from domain.')];
		$this->messages['AstAccountFromDomain'][1] = ['ERROR', _('Account %s:') . ' asteriskAccount_AstAccountFromDomain', _('Please enter a valid from domain.')];
		$this->messages['AstAccountPort'][0] = ['ERROR', _('Please enter a valid port number.')];
		$this->messages['AstAccountPort'][1] = ['ERROR', _('Account %s:') . ' asteriskAccount_AstAccountPort', _('Please enter a valid port number.')];
		$this->messages['AstAccountIPAddress'][0] = ['ERROR', _('The IP address is invalid.')];
		$this->messages['AstAccountIPAddress'][1] = ['ERROR', _('Account %s:') . ' asteriskAccount_AstAccountIPAddress', _('The IP address is invalid.')];
		$this->messages['AstAccountDefaultUser'][0] = ['ERROR', _('Please enter a valid default user.')];
		$this->messages['AstAccountDefaultUser'][1] = ['ERROR', _('Account %s:') . ' asteriskAccount_AstAccountDefaultUser', _('Please enter a valid default user.')];
	}

	/**
	 * {@inheritDoc}
	 */
	public function loadAttributesFromAccountCopy(array $ldapAttributes, array $attributesToIgnore = []): void {
		$attributesToIgnore = array_merge(baseModule::ATTRIBUTES_TO_IGNORE_ON_COPY_DEFAULT, self::ATTRIBUTES_TO_IGNORE_ON_COPY);
		parent::loadAttributesFromAccountCopy($ldapAttributes, $attributesToIgnore);
	}

	/**
	 * Returns the HTML meta data for the main account page.
	 *
	 * @return htmlElement HTML meta data
	 */
	function display_html_attributes() {
		$return = new htmlResponsiveRow();
		if (in_array('AsteriskSIPUser', $this->attributes['objectClass'])) {
			// caller ID
			$this->addSimpleInputTextField($return, 'AstAccountCallerID', _("Caller ID"), true);
			// host
			$this->addSimpleInputTextField($return, 'AstAccountHost', _("Host"), true);
			// context
			$this->addSimpleInputTextField($return, 'AstAccountContext', _("Account context"), true);
			// account type
			$this->addSimpleInputTextField($return, 'AstAccountType', _("Account type"), true);
			// user agent
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountUserAgent')) {
				$this->addSimpleInputTextField($return, 'AstAccountUserAgent', _("User agent"));
			}
			// AMA flags
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountAMAFlags')) {
				$AstAccountAMAFlagsOptions = ['-', 'default', 'omit', 'billing', 'documentation'];
				$AstAccountAMAFlags = [];
				if (isset($this->attributes['AstAccountAMAFlags'][0])) {
					$AstAccountAMAFlags[] = $this->attributes['AstAccountAMAFlags'][0];
					if (($this->attributes['AstAccountAMAFlags'][0] != '') && !in_array($this->attributes['AstAccountAMAFlags'][0], $AstAccountAMAFlagsOptions)) {
						$AstAccountAMAFlagsOptions[] = $this->attributes['AstAccountAMAFlags'][0];
					}
				}
				$AstAccountAMAFlagsInput = new htmlResponsiveSelect('AstAccountAMAFlags', $AstAccountAMAFlagsOptions, $AstAccountAMAFlags, _("AMA flags"), 'AstAccountAMAFlags');
				$return->add($AstAccountAMAFlagsInput);
			}
			// call groups
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountCallGroup')) {
				$this->addSimpleInputTextField($return, 'AstAccountCallGroup', _("Call groups"));
			}
			// DTMF flags
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountDTMFMode')) {
				$AstAccountDTMFModeOptions = ['-', 'inband', 'rfc2833', 'info', 'auto'];
				$AstAccountDTMFMode = [];
				if (isset($this->attributes['AstAccountDTMFMode'][0])) {
					$AstAccountDTMFMode[] = $this->attributes['AstAccountDTMFMode'][0];
					if (($this->attributes['AstAccountDTMFMode'][0] != '') && !in_array($this->attributes['AstAccountDTMFMode'][0], $AstAccountDTMFModeOptions)) {
						$AstAccountDTMFModeOptions[] = $this->attributes['AstAccountDTMFMode'][0];
					}
				}
				$AstAccountDTMFModeInput = new htmlResponsiveSelect('AstAccountDTMFMode', $AstAccountDTMFModeOptions, $AstAccountDTMFMode, _("DTFM flags"), 'AstAccountDTMFMode');
				$return->add($AstAccountDTMFModeInput);
			}
			// from user
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountFromUser')) {
				$this->addSimpleInputTextField($return, 'AstAccountFromUser', _("From user"));
			}
			// from domain
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountFromDomain')) {
				$this->addSimpleInputTextField($return, 'AstAccountFromDomain', _("From domain"));
			}
			// full contact
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountFullContact')) {
				$this->addSimpleInputTextField($return, 'AstAccountFullContact', _("Full contact"));
			}
			// insecure
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountInsecure')) {
				$AstAccountInsecureOptions = ['-', 'port', 'invite', 'port,invite'];
				$AstAccountInsecure = [];
				if (isset($this->attributes['AstAccountInsecure'][0])) {
					$AstAccountInsecure[] = $this->attributes['AstAccountInsecure'][0];
					if (($this->attributes['AstAccountInsecure'][0] != '') && !in_array($this->attributes['AstAccountInsecure'][0], $AstAccountInsecureOptions)) {
						$AstAccountInsecureOptions[] = $this->attributes['AstAccountInsecure'][0];
					}
				}
				$AstAccountInsecureInput = new htmlResponsiveSelect('AstAccountInsecure', $AstAccountInsecureOptions, $AstAccountInsecure, _("Insecure"), 'AstAccountInsecure');
				$return->add($AstAccountInsecureInput);
			}
			// mailbox
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountMailbox')) {
				$this->addSimpleInputTextField($return, 'AstAccountMailbox', _("Mailbox"));
			}
			// NAT
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountNAT')) {
				$AstAccountNATOptions = ['-', 'yes', 'no', 'never', 'route'];
				$AstAccountNAT = [];
				if (isset($this->attributes['AstAccountNAT'][0])) {
					$AstAccountNAT[] = $this->attributes['AstAccountNAT'][0];
					if (($this->attributes['AstAccountNAT'][0] != '') && !in_array($this->attributes['AstAccountNAT'][0], $AstAccountNATOptions)) {
						$AstAccountNATOptions[] = $this->attributes['AstAccountNAT'][0];
					}
				}
				$AstAccountNATInput = new htmlResponsiveSelect('AstAccountNAT', $AstAccountNATOptions, $AstAccountNAT, _("NAT"), 'AstAccountNAT');
				$return->add($AstAccountNATInput);
			}
			// deny
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountDeny')) {
				$this->addSimpleInputTextField($return, 'AstAccountDeny', _("Deny"));
			}
			// permit
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountPermit')) {
				$this->addSimpleInputTextField($return, 'AstAccountPermit', _("Permit"));
			}
			// pickup group
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountPickupGroup')) {
				$this->addSimpleInputTextField($return, 'AstAccountPickupGroup', _("Pickup group"));
			}
			// port
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountPort')) {
				$AstAccountPort = $this->addSimpleInputTextField($return, 'AstAccountPort', _("Port"));
				$AstAccountPort->setMinimumAndMaximumNumber(1);
			}
			// qualify
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountQualify')) {
				$this->addSimpleInputTextField($return, 'AstAccountQualify', _("Qualify"));
			}
			// restrict caller ID
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRestrictCID')) {
				$this->addSimpleInputTextField($return, 'AstAccountRestrictCID', _("Restrict caller ID"));
			}
			// RTP timeout
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRTPTimeout')) {
				$AstAccountRTPTimeout = $this->addSimpleInputTextField($return, 'AstAccountRTPTimeout', _("RTP timeout"));
				$AstAccountRTPTimeout->setMinimumAndMaximumNumber(-1);
			}
			// RTP hold timeout
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRTPHoldTimeout')) {
				$AstAccountRTPHoldTimeoutInput = $this->addSimpleInputTextField($return, 'AstAccountRTPHoldTimeout', _("RTP hold timeout"));
				$AstAccountRTPHoldTimeoutInput->setMinimumAndMaximumNumber(-1);
			}
			// disallowed codec
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountDisallowedCodec')) {
				$this->addSimpleInputTextField($return, 'AstAccountDisallowedCodec', _("Disallowed codec"));
			}
			// allowed codec
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountAllowedCodec')) {
				$this->addSimpleInputTextField($return, 'AstAccountAllowedCodec', _("Allowed codec"));
			}
			// music on hold
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountMusicOnHold')) {
				$this->addSimpleInputTextField($return, 'AstAccountMusicOnHold', _("Music on hold"));
			}
			// expiration timestamp
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountExpirationTimestamp')) {
				$this->addSimpleInputTextField($return, 'AstAccountExpirationTimestamp', _("Expiration timestamp"));
			}
			// registration context
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRegistrationContext')) {
				$this->addSimpleInputTextField($return, 'AstAccountRegistrationContext', _("Registration context"));
			}
			// registration extension
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRegistrationExten')) {
				$this->addSimpleInputTextField($return, 'AstAccountRegistrationExten', _("Registration extension"));
			}
			// can call forward
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountCanCallForward')) {
				$AstAccountCanCallForwardOptions = ['-', 'yes', 'no'];
				$AstAccountCanCallForward = [];
				if (isset($this->attributes['AstAccountCanCallForward'][0])) {
					$AstAccountCanCallForward[] = $this->attributes['AstAccountCanCallForward'][0];
					if (($this->attributes['AstAccountCanCallForward'][0] != '') && !in_array($this->attributes['AstAccountCanCallForward'][0], $AstAccountCanCallForwardOptions)) {
						$AstAccountCanCallForwardOptions[] = $this->attributes['AstAccountCanCallForward'][0];
					}
				}
				$AstAccountCanCallForwardInput = new htmlResponsiveSelect('AstAccountCanCallForward', $AstAccountCanCallForwardOptions, $AstAccountCanCallForward, _("Can call forward"), 'AstAccountCanCallForward');
				$return->add($AstAccountCanCallForwardInput);
			}
			// IP address
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountIPAddress')) {
				$this->addSimpleInputTextField($return, 'AstAccountIPAddress', _("IP address"));
			}
			// default user
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountDefaultUser')) {
				$this->addSimpleInputTextField($return, 'AstAccountDefaultUser', _("Default user"));
			}
			// registration server
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRegistrationServer')) {
				$this->addSimpleInputTextField($return, 'AstAccountRegistrationServer', _("Registration server"));
			}
			// last qualify milliseconds
			if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountLastQualifyMilliseconds')) {
				$this->addSimpleInputTextField($return, 'AstAccountLastQualifyMilliseconds', _("Last qualify milliseconds"));
			}
		}
		else {
			$return->add(new htmlButton('addObjectClass', _('Add Asterisk account')));
		}
		return $return;
	}

	/**
	 * {@inheritDoc}
	 */
	public function process_attributes(): array {
		if (isset($_POST['addObjectClass'])) {
			$this->attributes['objectClass'][] = 'AsteriskSIPUser';
			return [];
		}
		if (!in_array('AsteriskSIPUser', $this->attributes['objectClass'])) {
			return [];
		}
		$errors = [];
		$this->attributes['AstAccountCallerID'] = [];
		$this->attributes['AstAccountHost'] = [];
		$this->attributes['AstAccountContext'] = [];
		$this->attributes['AstAccountType'] = [];
		if (isset($_POST['AstAccountCallerID'])) {
			$this->attributes['AstAccountCallerID'][0] = $_POST['AstAccountCallerID'];
			// check if caller ID is empty
			if ($this->attributes['AstAccountCallerID'][0] == '') {
				$errors[] = $this->messages['AstAccountCallerID'][0];
			}
			// check format
			elseif (!get_preg($this->attributes['AstAccountCallerID'][0], 'username')) {
				$errors[] = $this->messages['AstAccountCallerID'][1];
			}
			// check for duplicate caller ID
			elseif (!isset($this->orig['AstAccountCallerID'][0])
				|| ($this->orig['AstAccountCallerID'][0] != $this->attributes['AstAccountCallerID'][0])) {
				$entries = searchLDAPByAttribute('AstAccountCallerID', $this->attributes['AstAccountCallerID'][0], 'AsteriskSIPUser', ['dn'], ['user']);
				if (count($entries) > 0) {
					$errors[] = $this->messages['AstAccountCallerID'][2];
				}
			}
		}
		if (isset($_POST['AstAccountHost'])) {
			$this->attributes['AstAccountHost'][0] = $_POST['AstAccountHost'];
			if ($this->attributes['AstAccountHost'][0] == '') {
				$errors[] = $this->messages['AstAccountHost'][0];
			}
			elseif (!get_preg($this->attributes['AstAccountHost'][0], 'hostname')) {
				$errors[] = $this->messages['AstAccountHost'][1];
			}
		}
		if (isset($_POST['AstAccountContext'])) {
			$this->attributes['AstAccountContext'][0] = $_POST['AstAccountContext'];
			if ($this->attributes['AstAccountContext'][0] == '') {
				$errors[] = $this->messages['AstAccountContext'][0];
			}
			elseif (!get_preg($this->attributes['AstAccountContext'][0], 'username')) {
				$errors[] = $this->messages['AstAccountContext'][1];
			}
		}
		if (isset($_POST['AstAccountType'])) {
			$this->attributes['AstAccountType'][0] = $_POST['AstAccountType'];
			if ($this->attributes['AstAccountType'][0] == '') {
				$errors[] = $this->messages['AstAccountType'][0];
			}
			elseif (!get_preg($this->attributes['AstAccountType'][0], 'username')) {
				$errors[] = $this->messages['AstAccountType'][1];
			}
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountUserAgent')) {
			$this->attributes['AstAccountUserAgent'][0] = $_POST['AstAccountUserAgent'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountAMAFlags')) {
			if (($_POST['AstAccountAMAFlags'] == '-') && isset($this->attributes['AstAccountAMAFlags'][0])) {
				unset($this->attributes['AstAccountAMAFlags'][0]);
			}
			else {
				$this->attributes['AstAccountAMAFlags'][0] = $_POST['AstAccountAMAFlags'];
			}
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountCallGroup')) {
			$this->attributes['AstAccountCallGroup'][0] = $_POST['AstAccountCallGroup'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountDTMFMode')) {
			if (($_POST['AstAccountDTMFMode'] == '-') && isset($this->attributes['AstAccountDTMFMode'][0])) {
				unset($this->attributes['AstAccountDTMFMode'][0]);
			}
			else {
				$this->attributes['AstAccountDTMFMode'][0] = $_POST['AstAccountDTMFMode'];
			}
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountFromUser')) {
			$this->attributes['AstAccountFromUser'][0] = $_POST['AstAccountFromUser'];
			if (($_POST['AstAccountFromUser'] != '') && !get_preg($_POST['AstAccountFromUser'], 'username')) {
				$errors[] = $this->messages['AstAccountFromUser'][0];
			}
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountFromDomain')) {
			$this->attributes['AstAccountFromDomain'][0] = $_POST['AstAccountFromDomain'];
			if (($_POST['AstAccountFromDomain'] != '') && !get_preg($_POST['AstAccountFromDomain'], 'domainname')) {
				$errors[] = $this->messages['AstAccountFromDomain'][0];
			}
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountFullContact')) {
			$this->attributes['AstAccountFullContact'][0] = $_POST['AstAccountFullContact'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountInsecure')) {
			if (($_POST['AstAccountInsecure'] == '-') && isset($this->attributes['AstAccountInsecure'][0])) {
				unset($this->attributes['AstAccountInsecure'][0]);
			}
			else {
				$this->attributes['AstAccountInsecure'][0] = $_POST['AstAccountInsecure'];
			}
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountMailbox')) {
			$this->attributes['AstAccountMailbox'][0] = $_POST['AstAccountMailbox'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountNAT')) {
			if (($_POST['AstAccountNAT'] == '-') && isset($this->attributes['AstAccountNAT'][0])) {
				unset($this->attributes['AstAccountNAT'][0]);
			}
			else {
				$this->attributes['AstAccountNAT'][0] = $_POST['AstAccountNAT'];
			}
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountDeny')) {
			$this->attributes['AstAccountDeny'][0] = $_POST['AstAccountDeny'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountPermit')) {
			$this->attributes['AstAccountPermit'][0] = $_POST['AstAccountPermit'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountPickupGroup')) {
			$this->attributes['AstAccountPickupGroup'][0] = $_POST['AstAccountPickupGroup'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountPort')) {
			$this->attributes['AstAccountPort'][0] = $_POST['AstAccountPort'];
			if (($_POST['AstAccountPort'] != '') && !get_preg($_POST['AstAccountPort'], 'digit')) {
				$errors[] = $this->messages['AstAccountPort'][0];
			}
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountQualify')) {
			$this->attributes['AstAccountQualify'][0] = $_POST['AstAccountQualify'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRestrictCID')) {
			$this->attributes['AstAccountRestrictCID'][0] = $_POST['AstAccountRestrictCID'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRTPTimeout')) {
			$this->attributes['AstAccountRTPTimeout'][0] = $_POST['AstAccountRTPTimeout'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRTPHoldTimeout')) {
			$this->attributes['AstAccountRTPHoldTimeout'][0] = $_POST['AstAccountRTPHoldTimeout'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountDisallowedCodec')) {
			$this->attributes['AstAccountDisallowedCodec'][0] = $_POST['AstAccountDisallowedCodec'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountAllowedCodec')) {
			$this->attributes['AstAccountAllowedCodec'][0] = $_POST['AstAccountAllowedCodec'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountMusicOnHold')) {
			$this->attributes['AstAccountMusicOnHold'][0] = $_POST['AstAccountMusicOnHold'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountExpirationTimestamp')) {
			$this->attributes['AstAccountExpirationTimestamp'][0] = $_POST['AstAccountExpirationTimestamp'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRegistrationContext')) {
			$this->attributes['AstAccountRegistrationContext'][0] = $_POST['AstAccountRegistrationContext'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRegistrationExten')) {
			$this->attributes['AstAccountRegistrationExten'][0] = $_POST['AstAccountRegistrationExten'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountCanCallForward')) {
			if (($_POST['AstAccountCanCallForward'] == '-') && isset($this->attributes['AstAccountCanCallForward'][0])) {
				unset($this->attributes['AstAccountCanCallForward'][0]);
			}
			else {
				$this->attributes['AstAccountCanCallForward'][0] = $_POST['AstAccountCanCallForward'];
			}
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountIPAddress')) {
			$this->attributes['AstAccountIPAddress'][0] = $_POST['AstAccountIPAddress'];
			if (($_POST['AstAccountIPAddress'] != '') && !get_preg($_POST['AstAccountIPAddress'], 'ip')) {
				$errors[] = $this->messages['AstAccountIPAddress'][0];
			}
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountDefaultUser')) {
			$this->attributes['AstAccountDefaultUser'][0] = $_POST['AstAccountDefaultUser'];
			if (($_POST['AstAccountDefaultUser'] != '') && !get_preg($_POST['AstAccountDefaultUser'], 'username')) {
				$errors[] = $this->messages['AstAccountDefaultUser'][0];
			}
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountRegistrationServer')) {
			$this->attributes['AstAccountRegistrationServer'][0] = $_POST['AstAccountRegistrationServer'];
		}
		if (!$this->isBooleanConfigOptionSet('asteriskAccount_hideAstAccountLastQualifyMilliseconds')) {
			$this->attributes['AstAccountLastQualifyMilliseconds'][0] = $_POST['AstAccountLastQualifyMilliseconds'];
		}
		return $errors;
	}

	/**
	 * {@inheritDoc}
	 */
	public function save_attributes(): array {
		if (!in_array('AsteriskSIPUser', $this->attributes['objectClass'])) {
			return [];
		}
		return $this->getAccountContainer()->save_module_attributes($this->attributes, $this->orig);
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_pdfEntries(array $pdfKeys, string $typeId): array {
		$return = [];
		$this->addSimplePDFField($return, 'AstAccountCallerID', _('Caller ID'));
		$this->addSimplePDFField($return, 'AstAccountContext', _('Account context'));
		$this->addSimplePDFField($return, 'AstAccountHost', _('Host'));
		$this->addSimplePDFField($return, 'AstAccountType', _('Account type'));
		$this->addSimplePDFField($return, 'AstAccountUserAgent', _('User agent'));
		$this->addSimplePDFField($return, 'AstAccountAMAFlags', _('AMA flags'));
		$this->addSimplePDFField($return, 'AstAccountCallGroup', _('Call groups'));
		$this->addSimplePDFField($return, 'AstAccountDTMFMode', _('DTMF mode'));
		$this->addSimplePDFField($return, 'AstAccountFromUser', _('From user'));
		$this->addSimplePDFField($return, 'AstAccountFromDomain', _('From domain'));
		$this->addSimplePDFField($return, 'AstAccountFullContact', _('Full contact'));
		$this->addSimplePDFField($return, 'AstAccountInsecure', _('Insecure'));
		$this->addSimplePDFField($return, 'AstAccountMailbox', _('Mailbox'));
		$this->addSimplePDFField($return, 'AstAccountNAT', _('NAT'));
		$this->addSimplePDFField($return, 'AstAccountDeny', _('Deny'));
		$this->addSimplePDFField($return, 'AstAccountPermit', _('Permit'));
		$this->addSimplePDFField($return, 'AstAccountPickupGroup', _('Pickup group'));
		$this->addSimplePDFField($return, 'AstAccountPort', _('Port'));
		$this->addSimplePDFField($return, 'AstAccountQualify', _('Qualify'));
		$this->addSimplePDFField($return, 'AstAccountRestrictCID', _('Restrict caller ID'));
		$this->addSimplePDFField($return, 'AstAccountRTPTimeout', _('RTP timeout'));
		$this->addSimplePDFField($return, 'AstAccountRTPHoldTimeout', _('RTP hold timeout'));
		$this->addSimplePDFField($return, 'AstAccountDisallowedCodec', _('Disallowed codec'));
		$this->addSimplePDFField($return, 'AstAccountAllowedCodec', _('Allowed codec'));
		$this->addSimplePDFField($return, 'AstAccountMusicOnHold', _('Music on hold'));
		$this->addSimplePDFField($return, 'AstAccountExpirationTimestamp', _('Expiration timestamp'));
		$this->addSimplePDFField($return, 'AstAccountRegistrationContext', _('Registration context'));
		$this->addSimplePDFField($return, 'AstAccountRegistrationExten', _('Registration extension'));
		$this->addSimplePDFField($return, 'AstAccountCanCallForward', _('Can call forward'));
		$this->addSimplePDFField($return, 'AstAccountIPAddress', _('IP address'));
		$this->addSimplePDFField($return, 'AstAccountDefaultUser', _('Default user'));
		$this->addSimplePDFField($return, 'AstAccountRegistrationServer', _('Registration server'));
		$this->addSimplePDFField($return, 'AstAccountLastQualifyMilliseconds', _('Last qualify milliseconds'));
		return $return;
	}

	/**
	 * {@inheritDoc}
	 */
	public function build_uploadAccounts(array $rawAccounts, array $ids, array &$partialAccounts, array $selectedModules, ConfiguredType &$type): array {
		$messages = [];
		for ($i = 0; $i < count($rawAccounts); $i++) {
			// add object class
			if (!in_array("AsteriskSIPUser", $partialAccounts[$i]['objectClass'])) {
				$partialAccounts[$i]['objectClass'][] = "AsteriskSIPUser";
			}
			// add account caller id
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountCallerID', 'AstAccountCallerID',
				'username', $this->messages['AstAccountCallerID'][3], $messages);
			// add host
			if (empty($rawAccounts[$i][$ids['asteriskAccount_AstAccountHost']])) {
				// default value
				$partialAccounts[$i]['AstAccountHost'] = 'dynamic';
			}
			else {
				$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountHost', 'AstAccountHost',
					'realname', $this->messages['AstAccountHost'][2], $messages);
			}
			//add context
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountContext', 'AstAccountContext',
				'realname', $this->messages['AstAccountContext'][2], $messages);
			//add account type
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountType', 'AstAccountType',
				'username', $this->messages['AstAccountType'][2], $messages);
			//add password
			if ($rawAccounts[$i][$ids['asteriskAccount_AstAccountRealmedPassword']] != "") {
				$attributes = ['AstAccountCallerID' => $partialAccounts[$i]['AstAccountCallerID']]; // fake attribute list for password building
				$pwdString = asteriskAccount::buildPasswordString($attributes, $this->moduleSettings, $rawAccounts[$i][$ids['asteriskAccount_AstAccountRealmedPassword']]);
				$partialAccounts[$i]['AstAccountRealmedPassword'] = $pwdString;
			}

			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountUserAgent', 'AstAccountUserAgent');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountAMAFlags', 'AstAccountAMAFlags');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountCallGroup', 'AstAccountCallGroup');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountDTMFMode', 'AstAccountDTMFMode');

			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountFromUser', 'AstAccountFromUser',
				'username', $this->messages['AstAccountFromUser'][1], $messages);

			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountFromDomain', 'AstAccountFromDomain',
				'domainname', $this->messages['AstAccountFromDomain'][1], $messages);

			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountFullContact', 'AstAccountFullContact');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountInsecure', 'AstAccountInsecure');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountMailbox', 'AstAccountMailbox');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountNAT', 'AstAccountNAT');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountDeny', 'AstAccountDeny');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountPermit', 'AstAccountPermit');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountPickupGroup', 'AstAccountPickupGroup');

			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountPort', 'AstAccountPort',
				'digit', $this->messages['AstAccountPort'][1], $messages);

			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountQualify', 'AstAccountQualify');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountRestrictCID', 'AstAccountRestrictCID');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountRTPTimeout', 'AstAccountRTPTimeout');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountRTPHoldTimeout', 'AstAccountRTPHoldTimeout');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountDisallowedCodec', 'AstAccountDisallowedCodec');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountAllowedCodec', 'AstAccountAllowedCodec');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountMusicOnHold', 'AstAccountMusicOnHold');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountExpirationTimestamp', 'AstAccountExpirationTimestamp');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountRegistrationContext', 'AstAccountRegistrationContext');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountRegistrationExten', 'AstAccountRegistrationExten');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountCanCallForward', 'AstAccountCanCallForward');

			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountIPAddress', 'AstAccountIPAddress',
				'ip', $this->messages['AstAccountIPAddress'][1], $messages);

			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountDefaultUser', 'AstAccountDefaultUser',
				'username', $this->messages['AstAccountDefaultUser'][1], $messages);

			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountRegistrationServer', 'AstAccountRegistrationServer');
			$this->mapSimpleUploadField($rawAccounts, $ids, $partialAccounts, $i, 'asteriskAccount_AstAccountLastQualifyMilliseconds', 'AstAccountLastQualifyMilliseconds');
		}
		return $messages;
	}


	/**
	 * {@inheritDoc}
	 */
	public function managesPasswordAttributes(): bool {
		return in_array('AsteriskSIPUser', $this->attributes['objectClass']);
	}

	/**
	 * {@inheritDoc}
	 */
	public function supportsForcePasswordChange(): bool {
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	public function passwordChangeRequested(string $password, array $modules, bool $forcePasswordChange): array {
		if (!in_array(static::class, $modules)) {
			return [];
		}
		$this->attributes['AstAccountRealmedPassword'][0] = asteriskAccount::buildPasswordString($this->attributes, $this->moduleSettings, $password);
		return [];
	}

	/**
	 * Builds the password string for the password attribute.
	 *
	 * @param array<string, string[]|string> $attributes LDAP attributes
	 * @param array<string, string[]> $moduleSettings module configuration settings
	 * @param string $password password
	 * @return string value for password attribute
	 */
	public static function buildPasswordString(&$attributes, &$moduleSettings, $password): string {
		$astRealm = self::ASTERISK_DEFAULT_REALM;
		if (!empty($moduleSettings['asteriskAccount_AsteriskRealm'][0])) {
			$astRealm = $moduleSettings['asteriskAccount_AsteriskRealm'][0];
		}
		return asteriskAccount::hashPassword($attributes['AstAccountCallerID'][0] . ":" . $astRealm . ":" . $password);
	}

	/**
	 * Hashes a password value to Asterisk format.
	 *
	 * @param string $password password
	 * @return string hash
	 */
	private static function hashPassword(string $password): string {
		return "{MD5}" . md5($password);
	}

	/**
	 * {@inheritDoc}
	 */
	public function checkSelfServiceOptions(array $fields, array $attributes, bool $passwordChangeOnly, array $readOnlyFields): array {
		$return = ['messages' => [], 'add' => [], 'del' => [], 'mod' => [], 'info' => []];
		if (!isset($attributes['objectClass']) || !in_array_ignore_case('AsteriskSIPUser', $attributes['objectClass'])) {
			return $return;
		}
		if (isset($_POST['posixAccount_password']) && ($_POST['posixAccount_password'] != '')) {
			if ($_POST['posixAccount_password'] != $_POST['posixAccount_password2']) {
				return $return;
			}
			elseif (!get_preg($_POST['posixAccount_password'], 'password')) {
				return $return;
			}
			elseif (in_array('syncAsteriskPassword', $fields)) {
				// sync password
				$return['mod']['AstAccountRealmedPassword'][0] = asteriskAccount::buildPasswordString($attributes, $this->selfServiceSettings->moduleSettings, $_POST['posixAccount_password']);
			}
		}
		return $return;
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_configOptions(array $scopes, array $allScopes): htmlElement|array {
		$configContainer = new htmlResponsiveRow();
		$configContainer->add(new htmlResponsiveInputField(_('Asterisk realm'), 'asteriskAccount_AsteriskRealm', null, 'AsteriskRealm'));
		$configContainer->addVerticalSpacer('1rem');
		$configHiddenHead = new htmlGroup();
		$configHiddenHead->addElement(new htmlOutputText(_('Hidden options')));
		$configHiddenHead->addElement(new htmlHelpLink('hiddenOptions'));
		$configContainer->add($configHiddenHead);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountUserAgent', false, _('User agent'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountAMAFlags', false, _('AMA flags'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountCallGroup', false, _('Call groups'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountDTMFMode', false, _('DTFM flags'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountFromUser', false, _('From user'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountFromDomain', false, _('From domain'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountFullContact', false, _('Full contact'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountInsecure', false, _('Insecure'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountMailbox', false, _('Mailbox'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountNAT', false, _('NAT'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountDeny', false, _('Deny'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountPermit', false, _('Permit'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountPickupGroup', false, _('Pickup group'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountPort', false, _('Port'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountQualify', false, _('Qualify'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountRestrictCID', false, _('Restrict caller ID'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountRTPTimeout', false, _('RTP timeout'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountRTPHoldTimeout', false, _('RTP hold timeout'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountDisallowedCodec', false, _('Disallowed codec'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountAllowedCodec', false, _('Allowed codec'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountMusicOnHold', false, _('Music on hold'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountExpirationTimestamp', false, _('Expiration timestamp'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountRegistrationContext', false, _('Registration context'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountRegistrationExten', false, _('Registration extension'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountCanCallForward', false, _('Can call forward'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountIPAddress', false, _('IP address'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountDefaultUser', false, _('Default user'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountRegistrationServer', false, _('Registration server'), null, true), 12, 4);
		$configContainer->add(new htmlResponsiveInputCheckbox('asteriskAccount_hideAstAccountLastQualifyMilliseconds', false, _('Last qualify milliseconds'), null, true), 12, 4);
		$configContainer->add(new htmlOutputText(''), 0, 4);
		return $configContainer;
	}

	/**
	 * @inheritDoc
	 */
	public function supportsPasswordQuickChangePage(): bool {
		return true;
	}

	/**
	 * @inheritDoc
	 */
	public function addPasswordQuickChangeAccountDetails(htmlResponsiveRow $row): void {
		// no details
	}

	/**
	 * @inheritDoc
	 */
	public function getPasswordQuickChangeOptions(bool $forcePasswordChangeByDefault): array {
		$options = [];
		if (in_array_ignore_case('AsteriskSIPUser', $this->attributes['objectClass'])) {
			$options[] = new PasswordQuickChangeOption('syncAsterisk', _('Change Asterisk password'));
		}
		return $options;
	}

	/**
	 * @inheritDoc
	 */
	public function getPasswordQuickChangeChanges(string $password): array {
		$attrs = [];
		if (isset($_POST['syncAsterisk'])) {
			$attrs['AstAccountRealmedPassword'][0] = self::buildPasswordString($this->attributes, $this->moduleSettings, $password);
		}
		return $attrs;
	}

	/**
	 * @inheritDoc
	 */
	public function getPasswordQuickChangePasswordStrengthUserName(): ?string {
		return null;
	}

	/**
	 * @inheritDoc
	 */
	public function getPasswordQuickChangePasswordStrengthAttributes(): array {
		return [];
	}

	/**
	 * @inheritDoc
	 */
	public function getPasswordQuickChangeIsPasswordInHistory(string $password): bool {
		return false;
	}

	/**
	 * @inheritDoc
	 */
	public function getListAttributeDescriptions(ConfiguredType $type): array {
		return [
			'astaccountcallerid' => _('Caller ID'),
			'astaccountcontext' => _('Account context'),
			'astaccounthost' => _('Host'),
			'astaccounttype' => _('Account type'),
			'astaccountuseragent' => _('User agent'),
			'astaccountamaflags' => _('AMA flags'),
			'astaccountcallgroup' => _('Call groups'),
			'astaccountdtmfmode' => _('DTFM flags'),
			'astaccountfromuser' => _('From user'),
			'astaccountfromdomain' => _('From domain'),
			'astaccountfullcontact' => _('Full contact'),
			'astaccountinsecure' => _('Insecure'),
			'astaccountmailbox' => _('Mailbox'),
			'astaccountnat' => _('NAT'),
			'astaccountdeny' => _('Deny'),
			'astaccountpermit' => _('Permit'),
			'astaccountpickupgroup' => _('Pickup group'),
			'astaccountport' => _('Port'),
			'astaccountqualify' => _('Qualify'),
			'astaccountrestrictcid' => _('Restrict caller ID'),
			'astaccountrtptimeout' => _('RTP timeout'),
			'astaccountrtpholdtimeout' => _('RTP hold timeout'),
			'astaccountdisallowedcodec' => _('Disallowed codec'),
			'astaccountallowedcodec' => _('Allowed codec'),
			'astaccountmusiconhold' => _('Music on hold'),
			'astaccountexpirationtimestamp' => _('Expiration timestamp'),
			'astaccountregistrationcontext' => _('Registration context'),
			'astaccountregistrationexten' => _('Registration extension'),
			'astaccountcancallforward' => _('Can call forward'),
			'astaccountipaddress' => _('IP address'),
			'astaccountdefaultuser' => _('Default user'),
			'astaccountregistrationserver' => _('Registration server'),
			'astaccountlastqualifymilliseconds' => _('Last qualify milliseconds'),
		];
	}

}
