<?php

/**
 * Class file for API key management.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package clearFusionCMS
 * @copyright Copyright (c) 2012 - 2016 clearFusionCMS. All rights reserved.
 * @link http://clearfusioncms.com
 */

/**
 * cfBackendApikeysAction Class
 *
 * Action class to manage the API keys within the system.
 *
 * @package clearFusionCMS
 */
class cfBackendApikeysAction extends cfBackendControllerAction {
	/**
	 * Process the action.
	 */
	function process() {
		$this->testPermission('core.apikeys');
		$this->breadcrumbTrail->addCrumb(__('API Keys', 'cfBackend'), $this->assemble('', 'apikeys'));

		$keyMgr = new flApiKeyMgr($this->db);
		$acMgr = new flAcMgr($this->db);

		// Get the role that we're currently editing
		$edKey = $this->request->getParam('*');
		$delKey = '';
		$addKey = false;
		if($edKey !== null && count($edKey) == 1 && preg_match('/^[a-f0-9]{40}$/', $edKey[0]))
			$edKey = $edKey[0];
		else if($edKey !== null && count($edKey) == 2 && preg_match('/^[a-f0-9]{40}$/', $edKey[0]) && $edKey[1] == 'delete') {
			$delKey = $edKey[0];
			$edKey = '';
		}
		else if($edKey !== null && count($edKey) == 1 && $edKey[0] == 'add') {
			$addKey = true;
			$edKey = '';
		}
		else
			$edKey = '';

		$edmsg = $this->session->flashGet('cfManagerOpSuccess', '');
		if($addKey) {
			// Build form for adding roles
			$formAdd = new flForm($this->request, 'addkeyform', $this->assemble('', 'apikeys', array('add')), true);

			$newDesc = new flFormElementText($formAdd, 'newdesc');
			$newDesc->setLabel(__('API Key Description', 'cfBackend'))
				->setRequired()
				->addValidator(new flFormValidatorRequired())
				->addValidator(new flFormValidatorMaxLength(255));

			$roles = new flFormElementChecklist($formAdd, 'roles', array_keys($keyMgr->getKeyRoles($edKey)), $roleList = $acMgr->getRoles());
			$roles->setLabel(__('Assign Roles', 'cfBackend'))
				->setDescription(__('The API key will have the permissions granted to the roles assigned to it.', 'cfBackend'));

			$addSubmit = new flFormElementSubmit($formAdd, 'addkey');
			$addSubmit->setCaption(__('Add Key', 'cfBackend'))->setCSS('primary');

			$formAdd->focusElement($newDesc);

			$formAdd->groupElements('submit', array($addSubmit), new flFormDecoratorList(new flFormDecoratorDl()));

			if($formAdd->isSubmitted() && $formAdd->isValid()) {
				if($id = $keyMgr->createKey($newDesc->getValue())) {
					$keyMgr->setKeyRoles($id, $roles->getValue());

					$auditRoles = [];
					foreach($roles->getValue() as $v) {
						$auditRoles[] = $roleList[$v];
					}
					cfApiKeyAudit::created($newDesc->getValue(), $auditRoles);

					$this->session->flashSet('cfManagerOpSuccess', $this->alertMessage('Success', __('The new API key has been created.', 'cfBackend')));
					$this->response->redirectTo($this->assemble('', 'apikeys', array($id), true));
				}
				else
					$edmsg = $this->alertMessage('Error', __('Unable to create new API key.', 'cfBackend'));
			}

			$content = $this->renderBlock(__('Add API Key', 'cfBackend'), $formAdd->render($this->tpl->getCodeBuffer()), '', $edmsg);
		}
		else if($edKey) {

			// Build the edit form
			$form = new flForm($this->request, 'edkeyform', $this->assemble('', 'apikeys', array($edKey)), true);

			$keyDisp = new flFormElementText($form, 'apikey', $edKey);
			$keyDisp->setLabel(__('API Key', 'cfBackend'))
				->setReadonly();

			$desc = new flFormElementText($form, 'keydesc', $keyMgr->getKeyDesc($edKey));
			$desc->setLabel(__('API Key Description', 'cfBackend'))
				->setRequired()
				->addValidator(new flFormValidatorRequired())
				->addValidator(new flFormValidatorMaxLength(255));

			$roles = new flFormElementChecklist($form, 'roles', array_keys($prevRoles = $keyMgr->getKeyRoles($edKey)), $roleList = $acMgr->getRoles());
			$roles->setLabel(__('Assign Roles', 'cfBackend'))
				->setDescription(__('The API key will have the permissions granted to the roles assigned to it.', 'cfBackend'));

			$submit = new flFormElementSubmit($form, 'updatekey');
			$submit->setCaption(__('Update Key', 'cfBackend'))->setCSS('primary');

			$delKeySubmit = new flFormElementSubmit($form, 'deletekey');
			$delKeySubmit->setCaption(__('Delete Key', 'cfBackend'))
					->setCSS('danger');

			$form->groupElements('keydesc', array($keyDisp, $desc, $roles), new flFormDecoratorList(new flFormDecoratorDl()))
				->groupElements('submit', array($submit, $delKeySubmit), new flFormDecoratorList(new flFormDecoratorDl()));

			$form->focusElement($desc);

			if($form->isSubmitted() && $form->isValid()) {
				$auditRoles = [];
				$lastRoles = [];
				foreach($roles->getValue() as $v) {
					$auditRoles[] = $roleList[$v];
				}
				foreach($prevRoles as $v => $n) {
					$lastRoles[$n] = $roleList[$v];
				}
				cfApiKeyAudit::update($desc->getValue(), $auditRoles, [
					'description' => $keyMgr->getKeyDesc($edKey),
					'roles' => $lastRoles
				]);

				$keyMgr->updateKeyDesc($edKey, $desc->getValue());
				$keyMgr->setKeyRoles($edKey, $roles->getValue());

				// If deleting role
				if($delKeySubmit->getValue())
					$this->response->redirectTo($this->assemble('', 'apikeys', array($edKey, 'delete')));
				$edmsg = $this->alertMessage('Success', __('The API key has been updated.', 'cfBackend'));
			}
		}
		else if($delKey) {
			$form = new flForm($this->request, 'delkeyform', $this->assemble('', 'apikeys', array($delKey, 'delete')), true);

			$keyDisp = new flFormElementText($form, 'apikey', $delKey);
			$keyDisp->setLabel(__('API Key', 'cfBackend'))
				->setReadonly()
				->setDisabled();

			$name = new flFormElementText($form, 'keydesc', $keyMgr->getKeyDesc($delKey));
			$name->setLabel(__('API Key Description', 'cfBackend'))
				->setDisabled()
				->setReadonly();

			$delSubmit = new flFormElementSubmit($form, 'deleterole');
			$delSubmit->setCaption(__('Permanently Delete API Key', 'cfBackend'))
				->setCSS('danger')
				->setDescription($this->alertMessage('Warning', __('Deleting the API Key may cause loss of access by external systems, this action cannot be undone!', 'cfBackend')));

			if($form->isSubmitted() && $form->isValid()) {
				$roleList = $acMgr->getRoles();
				$prevRoles = $keyMgr->getKeyRoles($delKey);
				$lastRoles = [];
				foreach($prevRoles as $v => $n) {
					$lastRoles[$n] = $roleList[$v];
				}
				cfApiKeyAudit::delete($keyMgr->getKeyDesc($delKey), [
					'roles' => $lastRoles
				]);

				$keyMgr->deleteKey($delKey);
				$this->response->redirectTo($this->assemble('', 'apikeys', array(), true));
			}
		}

		$keyTree = new flWidgetTreeView();
		$keys = $keyMgr->getKeys();
		foreach($keys as $id => $desc) {
			$keyTree->addNode(new flWidgetTreeLinkNode($desc, $this->assemble('', 'apikeys', array($id))))
				->setLastNodeActive($id == $edKey || $id == $delKey);
		}

		$keyRootTree = new flWidgetTreeView();
		$keyRootTree->addNode(count($keys) ? new flWidgetTreeChildNode(__('API Keys', 'cfBackend'), $keyTree, null) : new flWidgetTreeNode(__('API Keys', 'cfBackend'), flWidgetTreeNode::CONTAINER));

		$tree = $keyRootTree->render();

		if($edKey || $delKey) {
			if(($content = $this->lockObject('cfAPIKeys', $edKey ? $edKey : $delKey, true)) === true)
				$content = $this->renderBlock($edKey ? __('Edit Key', 'cfBackend') : __('Delete Key', 'cfBackend'), $form->render($this->tpl->getCodeBuffer()), '', $edmsg);
		}
		else if(!$addKey)
			$content = '';

		// Build floating action menu
		$fam = new flWidgetFloatingActionMenu();
		$fam->setCSS('[[+css]]')
			->addAction('add', __('Add Key', 'cfBackend'), $this->assemble('', 'apikeys', array('add')));

		$this->tpl->registerChunk('fam', $fam->render());

		$this->renderPage(__('API Keys', 'cfBackend'), $content, $this->renderBlock(__('API Keys', 'cfBackend'), $tree));
	}
}
