<?php

class LoginEmail_model extends Database_model {

	private $userid;
	private $userdata;
	private $settings;

	public function __activate() {
	}

	public function UserID() {
		if (func_num_args()) {
			$this->userid = func_get_arg(0);
			return $this;
		}
		return $this->userid;
	}
	
	private function _generateVerificationCode($n = 6) {
        $result = '';
        for($i = 0; $i < $n; $i++) {
            $result .= mt_rand(0, 9);
        }
        return $result;
    }
	    
	public function UserData() {
		if (func_num_args()) {
			$this->userdata = func_get_arg(0);
			return $this;
		}
		return $this->userdata;
	}

    public function PasswordCrypt($password, $salt = '') {
        if ($salt == '') {
            if (function_exists('random_bytes')) {
                $salt = random_bytes(16);
            } else {
                $salt = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM);
            }
            $record = unpack('H*salt', $salt);
            $salt = $record['salt'];
            if (CRYPT_BLOWFISH == 1) {
                $salt = '$2a$07$' . $salt . '$';
            } elseif (CRYPT_MD5 == 1) {
                $salt = '$1$' . $salt .'$';
            } else {
                $salt = $salt;
            }
        }
        return crypt($password, $salt);
    }
    
	public function GenerateAuthenticationCode($return_url = '', $isLogin = FALSE) {
		if (is_null($return_url)) $return_url = '/';
		$code = $this->_generateVerificationCode();
		$expiry = time() + 86400;
		$this->tablename('users_verification')->InsertOrUpdate([
			'uv_user_id' => $this->userid,
			'uv_code' => $code,
		],[
			'uv_date' => $expiry,
			'uv_return' => $return_url,
            'uv_login' => $isLogin ? 1 : 0,
		]);
		return $code;
	}
	
	public function VerifyAddress($hash, $auth, $isForgotten = FALSE) {
		if (empty($hash)) return 1106;
		
		$userdata = $this->Tablename('users_source')
						->Query('select * from users_source as us ' .
                                'left join users as u on u.user_id=us.us_user_id' )
						->Where('us_source=%s', 'internal')
						->Where('us_hash=%s', $hash)
						->Get()		// returns null on db error
						->first();
		$this->DbErr();
		$this->userdata = $userdata;
		if ($userdata === NULL) return 1101;				// invalid hash
		$this->userid = $userdata->us_user_id;
		
		if (!empty($userdata->verified) & !$isForgotten) return 1102;		// already verified (check not needed for Forgotten password email)
		if ($auth == '') return 0;
		
		$data = $this->Query('select * from users_verification')
					->where('uv_user_id=%d', $this->userid)
					->where('uv_code=%s', $auth)
					->Get()
					->First();

		if ($data === NULL) return 1103;
		
		if ($data->uv_date < time()) return 1104;

		if (empty($data->uv_login) == $isForgotten) return 1105;			// link for invitation was used for forgotten password, or vices versa
	
		// code is good. activate account, return redirect url
		$userdata->verified = 1;
		$userdata->Saverecord([
			'us_source' => 'internal',
			'us_hash' => $hash,
		]);
//		$this->dberr();
//		exit;
		
		return $data->uv_return;
	}
    
    public function ForgottenPasswordEmail($email, $userhash, $hostname) {
		
		$automated_sender = $this->getSender();
		
		$code = $this->GenerateAuthenticationCode('', TRUE);

		// send verification email
		$data = [
			'link' => $hostname . '/email-link/' . $userhash . '/' . $code 
		];
		$m = new \mailobject();
		$sendresult = $m->template('loginEmail::forgotten')
			->to($email)
			->from($automated_sender)
			->data($data)
			->Send();
		//** if link is clicked, then mark email as verified
    }
	
	public function errormessage($returncode) {
		switch($returncode) {
			case 1101:
				return 'Invalid hash in link';
			case 1102:
				return 'Account has already been verified';
			case 1103:
			case 1105:
				return 'Invalid Authentication Code';
			case 1104:
				return 'Authentication Code has expired';
			case 1106:
				return 'No hash specified in link';
			default:
				return "Unknown Error ($returncode)";
		}
	}

            // if old password specified, do we have enough variance?
	public function ChangePassword($pass1, $pass2) {
		if ($pass1 != $pass2) {
			return ['result' => 1270, 'message' => 'Passwords must match'];
		}
		// suitable password?
		// - sufficiently complex?
		// - meet required groups?
		// - length requirement?
		$analyze = hook_execute('password.check', NULL, $pass1);
		$min_length = $analyze['settings']['min'];
		if ($analyze['length'] < $min_length) {
			return ['result' => 1271, 'message' => sprintf('Password is too short: Add %d character(s).', $min_length - $analyze['length']) ];
		}
		$ngroup = 0;
		$missing = [];
		foreach($analyze['groups'] as $group => $count) {
			if ($count) {
				$ngroup++;
			} else {
				switch($group) {
					case 'symbol':
						$missing[] = 'Symbols';
						break;
					case 'upper':
						$missing[] = 'Uppercase letters';
						break;
					case 'lower':
						$missing[] = 'Lowercase letters';
						break;
					case 'numeric':
						$missing[] = 'Numbers';
						break;
				}
			}
		}
		if ($ngroup < $analyze['settings']['groups']) {
			$groups = array_pop($missing);
			if (count($missing)) {
				$groups = join(', ', $missing) . ' or ' . $groups;
			}
			return ['result' => 1272, 'message' => 'Please add more ' . $groups];
		}
		
		// are we permitted to change this password?
        $this->level = isset($_SESSION['current_user']) ? $_SESSION['current_user']['u_level'] : 0;
		$this->user_id = isset($_SESSION['current_user']) ? $_SESSION['current_user']['u_id'] : 0;
        if ($this->level < 9) {
            if ($this->user_id != $this->userdata->us_user_id) {
                return ['result' => 1273, 'message' => 'Permission Denied'];
            }
        }
        
        // check last x passwords;  but keep last 20 passwords
        if ($this->userdata->us_lastpwd == '') {
            $history = [];
        } else {
            $history = unserialize($this->userdata->us_lastpwd);
        }
        $history_count = $analyze['settings']['history'];
        $check = array_slice($history, 0, $history_count);
        
        foreach($check as $old_password_hash) {
            if ($this->PasswordCrypt($pass1, $old_password_hash) == $old_password_hash) {
                return ['result' => 1274, 'message' => sprintf('Password must not be one of your last %d passwords.', $history_count) ];
            }
        }
        
        $new_password = $this->PasswordCrypt($pass1);
        array_unshift($history, $new_password);
        $history = array_slice($history, 0, 20);
        
        // save new password, set date/time
        $this->tablename('users_source')->InsertOrUpdate([
            'us_source' => $this->userdata->us_source,
            'us_hash' => $this->userdata->us_hash,
        ],[
            'password_hash' => $new_password,
            'verified' => 1,
            'us_lastpwd' =>  serialize($history),
            'us_lastchange' => time(),
        ]);
      
		
		// redirect to original url
		return ['result' => -9999, 'message' => 'Password Changed'];
        // https://pkp.the-computer-site.com/email-link/56104096ebc5a932e5d0ad5ad5cec0d2/566013/password
	}
	
	// get user_id, email address and bounce url
	public function retrieveResendDetails($hash) {
				
		$userdata = $this->Query('select * from users_source as us ' .
						'left join users as u on u.user_id=us.us_user_id' )
				->Where('us_source=%s', 'internal')
				->Where('us_hash=%s', $hash)
				->Get()		// returns null on db error
				->first();
		$this->DbErr();
		$user_id = $userdata->us_user_id;
		$email = $userdata->email;
		
		$data = $this->Query('select * from users_verification')
			->where('uv_user_id=%d', $user_id)
			->orderBy('uv_date desc')
			->Get()
			->First();
		$this->DbErr();
		$bounce = $data->uv_return;
		return [ $user_id, $email, $bounce ];

	}
	
	public function getCompany() {
		$settings = $this->SettingGet('config.module_info', NULL);
		return $settings['companyname'] ?? 'Acme Corporation';
	}
	
	// get Sender email address from pkpCore
	public function getSender() {
		$settings = $this->SettingGet('config.module_info', NULL);
		$replyto = $settings['replyto'];
		
		$settings = $GLOBALS['loader']->LoadModuleSettings('pkpCore');
		$automated_sender = $settings['automated_sender'] ?? '';
		if (empty($automated_sender)) $automated_sender = $replyto;
		return $automated_sender;
	}
	
}