<?php
/**
 * Class represents records from table saved_pass
 * {autogenerated}
 * @property int $saved_pass_id 
 * @property int $user_id 
 * @property string $format 
 * @property string $pass 
 * @property string $salt 
 * @property timestamp $time 
 * @see Am_Table
 */

class SavedPass extends Am_Record {
}

class SavedPassTable extends Am_Table {
    const PASSWORD_PLAIN = 'plain';
    const PASSWORD_MD5 = 'md5';
    const PASSWORD_CRYPT = 'crypt';
    const PASSWORD_MD5_MD5_PASS_SALT = 'md5(md5(pass).salt)';
    const PASSWORD_MD5_SALT_MD5_PASS = 'md5(salt.md5(pass))';
    const PASSWORD_SHA1_SHA1_PASS_SALT = 'sha1(sha1(pass).salt)';
    const PASSWORD_SHA256_SHA256_PASS_SALT = 'sha256(sha256(pass).salt)';
    const PASSWORD_PHPASS = 'phpass';
    
    protected $_key = 'saved_pass_id';
    protected $_table = '?_saved_pass';
    protected $_recordClass = 'SavedPass';

    /** @return SavedPass|null */
    function findSaved(User $user, $format)
    {
        if ($format === SavedPassTable::PASSWORD_PHPASS)
            $row = array(
                'saved_pass_id' => 0,
                'user_id' => $user->user_id,
                'format' => $format,
                'pass' => $user->pass,
                'salt' => null,
                'time' => null,
            );
        else
            $row = $this->_db->selectRow(
                "SELECT * FROM ?_saved_pass WHERE user_id=?d AND format=?",
                $user->user_id, $format);
        if ($row)
            return $this->createRecord()->fromRow($row);
    }

    function setPass(Am_Event_SetPassword $event)
    {
        $user = $event->getUser();
        $pass = $event->getPassword();
        $sql = array();
        ////
        $obj = $this->getDi()->savedPassRecord;
        $obj->format = self::PASSWORD_PHPASS;
        $obj->pass = $user->pass;
        $obj->salt = null;
        $obj->toggleFrozen(true);
        $event->addSaved($obj);
        ////
        foreach ($this->getPasswordFormats() as $format => $callback)
        {
            $salt = null;
            $arr = array($pass, & $salt, $user);
            $password = $callback ? 
                call_user_func_array($callback, $arr) :
                self::crypt($pass, $format, $salt, $user);
            $sql[] = $this->_db->expandPlaceholders(
                array("(?d, ?, ?, ?)",
                $user->user_id, $format, $password, $salt)
                );
            $obj = $this->getDi()->savedPassRecord;
            $obj->format = $format;
            $obj->pass = $password;
            $obj->salt = $salt;
            $obj->toggleFrozen(true);
            $event->addSaved($obj);
        }
        if ($sql)
            $this->_db->query("INSERT INTO ?_saved_pass
                (user_id, format, pass, salt)
                VALUES " . implode(",", $sql) .
                " ON DUPLICATE KEY UPDATE pass=VALUES(pass), salt=VALUES(salt)");
    }    
    /** @return array of array(type, [optional]callback) */
    function getPasswordFormats()
    {
        $types = array(
            self::PASSWORD_MD5_MD5_PASS_SALT => null,
            self::PASSWORD_CRYPT => null,
        );
        foreach ($this->getDi()->plugins_protect->loadEnabled()->getAllEnabled() as $pl)
        {
            $format = $pl->getPasswordFormat();
            if ($format === null) continue;
            if ($format === self::PASSWORD_PHPASS) continue;
            $types[$format] = array($pl, 'cryptPassword');
        }
        return $types;
    }
    static function crypt($pass, $methodConst, & $salt = null, User $user = null)
    {
        switch ($methodConst)
        {
            case self::PASSWORD_PHPASS:
                $ph = new PasswordHash(8, true);
                // validation request
                if ($salt && $ph->CheckPassword($pass, $salt))
                    return $salt;
                // new password
                return $ph->HashPassword($pass);
            case self::PASSWORD_PLAIN: return $pass;
            case self::PASSWORD_MD5:   return md5($pass);
            case self::PASSWORD_CRYPT: return crypt($pass);
            case self::PASSWORD_MD5_MD5_PASS_SALT:
                if (empty($salt))
                    $salt = Am_Di::getInstance()->app->generateRandomString(3);
                return md5(md5($pass) . $salt);
            case self::PASSWORD_MD5_SALT_MD5_PASS:
                if (empty($salt))
                    $salt = Am_Di::getInstance()->app->generateRandomString(3);
                return md5($salt . md5($pass));
            case self::PASSWORD_SHA1_SHA1_PASS_SALT:
                if (empty($salt))
                    $salt = Am_Di::getInstance()->app->generateRandomString(3);
                return sha1(sha1($pass) . $salt);
            case self::PASSWORD_SHA256_SHA256_PASS_SALT:
                if (empty($salt))
                    $salt = Am_Di::getInstance()->app->generateRandomString(3);
                return hash('sha256', hash('sha256', $pass) . $salt);
            default:
                throw new Am_Exception_InternalError("Unknown crypt method passed: [" . htmlentities($methodConst) . "]");
        }
    }
}
