from contextlib import contextmanager
import base64
import posixpath
import re
import urllib

import time

from parallels.core.logging import get_logger
from parallels.core.registry import Registry
from parallels.core.reports.model.issue import Issue
from parallels.core.utils.common import safe_format, if_not_none, is_empty
from parallels.plesk.hosting_description.model import ForwardingType
from parallels.plesk.hosting_description.utils import create_dict
from parallels.plesk.source.plesk import messages
from parallels.plesk.source.plesk.hosting_description.plesk_entity.custom_button import CustomButton
from parallels.plesk.source.plesk.hosting_description.plesk_entity.status import Status
from parallels.plesk.source.plesk.hosting_description.provider.config import (
    ALLOWED_TEMPLATE_ITEMS, ALLOWED_LIMITS, ALLOWED_PERMISSIONS
)
from parallels.plesk.source.plesk.hosting_description.plesk_entity_utils import create_plesk_entity_factory
from parallels.plesk.source.plesk.hosting_description.provider.utils import entity_data_dump

logger = get_logger(__name__)


class PleskHostingDescriptionProvider(object):
    """
    Use the method _entity_dump() and the decorator entity_data_dump() to catch unhandled exceptions
    """

    def __init__(self, selection, dump_agent):
        """
        :type selection: parallels.core.utils.pmm.agent.DumpAll|parallels.core.utils.pmm.agent.DumpSelected
        :type dump_agent: parallels.core.utils.pmm.agent.PmmMigrationAgentBase
        """
        self._dump_agent = dump_agent
        self._plesk_entity = create_plesk_entity_factory(selection, dump_agent)
        self._current_entities = []

    def get_description(self):
        """Returns hosting description according to schema parallels/plesk/hosting_description/schema.json
        :rtype: dict
        """
        plesk_admin = self._plesk_entity.get_admin()
        return create_dict(
            reseller_plans=self.get_reseller_plans(),
            hosting_plans=self.get_hosting_plans(plesk_admin.client_id),
            resellers=self.get_resellers(),
            customers=self.get_customers(plesk_admin.client_id),
            subscriptions=self.get_subscriptions(plesk_admin.client_id),
            users=self._get_users(plesk_admin),
            roles=self._get_roles(plesk_admin),
            database_servers=self.get_database_servers(),
        )

    @entity_data_dump(messages.DATA_DUMP_RESELLER_PLANS, [])
    def get_reseller_plans(self):
        """
        :rtype: list
        """
        reseller_plans = []
        for plesk_template in self._plesk_entity.get_templates(template_type='reseller'):
            with self._entity_dump(messages.ENTITY_DUMP_RESELLER_PLAN, plesk_template.name):
                reseller_plans.append(self._get_reseller_plan(plesk_template))
        return reseller_plans

    @entity_data_dump(messages.DATA_DUMP_HOSTING_PLANS, [])
    def get_hosting_plans(self, owner_id):
        """
        :type owner_id: str|unicode
        :rtype: list
        """
        hosting_plans = []
        plesk_templates = self._plesk_entity.get_templates(owner_id=owner_id, template_type='domain')
        plesk_templates += self._plesk_entity.get_templates(owner_id=owner_id, template_type='domain_addon')
        for plesk_template in plesk_templates:
            with self._entity_dump(messages.ENTITY_DUMP_HOSTING_PLAN, plesk_template.name):
                hosting_plans.append(self._get_hosting_plan(plesk_template))
        return hosting_plans

    @entity_data_dump(messages.DATA_DUMP_RESELLERS, [])
    def get_resellers(self):
        """
        :rtype: list
        """
        resellers = []
        for plesk_client in self._plesk_entity.get_clients(client_type='reseller'):
            with self._entity_dump(messages.ENTITY_DUMP_RESELLER, plesk_client.login):
                resellers.append(self._get_reseller(plesk_client))
        return resellers

    @entity_data_dump(messages.DATA_DUMP_CUSTOMERS, [])
    def get_customers(self, owner_id):
        """
        :type owner_id: str|unicode
        :rtype: list
        """
        customers = []
        for plesk_client in self._plesk_entity.get_clients(owner_id=owner_id, client_type='client'):
            with self._entity_dump(messages.ENTITY_DUMP_CUSTOMER, plesk_client.login):
                customers.append(self._get_customer(plesk_client))
        return customers

    @entity_data_dump(messages.DATA_DUMP_SUBSCRIPTIONS, [])
    def get_subscriptions(self, owner_id):
        """
        :type owner_id: str|unicode
        :rtype: list
        """
        subscriptions = []
        for plesk_domain in self._plesk_entity.get_domains(owner_id=owner_id, webspace_id='0'):
            with self._entity_dump(messages.ENTITY_DUMP_SUBSCRIPTION, plesk_domain.name):
                subscriptions.append(self._get_subscription(plesk_domain))
        return subscriptions

    @entity_data_dump(messages.DATA_DUMP_DATABASES, [])
    def get_database_servers(self):
        """
        :rtype: list
        """
        database_servers = []
        for plesk_database_server in self._plesk_entity.get_all_database_servers().values():
            database_server = self._get_database_server(plesk_database_server.database_server_id, include_admin=True)
            if database_server:
                database_servers.append(database_server)
        return database_servers

    def _get_reseller_plan(self, plesk_template):
        """
        :type plesk_template: parallels.plesk.source.plesk.hosting_description.plesk_entity.template.Template
        :rtype: dict
        """
        plan_items, ip_addresses, applications_filter = None, None, None
        plesk_template_items = self._plesk_entity.get_selected_templates_items().get(plesk_template.template_id)
        if plesk_template_items:
            plan_items = {}
            for name, value in plesk_template_items.items():
                if name == 'aps_bundle_filter_id':
                    applications_filter = self._get_applications_filter(value)
                elif name == 'tmpl_pool_id':
                    ip_addresses = self._get_ip_addresses(value)
                elif self._is_template_item_allowed(name):
                    plan_items[name] = value
            plan_items['overuse'] = self._get_overuse(plesk_template_items)
        return create_dict(
            name=plesk_template.name,
            guid=plesk_template.guid,
            items=plan_items,
            ip_addresses=ip_addresses,
            applications_filter=applications_filter,
        )

    def _get_hosting_plan(self, plesk_template):
        """
        :type plesk_template: parallels.plesk.source.plesk.hosting_description.plesk_entity.template.Template
        :rtype: dict
        """
        default_db_servers = []
        plan_items, log_rotation, applications_filter, php_settings, web_server_settings = None, None, None, None, None
        plesk_template_items = self._plesk_entity.get_selected_templates_items().get(plesk_template.template_id)
        if plesk_template_items:
            plan_items = {}
            for name, value in plesk_template_items.items():
                if name == 'aps_bundle_filter_id':
                    applications_filter = self._get_applications_filter(value)
                elif name == 'logrotation_id':
                    log_rotation = self._get_log_rotation(value)
                elif name == 'phpSettingsId':
                    php_settings = self._get_php_settings(value)
                elif name == 'webServerSettingsId':
                    web_server_settings = self._get_hosting_plan_web_server_settings(value)
                elif name in ['default_server_mysql', 'default_server_mssql', 'default_server_postgresql']:
                    database_server = self._get_database_server(value)
                    if database_server:
                        default_db_servers.append(database_server)
                elif name == 'php_handler_id':
                    plan_items['php_handler_id'] = value
                    service_node_id = self._plesk_entity.get_management_service_node_id()
                    if service_node_id is not None:
                        php_handler = self._plesk_entity.get_php_handlers(service_node_id).get(value)
                        if php_handler is not None:
                            plan_items['php_handler_type'] = php_handler.handler_type
                            plan_items['php_version'] = php_handler.version
                elif self._is_template_item_allowed(name):
                    plan_items[name] = value
            if plesk_template.template_type == 'domain':
                plan_items['overuse'] = self._get_overuse(plesk_template_items)
        return create_dict(
            name=plesk_template.name,
            guid=plesk_template.guid,
            is_addon=plesk_template.is_addon == 'true',
            items=plan_items,
            log_rotation=log_rotation,
            applications_filter=applications_filter,
            php_settings=php_settings,
            web_server_settings=web_server_settings,
            default_db_servers=default_db_servers if default_db_servers else None,
        )

    def _get_reseller(self, plesk_client):
        """
        :type plesk_client: parallels.plesk.source.plesk.hosting_description.plesk_entity.client.Client
        :rtype: dict
        """
        password, password_type = self._get_plesk_client_password(plesk_client)
        cl_params = self._plesk_entity.get_selected_cl_params().get(plesk_client.client_id)
        return create_dict(
            login=plesk_client.login,
            password=password,
            password_type=self._encode_password_type(password_type),
            guid=plesk_client.guid,
            external_id=plesk_client.external_id,
            locale=plesk_client.locale if plesk_client.locale else None,
            creation_date=plesk_client.creation_date,
            disabled_by=self._encode_status(plesk_client.status),
            description=plesk_client.description,
            contact_info=self._get_plesk_client_contact_info(plesk_client),
            subscription_info=self._get_subscription_info(plesk_client.client_id, 'client'),
            custom_buttons=self._get_custom_buttons(plesk_client),
            iis_application_pool=self._get_iis_application_pool(plesk_client.client_id, 'client'),
            limits=self._get_limits(plesk_client.limits_id, cl_params),
            permissions=self._get_permissions(plesk_client.permissions_id),
            applications_filter=self._get_object_applications_filter(plesk_client.client_id, 'client'),
            ip_addresses=self._get_ip_addresses(plesk_client.ip_pool_id),
            users=self._get_users(plesk_client),
            roles=self._get_roles(plesk_client),
            hosting_plans=self.get_hosting_plans(plesk_client.client_id),
            customers=self.get_customers(plesk_client.client_id),
            subscriptions=self.get_subscriptions(plesk_client.client_id),
        )

    def _get_customer(self, plesk_client):
        """
        :type plesk_client: parallels.plesk.source.plesk.hosting_description.plesk_entity.client.Client
        :rtype: dict
        """
        password, password_type = self._get_plesk_client_password(plesk_client)
        return create_dict(
            login=plesk_client.login,
            password=password,
            password_type=self._encode_password_type(password_type),
            guid=plesk_client.guid,
            external_id=plesk_client.external_id,
            locale=plesk_client.locale if plesk_client.locale else None,
            creation_date=plesk_client.creation_date,
            disabled_by=self._encode_status(plesk_client.status),
            description=plesk_client.description,
            contact_info=self._get_plesk_client_contact_info(plesk_client),
            custom_buttons=self._get_custom_buttons(plesk_client),
            iis_application_pool=self._get_iis_application_pool(plesk_client.client_id, 'client'),
            users=self._get_users(plesk_client),
            roles=self._get_roles(plesk_client),
            subscriptions=self.get_subscriptions(plesk_client.client_id),
        )

    def _get_subscription(self, plesk_domain):
        """
        :type plesk_domain: parallels.plesk.source.plesk.hosting_description.plesk_entity.domain.Domain
        :rtype: dict
        """
        hosting = self._plesk_entity.get_selected_hosting().get(plesk_domain.domain_id)
        forwarding = self._plesk_entity.get_selected_forwarding().get(plesk_domain.domain_id)
        plesk_subscription_properties = self._plesk_entity.get_subscription_properties(plesk_domain.domain_id, 'domain')
        limits_id = plesk_subscription_properties.get('limitsId')
        permissions_id = plesk_subscription_properties.get('permissionsId')
        ip_addresses = self._get_domain_ip_addresses(plesk_domain.domain_id)
        default_db_servers = []
        for property_name in ['default_server_mysql', 'default_server_mssql', 'default_server_postgresql']:
            database_server_id = plesk_subscription_properties.get(property_name)
            if database_server_id is not None:
                database_server = self._get_database_server(database_server_id)
                if database_server:
                    default_db_servers.append(database_server)
        return create_dict(
            name=plesk_domain.name,
            guid=plesk_domain.guid,
            external_id=plesk_domain.external_id,
            creation_date=plesk_domain.creation_date,
            domain_disabled_by=self._encode_status(plesk_domain.status),
            subscription_disabled_by=self._encode_status(plesk_domain.webspace_status),
            target_document_root=self._get_domain_document_root(plesk_domain) if hosting else None,
            forwarding_url=forwarding.redirect if forwarding else None,
            forwarding_type=self._get_forwarding_type(plesk_domain.hosting_type, forwarding) if forwarding else None,
            no_web_hosting=False if hosting or forwarding else True,
            web_hosting_settings=self._get_domain_web_hosting_settings(plesk_domain.domain_id) if hosting else None,
            php_settings=self._get_php_settings(self._plesk_entity.get_domain_parameter(plesk_domain.domain_id, 'phpSettingsId')),
            web_server_settings=self._get_domain_web_server_settings(plesk_domain),
            log_rotation=self._get_log_rotation(self._plesk_entity.get_domain_parameter(plesk_domain.domain_id, 'logrotation_id')),
            hotlink_protection=self._get_hotlink_protection(plesk_domain.domain_id),
            dynamic_ip_security=self._get_dynamic_ip_security(plesk_domain.domain_id),
            iis_application_pool=self._get_iis_application_pool(plesk_domain.domain_id, 'domain'),
            anonymous_ftp=self._get_anonymous_ftp(plesk_domain.domain_id),
            description=plesk_domain.description,
            reseller_description=plesk_domain.reseller_description,
            admin_description=plesk_domain.admin_description,
            sys_user=self._get_sys_user(hosting.sys_user_id) if hosting else None,
            dns_zone=self._get_dns_zone(plesk_domain.dns_zone_id),
            subscription_info=self._get_subscription_info(plesk_domain.domain_id, 'domain'),
            applications_filter=self._get_object_applications_filter(plesk_domain.domain_id, 'domain'),
            ip_addresses=ip_addresses if ip_addresses else None,
            limits=self._get_limits(limits_id, plesk_subscription_properties) if limits_id else None,
            permissions=self._get_permissions(permissions_id) if permissions_id else None,
            default_db_servers=default_db_servers if default_db_servers else None,
            ftp_users=self._get_ftp_users(plesk_domain.domain_id),
            web_users=self._get_web_users(plesk_domain.domain_id),
            certificates=self._get_certificates(plesk_domain.domain_id),
            aps_applications=self._get_applications(plesk_domain.domain_id),
            protected_directories=self._get_protected_directories(plesk_domain) if hosting else None,
            mail_service=self._get_mail_service(plesk_domain.domain_id),
            mail_service_limits=self._get_mail_service_limits(plesk_domain.domain_id),
            mail_lists_service=self._get_mail_lists_service(plesk_domain.domain_id),
            addon_domains=self._get_addon_domains(plesk_domain.domain_id),
            subdomains=self._get_subdomains(plesk_domain.domain_id),
            aliases=self._get_domain_aliases(plesk_domain.domain_id),
            databases=self._get_databases(plesk_domain.domain_id),
            database_users=self._get_any_database_users(plesk_domain.domain_id),
            scheduled_tasks=self._get_scheduled_tasks(hosting.sys_user_id) if hosting else None,
        )

    @entity_data_dump(messages.DATA_DUMP_DOMAINS, [])
    def _get_addon_domains(self, webspace_id):
        """
        :type webspace_id: str|unicode
        :rtype: list
        """
        addon_domains = []
        for plesk_domain in self._plesk_entity.get_domains(webspace_id=webspace_id, parent_domain_id='0'):
            with self._entity_dump(messages.ENTITY_DUMP_DOMAIN, plesk_domain.name):
                addon_domains.append(self._get_addon_domain(plesk_domain))
        return addon_domains

    def _get_addon_domain(self, plesk_domain):
        """
        :type plesk_domain: parallels.plesk.source.plesk.hosting_description.plesk_entity.domain.Domain
        :rtype: dict
        """
        hosting = self._plesk_entity.get_selected_hosting().get(plesk_domain.domain_id)
        forwarding = self._plesk_entity.get_selected_forwarding().get(plesk_domain.domain_id)
        return create_dict(
            name=plesk_domain.name,
            guid=plesk_domain.guid,
            creation_date=plesk_domain.creation_date,
            disabled_by=self._encode_status(plesk_domain.status),
            target_document_root=self._get_domain_document_root(plesk_domain) if hosting else None,
            forwarding_url=forwarding.redirect if forwarding else None,
            forwarding_type=self._get_forwarding_type(plesk_domain.hosting_type, forwarding) if forwarding else None,
            no_web_hosting=False if hosting or forwarding else True,
            web_hosting_settings=self._get_domain_web_hosting_settings(plesk_domain.domain_id) if hosting else None,
            php_settings=self._get_php_settings(self._plesk_entity.get_domain_parameter(plesk_domain.domain_id, 'phpSettingsId')),
            web_server_settings=self._get_domain_web_server_settings(plesk_domain),
            log_rotation=self._get_log_rotation(self._plesk_entity.get_domain_parameter(plesk_domain.domain_id, 'logrotation_id')),
            hotlink_protection=self._get_hotlink_protection(plesk_domain.domain_id),
            dynamic_ip_security=self._get_dynamic_ip_security(plesk_domain.domain_id),
            iis_application_pool=self._get_iis_application_pool(plesk_domain.domain_id, 'domain'),
            dns_zone=self._get_dns_zone(plesk_domain.dns_zone_id),
            certificates=self._get_certificates(plesk_domain.domain_id),
            aps_applications=self._get_applications(plesk_domain.domain_id),
            protected_directories=self._get_protected_directories(plesk_domain) if hosting else None,
            mail_service=self._get_mail_service(plesk_domain.domain_id),
            mail_lists_service=self._get_mail_lists_service(plesk_domain.domain_id),
            aliases=self._get_domain_aliases(plesk_domain.domain_id),
        )

    @entity_data_dump(messages.DATA_DUMP_DOMAINS, [])
    def _get_subdomains(self, webspace_id):
        """
        :type webspace_id: str|unicode
        :rtype: list
        """
        subdomains = []
        for plesk_domain in self._plesk_entity.get_domains(webspace_id=webspace_id):
            if plesk_domain.parent_domain_id == '0':
                continue
            parent_plesk_domain = self._plesk_entity.get_selected_domains().get(plesk_domain.parent_domain_id)
            if not parent_plesk_domain:
                continue
            with self._entity_dump(messages.ENTITY_DUMP_SUBDOMAIN, plesk_domain.name):
                subdomains.append(self._get_subdomain(plesk_domain, parent_plesk_domain))
        return subdomains

    def _get_subdomain(self, plesk_domain, parent_plesk_domain):
        """
        :type plesk_domain: parallels.plesk.source.plesk.hosting_description.plesk_entity.domain.Domain
        :type parent_plesk_domain: parallels.plesk.source.plesk.hosting_description.plesk_entity.domain.Domain
        :rtype: dict
        """
        hosting = self._plesk_entity.get_selected_hosting().get(plesk_domain.domain_id)
        forwarding = self._plesk_entity.get_selected_forwarding().get(plesk_domain.domain_id)
        return create_dict(
            name=plesk_domain.name,
            parent_domain_name=parent_plesk_domain.name,
            guid=plesk_domain.guid,
            creation_date=plesk_domain.creation_date,
            disabled_by=self._encode_status(plesk_domain.status),
            target_document_root=self._get_domain_document_root(plesk_domain) if hosting else None,
            forwarding_url=forwarding.redirect if forwarding else None,
            forwarding_type=self._get_forwarding_type(plesk_domain.hosting_type, forwarding) if forwarding else None,
            no_web_hosting=False if hosting or forwarding else True,
            web_hosting_settings=self._get_domain_web_hosting_settings(plesk_domain.domain_id) if hosting else None,
            php_settings=self._get_php_settings(self._plesk_entity.get_domain_parameter(plesk_domain.domain_id, 'phpSettingsId')),
            web_server_settings=self._get_domain_web_server_settings(plesk_domain),
            log_rotation=self._get_log_rotation(self._plesk_entity.get_domain_parameter(plesk_domain.domain_id, 'logrotation_id')),
            hotlink_protection=self._get_hotlink_protection(plesk_domain.domain_id),
            dynamic_ip_security=self._get_dynamic_ip_security(plesk_domain.domain_id),
            iis_application_pool=self._get_iis_application_pool(plesk_domain.domain_id, 'domain'),
            dns_zone=self._get_dns_zone(plesk_domain.dns_zone_id),
            aps_applications=self._get_applications(plesk_domain.domain_id),
            protected_directories=self._get_protected_directories(plesk_domain) if hosting else None,
        )

    @entity_data_dump(messages.DATA_DUMP_WEB_HOSTING_SETTINGS, {})
    def _get_domain_web_hosting_settings(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: dict
        """
        web_hosting_settings = {}
        plesk_domain = self._plesk_entity.get_selected_domains().get(domain_id)
        hosting = self._plesk_entity.get_selected_hosting().get(domain_id)
        if plesk_domain and hosting:
            web_hosting_settings.update(create_dict(
                ssl=hosting.ssl == 'true',
                ssi=hosting.ssi == 'true',
                ssi_html=hosting.ssi_html == 'true',
                php=hosting.php == 'true',
                php_handler_id=hosting.php_handler_id,
                cgi=hosting.cgi == 'true',
                fastcgi=hosting.fastcgi == 'true' if hosting.fastcgi is not None else None,
                perl=hosting.perl == 'true',
                python=hosting.python == 'true',
                asp=hosting.asp == 'true',
                write_modify=not is_empty(hosting.write_modify),
                webdeploy=hosting.webdeploy == 'true',
                webstat=hosting.webstat,
            ))
            if plesk_domain.cert_rep_id != '0' and hosting.certificate_id != '0':
                if hosting.certificate_id in self._plesk_entity.get_selected_repositories().get(plesk_domain.cert_rep_id, []):
                    certificate = self._plesk_entity.get_selected_certificates().get(hosting.certificate_id)
                    if certificate:
                        web_hosting_settings['ssl_certificate'] = certificate.name
            if self._dump_agent.source_server().is_windows():
                if hosting.asp_dot_net == 'true':
                    web_hosting_settings.update(create_dict(
                        asp_dot_net=True,
                        managed_runtime_version=hosting.managed_runtime_version,
                    ))
                else:
                    web_hosting_settings['asp_dot_net'] = False
            service_node_id = self._plesk_entity.get_management_service_node_id()
            if service_node_id is not None:
                php_handler = self._plesk_entity.get_php_handlers(service_node_id).get(hosting.php_handler_id)
                if php_handler is not None:
                    web_hosting_settings['php_handler_type'] = php_handler.handler_type
                    web_hosting_settings['php_version'] = php_handler.version
        return web_hosting_settings

    @entity_data_dump(messages.DATA_DUMP_IP_ADDRESSES, [])
    def _get_domain_ip_addresses(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: list
        """
        ip_addresses = []
        plesk_domain = self._plesk_entity.get_selected_domains().get(domain_id)
        if not plesk_domain:
            return ip_addresses
        plesk_client = self._plesk_entity.get_selected_clients().get(plesk_domain.owner_id)
        if not plesk_client:
            return ip_addresses
        plesk_domain_web_service = self._plesk_entity.get_domain_service(domain_id, 'web')
        if not plesk_domain_web_service:
            return ip_addresses
        for ip_address_id in self._plesk_entity.get_selected_ip_collections().get(plesk_domain_web_service.ip_collection_id, []):
            for plesk_ip_pool_item in self._plesk_entity.get_selected_ip_pools_items().get(plesk_client.ip_pool_id, []):
                if plesk_ip_pool_item.ip_address_id != ip_address_id:
                    continue
                plesk_ip_address = self._plesk_entity.get_selected_ip_addresses().get(ip_address_id)
                if plesk_ip_address:
                    ip_addresses.append(create_dict(
                        type=plesk_ip_pool_item.ip_address_type,
                        address=plesk_ip_address.ip_address,
                    ))
        return ip_addresses

    @entity_data_dump(messages.DATA_DUMP_FTP_USERS, [])
    def _get_ftp_users(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: list
        """
        ftp_users = []
        plesk_domain = self._plesk_entity.get_selected_domains().get(domain_id)
        if not plesk_domain:
            return ftp_users
        for plesk_ftp_user in self._plesk_entity.get_ftp_users(domain_id=domain_id):
            plesk_sys_user = self._plesk_entity.get_selected_sys_users().get(plesk_ftp_user.sys_user_id)
            if not plesk_sys_user:
                continue
            with self._entity_dump(messages.ENTITY_DUMP_FTP_USER, plesk_sys_user.login):
                ftp_users.append(self._get_ftp_user(plesk_ftp_user, plesk_sys_user, plesk_domain))
        return ftp_users

    def _get_ftp_user(self, plesk_ftp_user, plesk_sys_user, plesk_domain):
        """
        :type plesk_ftp_user: parallels.plesk.source.plesk.hosting_description.plesk_entity.ftp_user.FtpUser
        :type plesk_sys_user: parallels.plesk.source.plesk.hosting_description.plesk_entity.sys_user.SysUser
        :type plesk_domain: parallels.plesk.source.plesk.hosting_description.plesk_entity.domain.Domain
        :rtype: dict
        """
        password, password_type = self._get_plesk_sys_user_password(plesk_sys_user)
        allow_read, allow_write = self._decode_ftp_user_permission(plesk_ftp_user.permission)
        # Calculate system user`s home directory path relative to webspace root directory
        # /var/www/vhosts/a10-52-162-134.qa.plesk.ru/user/au134add -> /user/au134add
        home_directory = None
        pos = plesk_sys_user.home.find(plesk_domain.ascii_name)
        if pos != -1:
            home_directory = plesk_sys_user.home[pos + len(plesk_domain.ascii_name):]
        return create_dict(
            login=plesk_sys_user.login,
            password=password,
            password_type=password_type,
            home_directory=home_directory,
            disk_quota=plesk_sys_user.quota,
            allow_read=allow_read,
            allow_write=allow_write,
        )

    @entity_data_dump(messages.DATA_DUMP_WEB_USERS, [])
    def _get_web_users(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: list
        """
        web_users = []
        for plesk_web_user in self._plesk_entity.get_web_users(domain_id=domain_id):
            plesk_sys_user = self._plesk_entity.get_selected_sys_users().get(plesk_web_user.sys_user_id)
            if not plesk_sys_user:
                continue
            with self._entity_dump(messages.ENTITY_DUMP_WEB_USER, plesk_sys_user.login):
                web_users.append(self._get_web_user(plesk_web_user, plesk_sys_user))
        return web_users

    def _get_web_user(self, plesk_web_user, plesk_sys_user):
        """
        :type plesk_web_user: parallels.plesk.source.plesk.hosting_description.plesk_entity.web_user.WebUser
        :type plesk_sys_user: parallels.plesk.source.plesk.hosting_description.plesk_entity.sys_user.SysUser
        :rtype: dict
        """
        password, password_type = self._get_plesk_sys_user_password(plesk_sys_user)
        return create_dict(
            login=plesk_sys_user.login,
            password=password,
            password_type=password_type,
            disk_quota=plesk_sys_user.quota,
            php=plesk_web_user.php == 'true',
            asp_dot_net=plesk_web_user.asp_dot_net == 'true',
            write_modify=bool(plesk_web_user.php),
            cgi=plesk_web_user.cgi == 'true',
            fastcgi=plesk_web_user.fastcgi == 'true',
        )

    @entity_data_dump(messages.DATA_DUMP_SSL_CERTIFICATES, [])
    def _get_certificates(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: list
        """
        certificates = []
        plesk_domain = self._plesk_entity.get_selected_domains().get(domain_id)
        if not plesk_domain:
            return certificates
        for certificate_id in self._plesk_entity.get_selected_repositories().get(plesk_domain.cert_rep_id, []):
            certificate = self._plesk_entity.get_selected_certificates().get(certificate_id)
            if not certificate:
                continue
            with self._entity_dump(messages.ENTITY_DUMP_SSL_CERTIFICATE, certificate.name):
                certificates.append(self._get_certificate(certificate))
        return certificates

    def _get_certificate(self, certificate):
        """
        :type certificate: parallels.plesk.source.plesk.hosting_description.plesk_entity.certificate.Certificate
        :rtype: dict
        """
        return create_dict(
            name=certificate.name,
            certificate=urllib.unquote_plus(certificate.cert) if certificate.cert else None,
            private_key=urllib.unquote_plus(certificate.pvt_key) if certificate.pvt_key else None,
            signing_request=urllib.unquote_plus(certificate.csr) if certificate.csr else None,
            ca_certificate=urllib.unquote_plus(certificate.ca_cert) if certificate.ca_cert else None,
        )

    @entity_data_dump(messages.DATA_DUMP_APPLICATIONS, [])
    def _get_applications(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: list
        """
        applications = []
        for application_node in self._plesk_entity.get_applications(domain_id):
            application_name = application_node.findtext('sapp-spec/sapp-name')
            if not application_name:
                continue
            with self._entity_dump(messages.ENTITY_DUMP_APPLICATION, application_name):
                applications.append(self._get_application(application_name, application_node))
        return applications

    def _get_application(self, application_name, application_node):
        """
        :type application_node: xml.etree.ElementTree.Element
        :rtype: dict
        """
        application_context_node = application_node.find('context')
        application_settings = {
            setting.findtext('name'): setting.findtext('value')
            for setting in application_node.findall('sapp-settings/setting')
        }
        force_updates = None
        if 'forceUpdates' in application_settings:
            force_updates = bool(
                application_settings['forceUpdates'] == 'true' or
                application_settings['forceUpdates'] == base64.b64encode('true')
            )
        return create_dict(
            name=application_name,
            installation_directory=create_dict(
                path=application_node.findtext('sapp-installdir/sapp-prefix', ''),
                ssl=bool(application_node.findall('sapp-installdir/sapp-ssl')),
                aps_registry_id=application_node.findtext('sapp-installdir/aps-registry-id'),
            ),
            package_id=application_node.findtext('sapp-spec/sapp-package-id'),
            version=application_node.findtext('sapp-spec/sapp-version'),
            release=application_node.findtext('sapp-spec/sapp-release'),
            context=application_context_node.get('type') if application_context_node is not None else None,
            aps_registry_id=application_node.findtext('aps-registry-id'),
            force_updates=force_updates,
            license=self._get_application_license(application_node),
            controller_dump=application_node.findtext('sapp-apsc'),
            databases=self._get_application_databases(application_node),
            entry_points=self._get_application_entry_points(application_node),
        )

    @entity_data_dump(messages.DATA_DUMP_APPLICATION_LICENSE)
    def _get_application_license(self, application_node):
        """
        :type application_node: xml.etree.ElementTree.Element
        :rtype: dict | None
        """
        license_node = application_node.find('sapp-license')
        if not license_node:
            return None
        license_type = license_node.findtext('license-type')
        activation_code = license_node.findtext('activation-code')
        if license_type and activation_code:
            use_stub_node = license_node.find('use-stub')
            return create_dict(
                type=license_type,
                activation_code=activation_code,
                aps_registry_id=license_node.findtext('aps-registry-id'),
                use_stub=use_stub_node.text == 'true' if use_stub_node else None,
            )
        return None

    @entity_data_dump(messages.DATA_DUMP_DATABASES, [])
    def _get_application_databases(self, application_node):
        """
        :type application_node: xml.etree.ElementTree.Element
        :rtype: list
        """
        databases = []
        for database_node in application_node.findall('database'):
            plesk_database = self._plesk_entity.get_selected_databases().get(database_node.get('id'))
            if plesk_database:
                with self._entity_dump(messages.ENTITY_DUMP_DATABASE, plesk_database.name):
                    databases.append(self._get_database(plesk_database, database_node.get('aps-registry-id')))
        return databases

    @entity_data_dump(messages.DATA_DUMP_APPLICATION_ENTRY_POINTS, [])
    def _get_application_entry_points(self, application_node):
        """
        :type application_node: xml.etree.ElementTree.Element
        :rtype: list
        """
        entry_points = []
        for entry_point_node in application_node.findall('sapp-entry-point'):
            label = entry_point_node.findtext('label')
            description = entry_point_node.findtext('description')
            permission_name, permission_class = None, None
            permission_node = entry_point_node.find('limits-and-permissions/permission')
            if permission_node is not None:
                permission_name = permission_node.get('name')
                permission_class = permission_node.get('class')
            if label and description and permission_name and permission_class:
                hidden_node = entry_point_node.find('hidden')
                http_node = entry_point_node.find('http')
                entry_points.append(create_dict(
                    label=label,
                    description=description,
                    permission={
                        'name': permission_name,
                        'class': permission_class,
                    },
                    hidden=hidden_node.text == 'true' if hidden_node is not None else None,
                    http=http_node.text == 'true' if http_node is not None else None,
                    icon=entry_point_node.findtext('icon'),
                ))
        return entry_points

    @entity_data_dump(messages.DATA_DUMP_PROTECTED_DIRECTORIES, [])
    def _get_protected_directories(self, plesk_domain):
        """
        :type plesk_domain: parallels.plesk.source.plesk.hosting_description.plesk_entity.domain.Domain
        :rtype: list
        """
        protected_directories = []
        for plesk_protected_directory in self._plesk_entity.get_protected_directories(domain_id=plesk_domain.domain_id):
            protected_directories.append(create_dict(
                path=plesk_protected_directory.path,
                title=plesk_protected_directory.realm,
                users=self._get_protected_directories_users(plesk_protected_directory.protected_directory_id)
            ))
        return protected_directories

    @entity_data_dump(messages.DATA_DUMP_PROTECTED_DIRECTORIES_USERS, [])
    def _get_protected_directories_users(self, protected_directory_id):
        """
        :type protected_directory_id: str | unicode
        :rtype: list
        """
        protected_directories_users = []
        for plesk_protected_directory_user in self._plesk_entity.get_protected_directories_users(protected_directory_id=protected_directory_id):
            password, password_type = self._get_plesk_protected_directory_user_password(plesk_protected_directory_user)
            protected_directories_users.append(create_dict(
                login=plesk_protected_directory_user.login,
                password=password,
                password_type=password_type,
            ))
        return protected_directories_users

    @entity_data_dump(messages.DATA_DUMP_MAILBOXES, {})
    def _get_mail_service(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: dict
        """
        plesk_domain_mail_service = self._plesk_entity.get_domain_service(domain_id, 'mail')
        webmail_type, outgoing_messages_limit = None, None
        if plesk_domain_mail_service and plesk_domain_mail_service.parameters_id not in [None, '0']:
            webmail_type = self._plesk_entity.get_parameter(plesk_domain_mail_service.parameters_id, 'webmail_type')
            outgoing_messages_limit = self._plesk_entity.get_parameter(plesk_domain_mail_service.parameters_id, 'outgoing_messages_limit')
        grey_listing = None
        if not self._dump_agent.source_server().is_windows():
            grey_listing = self._plesk_entity.get_domain_parameter(domain_id, 'gl_filter') != 'off'
        if not plesk_domain_mail_service:
            return None
        return create_dict(
            disabled_by=self._encode_status(plesk_domain_mail_service.status),
            webmail=webmail_type,
            catch_all_action=self._get_catch_all_action(domain_id),
            grey_listing=grey_listing,
            outgoing_messages_limit=outgoing_messages_limit,
            domain_keys=self._get_domain_keys(domain_id),
            mail_users=self._get_mail_users(domain_id),
        )

    @entity_data_dump(messages.DATA_DUMP_MAIL_SETTINGS)
    def _get_catch_all_action(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: dict | None
        """
        plesk_domain_mail_service = self._plesk_entity.get_domain_service(domain_id, 'mail')
        if not plesk_domain_mail_service or plesk_domain_mail_service.parameters_id in [None, '0']:
            return None
        nonexist_mail = self._plesk_entity.get_parameter(plesk_domain_mail_service.parameters_id, 'nonexist_mail')
        if nonexist_mail in ['reject', 'discard']:
            return create_dict(
                name='reject',
            )
        elif nonexist_mail == 'catch':
            catch_mode = self._plesk_entity.get_parameter(plesk_domain_mail_service.parameters_id, 'catch_mode')
            if catch_mode == 'catch_ip':
                catch_ip = self._plesk_entity.get_parameter(plesk_domain_mail_service.parameters_id, 'catch_ip')
                if catch_ip:
                    return create_dict(
                        name='forward',
                        forward_ip=catch_ip,
                    )
                return None
            catch_address = self._plesk_entity.get_parameter(plesk_domain_mail_service.parameters_id, 'catch_addr')
            if catch_address:
                return create_dict(
                    name='forward',
                    forward_email=catch_address,
                )
        elif nonexist_mail == 'bounce':
            bounce_message = self._plesk_entity.get_parameter(plesk_domain_mail_service.parameters_id, 'bounce_mess')
            if bounce_message:
                return create_dict(
                    name='bounce',
                    bounce_message=bounce_message,
                )
        return None

    @entity_data_dump(messages.DATA_DUMP_DOMAIN_KEYS)
    def _get_domain_keys(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: dict | None
        """
        plesk_domain = self._plesk_entity.get_selected_domains().get(domain_id)
        if not plesk_domain:
            return None
        plesk_domain_mail_service = self._plesk_entity.get_domain_service(domain_id, 'mail')
        if not plesk_domain_mail_service or plesk_domain_mail_service.parameters_id in [None, '0']:
            return None
        domain_keys_sign = self._plesk_entity.get_parameter(plesk_domain_mail_service.parameters_id, 'domain_keys_sign')
        if domain_keys_sign is None:
            return None
        private_key, public_key = None, None
        if self._dump_agent.source_server().is_windows():
            private_key = self._plesk_entity.get_parameter(plesk_domain_mail_service.parameters_id, 'domain_key')
            public_key = self._plesk_entity.get_parameter(plesk_domain_mail_service.parameters_id, 'domain_key_public')
        else:
            domain_key_host = 'default._domainkey.%s.' % plesk_domain.name
            for plesk_dns_record in self._plesk_entity.get_dns_records(dns_zone_id=plesk_domain.dns_zone_id):
                if plesk_dns_record.host == domain_key_host:
                    public_key = plesk_dns_record.value[2:-1]
                    break
            if public_key:
                with self._dump_agent.source_server().runner() as runner:
                    private_key_path = posixpath.join('/etc/domainkeys', plesk_domain.ascii_name, 'default')
                    if runner.file_exists(private_key_path):
                        private_key = runner.get_file_contents(private_key_path)
        return create_dict(
            enabled=domain_keys_sign == 'true',
            private_key=private_key if public_key else None,
            public_key=public_key if private_key else None,
        )

    @entity_data_dump(messages.DATA_DUMP_MAILBOXES, [])
    def _get_mail_users(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: list
        """
        mail_users = []
        plesk_domain = self._plesk_entity.get_selected_domains().get(domain_id)
        if not plesk_domain:
            return mail_users
        for plesk_mail_name in self._plesk_entity.get_mail_names(domain_id=domain_id):
            with self._entity_dump(messages.ENTITY_DUMP_MAILBOX, plesk_mail_name.name):
                mail_users.append(self._get_mail_user(plesk_mail_name, plesk_domain))
        return mail_users

    def _get_mail_user(self, plesk_mail_name, plesk_domain):
        """
        :type plesk_mail_name: parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_name.MailName
        :type plesk_domain: parallels.plesk.source.plesk.hosting_description.plesk_entity.domain.Domain
        :rtype: dict
        """
        password, password_type = self._get_plesk_mail_user_password(plesk_mail_name)
        if self._dump_agent.source_server().is_windows():
            spamfilter = create_dict(
                enabled=False,
            )
            plesk_sa_mail_name = self._plesk_entity.get_all_sa_mail_names().get(
                '%s@%s' % (plesk_mail_name.name, plesk_domain.ascii_name)
            )
            if plesk_sa_mail_name:
                def get_value(name):
                    return self._plesk_entity.get_sa_parameter_value(plesk_sa_mail_name.mail_name_id, name)

                def get_values(name):
                    return self._plesk_entity.get_sa_parameter_values(plesk_sa_mail_name.mail_name_id, name)

                actions_map = {'save': 'mark', 'delete': 'delete'}
                spamfilter.update(create_dict(
                    enabled=plesk_sa_mail_name.flt_enabled in ['user', 'both'],
                    action=actions_map.get(plesk_sa_mail_name.spam_action),
                    subject_text=plesk_sa_mail_name.rw_subject_tag,
                    body_text=get_value('report_text'),
                    sensitivity=plesk_sa_mail_name.hits_required,
                    white_list=if_not_none(self._plesk_entity.get_all_sa_lists().get(plesk_sa_mail_name.name, {}).get('white'), lambda l: filter(bool, l)),
                    black_list=if_not_none(self._plesk_entity.get_all_sa_lists().get(plesk_sa_mail_name.name, {}).get('black'), lambda l: filter(bool, l)),
                    trusted_languages=if_not_none(get_values('ok_language'), lambda l: filter(bool, l)),
                    trusted_locales=if_not_none(get_values('ok_locale'), lambda l: filter(bool, l)),
                    trusted_networks=if_not_none(get_values('trusted_network'), lambda l: filter(bool, l)),
                ))
            antivir_incoming = self._plesk_entity.get_mail_name_parameter(plesk_mail_name.mail_name_id, 'antivir_incoming')
            antivir_outgoing = self._plesk_entity.get_mail_name_parameter(plesk_mail_name.mail_name_id, 'antivir_outgoing')
            if antivir_incoming == 'true' and antivir_outgoing == 'true':
                antivirus = 'inout'
            elif antivir_incoming == 'true':
                antivirus = 'in'
            elif antivir_outgoing == 'true':
                antivirus = 'out'
            else:
                antivirus = 'none'
        else:
            spamfilter = create_dict(
                enabled=plesk_mail_name.spamfilter == 'true',
            )
            plesk_spam_filter = self._plesk_entity.get_all_spam_filters().get(
                '%s@%s' % (plesk_mail_name.name, plesk_domain.ascii_name)
            )
            if plesk_spam_filter:
                def get_value(name):
                    return self._plesk_entity.get_spam_filter_preference_value(plesk_spam_filter.spam_filter_id, name)

                def get_values(name):
                    return self._plesk_entity.get_spam_filter_preference_values(plesk_spam_filter.spam_filter_id, name)

                actions_map = {'text': 'mark', 'move': 'move', 'delete': 'delete'}
                spamfilter.update(create_dict(
                    action=actions_map.get(get_value('action')),
                    subject_text=if_not_none(get_value('rewrite_header'), lambda s: re.sub('^subject ', '', s)),
                    sensitivity=get_value('required_score'),
                    white_list=if_not_none(get_values('whitelist_from'), lambda l: filter(bool, l)),
                    black_list=if_not_none(get_values('blacklist_from'), lambda l: filter(bool, l)),
                ))
            virusfilter_map = {'incoming': 'in', 'outgoing': 'out', 'any': 'inout'}
            antivirus = virusfilter_map.get(plesk_mail_name.virusfilter, 'none')
        outgoing_messages_limit = self._plesk_entity.get_mail_name_parameter(plesk_mail_name.mail_name_id, 'outgoing_messages_limit')
        return create_dict(
            name=plesk_mail_name.name,
            password=password,
            password_type=self._encode_password_type(password_type),
            disk_quota=plesk_mail_name.mbox_quota,
            mailbox=plesk_mail_name.postbox == 'true',
            deployer_action='update',
            description=plesk_mail_name.description,
            aliases=self._plesk_entity.get_selected_mail_aliases().get(plesk_mail_name.mail_name_id),
            forwarding=self._get_mail_forwarding(plesk_mail_name),
            auto_reply=self._get_mail_auto_reply(plesk_mail_name),
            spamfilter=spamfilter,
            antivirus=antivirus,
            outgoing_messages_limit=outgoing_messages_limit,
        )

    @entity_data_dump(messages.DATA_DUMP_MAIL_FORWARDING)
    def _get_mail_forwarding(self, plesk_mail_name):
        """
        :type plesk_mail_name: parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_name.MailName
        :rtype: dict | None
        """
        addresses = self._plesk_entity.get_selected_mail_redirects().get(plesk_mail_name.mail_name_id)
        if not addresses:
            return None
        return create_dict(
            addresses=addresses,
            enabled=plesk_mail_name.mail_group == 'true',
        )

    @entity_data_dump(messages.DATA_DUMP_MAIL_AUTO_REPLY_SETTINGS)
    def _get_mail_auto_reply(self, plesk_mail_name):
        """
        :type plesk_mail_name: parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_name.MailName
        :rtype: dict | None
        """
        for plesk_mail_responder in self._plesk_entity.get_mail_responders(mail_name_id=plesk_mail_name.mail_name_id):
            if plesk_mail_responder.subject:
                forwarding = self._plesk_entity.get_selected_mail_responders_forwarding().get(plesk_mail_responder.mail_responder_id)
                return create_dict(
                    subject=plesk_mail_responder.subject,
                    message=plesk_mail_responder.text if plesk_mail_responder.text else '',
                    enabled=plesk_mail_name.auto_responder == 'true',
                    forwarding_address=forwarding[0] if forwarding else '',
                    content_type=plesk_mail_responder.content_type if plesk_mail_responder.content_type else None,
                    responses_frequency=plesk_mail_responder.ans_freq if plesk_mail_responder.ans_freq else None,
                    attachments=self._plesk_entity.get_selected_mail_responders_attachments().get(plesk_mail_responder.mail_responder_id, []),
                )
        return None

    @entity_data_dump(messages.DATA_DUMP_MAIL_SETTINGS, {})
    def _get_mail_service_limits(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: dict
        """
        plesk_subscription_properties = self._plesk_entity.get_subscription_properties(domain_id, 'domain')
        return create_dict(
            outgoing_messages_domain_limit=plesk_subscription_properties.get('outgoing_messages_domain_limit'),
            outgoing_messages_enable_sendmail=plesk_subscription_properties.get('outgoing_messages_enable_sendmail'),
            outgoing_messages_mbox_limit=plesk_subscription_properties.get('outgoing_messages_mbox_limit'),
            outgoing_messages_subscription_limit=plesk_subscription_properties.get('outgoing_messages_subscription_limit'),
        )

    @entity_data_dump(messages.DATA_DUMP_MAILLISTS, {})
    def _get_mail_lists_service(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: dict
        """
        plesk_domain_mail_lists_service = self._plesk_entity.get_domain_service(domain_id, 'maillists')
        return create_dict(
            disabled_by=self._encode_status(plesk_domain_mail_lists_service.status) if plesk_domain_mail_lists_service else None,
            mail_lists=self._get_mail_lists(domain_id),
        )

    @entity_data_dump(messages.DATA_DUMP_MAILLISTS, [])
    def _get_mail_lists(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: list
        """
        mail_lists = []
        for plesk_mail_list in self._plesk_entity.get_mail_lists(domain_id=domain_id):
            plesk_mail_list_settings = self._plesk_entity.get_mail_list_settings(domain_id, plesk_mail_list.name)
            if not plesk_mail_list_settings:
                continue
            with self._entity_dump(messages.ENTITY_DUMP_MAILLIST, plesk_mail_list.name):
                mail_lists.append(self._get_mail_list(plesk_mail_list, plesk_mail_list_settings))
        return mail_lists

    def _get_mail_list(self, plesk_mail_list, plesk_mail_list_settings):
        """
        :type plesk_mail_list: parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_list.MailList
        :type plesk_mail_list_settings: dict
        :rtype: dict
        """
        return create_dict(
            name=plesk_mail_list.name,
            owners=plesk_mail_list_settings['owners'],
            password=plesk_mail_list_settings.get('password'),
            password_type=plesk_mail_list_settings.get('password_type'),
            disabled_by=self._encode_status(plesk_mail_list.status),
            subscribers=plesk_mail_list_settings['subscribers'],
        )

    @entity_data_dump(messages.DATA_DUMP_DOMAIN_ALIASES, [])
    def _get_domain_aliases(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: list
        """
        domain_aliases = []
        for plesk_domain_alias in self._plesk_entity.get_domain_aliases(domain_id=domain_id):
            with self._entity_dump(messages.ENTITY_DUMP_DOMAIN_ALIAS, plesk_domain_alias.name):
                domain_aliases.append(self._get_domain_alias(plesk_domain_alias))
        return domain_aliases

    def _get_domain_alias(self, plesk_domain_alias):
        """
        :type plesk_domain_alias: parallels.plesk.source.plesk.hosting_description.plesk_entity.domain_alias.DomainAlias
        :rtype: dict
        """
        return create_dict(
            name=plesk_domain_alias.name,
            mail=plesk_domain_alias.mail == 'true',
            web=plesk_domain_alias.web == 'true',
            tomcat=plesk_domain_alias.tomcat == 'true',
            dns=plesk_domain_alias.dns == 'true',
            seo_redirect=plesk_domain_alias.seo_redirect == 'true',
            disabled_by=self._encode_status(plesk_domain_alias.status),
            dns_zone=self._get_dns_zone(plesk_domain_alias.dns_zone_id),
        )

    @entity_data_dump(messages.DATA_DUMP_DATABASES, [])
    def _get_databases(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: list
        """
        databases = []
        for plesk_database in self._plesk_entity.get_databases(domain_id=domain_id):
            if self._plesk_entity.get_aps_resources(plesk_id=plesk_database.database_id, plesk_type='db'):
                continue
            with self._entity_dump(messages.ENTITY_DUMP_DATABASE, plesk_database.name):
                databases.append(self._get_database(plesk_database))
        return databases

    def _get_database(self, plesk_database, aps_registry_id=None):
        """
        :type plesk_database: parallels.plesk.source.plesk.hosting_description.plesk_entity.database.Database
        :type aps_registry_id: str | unicode | None
        :rtype: dict
        """
        plesk_database_server = self._plesk_entity.get_all_database_servers().get(plesk_database.database_server_id)
        related_domain_name = None
        for plesk_domain in self._plesk_entity.get_subscription_domains(plesk_database.domain_id):
            if self._plesk_entity.get_domain_parameter(plesk_domain.domain_id, 'lastDatabaseSelectedId') == plesk_database.database_id:
                related_domain_name = plesk_domain.name
                break
        return create_dict(
            name=plesk_database.name,
            type=plesk_database.database_server_type,
            version=plesk_database_server.version if plesk_database_server and plesk_database_server.version else None,
            aps_registry_id=aps_registry_id,
            server=self._get_database_server(plesk_database.database_server_id),
            related_domain_name=related_domain_name,
            users=self._get_database_users(plesk_database.database_id),
        )

    @entity_data_dump(messages.DATA_DUMP_DATABASES)
    def _get_database_server(self, database_server_id, include_admin=False):
        """
        :type database_server_id: str | unicode
        :rtype: dict | None
        """
        plesk_database_server = self._plesk_entity.get_all_database_servers().get(database_server_id)
        if plesk_database_server:
            return create_dict(
                type=plesk_database_server.server_type,
                host=plesk_database_server.host,
                port=plesk_database_server.port,
                admin=create_dict(
                    login=plesk_database_server.admin_login,
                    password=plesk_database_server.admin_password,
                    password_type=self._encode_password_type(plesk_database_server.admin_password_type),
                ) if include_admin else None,
            )
        return None

    @entity_data_dump(messages.DATA_DUMP_DATABASES_USERS, [])
    def _get_database_users(self, database_id):
        """
        :type database_id: str | unicode
        :rtype: list
        """
        database_users = []
        for plesk_database_user in self._plesk_entity.get_database_users(database_id=database_id):
            with self._entity_dump(messages.ENTITY_DUMP_DATABASE_USER, plesk_database_user.login):
                database_users.append(self._get_database_user(plesk_database_user))
        return database_users

    def _get_database_user(self, plesk_database_user):
        """
        :type plesk_database_user: parallels.plesk.source.plesk.hosting_description.plesk_entity.database_user.DatabaseUser
        :rtype: dict
        """
        password, password_type = self._get_plesk_database_user_password(plesk_database_user)
        return create_dict(
            login=plesk_database_user.login,
            password=password,
            password_type=self._encode_password_type(password_type),
        )

    @entity_data_dump(messages.DATA_DUMP_DATABASES_USERS, [])
    def _get_any_database_users(self, domain_id):
        """
        :type domain_id: str | unicode
        :rtype: list
        """
        database_users = []
        for plesk_database_user in self._plesk_entity.get_database_users(domain_id=domain_id):
            if plesk_database_user.database_id != '0':
                continue
            with self._entity_dump(messages.ENTITY_DUMP_DATABASE_USER, plesk_database_user.login):
                database_users.append(self._get_any_database_user(plesk_database_user))
        return database_users

    def _get_any_database_user(self, plesk_database_user):
        """
        :type plesk_database_user: parallels.plesk.source.plesk.hosting_description.plesk_entity.database_user.DatabaseUser
        :rtype: dict
        """
        password, password_type = self._get_plesk_database_user_password(plesk_database_user)
        return create_dict(
            login=plesk_database_user.login,
            password=password,
            password_type=self._encode_password_type(password_type),
            server=self._get_database_server(plesk_database_user.database_server_id),
        )

    @entity_data_dump(messages.DATA_DUMP_SCHEDULED_TASKS, [])
    def _get_scheduled_tasks(self, sys_user_id):
        """
        :type sys_user_id: str | unicode
        :rtype: list
        """
        scheduled_tasks = []
        for plesk_scheduled_task in self._plesk_entity.get_scheduled_tasks(sys_user_id=sys_user_id):
            scheduled_task = create_dict(
                type=plesk_scheduled_task.scheduled_task_type,
                command=plesk_scheduled_task.command,
                command_arguments=plesk_scheduled_task.arguments,
                period=self._encode_scheduled_task_period(plesk_scheduled_task.period),
                minute=plesk_scheduled_task.minute,
                hour=plesk_scheduled_task.hour,
                day_of_week=plesk_scheduled_task.day_of_week,
                day_of_month=plesk_scheduled_task.day_of_month,
                month=plesk_scheduled_task.month,
                is_active=plesk_scheduled_task.is_active == '1',
                description=plesk_scheduled_task.description,
                notify=self._encode_scheduled_task_notify(plesk_scheduled_task.notify),
                notifications_email=plesk_scheduled_task.email if plesk_scheduled_task.email else None,
                php_handler_id=plesk_scheduled_task.php_handler_id,
            )
            php_handler = self._plesk_entity.get_php_handlers(plesk_scheduled_task.service_node_id).get(plesk_scheduled_task.php_handler_id)
            if php_handler is not None:
                scheduled_task['php_version'] = php_handler.version
            scheduled_tasks.append(scheduled_task)
        return scheduled_tasks

    @entity_data_dump(messages.DATA_DUMP_DNS_ZONE)
    def _get_dns_zone(self, dns_zone_id):
        """
        :type dns_zone_id: str | unicode
        :rtype: dict | None
        """
        service_node_id = self._plesk_entity.get_management_service_node_id()
        if service_node_id is not None and not self._plesk_entity.is_dns_enabled(service_node_id):
            return None
        plesk_dns_zone = self._plesk_entity.get_selected_dns_zones().get(dns_zone_id)
        if not plesk_dns_zone:
            return None
        return create_dict(
            type=plesk_dns_zone.dns_zone_type,
            serial_format=plesk_dns_zone.serial_format,
            ttl=self._encode_dns_zone_parameter(plesk_dns_zone.ttl, plesk_dns_zone.ttl_unit),
            refresh=self._encode_dns_zone_parameter(plesk_dns_zone.refresh, plesk_dns_zone.refresh_unit),
            retry=self._encode_dns_zone_parameter(plesk_dns_zone.retry, plesk_dns_zone.retry_unit),
            expire=self._encode_dns_zone_parameter(plesk_dns_zone.expire, plesk_dns_zone.expire_unit),
            minimum=self._encode_dns_zone_parameter(plesk_dns_zone.minimum, plesk_dns_zone.minimum_unit),
            email=plesk_dns_zone.email,
            disabled_by=self._encode_status(plesk_dns_zone.status),
            dns_records=self._get_dns_records(plesk_dns_zone.dns_zone_id),
        )

    @entity_data_dump(messages.DATA_DUMP_DNS_RECORDS, [])
    def _get_dns_records(self, dns_zone_id):
        """
        :type dns_zone_id: str|unicode
        :rtype: list
        """
        return [
            create_dict(
                type=plesk_dns_record.dns_record_type,
                src=plesk_dns_record.host,
                dst=plesk_dns_record.value,
                opt=plesk_dns_record.opt,
            )
            for plesk_dns_record in self._plesk_entity.get_dns_records(dns_zone_id=dns_zone_id)
        ]

    @entity_data_dump(messages.DATA_DUMP_CONTACT_INFO)
    def _get_plesk_client_contact_info(self, plesk_client):
        """
        :type plesk_client: parallels.plesk.source.plesk.hosting_description.plesk_entity.client.Client
        :rtype: dict | None
        """
        contact_info = create_dict(
            name=plesk_client.name,
            email=plesk_client.email,
            company=plesk_client.company,
            country_code=plesk_client.country,
            zip=plesk_client.postal_code,
            state=plesk_client.state,
            city=plesk_client.city,
            address=plesk_client.address,
            phone=plesk_client.phone,
            fax=plesk_client.fax,
        )
        plesk_user = self._plesk_entity.get_selected_users().get(plesk_client.login)
        if plesk_user and plesk_user.owner_id == plesk_client.client_id and plesk_user.is_built_in == '1':
            contact_info.update(create_dict(
                im=plesk_user.im_number,
                im_type=plesk_user.im_type,
                comment=plesk_user.additional_info,
            ))
        return contact_info

    @entity_data_dump(messages.DATA_DUMP_CONTACT_INFO)
    def _get_plesk_user_contact_info(self, plesk_user):
        """
        :type plesk_user: parallels.plesk.source.plesk.hosting_description.plesk_entity.user.User
        :rtype: dict | None
        """
        return create_dict(
            name=plesk_user.contact_name,
            email=plesk_user.email,
            company=plesk_user.company_name,
            country_code=plesk_user.country,
            zip=plesk_user.postal_code,
            state=plesk_user.state,
            city=plesk_user.city,
            address=plesk_user.address,
            phone=plesk_user.phone,
            fax=plesk_user.fax,
            im=plesk_user.im_number,
            im_type=plesk_user.im_type,
            comment=plesk_user.additional_info,
        )

    @entity_data_dump(messages.DATA_DUMP_SUBSCRIPTION_INFO)
    def _get_subscription_info(self, object_id, object_type):
        """
        :type object_id: str|unicode
        :type object_type: str|unicode
        :rtype: dict | None
        """
        for plesk_subscription in self._plesk_entity.get_subscriptions(object_id=object_id, object_type=object_type):
            plans = []
            for plesk_subscription_plan in self._plesk_entity.get_subscriptions_plans(plesk_subscription.subscription_id):
                plesk_template = self._plesk_entity.get_selected_templates().get(plesk_subscription_plan.plan_id)
                if plesk_template:
                    plans.append({
                        'guid': plesk_template.guid,
                        'quantity': plesk_subscription_plan.quantity,
                        'is_addon': plesk_template.is_addon == 'true',
                    })
            return create_dict(
                custom=plesk_subscription.custom == 'true',
                locked=plesk_subscription.locked == 'true',
                synchronized=plesk_subscription.synchronized == 'true',
                plans=plans,
            )
        return None

    @entity_data_dump(messages.DATA_DUMP_CUSTOM_BUTTONS, [])
    def _get_custom_buttons(self, plesk_client):
        """
        :type plesk_client: parallels.plesk.source.plesk.hosting_description.plesk_entity.client.Client
        :rtype: list
        """
        custom_buttons = []
        if plesk_client.client_type == 'admin':
            level = CustomButton.LEVEL_ADMIN
        elif plesk_client.client_type == 'reseller':
            level = CustomButton.LEVEL_RESELLER
        else:
            level = CustomButton.LEVEL_CLIENT
        for custom_button in self._plesk_entity.get_custom_buttons(level=level, level_id=plesk_client.client_id):
            url_options, visible_to_sublogins, open_in_same_frame, no_frames = self._decode_custom_button_options(custom_button.options)
            custom_buttons.append(create_dict(
                url=custom_button.url,
                url_options=url_options,
                label=custom_button.text,
                tooltip_text=custom_button.context_help,
                priority=custom_button.sort_key,
                location=custom_button.place,
                visible_to_sublogins=visible_to_sublogins,
                open_in_same_frame=open_in_same_frame,
                no_frames=no_frames,
            ))
        return custom_buttons

    @entity_data_dump(messages.DATA_DUMP_IIS_APPLICATION_POOL_SETTINGS)
    def _get_iis_application_pool(self, owner_id, owner_type):
        """
        :type owner_id: str|unicode
        :type owner_type: str|unicode
        :rtype: dict|None
        """
        if not self._dump_agent.source_server().is_windows():
            return None
        for iis_application_pool in self._plesk_entity.get_iis_app_pools(owner_id, owner_type):
            return create_dict(
                turned_on=True,
                identity=iis_application_pool.identity,
                max_processes=iis_application_pool.max_processes,
                cpu_limit=iis_application_pool.cpu_limit,
                cpu_limit_action=iis_application_pool.cpu_limit_action,
                cpu_limit_interval=iis_application_pool.cpu_limit_interval,
                idle_timeout=iis_application_pool.idle_timeout,
                idle_timeout_action=iis_application_pool.idle_timeout_action,
                recycling_by_time=iis_application_pool.recycling_by_time,
                recycling_by_requests=iis_application_pool.recycling_by_requests,
                recycling_by_virtual_memory=iis_application_pool.recycling_by_virtual_memory,
                recycling_by_private_memory=iis_application_pool.recycling_by_private_memory,
                managed_pipeline_mode=iis_application_pool.managed_pipeline_mode,
            )
        return create_dict(
            turned_on=False,
        )

    @entity_data_dump(messages.DATA_DUMP_SUBSCRIPTION_USERS, [])
    def _get_users(self, plesk_client):
        """
        :type plesk_client: parallels.plesk.source.plesk.hosting_description.plesk_entity.client.Client
        :rtype: list
        """
        users = []
        for plesk_user in self._plesk_entity.get_users(owner_id=plesk_client.client_id, is_built_in='0'):
            plesk_role = self._plesk_entity.get_selected_roles().get(plesk_user.role_id)
            if not plesk_role:
                continue
            with self._entity_dump(messages.ENTITY_DUMP_SUBSCRIPTION_USER, plesk_user.login):
                users.append(self._get_user(plesk_user, plesk_role))
        return users

    def _get_user(self, plesk_user, plesk_role):
        """
        :type plesk_user: parallels.plesk.source.plesk.hosting_description.plesk_entity.user.User
        :type plesk_role: parallels.plesk.source.plesk.hosting_description.plesk_entity.role.Role
        :rtype: dict
        """
        password, password_type = self._get_plesk_user_password(plesk_user)
        plesk_domain = self._plesk_entity.get_selected_domains().get(plesk_user.subscription_domain_id)
        return create_dict(
            login=plesk_user.login,
            password=password,
            password_type=self._encode_password_type(password_type),
            guid=plesk_user.guid,
            locale=plesk_user.locale if plesk_user.locale else None,
            creation_date=plesk_user.creation_date[:10],
            disabled_by=self._encode_status(Status.ENABLED if plesk_user.is_locked == '0' else Status.DISABLED_BY_ADMIN),
            role=plesk_role.name,
            subscription_name=plesk_domain.name if plesk_domain else None,
            external_email=plesk_user.external_email,
            contact_info=self._get_plesk_user_contact_info(plesk_user),
        )

    @entity_data_dump(messages.DATA_DUMP_SUBSCRIPTION_USERS_ROLES, [])
    def _get_roles(self, plesk_client):
        """
        :type plesk_client: parallels.plesk.source.plesk.hosting_description.plesk_entity.client.Client
        :rtype: list
        """
        roles_ids = set()
        for plesk_user in self._plesk_entity.get_users(owner_id=plesk_client.client_id, is_built_in='0'):
            roles_ids.add(plesk_user.role_id)
        roles = []
        for role_id in roles_ids:
            plesk_role = self._plesk_entity.get_selected_roles().get(role_id)
            if not plesk_role or plesk_role.is_built_in != '0':
                continue
            with self._entity_dump(messages.ENTITY_DUMP_SUBSCRIPTION_USER_ROLE, plesk_role.name):
                roles.append(self._get_role(plesk_role))
        return roles

    def _get_role(self, plesk_role):
        """
        :type plesk_role: parallels.plesk.source.plesk.hosting_description.plesk_entity.role.Role
        :rtype: dict
        """
        permissions = {}
        for role_general_permission in self._plesk_entity.get_selected_roles_general_permissions().get(plesk_role.role_id, []):
            general_permission = self._plesk_entity.get_all_general_permissions().get(role_general_permission.general_permission_id)
            if general_permission:
                permissions[general_permission.code] = 'true' if role_general_permission.is_allowed == '1' else 'false'
        service_permissions = []
        for role_service_permission in self._plesk_entity.get_selected_roles_service_permissions().get(plesk_role.role_id, []):
            service_permission = self._plesk_entity.get_selected_service_permissions().get(role_service_permission.service_permission_id)
            if service_permission:
                service_instance = self._plesk_entity.get_selected_service_instances().get(service_permission.service_instance_id)
                service_provider = self._plesk_entity.get_all_service_providers().get(service_permission.service_provider_id)
                if service_instance and service_provider:
                    service_permissions.append(create_dict(
                        classname=service_provider.classname,
                        description=service_instance.description,
                        external_id=service_instance.external_id,
                        permission_code=service_permission.permission_code,
                        permission_class=service_permission.permission_class,
                        value='true',
                    ))
        return {
            'name': plesk_role.name,
            'permissions': permissions,
            'service_permissions': service_permissions,
        }

    @entity_data_dump(messages.DATA_DUMP_APPLICATIONS_FILTER)
    def _get_object_applications_filter(self, object_id, object_type):
        """
        :type object_id: str|unicode
        :type object_type: str|unicode
        :rtype: dict | None
        """
        plesk_subscription_properties = self._plesk_entity.get_subscription_properties(object_id, object_type)
        applications_filter_id = plesk_subscription_properties.get('aps_bundle_filter_id')
        if applications_filter_id is not None:
            return self._get_applications_filter(applications_filter_id)
        return None

    @entity_data_dump(messages.DATA_DUMP_APPLICATIONS_FILTER)
    def _get_applications_filter(self, applications_filter_id):
        """
        :type applications_filter_id: str|unicode
        :rtype: dict | None
        """
        plesk_applications_filter = self._plesk_entity.get_selected_aps_filters().get(applications_filter_id)
        if plesk_applications_filter:
            return create_dict(
                type=plesk_applications_filter.filter_type,
                names=[
                    plesk_applications_filter_item.value
                    for plesk_applications_filter_item in self._plesk_entity.get_selected_aps_filters_items().get(plesk_applications_filter.filter_id, [])
                    if plesk_applications_filter_item.name == 'name'
                ]
            )
        return None

    @entity_data_dump(messages.DATA_DUMP_LOG_ROTATION_SETTINGS)
    def _get_log_rotation(self, log_rotation_id):
        """
        :type log_rotation_id: str|unicode|None
        :rtype: dict | None
        """
        if log_rotation_id is None:
            return None
        plesk_log_rotation = self._plesk_entity.get_selected_log_rotations().get(log_rotation_id)
        if plesk_log_rotation:
            return create_dict(
                enabled=plesk_log_rotation.turned_on == 'true',
                period_type=plesk_log_rotation.period_type,
                period=plesk_log_rotation.period,
                max_number_of_log_files=plesk_log_rotation.max_number_of_log_files,
                compress=plesk_log_rotation.compress_enable == 'true',
                email=plesk_log_rotation.email,
            )
        return None


    @entity_data_dump(messages.DATA_DUMP_HOTLINK_PROTECTION)
    def _get_hotlink_protection(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: dict | None
        """
        plesk_hotlink_protection = self._plesk_entity.get_selected_hotlink_protection().get(domain_id)
        if not plesk_hotlink_protection:
            return None
        return create_dict(
            enabled=plesk_hotlink_protection.enabled == 'true',
            extensions=plesk_hotlink_protection.extensions.split(','),
            friends=self._plesk_entity.get_selected_hotlink_friends().get(domain_id, []),
        )

    @entity_data_dump(messages.DATA_DUMP_DYNAMIC_IP_SECURITY)
    def _get_dynamic_ip_security(self, domain_id):
        """
        :type domain_id: str|unicode
        :rtype: dict | None
        """
        plesk_dynamic_ip_security = self._plesk_entity.get_selected_dynamic_ip_security().get(domain_id)
        if not plesk_dynamic_ip_security:
            return None
        return create_dict(
            deny_by_concurrent_requests=plesk_dynamic_ip_security.is_deny_by_concurrent_requests == '1',
            max_concurrent_requests=plesk_dynamic_ip_security.max_concurrent_requests,
            deny_by_request_rate=plesk_dynamic_ip_security.is_deny_by_request_rate == '1',
            max_requests=plesk_dynamic_ip_security.max_requests,
            request_interval=plesk_dynamic_ip_security.request_interval,
        )

    @entity_data_dump(messages.DATA_DUMP_ANONYMOUS_FTP)
    def _get_anonymous_ftp(self, domain_id):
        """
        :type domain_id: str|unicode|None
        :rtype: dict | None
        """
        plesk_anonymous_ftp = self._plesk_entity.get_selected_anonymous_ftp().get(domain_id)
        if plesk_anonymous_ftp:
            return create_dict(
                enabled=plesk_anonymous_ftp.status == 'true',
                display_login_message=plesk_anonymous_ftp.display_login == 'true',
                login_message=plesk_anonymous_ftp.login_text,
                allow_uploading=plesk_anonymous_ftp.incoming == 'true',
                allow_downloading=plesk_anonymous_ftp.incoming_readable == 'true',
                allow_directories_creation=plesk_anonymous_ftp.incoming_subdirs == 'true',
                max_connections=plesk_anonymous_ftp.max_conn,
                disk_quota=plesk_anonymous_ftp.quota,
                bandwidth=plesk_anonymous_ftp.bandwidth,
            )
        return None

    @entity_data_dump(messages.DATA_DUMP_PHP_SETTINGS)
    def _get_php_settings(self, php_settings_id):
        """
        :type php_settings_id: str|unicode|None
        :rtype: dict | None
        """
        if php_settings_id is None:
            return None
        plesk_php_settings = self._plesk_entity.get_selected_php_settings().get(php_settings_id)
        if plesk_php_settings:
            plesk_note = None
            if plesk_php_settings.note_id != '0':
                plesk_note = self._plesk_entity.get_selected_notes().get(plesk_php_settings.note_id)
            return create_dict(
                notice=plesk_note.text if plesk_note else None,
                settings=self._plesk_entity.get_selected_php_settings_parameters().get(php_settings_id, {})
            )
        return None

    @entity_data_dump(messages.DATA_DUMP_WEB_SERVER_SETTINGS)
    def _get_hosting_plan_web_server_settings(self, web_server_settings_id):
        """
        :type web_server_settings_id: str|unicode|None
        :rtype: dict | None
        """
        if web_server_settings_id is None:
            return None
        plesk_web_server_settings = self._plesk_entity.get_selected_web_server_settings().get(web_server_settings_id)
        if plesk_web_server_settings is None:
            return None
        web_server_settings = {
            name: base64.b64encode(value)
            for name, value in plesk_web_server_settings.items()
            if self._is_template_item_allowed(name) and value is not None
        }
        return web_server_settings

    @entity_data_dump(messages.DATA_DUMP_WEB_SERVER_SETTINGS)
    def _get_domain_web_server_settings(self, plesk_domain):
        """
        :type plesk_domain: parallels.plesk.source.plesk.hosting_description.plesk_entity.domain.Domain
        :rtype: dict | None
        """
        web_server_settings_id = self._plesk_entity.get_domain_parameter(plesk_domain.domain_id, 'webServerSettingsId')
        if web_server_settings_id is None:
            return None
        plesk_web_server_settings = self._plesk_entity.get_selected_web_server_settings().get(web_server_settings_id)
        if plesk_web_server_settings is None:
            return None
        web_server_settings = {
            name: base64.b64encode(value)
            for name, value in plesk_web_server_settings.items() if value is not None
        }
        if not self._dump_agent.source_server().is_windows():
            additional_settings = {
                'additionalSettings': 'vhost.conf',
                'additionalSslSettings': 'vhost_ssl.conf',
                'additionalNginxSettings': 'vhost_nginx.conf',
            }
            with self._dump_agent.source_server().runner() as runner:
                for name, config_file in additional_settings.items():
                    with self._entity_dump(messages.DATA_DUMP_WEB_SERVER_SETTINGS, config_file):
                        config_file_path = posixpath.join(
                            self._dump_agent.source_server().vhosts_dir, 'system', plesk_domain.ascii_name, 'conf', config_file
                        )
                        if runner.file_exists(config_file_path):
                            value = runner.get_file_contents(config_file_path)
                            web_server_settings[name] = base64.b64encode(value)
            self._disabled_nginx_website_settings(self._dump_agent.source_server().nginx_enabled, web_server_settings)

        return web_server_settings

    @entity_data_dump(messages.DATA_DUMP_IP_ADDRESSES, [])
    def _get_ip_addresses(self, ip_pool_id):
        """
        :type ip_pool_id: str|unicode
        :rtype: list
        """
        ip_addresses = []
        for plesk_ip_pool_item in self._plesk_entity.get_selected_ip_pools_items().get(ip_pool_id, []):
            plesk_ip_address = self._plesk_entity.get_selected_ip_addresses().get(plesk_ip_pool_item.ip_address_id)
            if plesk_ip_address:
                ip_addresses.append({
                    'type': plesk_ip_pool_item.ip_address_type,
                    'address': plesk_ip_address.ip_address,
                })
        return ip_addresses

    def _get_sys_user(self, sys_user_id):
        """
        :type sys_user_id: str|unicode
        :rtype: dict | None
        """
        plesk_sys_user = self._plesk_entity.get_selected_sys_users().get(sys_user_id)
        if plesk_sys_user is None:
            return None
        with self._entity_dump(messages.ENTITY_DUMP_SYSTEM_USER, plesk_sys_user.login):
            password, password_type = self._get_plesk_sys_user_password(plesk_sys_user)
            return create_dict(
                login=plesk_sys_user.login,
                password=password,
                password_type=self._encode_password_type(password_type),
                shell=plesk_sys_user.shell if plesk_sys_user.shell else None,
            )

    @entity_data_dump(messages.DATA_DUMP_LIMITS)
    def _get_limits(self, limits_id, params=None):
        """
        :type limits_id: str|unicode
        :type params: dict[str|unicode, str|unicode] | None
        :rtype: dict | None
        """
        plesk_limits = self._plesk_entity.get_selected_limits().get(limits_id)
        if plesk_limits is None:
            return None
        limits = {}
        for name, value in plesk_limits.items():
            if self._is_limit_allowed(name):
                limits[name] = value

        limits['overuse'] = self._get_overuse(plesk_limits, params=params)

        if 'expiration' in plesk_limits:
            limits['expiration'] = self._convert_expiration_date_format(plesk_limits['expiration'])

        return limits

    @entity_data_dump(messages.DATA_DUMP_PERMISSIONS)
    def _get_permissions(self, permissions_id):
        """
        :type permissions_id: str|unicode
        :rtype: dict | None
        """
        plesk_permissions = self._plesk_entity.get_selected_permissions().get(permissions_id)
        if plesk_permissions is None:
            return None
        permissions = {}
        for name, value in plesk_permissions.items():
            if self._is_permission_allowed(name):
                permissions[name] = value
        return permissions

    @entity_data_dump(messages.DATA_DUMP_DOCUMENT_ROOT)
    def _get_domain_document_root(self, plesk_domain):
        """
        :type plesk_domain: parallels.plesk.source.plesk.hosting_description.plesk_entity.domain.Domain
        :rtype: str|unicode|None
        """
        if plesk_domain.webspace_id == '0':
            main_domain = plesk_domain
        else:
            main_domain = self._plesk_entity.get_selected_domains().get(plesk_domain.webspace_id)
        if not main_domain:
            return None
        hosting = self._plesk_entity.get_selected_hosting().get(plesk_domain.domain_id)
        if not hosting:
            return None
        pos = hosting.www_root.find(main_domain.ascii_name)
        if pos == -1:
            return None
        return hosting.www_root[pos + len(main_domain.ascii_name) + 1:]

    def _get_forwarding_type(self, hosting_type, forwarding):
        """
        :type hosting_type: str|unicode
        :type forwarding: parallels.plesk.source.plesk.hosting_description.plesk_entity.forwarding.Forwarding
        :rtype: str|unicode
        """
        if hosting_type == 'frm_fwd':
            return ForwardingType.FRAME_FORWARDING
        if forwarding.http_code == '302':
            return ForwardingType.MOVED_TEMPORARILY
        return ForwardingType.MOVED_PERMANENTLY

    def _get_overuse(self, items, params=None):
        """
        :type items: dict[str|unicode, str|unicode]
        :type params: dict[str|unicode, str|unicode] | None
        :rtype: str|unicode
        """
        overuse = 'normal'

        overuse_block = items.get('overuse_block')
        overuse_suspend = items.get('overuse_suspend')
        overuse_notify = items.get('overuse_notify')

        if params is not None:
            if 'OveruseBlock' in params:
                overuse_block = params['OveruseBlock']
            if 'OveruseSuspend' in params:
                overuse_suspend = params['OveruseSuspend']
            if 'OveruseNotify' in params:
                overuse_notify = params['OveruseNotify']

        if overuse_block == 'true':
            if overuse_suspend == 'true':
                overuse = 'block'
            else:
                overuse = 'not_suspend_notify' if overuse_notify == 'true' else 'not_suspend'
        elif overuse_notify == 'true':
            overuse = 'notify'
        return overuse

    @entity_data_dump(messages.DATA_DUMP_EXPIRATION_LIMIT)
    def _convert_expiration_date_format(self, expiration):
        """Convert expiration data format from database style (Unix timestamp) to backup dump style (YYYY-MM-DD)

        :type expiration: str | unicode
        :rtype: str | unicode
        """
        if is_empty(expiration) or expiration == "-1":
            return "-1"
        else:
            time_struct = time.localtime(int(expiration))
            return '%04d-%02d-%02d' % (time_struct.tm_year, time_struct.tm_mon, time_struct.tm_mday)

    def _encode_status(self, status):
        """
        :type status: str|unicode
        :rtype: list[str|unicode]
        """
        encoded_status = []
        try:
            status_mask = int(status)
            encoding_map = {
                'domadm': Status.DISABLED_BY_DOMAIN_ADMIN,
                'parent': Status.DISABLED_WITH_PARENT,
                'backup': Status.DISABLED_BY_BACKUP_MANAGER,
                'admin': Status.DISABLED_BY_ADMIN,
                'reseller': Status.DISABLED_BY_RESELLER,
                'client': Status.DISABLED_BY_CLIENT,
                'expired': Status.EXPIRED,
            }
            for name, value in encoding_map.items():
                if name != 'backup' and value & status_mask:
                    encoded_status.append(name)
        except Exception as e:
            logger.exception()
            logger.fwarn(messages.FAILED_TO_PARSE_STATUS, status=status, error=e)
        return encoded_status

    def _encode_dns_zone_parameter(self, value, unit):
        """
        :type value: str | unicode
        :type unit: str | unicode
        :rtype: str | unicode | None
        """
        encoded_value = None
        try:
            units_map = {
                '1': '',
                '60': 'M',
                '3600': 'H',
                '86400': 'D',
                '604800': 'W',
            }
            encoded_value = '%s%s' % (value, units_map[unit])
        except Exception as e:
            logger.exception()
            logger.fwarn(messages.FAILED_TO_ENCODE_DNS_ZONE_PARAMETER, value=value, unit=unit, error=e)
        return encoded_value

    def _encode_password_type(self, password_type):
        """
        :type password_type: str | unicode | None
        :rtype: str | unicode | None
        """
        if password_type is None:
            return None
        password_types_map = {
            'plain': 'plain',
            'sym': 'sym',
            'crypt': 'hash',
        }
        return password_types_map.get(password_type)

    def _encode_scheduled_task_period(self, period):
        """
        :type period: str | unicode | None
        :rtype: str | unicode | None
        """
        if period is None:
            return None
        periods_map = {
            '0': 'cron_style',
            '3600': 'hourly',
            '86400': 'daily',
            '604800': 'weekly',
            '2592000': 'monthly',
            '31536000': 'yearly',
        }
        return periods_map.get(period)

    def _encode_scheduled_task_notify(self, notify):
        """
        :type notify: str | unicode | None
        :rtype: str | unicode | None
        """
        if notify is None:
            return None
        notification_map = {
            'ignore': 'do_not_notify',
            'errors': 'errors_only',
            'always': 'every_time',
        }
        return notification_map.get(notify)

    def _decode_custom_button_options(self, options):
        """
        :type options: str|unicode
        :rtype: tuple[list[str|unicode]|None, bool, bool, bool|None]
        """
        url_options, visible_to_sublogins, open_in_same_frame, no_frames = None, False, False, None
        try:
            options_mask = int(options)
            visible_to_sublogins = bool(options_mask & 128)
            open_in_same_frame = bool(options_mask & 256)
            no_frames = bool(options_mask & 64)
            url_options = []
            url_options_map = {
                'domain-id': 1,
                'domain-name': 32,
                'ftp-login': 512,
                'ftp-password': 1024,
                'client-id': 2,
                'client-company-name': 4,
                'client-contact-name': 8,
                'client-email': 16,
            }
            for name, value in url_options_map.items():
                if options_mask & value:
                    url_options.append(name)
        except Exception as e:
            logger.exception()
            logger.fwarn(messages.FAILED_TO_PARSE_CUSTOM_BUTTON_OPTIONS, options=options, error=e)
        return url_options, visible_to_sublogins, open_in_same_frame, no_frames

    def _decode_ftp_user_permission(self, permission):
        """
        :type permission: str|unicode
        :rtype: tuple[bool, bool]
        """
        allow_read, allow_write = False, False
        try:
            permission_mask = int(permission)
            allow_read = (permission_mask & 1) != 0
            allow_write = (permission_mask & 2) != 0
        except Exception as e:
            logger.exception()
            logger.fwarn(messages.FAILED_TO_PARSE_FTP_USER_PERMISSION, permission=permission, error=e)
        return allow_read, allow_write

    def _get_plesk_client_password(self, plesk_client):
        """
        :type plesk_client: parallels.plesk.source.plesk.hosting_description.plesk_entity.client.Client
        :rtype: tuple[str|unicode|None, str|unicode|None]
        """
        plesk_user = self._plesk_entity.get_selected_users().get(plesk_client.login)
        if (
            plesk_user and plesk_user.owner_id == plesk_client.client_id and
            plesk_user.is_built_in == '1' and plesk_user.password_type == 'plain'
        ):
            return plesk_user.password, plesk_user.password_type
        plesk_account = self._plesk_entity.get_selected_accounts().get(plesk_client.account_id)
        if plesk_account and plesk_account.password_type in ['plain', 'crypt']:
            return plesk_account.password, plesk_account.password_type
        return None, None

    def _get_plesk_user_password(self, plesk_user):
        """
        :type plesk_user: parallels.plesk.source.plesk.hosting_description.plesk_entity.user.User
        :rtype: tuple[str|unicode|None, str|unicode|None]
        """
        if plesk_user.password_type == 'plain':
            return plesk_user.password, plesk_user.password_type
        return None, None

    def _get_plesk_mail_user_password(self, plesk_mail_user):
        """
        :type plesk_mail_user: parallels.plesk.source.plesk.hosting_description.plesk_entity.mail_name.MailName
        :rtype: tuple[str|unicode|None, str|unicode|None]
        """
        plesk_account = self._plesk_entity.get_selected_accounts().get(plesk_mail_user.account_id)
        if plesk_account and plesk_account.password_type in ['plain', 'crypt']:
            return plesk_account.password, plesk_account.password_type
        return None, None

    def _get_plesk_database_user_password(self, plesk_database_user):
        """
        :type plesk_database_user: parallels.plesk.source.plesk.hosting_description.plesk_entity.database_user.DatabaseUser
        :rtype: tuple[str|unicode|None, str|unicode|None]
        """
        plesk_account = self._plesk_entity.get_selected_accounts().get(plesk_database_user.account_id)
        if plesk_account and plesk_account.password_type in ['plain', 'crypt']:
            return plesk_account.password, plesk_account.password_type
        return None, None

    def _get_plesk_sys_user_password(self, plesk_sys_user):
        """
        :type plesk_sys_user: parallels.plesk.source.plesk.hosting_description.plesk_entity.sys_user.SysUser
        :rtype: tuple[str|unicode|None, str|unicode|None]
        """
        plesk_account = self._plesk_entity.get_selected_accounts().get(plesk_sys_user.account_id)
        if plesk_account and plesk_account.password_type in ['plain', 'crypt']:
            return plesk_account.password, plesk_account.password_type
        return None, None

    def _get_plesk_protected_directory_user_password(self, plesk_protected_directory_user):
        """
        :type plesk_protected_directory_user: parallels.plesk.source.plesk.hosting_description.plesk_entity.protected_directory_user.ProtectedDirectoryUser
        :rtype: tuple[str|unicode|None, str|unicode|None]
        """
        plesk_account = self._plesk_entity.get_selected_accounts().get(plesk_protected_directory_user.account_id)
        if plesk_account and plesk_account.password_type == 'plain':
            return plesk_account.password, plesk_account.password_type
        return None, None

    def _is_template_item_allowed(self, name):
        """
        :type name: str | unicode
        :rtype: bool
        """
        return name in ALLOWED_TEMPLATE_ITEMS

    def _is_limit_allowed(self, name):
        """
        :type name: str | unicode
        :rtype: bool
        """
        return name in ALLOWED_LIMITS

    def _is_permission_allowed(self, name):
        """
        :type name: str | unicode
        :rtype: bool
        """
        return name in ALLOWED_PERMISSIONS

    @contextmanager
    def _entity_dump(self, entity_type, entity_name):
        try:
            self._current_entities.append((entity_type, entity_name))
            yield
        except Exception as e:
            logger.exception()
            entity_path = '/'.join([
                '%s (%s)' % (current_entity_type, current_entity_name)
                for current_entity_type, current_entity_name in self._current_entities
            ])
            Registry.get_instance().get_context().pre_check_report.add_issue(
                'dump_data',
                Issue.SEVERITY_WARNING,
                safe_format(messages.FAILED_TO_DUMP_ENTITY, entity_path=entity_path, error=e)
            )
        finally:
            self._current_entities.pop()

    def _disabled_nginx_website_settings(self, nginx_enabled, settings):
        keys_to_fix = {
            'nginxProxyMode': str(nginx_enabled).lower(),
            'nginxServePhp': str(not nginx_enabled).lower(),
            'nginxServeStatic': str(not nginx_enabled).lower(),
            'nginxTransparentMode': str(not nginx_enabled).lower()
        }
        for key, value in keys_to_fix.items():
            if settings.get(key) is None:
                settings[key] = base64.b64encode(value)
