<?php

class AdministratorsController extends AppBackendController {

	var $system = true;
	var $name = 'Administrators';
	var $front = [ 'login' ];
	var $paginate = [];

	# ~ Handle authorization - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	function beforeFilter() {
		parent::beforeFilter();

		# Allow administrator to login
		$this->Auth->allow('login', 'provider', 'recovery', 'logout');

		# Allow access to profile settings for logged in administrator
		if ($this->Auth->user()) {
			$this->Auth->allow([ 'profile', 'customize', 'logout' ]);
		}
	}

	# ~ Prior to showing the form  - - - - - - - - - - - - - - - - - - - - - - - - #
	function beforeForm() {
		if (!empty($this->request->data) && empty($this->request->data['Administrator']['id']) && !isset($this->request->data['Administrator']['group_id'])) {
			$this->request->data['Administrator']['group_id'] = 2;
		}
	}

	# ~ Show the profile of the use	 - - - - - - - - - - - - - - - - - - - - - - - #
	function profile() {

		# Update the profile
		if (!empty($this->request->data['Administrator'])) {
			$this->request->data['Administrator']['id'] = $this->Auth->user('id');
			$data = $this->request->data['Administrator'];
			$error = null;

			# If not password change
			unset($this->request->data['Administrator']['password']);
			unset($this->request->data['Administrator']['password_change']);

			# Build data to save
			$save = [
				'Administrator' => [
					'id'        => $this->Auth->user('id'),
					'full_name' => $data['full_name'],
					'email'     => $data['email'] ]
			];

			# Update photo
			unset($save['Photo']);
			if (!empty($data['token'])) {
				$save['Photo'] = Upload::prepareUploadFromToken('Administrator', 'Photo', $data['token'], null, [], $this->Auth->user('id'));
			}

			# Save
			if ($this->Administrator->saveAll($save)) {

				# Update the session info
				$this->Administrator->clearCache();
				$this->request->data = $this->Administrator->find('first', $this->Auth->user('id'));
				$this->request->data['Administrator']['password'] = null;
				$this->Session->write('Auth.Administrator', $this->request->data['Administrator']);

			} else {
				print_r($this->Administrator->validationErrors);
				die;
				$error = __('An unknown error has occurred');
			}

			# Set the message
			if (empty($error)) {
				$this->Session->setFlash(__('Your profile has been updated', 'danger'));
			} else {
				$this->Session->setFlash(__('An unknown error has occurred', 'danger'));
			}

			echo json_encode([
				'success' => empty($error),
				'error'   => $error
					? $error
					: __('Your profile has been updated')
			]);
			exit;

		} else {
			$this->redirect('/');
		}
	}

	# ~ Change the password- - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	function password() {
		$data = @$this->request->data['Administrator'];
		$this->Administrator->create();
		$error = null;

		# Password entered
		if (empty($data['new_password'])) {
			$error = __('Please supply your new password');

			# Do passwords match
		} else if ($data['new_password'] != $data['new_password_again']) {
			$error = __('Passwords do not match');

			# Minimal password strength
		} else if (strlen($data['new_password']) < Configure::read('Security.password_length')) {
			$error = sprintf(__('Password must be at least %d characters long'), Configure::read('Security.password_length'));

			# Save the password
		} else if (!$this->Administrator->save([ 'id' => $this->Auth->user('id'), 'password' => $data['new_password'] ])) {
			$error = __('Internal error has occurred, please try again');
		}

		# Set the message
		if (empty($error)) {
			$this->Session->setFlash(__('Your password has been changed'));
		} else {
			$this->Session->setFlash(__('An unknown error has occurred', 'danger'));
		}

		# Password successfully changed
		echo json_encode([
			'success' => empty($error),
			'error'   => $error
				? $error
				: __('Your password has been changed')
		]);
		exit;
	}

	# ~ Log the administrator into the CMS - - - - - - - - - - - - - - - - - - - - #
	function login() {
		$this->Administrator->clearCache();
		$this->layout = 'outside';

		if (!empty($this->request->data['Administrator']['login'])) {

			// debug(Security::hash($this->request->data['Administrator']['password'], false, true)); die;

			# Try to login
			if ($this->Auth->login()) {
				$administrator = $this->Auth->user();

				# If login OK, erase recovery_hash from DB.
				if (!empty($administrator['recovery_hash'])) {
					unset($administrator['style'], $administrator['customization']);
					$administrator['recovery_hash'] = null;
					$this->Administrator->save($administrator);
				}

				# Redirect
				$this->redirect([ P => null, C => 'administrators', A => 'login' ]);

				# If login not OK, check if administrator provided new generated password.
			} else {

				# Get password and create recovery_hash.
				$hashedPassword = Security::hash($this->request->data['Administrator']['password'], false, true);

				# Find administrator with same username and recovery_hash.
				$administrator = $this->Administrator->find('first', [
					'fields'     => [ 'Administrator.id', 'Administrator.group_id', 'Administrator.email', 'Administrator.password', 'Administrator.recovery_hash' ],
					'conditions' => [
						'Administrator.username'      => $this->request->data['Administrator']['username'],
						'Administrator.recovery_hash' => $hashedPassword ]
				]);

				# If administrator exists, change it's password to new one.
				if (!empty($administrator)) {
					$administrator['Administrator']['password'] = $this->request->data['Administrator']['password'];
					$administrator['Administrator']['recovery_hash'] = null;
					$this->Administrator->saveAll($administrator);

					# Try to login the administrator
					$this->Auth->login($administrator['Administrator']);
					$this->Session->setFlash(__('Password changed. Please change password to one that you will remember'));

					# Redirect administrator to profile page, and show message about pass change.
					$this->redirect([ C => 'administrators', A => 'profile' ]);

					# Wrong username and/or password.
				} else {

					# Show generated token
					$more = null;
					if (LOCALHOST) {
						$more = '<br />' . '<span style="font-size: 90%">[ ' . Security::hash($this->request->data['Administrator']['password'], false, true) . ' ]</span>';
					} else {
						$more = '<br />' . __('Please try again');
					}

					# Show the error
					$this->Session->setFlash(__('Username or password are incorrect') . $more, 'danger');
				}
			}

		}

		# If logged in
		if ($this->Auth->user()) {

			# phpMyAdmin signOn
			$config = ConnectionManager::$config->default;
			session_write_close();
			session_set_cookie_params(0, '/', '', 0);
			$session = session_name('pmaSignonSession');
			session_start();
			$_SESSION['PMA_single_signon_host'] = $config['host'];
			$_SESSION['PMA_single_signon_user'] = $config['login'];
			$_SESSION['PMA_single_signon_password'] = $config['password'];
			session_write_close();
			session_name($session);
			session_start();

			$referer = $this->Auth->redirect();
			if (!$referer || $referer == '/' || $referer == $this->request->here) {
				$referer = Configure::read('Backend.homepage') ? Configure::read('Backend.homepage') : '/';
			}
			$this->redirect($referer);
		}

		# Alternative logins, via providers
		$providers = [];
		$config = Configure::read('Backend');
		foreach ($config as $item => $value) {
			if (preg_match('~^provider_(?<provider>[a-z]+)(_(?<key>.+))?~', $item, $match)) {
				$key = key_exists('key', $match) ? $match['key'] : 'key';
				$providers[$match['provider']][$key] = $value;
			}
		}
		foreach ($providers as $provider => $params) {
			foreach ($params as $key => $value) {
				if (empty($value)) {
					unset($providers[$provider]);
					break;
				}
			}
		}

		$this->set(compact('providers'));
	}

	# ~ Log the use out of the system  - - - - - - - - - - - - - - - - - - - - - - #
	function logout() {

		# phpMyAdmin signOut
		$session = session_name('pmaSignonSession');
		if (ini_get('session.use_cookies')) {
			$params = session_get_cookie_params();
			setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
		}
		session_destroy();

		$this->Auth->logout();
		$this->redirect([ C => 'administrators', A => 'login' ]);
	}

	# ~ Recover password for supplied email- - - - - - - - - - - - - - - - - - - - #
	function recovery() {
		$this->layout = 'outside';

		# Make sure the administrator is not logged in
		if ($this->Auth->user()) {
			$this->redirect([ C => 'administrators', A => 'login' ]);
		}

		# If the data has been submited
		if (!empty($this->request->data)) {

			# Get provided email
			$email = $this->request->data['Administrator']['email'];
			if (!preg_match(RFC_2822, $email)) {
				$this->Session->setFlash(__('Please supply a valid email address'), 'danger');
				return;
			}

			# Find administrator with that email
			$administrator = $this->Administrator->find('first', [
					'fields'     => [ 'Administrator.id', 'Administrator.group_id', 'Administrator.email', 'Administrator.username' ],
					'recursive'  => -1,
					'conditions' => [
						'Administrator.email' => $email ]
				]
			);

			# If there is no administrator with that email, inform the user
			if (empty($administrator)) {
				$this->Session->setFlash(__('The supplied e-mail address is not registered on this domain'), 'danger');
				return;
			}

			# Generate random password
			$randomPass = getRandomString(10);

			# Save new password
			$administrator['Administrator']['recovery_hash'] = $randomPass;
			$this->Administrator->saveAll($administrator);

			# Send new password to the administrator
			if ($this->Administrator->sendNewPassword($administrator, $randomPass)) {
				$this->Session->setFlash(__('Temporally password has been sent to') . ' ' . $administrator['Administrator']['email']);
				$this->redirect([ C => 'administrators', A => 'login' ]);
			} else {
				$this->Session->setFlash(__('Unknown error occured, please try again in a few minutes', 'danger') . $administrator['Administrator']['email']);
			}
		}
	}

	# ~ Customization functions- - - - - - - - - - - - - - - - - - - - - - - - - - #
	function customize($model = null, $type = 'showFields') {

		# Plugin split
		$modelInstance = ClassRegistry::init($model);
		list(, $model) = pluginSplit($model);

		# Show fields
		if ($type == 'showFields') {
			$fields = array_keys($modelInstance->getVisibleFields());
			$fields = array_merge($fields, array_keys($modelInstance->hasOne));
			$this->set('fields', $fields);

			$this->set('model', $model);

			if (empty($this->request->data)) {
				$this->request->data['Administrator']['customization'] = $this->Administrator->field('customization', $this->Auth->user('id'));
				$this->set('ordering', isset($this->request->data['Administrator']['customization'][$model]) ? $this->request->data['Administrator']['customization'][$model] : []);

			} else {
				$i = 0;
				$count = 0;
				foreach ($this->request->data['Administrator']['customization'][$model] as $field => $value) {
					$this->request->data['Administrator']['customization'][$model][$field]['ordering'] = $i++;
					$count += !!$value['show'];
				}
				if ($count) {
					$this->request->data['Administrator']['id'] = $this->Auth->user('id');
					$customization = $this->Administrator->field('customization', $this->Auth->user('id'));
					$customization = Set::merge($customization, $this->request->data['Administrator']['customization']);
					$this->request->data['Administrator']['customization'] = serialize($customization);
					$this->Administrator->saveAll($this->request->data);
				} else {
					$this->Session->setFlash(__('Please select at least one field'), 'danger');
				}
				$this->redirect($this->referer());
			}

			# Ajax request
		} else if ($this->request->isAjax() && method_exists($this, 'customize_' . $type)) {
			$args = func_get_args();
			unset($args[1]);
			call_user_method_array('customize_' . $type, $this, $args);
			exit;

			# Bad request
		} else {
			$this->redirect($this->referer());
		}
	}

	# ~ Customize the ordering - - - - - - - - - - - - - - - - - - - - - - - - - - #
	function customize_ordering($model, $ordering) {
		$result = [];
		$ordering = explode(' ', trim($ordering));
		foreach ($ordering as $order => $field)
			if (substr($field, 0, 3) == 'th-') {
				$field = substr($field, 3);
				$result[$field]['ordering'] = $order;
			}
		$customization = $this->Auth->user('customization');
		$specific = isset($customization[$model])
			? $customization[$model]
			: [];

		$customization[$model] = Set::merge($specific, $result);
		$this->request->data['Administrator']['id'] = $this->Auth->user('id');
		$this->request->data['Administrator']['customization'] = serialize($customization);
		$this->Administrator->saveAll($this->request->data);
	}

	/**
	 * Login via a provider.
	 */
	public function provider($provider, $token = null) {

		# Sanitize
		$provider = preg_replace('[^a-z]', '', $provider);

		# Trigger the proper provider
		try {
			$method = "{$provider}Provider";
			if (method_exists($this, "{$provider}Provider")) {
				return $this->$method($token);
			}

			throw new Exception('Unknown provider.');

		} catch (Exception $e) {
			$name = Inflector::humanize($provider);
			$this->backtrack("Unable to login via {$name}.", 'danger', false);
		}
	}

	/**
	 * Intellex authentication.
	 */
	private function intellexProvider() {
		if (isset($this->request->query['token'])) {

			// Exchange token
			$data = json_decode(file_get_contents('https://auth.intellex.rs/api/validate/' . $this->request->query['token']), true);
			$user = $data['data'];

			// Save
			$admin = null;
			try {
				$admin = ClassRegistry::init('Administrator')->createAdministratorFromIntellexUser($user);

			} catch (Exception $e) {
				$this->Session->setFlash($e->getMessage(), 'danger');
				$this->redirect([ P => null, C => 'administrators', A => 'login' ]);
			}

			# If logged in
			if ($admin) {
				if ($admin['is_active']) {
					$this->Auth->login($admin);
				} else {
					$this->Session->setFlash('Your request has been submitted, you will be notified once the access has been granted to you', 'success');
				}
				$this->redirect([ P => null, C => 'administrators', A => 'login' ]);
			}

		} else {

			# Redirect to google page
			$this->redirect($gClient->createAuthUrl());
		}

		# If nothing happened
		$this->Session->setFlash(__('An error occurred, please try again later'), 'danger');
		$this->redirect([ P => null, C => 'administrators', A => 'login' ]);
	}

	/**
	 * Google authentication.
	 */
	private function googleProvider() {
		$redirectURL = BACK_BASE_URL . '/administrators/provider/google';

		# On error
		if (isset($this->request->query['error'])) {
			$error = $this->request->query['error'];

			if ($error == 'access_denied') {
				$this->Session->setFlash(__('Please approve permissions'), 'danger');
				$this->redirect([ P => null, C => 'administrators', A => 'login' ]);
			}
		}

		# Load the parameters
		$clientId = Configure::read('Backend.provider_google_client_id');
		$clientSecret = Configure::read('Backend.provider_google_client_secret');

		# Create google client
		$gClient = new Google_Client();
		$gClient->setApplicationName(__('Login to Vector CMS'));
		$gClient->setClientId($clientId);
		$gClient->setClientSecret($clientSecret);
		$gClient->setRedirectUri($redirectURL);
		$gClient->addScope("email");
		$gClient->addScope("profile");

		# Initialize Oauth service
		$service = new Google_Service_Oauth2($gClient);

		# If oauth code received
		if (isset($this->request->query['code'])) {

			# Authenticate
			$gClient->authenticate($this->request->query['code']);
			$this->Session->write('google_access_token', $gClient->getAccessToken());
			$this->redirect($redirectURL);
		}

		# If there is google access token in session
		if ($this->Session->check('google_access_token')) {

			# Set token
			$gClient->setAccessToken($this->Session->consume('google_access_token'));

			# Try to login
			$admin = null;
			try {
				$admin = ClassRegistry::init('Administrator')->createAdministratorFromGoogleUser($service->userinfo->get());

			} catch (Exception $e) {
				$this->Session->setFlash($e->getMessage(), 'danger');
				$this->redirect([ P => null, C => 'administrators', A => 'login' ]);
			}

			# If logged in
			if ($admin) {
				if ($admin['is_active']) {
					$this->Auth->login($admin);
				} else {
					$this->Session->setFlash('Your request has been submitted, you will be notified once the access has been granted to you', 'success');
				}
				$this->redirect([ P => null, C => 'administrators', A => 'login' ]);
			}

		} else {

			# Redirect to google page
			$this->redirect($gClient->createAuthUrl());
		}

		# If nothing happened
		$this->Session->setFlash(__('An error occurred, please try again later'), 'danger');
		$this->redirect([ P => null, C => 'administrators', A => 'login' ]);
	}

}