<?php

class LdapConnecter
{
    
    const PREVIEW_IMPORT = true;
    const IMPORT_LIMIT = 100;
    const IMPORT_FINISHED = -1;

    private $server;
    private $username;
    private $password;
    private $baseDN;
    private $filter;
    private $ldapKeys = array();
    private $ldapContacts = array();

    public function updateInterfacePreviewData($ldapInterface)
    {
        Log::d("Updating interface model ID $ldapInterface->id for preview.", "LdapConnecter");
        $this->setLdapCredentials($ldapInterface);
        $connection = $this->connectToServer();
        if (isset($connection)) {
            $ldapInterface->connected = true;
            $this->validateSearchParameter($connection, $ldapInterface);
        } else {
            $ldapInterface->connected = '';
        }
    }

    private function validateSearchParameter($connection, $ldapInterface)
    {
        $this->searchDirectory($connection);
        if (isset($this->ldapContacts)) {
            $this->setPreviewData($ldapInterface);
        } else {
            $ldapInterface->positivSearchResult = '';
        }
    }

    private function setPreviewData($ldapInterface)
    {
        Log::d("Setting preview data for filter settings: $this->filter", "LdapConnecter");
        $ldapInterface->positivSearchResult = true;
        $ldapInterface->ldapKeys = json_encode($this->ldapKeys);
        $ldapInterface->ldapData = json_encode($this->ldapContacts);
    }

    private function setLdapCredentials($ldapInterface)
    {
        $attributes = array('server', 'username', 'password', 'baseDN', 'filter');
        foreach ($attributes as $key) {
            if (property_exists($ldapInterface, $key)) {
                $this->$key = $ldapInterface->$key;
            }
        }
    }

    private function connectToServer()
    {
        Log::d("Connecting to LDAP server.", "LdapConnecter");
        $connection = @ldap_connect($this->server);
        if ($connection) {
            $this->setOptions($connection);
            $bind = @ldap_bind($connection, $this->username, $this->password);
            if ($bind) {
                return $connection;
            } else {
                Log::e("An error occurred while connecting to LDAP/AD server. Message: " . ldap_error($connection), "LdapConnecter");
            }
        }
    }

    private function setOptions($connection)
    {
        ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_set_option($connection, LDAP_OPT_REFERRALS, 0);
        ldap_set_option($connection, LDAP_OPT_NETWORK_TIMEOUT, 25);
    }

    public function getLdapContacts($ldapInterface, &$importStep)
    {
        $this->setLdapCredentials($ldapInterface);
        $connection = $this->connectToServer();
        if (isset($connection)) {
            $this->searchDirectory($connection, $importStep);
            if (isset($this->ldapContacts)) {
                return $this->ldapContacts;
            }
        }
        return array();
    }

    private function searchDirectory($connection, &$importStep = null)
    {
        $cookie = '';
        $step = 0;
        Log::d("Searching for server data.", "LdapConnecter");
        do {
            $step++;
            $importContacts = isset($importStep) ? ($importStep === $step) : LdapConnecter::PREVIEW_IMPORT;
            $this->fetchLdapContacts($connection, $cookie, $importContacts);
        } while ($cookie !== null && $cookie != '');
        if (isset($importStep) && ($step == $importStep)) {
            $importStep = LdapConnecter::IMPORT_FINISHED;
        } else {
            $importStep++;
        }
        $this->disconnect($connection);
    }

    private function fetchLdapContacts($connection, &$cookie, $importContacts)
    {
        $pageSize = LdapConnecter::IMPORT_LIMIT;
        ldap_control_paged_result($connection, $pageSize, true, $cookie);
        $result = @ldap_search($connection, $this->baseDN, $this->filter);
        if ($result) {
            if ($importContacts) {
                $this->extractResult($connection, $result);
            }
            @ldap_control_paged_result_response($connection, $result, $cookie);
        } else {
            Log::e("An error occurred while searching LDAP/AD server. Message: " . ldap_error($connection), "LdapConnecter");
        }
    }

    private function extractResult($connection, $result)
    {
        $contacts = ldap_get_entries($connection, $result);
        $totalItemCount = $this->getTotalItemCount($contacts);
        $this->filterContacts($contacts, $totalItemCount);
    }

    private function filterContacts($contacts, $totalItemCount)
    {
        for ($index = 0; $index < $totalItemCount; $index++) {
            $this->setContactData($contacts[$index]);
        }
    }

    private function setContactData($ldapContact)
    {
        $totalItemCount = $this->getTotalItemCount($ldapContact);
        $blacklistedKeys = $this->getBlacklistedKeys();
        $contact = array();
        for ($index = 0; $index < $totalItemCount; $index++) {
            $key = $ldapContact[$index];
            if (!in_array($key, $blacklistedKeys)) {
                $this->addAttribute($key, $ldapContact, $contact);
            }
        }
        if (!empty($contact)) {
            $this->ldapContacts[] = $contact;
        }
    }

    private function addAttribute($key, $ldapContact, &$contact)
    {
        $this->addLdapKeyForPreview($key);
        $attribute = $ldapContact[$key];
        $value = ($key != 'memberof') ? $this->getAttributesValue($attribute) : $this->getGroupString($attribute);
        $res = json_encode($value);
        if ($res != false) {
            $contact[$key] = $value;
        }
    }

    private function addLdapKeyForPreview($key)
    {
        if (!in_array($key, $this->ldapKeys)) {
            $this->ldapKeys[] = $key;
        }
    }

    private function getBlacklistedKeys()
    {
        return array(
            'objectclass', 'distinguishedname', 'instancetype', 'whencreated', 'whenchanged', 'usncreated',
            'objectsid', 'usnchanged', 'objectguid', 'useraccountcontrol', 'badpwdcount', 'codepage',
            'badpasswordtime', 'lastlogoff', 'lastlogon', 'logonhours', 'pwdlastset', 'primarygroupid',
            'admincount', 'accountexpires', 'logoncount', 'samaccounttype', 'objectcategory',
            'iscriticalsystemobject', 'dscorepropagationdata', 'lastlogontimestamp', 'localpolicyflags',
            'operatingsystem', 'operatingsystemversion', 'serverreferencebl', 'dnshostname', 'ridsetreferences',
            'serviceprincipalname', 'msds-supportedencryptiontypes', 'msdfsr-computerreferencebl',
            'showinadvancedviewonly', 'serviceprincipalname', 'msds-supportedencryptiontypes'
        );
    }

    private function getAttributesValue($attributesArray)
    {
        $attributesCount = $this->getTotalItemCount($attributesArray);
        if ($attributesCount > 1) {
            unset($attributesArray['count']);
            return implode(",", $attributesArray);
        } else {
            return is_array($attributesArray) ? $attributesArray[0] : $attributesArray;
        }
    }

    private function getGroupString($groups)
    {
        $menberOfString = array();
        $count = $this->getTotalItemCount($groups);
        for ($i = 0; $i < $count; $i++) {
            if (isset($groups[$i])) {
                $groupName = explode(',', $groups[$i]);
                array_push($menberOfString, str_replace('CN=', '', $groupName[0]));
            }
        }
        return implode(', ', $menberOfString);
    }

    private function getTotalItemCount($list)
    {
        return isset($list['count']) ? $list['count'] : 0;
    }

    private function disconnect($connection)
    {
        $unbind = @ldap_unbind($connection);
        if (!$unbind) {
            Log::e("An error occurred while disconnecting from LDAP/AD server. Message: " . ldap_error($connection), "LdapConnecter");
        }
    }
}
