<?php
/**
 * IMPORTANT NOTE
 * The code of this system is closed and a private proprietary of Flex Solutions.
 * A written authorization/license from Flex Solutions is required before using or modifying this code.
 * Email: sales@flexsolution.biz, Phone: +962795342585, Website: http://www.flexsolutions.biz
 */
 
// We will disable the warning in order to avoid warning messages in case the search method return an empty result
error_reporting(~E_WARNING);

/**
 *  This class will implement the basic LDAP methods required by this application, these methods are
 *  1- Connect to the LDAP server
 *  2- Bind a user to the LDAP server
 *  3- Search the ldap directory
 * @package application.models
 *
 *
 * @author Amaney
 * @package application.models
 */

 
class LDAP {
    /* Class Attribute */

    /**
     * @var String The ldap connnection
     */
    public static $ldap_conn;
    /**
     * @var String The first ldap server (for search)
     */
    public static $first_ldap_server;
    /**
     * @var String  The redundant ldap server (for search)
     */
    public static $second_ldap_server;
    /**
     * @var String The first authentication ldap server
     */
    public static $first_authentication_ldap_server;
    /**
     * @var String The second authentication ldap server
     */
    public static $second_authentication_ldap_server;
    /**
     * @var String The ldap server's port number
     */
    public static $ldap_port;
    /**
     * @var String The base DN
     */
    public static $base_dn;

    /**
     * This method will try to establish a connection to the ldap server
     * @param string $server_type it's either "search" or "authentication"
     * @return boolean
      The output of this method is eihter true or false
      True: The connection is established successfully.
      Flase: The connection cannot be established.

     */
    static function connect($server_type = "search") {
        // Get the connection settings from the Settings array (i.e. from the database)
        $settings_array = Setting::getSettingsArray();
        self::$first_ldap_server = isset($settings_array["first_ldap_server"]) ? $settings_array["first_ldap_server"] : "";
        self::$second_ldap_server = isset($settings_array["second_ldap_server"]) ? $settings_array["second_ldap_server"] : "";
        self::$ldap_port = isset($settings_array["ldap_port"]) ? $settings_array["ldap_port"] : "";
        self::$base_dn = isset($settings_array["base_dn"]) ? $settings_array["base_dn"] : "";
        self::$first_authentication_ldap_server = isset($settings_array["first_authentication_ldap_server"]) ? $settings_array["first_authentication_ldap_server"] : "";
        self::$second_authentication_ldap_server = isset($settings_array["second_authentication_ldap_server"]) ? $settings_array["second_authentication_ldap_server"] : "";

        // Connect to the first ldap server
        self::$ldap_conn = ldap_connect(self::$first_ldap_server, self::$ldap_port);
        // note:  self::$ldap_conn is always a resource even if primary is down
        // try anonymous login to test connection
        $anon = ldap_bind(self::$ldap_conn);

        // Check if the connection was successfully established, if not then try to connect to the second server
        if (!$anon) {
            // test failed, connect to failover host
            if (isset(self::$second_ldap_server))
                self::$ldap_conn = ldap_connect(self::$second_ldap_server, self::$ldap_port);
        } else {
            // test passed, unbind anonymous and reconnect to primary
            ldap_unbind(self::$ldap_conn);
            // Connect to the first ldap server
            self::$ldap_conn = ldap_connect(self::$first_ldap_server, self::$ldap_port);
        }

        //--------------------------------------------
        // Override the connection in case it will be used for the authentication
        if ($server_type == "authentication") {
            self::$ldap_conn = ldap_connect(self::$first_authentication_ldap_server, self::$ldap_port);
            // try anonymous login to test connection
            $anon = ldap_bind(self::$ldap_conn);

            // Check if the connection was successfully established, if not then try to connect to the second server
            if (!$anon) {
                // test failed, connect to failover host
                self::$ldap_conn = ldap_connect(self::$second_authentication_ldap_server, self::$ldap_port);
                // try anonymous login to test connection
                $anon = ldap_bind(self::$ldap_conn);
                if (!$anon) {
                    return "both_servers_failed";
                } else {
                    // test passed, unbind anonymous and reconnect to primary
                    ldap_unbind(self::$ldap_conn);
                    // Connect to the second ldap server
                    self::$ldap_conn = ldap_connect(self::$second_authentication_ldap_server, self::$ldap_port);
                }
            } else {
                // test passed, unbind anonymous and reconnect to primary
                ldap_unbind(self::$ldap_conn);
                // Connect to the first ldap server
                self::$ldap_conn = ldap_connect(self::$first_authentication_ldap_server, self::$ldap_port);
            }
        }

        if (self::$ldap_conn) {
            // The connection is established successfully.
            return true;
        } else {
            // The connection cannot be established.
            return false;
        }
    }

    /**
     * This method will try to bind a credentials to the ldap server
     *  
     * @param string $username The username that is needed to be binded
     * @param string $password The password that is needed to be binded in addition to the username
     * 
     * @return boolean  "True" The binding done  Or "Flase" The binding failed.
     */
    static function bind($username = "", $password = "") {
        $server_type = "search";

        // Check if the username and password are passed (=> connect for authentication not search), i.e. we need to connect to the authentication server
        if ($username != "" && $password != "") {
            $server_type = "authentication";
        }

        // The connection should be established first
        $connection = self::connect($server_type);
        if ($connection === "both_servers_failed") {
            return "both_servers_failed";
        } else if ($connection == true) {
            // Check if the passed credentials represent an anonymous user
            if ($username == "" && $password == "") {
                // bind to the ldap server anonymously
                $ldapbind = ldap_bind(self::$ldap_conn);
            } else {
                // bind to the ldap server normally
                // NOTE: for the active directory binding the username should be the email of the user (e.g. admin@exchange.slb.com but without the exchange => the email should be admin@slb.com)
                if (strpos($username, "exchange.") !== false) {
                    $username = str_replace("exchange.", "", $username);
                }

                $ldapbind = ldap_bind(self::$ldap_conn, $username, $password);
            }

            // Verify binding
            if (!$ldapbind) {
                // The binding done successfully.
                return false;
            } else {
                // The binding done failed.
                return true;
            }
        }
    }

    /**
     * This method will search for a user by his alias, if found it will set his email (to be returned)
     *
     * @param string $alias The alias of the user to be used in the search criteria
     * @param string &$email: The email of the user, it is an output parameter, it will be set if the user found.
     *
     * @return boolean The return value of this method is the error message, in case the search failed.
     */
    static function getUserEmailByAlias($alias, &$email) {
        try {
            // First, we will try to bind to the server anonymously (not that the bind will establish a connection)
            if (self::bind()) {
                // The rows returned by the search method
                $rows = "";
                $search_filter = "(alias=$alias)";

                // Perform the search, note that we will limit the search be 1 result since the alias is unique
                $results = ldap_search(self::$ldap_conn, self::$base_dn, $search_filter, array("mail"), 0, 1);

                // Check if the search failed
                if (!$results) {
                    $email = "";
                    return yii::t('default', 'This username is not exist in the LDAP directory.');
                } else {
                    // Get the rows from using the returned result
                    $rows = ldap_get_entries(self::$ldap_conn, $results);

                    // check if there is no rows with the returned result
                    if (!$rows || !isset($rows[0])) {
                        $email = "";
                        return yii::t('default', 'This username is not exist in the LDAP directory.');
                    } else {
                        // There search returned rows, so we will get the first one
                        $email = isset($rows[0]["mail"]) ? $rows[0]["mail"][0] : ""; // we used "" in case the email not set
                        return "";
                    }
                }
            } else // The bind failed {
                $email = "";
            return yii::t('default', 'Cannot connect to the LDAP server, please contact the system administrator.');
        } catch (Exception $exc) {
            $email = "";
            return yii::t('default', 'Cannot connect to the LDAP server, please contact the system administrator.');
        }
    }

    /**
     * This method will search for a user by his alias, if found it will set gridDataProvider (to be returned)
     *
     * @param string $alias The alias of the user to be used in the search criteria
     * @param string &$gridDataProvider: The email of the user, it is an output parameter, it will be set if the user found.
     * 
     * @return boolean The return value of this method is the error message, in case the search failed.
     */
    static function getUsers($person_info, &$gridDataProvider) {
        try {
            // First, we will try to bind to the server anonymously (not that the bind will establish a connection)
            if (self::bind()) {
                // The rows returned by the search method
                $gridDataProvider = null;
                $search_filter = "(|(cn=*$person_info*)(alias=*$person_info*)(employeeNumber=*$person_info*))";

                // Perform the search, note that we will limit the search be 1 result since the alias is unique
                $results = ldap_search(self::$ldap_conn, self::$base_dn, $search_filter, array("jpegphoto", "cn", "alias", "employeenumber", "employeetype", "mobile", "mail", "manager"), 0, 100);

                // Check if the search failed
                if (!$results) {
                    $gridDataProvider = null;
                    return yii::t('default', 'There is no result matching the requesting criteria');
                } else {
                    // Get the rows from using the returned result
                    $rows = ldap_get_entries(self::$ldap_conn, $results);

                    // check if there is no rows with the returned result
                    if (!$rows || !isset($rows[0])) {
                        $gridDataProvider = null;
                        return yii::t('default', 'There is no result matching the requesting criteria');
                    } else {
                        // There search returned rows, so we willencapsulation it in arrayDataProvider object 
                        $all_person_LDAP = array();
                        for ($i = 0; $i < $rows["count"]; $i++) {

                            $cn = isset($rows[$i]["cn"]) ? $rows[$i]["cn"][0] : "";
                            $alias = isset($rows[$i]["alias"]) ? $rows[$i]["alias"][0] : "";
                            $employeenumber = isset($rows[$i]["employeenumber"]) ? $rows[$i]["employeenumber"][0] : "";
                            $employeetype = isset($rows[$i]["employeetype"]) ? $rows[$i]["employeetype"][0] : "";
                            $mobile = isset($rows[$i]["mobile"]) ? $rows[$i]["mobile"][0] : "";
                            $mail = isset($rows[$i]["mail"]) ? $rows[$i]["mail"][0] : "";
                            $mgr = isset($rows[$i]["manager"]) ? $rows[$i]["manager"][0] : "";
                            //**********************************
                            $arr = explode(",", $mgr);
                            $manager = "";
                            // remove "cn=" from string
                            $manager = substr($arr[0], 3);
                            //**********************************
                            $photo_mime = "";
                            $photo_content = "";
                            if (isset($rows[$i]["jpegphoto"]) && $rows[$i]["jpegphoto"][0]) {
                                $photo_mime = self::getMimeType($rows[$i]["jpegphoto"][0]);
                                $photo_content = base64_encode($rows[$i]["jpegphoto"][0]);
                            }
                            //**********************************
                            $person_LDAP = array('jpegphoto_mime' => $photo_mime, 'jpegphoto_content' => $photo_content, 'id' => $cn, 'alias' => $alias, 'employeenumber' => $employeenumber, 'employeetype' => $employeetype, 'mobile' => $mobile, 'mail' => $mail, 'manager' => $manager);
                            array_push($all_person_LDAP, $person_LDAP);
                        }
                        $gridDataProvider = new CArrayDataProvider($all_person_LDAP);
                        return "";
                    }
                }
            } else {
                // The bind failed
                $gridDataProvider = null;
                return yii::t('default', 'Cannot connect to the LDAP server, please contact the system administrator.');
            }
        } catch (Exception $exc) {
            $gridDataProvider = null;
            return yii::t('default', 'Cannot connect to the LDAP server, please contact the system administrator.');
        }
    }

    /**
     * This method will search for a user by his alias, if found it will set Person (to be returned)
     *
     * @param string $alias The alias of the user to be used in the search criteria
     * @param string &$Person: The LDAP Person, it is an output parameter, it will be set if the user found.
     * 
     * @return string the error message, in case the search failed.
     */
    static function getPersonByAlias($alias, &$Person) {
        try {
            // First, we will try to bind to the server anonymously (not that the bind will establish a connection)
            if (self::bind()) {
                // The rows returned by the search method
                $Person = null;
                $search_filter = "(alias=$alias)";

                // Perform the search, note that we will limit the search be 1 result since the alias is unique
                $results = ldap_search(self::$ldap_conn, self::$base_dn, $search_filter, array("cn", "alias", "employeenumber", "employeetype", "mobile", "mail", "manager", "applicationmifareno", "jpegphoto"), 0, 100);

                // Check if the search failed
                if (!$results) {
                    $Person = null;
                    return yii::t('default', 'There is no result matching the requesting criteria');
                } else {
                    // Get the rows from using the returned result
                    $rows = ldap_get_entries(self::$ldap_conn, $results);

                    // check if there is no rows with the returned result
                    if (!$rows || !isset($rows[0])) {
                        $Person = null;
                        return yii::t('default', 'There is no result matching the requesting criteria');
                    } else {
                        // There search returned rows, so we willencapsulation it in arrayDataProvider object
                        $cn = isset($rows[0]["cn"]) ? $rows[0]["cn"][0] : "";
                        $alias = isset($rows[0]["alias"]) ? $rows[0]["alias"][0] : "";
                        $employeenumber = isset($rows[0]["employeenumber"]) ? $rows[0]["employeenumber"][0] : "";
                        $employeetype = isset($rows[0]["employeetype"]) ? $rows[0]["employeetype"][0] : "";
                        $mobile = isset($rows[0]["mobile"]) ? $rows[0]["mobile"][0] : "";
                        $mail = isset($rows[0]["mail"]) ? $rows[0]["mail"][0] : "";
                        $mgr = isset($rows[0]["manager"]) ? $rows[0]["manager"][0] : "";
                        $badge_number = isset($rows[0]["applicationmifareno"]) ? $rows[0]["applicationmifareno"][0] : "";
                        //**********************************
                        $arr = explode(",", $mgr);
                        $manager = "";
                        // remove "cn=" from string
                        $manager = substr($arr[0], 3);
                        //**********************************
                        $photo_mime = "";
                        $photo_content = "";
                        if (isset($rows[0]["jpegphoto"]) && $rows[0]["jpegphoto"][0]) {
                            $photo_mime = self::getMimeType($rows[0]["jpegphoto"][0]);
                            $photo_content = base64_encode($rows[0]["jpegphoto"][0]);
                        }
                        //**********************************
                        $Person = array('jpegphoto_mime' => $photo_mime, 'jpegphoto_content' => $photo_content, 'cn' => $cn, 'alias' => $alias, 'employeenumber' => $employeenumber, 'employeetype' => $employeetype, 'mobile' => $mobile, 'mail' => $mail, 'manager' => $manager, 'badge_number' => $badge_number);
                        return "";
                    }
                }
            } else {
                $Person = null;
                return yii::t('default', 'Cannot connect to the LDAP server, please contact the system administrator.');
            }
        } catch (Exception $exc) {
            $Person = null;
            return yii::t('default', 'Cannot connect to the LDAP server, please contact the system administrator.');
        }
    }

    /**
     * This method will search for a user by his cn, if found it will set Person (to be returned)
     *
     * @param string $cn The cn of the user to be used in the search criteria
     * @param string  &$Person The LDAP Person, it is an output parameter, it will be set if the user found.
     * 
     * @return string the error message, in case the search failed.
     */
    static function getPersonByCn($cn, &$Person) {
        try {
            // First, we will try to bind to the server anonymously (not that the bind will establish a connection)
            if (self::bind()) {
                // The rows returned by the search method
                $Person = null;
                $search_filter = "(cn=$cn)";

                // Perform the search, note that we will limit the search be 1 result since the alias is unique
                $results = ldap_search(self::$ldap_conn, self::$base_dn, $search_filter, array("cn", "alias", "employeenumber", "employeetype", "mobile", "mail", "manager", "applicationmifareno"), 0, 100);

                // Check if the search failed
                if (!$results) {
                    $Person = null;
                    return yii::t('default', 'There is no result matching the requesting criteria');
                } else {
                    // Get the rows from using the returned result
                    $rows = ldap_get_entries(self::$ldap_conn, $results);

                    // check if there is no rows with the returned result
                    if (!$rows || !isset($rows[0])) {
                        $Person = null;
                        return yii::t('default', 'There is no result matching the requesting criteria');
                    } else {
                        // There search returned rows, so we will encapsulation it in arrayDataProvider object
                        $cn = isset($rows[0]["cn"]) ? $rows[0]["cn"][0] : "";
                        $alias = isset($rows[0]["alias"]) ? $rows[0]["alias"][0] : "";
                        $employeenumber = isset($rows[0]["employeenumber"]) ? $rows[0]["employeenumber"][0] : "";
                        $employeetype = isset($rows[0]["employeetype"]) ? $rows[0]["employeetype"][0] : "";
                        $mobile = isset($rows[0]["mobile"]) ? $rows[0]["mobile"][0] : "";
                        $mail = isset($rows[0]["mail"]) ? $rows[0]["mail"][0] : "";
                        $manager = isset($rows[0]["manager"]) ? $rows[0]["manager"][0] : "";
                        $badge_number = isset($rows[0]["applicationmifareno"]) ? $rows[0]["applicationmifareno"][0] : "";
                        $Person = array('cn' => $cn, 'alias' => $alias, 'employeenumber' => $employeenumber, 'employeetype' => $employeetype, 'mobile' => $mobile, 'mail' => $mail, 'manager' => $manager, 'badge_number' => $badge_number);

                        return "";
                    }
                }
            } else {
                $Person = null;
                return yii::t('default', 'Cannot connect to the LDAP server, please contact the system administrator.');
            }
        } catch (Exception $exc) {
            $Person = null;
            return yii::t('default', 'Cannot connect to the LDAP server, please contact the system administrator.');
        }
    }

    /**
     * This method will search for a user by his Number, if found it will set Person (to be returned)
     *
     * @param string $number The employeenumber of the user to be used in the search criteria
     * @param string &$Person The LDAP Person, it is an output parameter, it will be set if the user found.
     * 
     * @return string the error message, in case the search failed.
     */
    static function getPersonByNumber($number, &$Person) {
        try {
            // First, we will try to bind to the server anonymously (not that the bind will establish a connection)
            if (self::bind()) {
                // The rows returned by the search method
                $Person = null;
                $search_filter = "(employeenumber=$number)";

                // Perform the search, note that we will limit the search be 1 result since the number is unique
                $results = ldap_search(self::$ldap_conn, self::$base_dn, $search_filter, array("cn", "alias", "employeenumber", "employeetype", "mobile", "mail", "manager", "applicationmifareno", "jpegphoto"), 0, 100);

                // Check if the search failed
                if (!$results) {
                    $Person = null;
                    return yii::t('default', 'This Employee Number {n} is not exist in the LDAP directory.', $number);
                } else {
                    // Get the rows from using the returned result
                    $rows = ldap_get_entries(self::$ldap_conn, $results);

                    // check if there is no rows with the returned result
                    if (!$rows || !isset($rows[0])) {
                        $Person = null;
                        return yii::t('default', 'This Employee Number {n} is not exist in the LDAP directory.', $number);
                    } else {
                        // There search returned rows, so we willencapsulation it in arrayDataProvider object
                        $cn = isset($rows[0]["cn"]) ? $rows[0]["cn"][0] : "";
                        $alias = isset($rows[0]["alias"]) ? $rows[0]["alias"][0] : "";
                        $employeenumber = isset($rows[0]["employeenumber"]) ? $rows[0]["employeenumber"][0] : "";
                        $employeetype = isset($rows[0]["employeetype"]) ? $rows[0]["employeetype"][0] : "";
                        $mobile = isset($rows[0]["mobile"]) ? $rows[0]["mobile"][0] : "";
                        $mail = isset($rows[0]["mail"]) ? $rows[0]["mail"][0] : "";
                        $manager = isset($rows[0]["manager"]) ? $rows[0]["manager"][0] : "";
                        $badge_number = isset($rows[0]["applicationmifareno"]) ? $rows[0]["applicationmifareno"][0] : "";
                        //**********************************
                        $photo_mime = "";
                        $photo_content = "";
                        if (isset($rows[0]["jpegphoto"]) && $rows[0]["jpegphoto"][0]) {
                            $photo_mime = self::getMimeType($rows[0]["jpegphoto"][0]);
                            $photo_content = base64_encode($rows[0]["jpegphoto"][0]);
                        }
                        //**********************************
                        $Person = array('jpegphoto_mime' => $photo_mime, 'jpegphoto_content' => $photo_content, 'cn' => $cn, 'alias' => $alias, 'employeenumber' => $employeenumber, 'employeetype' => $employeetype, 'mobile' => $mobile, 'mail' => $mail, 'manager' => $manager, 'badge_number' => $badge_number);

                        return "";
                    }
                }
            } else {
                $Person = null;
                return yii::t('default', 'Cannot connect to the LDAP server, please contact the system administrator.');
            }
        } catch (Exception $exc) {
            $Person = null;
            return yii::t('default', 'Cannot connect to the LDAP server, please contact the system administrator.');
        }
    }

    /**
     * This method will return the mime type of the image file returned from LDAP
     *
     * @param File $photo_content the LDAP file image
     * 
     * @return String the mime type (e.g. Image/jpeg)
     */
    public static function getMimeType($photo_content) {
        $tempFile = tempnam(Yii::app()->basePath . '\\temp\\', "ldap_image");
        file_put_contents($tempFile, $photo_content);
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mime = explode(';', $finfo->file($tempFile));
        unlink($tempFile);
        return $mime[0];
    }

}

