<?php

/**
 * Admin controller for handling actions on options and option groups.
 *
 * @package XenForo_Options
 */
class XenForo_ControllerAdmin_Option extends XenForo_ControllerAdmin_Abstract
{
	protected function _preDispatch($action)
	{
		$this->assertAdminPermission('option');
	}

	/**
	 * Assert that the option or group definition is editable and error
	 * if it is not.
	 */
	protected function _assertOptionOrGroupDefinitionEditable()
	{
		if (!$this->_getOptionModel()->canEditOptionAndGroupDefinitions())
		{
			throw new XenForo_Exception(new XenForo_Phrase('you_cannot_edit_option_or_group_definitions'), true);
		}
	}

	/**
	 * Index. This shows a list of option groups.
	 *
	 * @return XenForo_ControllerResponse_Abstract
	 */
	public function actionIndex()
	{
		$optionModel = $this->_getOptionModel();

		$groups = $optionModel->getOptionGroupList(array('join' => XenForo_Model_Option::FETCH_ADDON));

		$viewParams = array(
			'groups' => $optionModel->prepareOptionGroups($groups, false),
			'canEditOptionDefinitions' => $optionModel->canEditOptionAndGroupDefinitions()
		);

		return $this->responseView('XenForo_ViewAdmin_Option_ListGroups', 'option_group_list', $viewParams);
	}

	/**
	 * Lists all the options that belong to a particular group.
	 *
	 * @return XenForo_ControllerResponse_Abstract
	 */
	public function actionList()
	{
		$input = $this->_input->filter(array(
			'group_id' => XenForo_Input::STRING
		));

		if ($input['group_id'] === '')
		{
			return $this->responseRedirect(
				XenForo_ControllerResponse_Redirect::RESOURCE_CANONICAL_PERMANENT,
				XenForo_Link::buildAdminLink('options')
			);
		}

		$optionModel = $this->_getOptionModel();

		$fetchOptions = array('join' => XenForo_Model_Option::FETCH_ADDON);

		$group = $this->_getOptionGroupOrError($input['group_id'], $fetchOptions);
		if ($group['debug_only'] && !XenForo_Application::debugMode())
		{
			return $this->responseNoPermission();
		}

		$groups = $optionModel->getOptionGroupList($fetchOptions);
		$options = $optionModel->getOptionsInGroup($group['group_id'], $fetchOptions);

		$canEdit = $optionModel->canEditOptionAndGroupDefinitions();

		$viewParams = array(
			'group' => $group,
			'groups' => $optionModel->prepareOptionGroups($groups, false),
			'preparedOptions' => $optionModel->prepareOptions($options, false),
			'canEditGroup' => $canEdit,
			'canEditOptionDefinition' => $canEdit
		);

		return $this->responseView('XenForo_ViewAdmin_Option_ListOptions', 'option_list', $viewParams);
	}

	public function actionViewOption()
	{
		$optionId = $this->_input->filterSingle('option_id', XenForo_Input::STRING);

		$optionModel = $this->_getOptionModel();

		$group = $optionModel->getGroupForOption($optionId);

		if (!$group)
		{
			return $this->responseError(new XenForo_Phrase('requested_option_not_found'), 404);
		}

		return $this->responseRedirect(
			XenForo_ControllerResponse_Redirect::RESOURCE_CANONICAL_PERMANENT,
			XenForo_Link::buildAdminLink('options/list', $group) . "#_$optionId"
		);
	}

	public function actionGroupDisplayOrder()
	{
		$this->_assertOptionOrGroupDefinitionEditable();

		$optionModel = $this->_getOptionModel();
		$groups = $optionModel->getOptionGroupList();

		if ($this->isConfirmedPost())
		{
			// save adjusted display orders
			$displayOrders = $this->_input->filterSingle('group', array(XenForo_Input::UINT, 'array' => true));

			XenForo_Db::beginTransaction();

			foreach ($displayOrders AS $groupId => $displayOrder)
			{
				if (isset($groups[$groupId]) && $groups[$groupId]['display_order'] != $displayOrder)
				{
					$dw = XenForo_DataWriter::create('XenForo_DataWriter_OptionGroup', XenForo_DataWriter::ERROR_EXCEPTION);
					$dw->setExistingData($groupId);
					$dw->set('display_order', $displayOrder);
					$dw->save();
				}
			}

			XenForo_Db::commit();

			return $this->responseRedirect(
				XenForo_ControllerResponse_Redirect::SUCCESS,
				XenForo_Link::buildAdminLink('options/group-display-order')
			);
		}
		else
		{
			$viewParams = array(
				'groups' => $optionModel->prepareOptionGroups($groups)
			);

			return $this->responseView('XenForo_ViewAdmin_Option_GroupDisplayOrder', 'option_group_display_order', $viewParams);
		}
	}

	/**
	 * Saves a list of options. Lists come in via 2 arrays:
	 *  * options - [name] => value,
	 *  * options_listed - [] => name
	 * This is needed for checkbox options.
	 *
	 * @return XenForo_ControllerResponse_Abstract
	 */
	public function actionSave()
	{
		$this->_assertPostOnly();

		$input = $this->_input->filter(array(
			'group_id' => XenForo_Input::STRING,
			'options' => XenForo_Input::ARRAY_SIMPLE,
			'options_listed' => array(XenForo_Input::STRING, array('array' => true))
		));

		foreach ($input['options_listed'] AS $optionName)
		{
			if (!isset($input['options'][$optionName]))
			{
				$input['options'][$optionName] = '';
			}
		}

		$optionModel = $this->_getOptionModel();
		$optionModel->updateOptions($input['options']);

		$group = $optionModel->getOptionGroupById($input['group_id']);

		return $this->responseRedirect(
			XenForo_ControllerResponse_Redirect::SUCCESS,
			$this->getDynamicRedirect(XenForo_Link::buildAdminLink('options/list', $group))
		);
	}

	/**
	 * Helper to get the option group add/edit form controller response.
	 *
	 * @param array $group
	 *
	 * @return XenForo_ControllerResponse_Abstract
	 */
	protected function _getGroupAddEditResponse(array $group)
	{
		$optionModel = $this->_getOptionModel();
		$addOnModel = $this->_getAddOnModel();

		$viewParams = array(
			'group' => $group,
			'masterTitle' => $optionModel->getOptionGroupMasterTitlePhraseValue($group['group_id']),
			'masterDescription' => $optionModel->getOptionGroupMasterDescriptionPhraseValue($group['group_id']),
			'addOnOptions' => $addOnModel->getAddOnOptionsListIfAvailable(),
			'addOnSelected' => (isset($group['addon_id']) ? $group['addon_id'] : $addOnModel->getDefaultAddOnId())
		);

		return $this->responseView('XenForo_ViewAdmin_Option_EditGroup', 'option_group_edit', $viewParams);
	}

	/**
	 * Displays a form to add a group.
	 *
	 * @return XenForo_ControllerResponse_Abstract
	 */
	public function actionAddGroup()
	{
		$this->_assertOptionOrGroupDefinitionEditable();

		$group = $this->_getOptionModel()->getDefaultOptionGroup();
		return $this->_getGroupAddEditResponse($group);
	}

	/**
	 * Displays a form to edit an existing group.
	 *
	 * @return XenForo_ControllerResponse_Abstract
	 */
	public function actionEditGroup()
	{
		$this->_assertOptionOrGroupDefinitionEditable();

		$groupId = $this->_input->filterSingle('group_id', XenForo_Input::STRING);
		$group = $this->_getOptionGroupOrError($groupId);

		return $this->_getGroupAddEditResponse($group);
	}

	/**
	 * Inserts a new group or updates an existing one.
	 *
	 * @return XenForo_ControllerResponse_Abstract
	 */
	public function actionSaveGroup()
	{
		$this->_assertPostOnly();
		$this->_assertOptionOrGroupDefinitionEditable();

		$input = $this->_input->filter(array(
			'original_group_id' => XenForo_Input::STRING,
			'group_id' => XenForo_Input::STRING,
			'display_order' => XenForo_Input::UINT,
			'debug_only' => XenForo_Input::UINT,
			'addon_id' => XenForo_Input::STRING
		));
		$titlePhrase = $this->_input->filterSingle('title', XenForo_Input::STRING);
		$descriptionPhrase = $this->_input->filterSingle('description', XenForo_Input::STRING);

		$dw = XenForo_DataWriter::create('XenForo_DataWriter_OptionGroup');
		if ($input['original_group_id'])
		{
			$dw->setExistingData($input['original_group_id']);
		}

		$dw->set('group_id', $input['group_id']);
		$dw->set('display_order', $input['display_order']);
		$dw->set('debug_only', $input['debug_only']);
		$dw->set('addon_id', $input['addon_id']);
		$dw->setExtraData(XenForo_DataWriter_OptionGroup::DATA_TITLE, $titlePhrase);
		$dw->setExtraData(XenForo_DataWriter_OptionGroup::DATA_DESCRIPTION, $descriptionPhrase);
		$dw->save();

		$redirectType = ($input['original_group_id']
			? XenForo_ControllerResponse_Redirect::RESOURCE_CREATED
			: XenForo_ControllerResponse_Redirect::RESOURCE_UPDATED);

		return $this->responseRedirect(
			$redirectType,
			XenForo_Link::buildAdminLink('options/list', $dw->getMergedData())
		);
	}

	/**
	 * Displays a form to delete an existing option group.
	 *
	 * @return XenForo_ControllerResponse_Abstract
	 */
	public function actionDeleteGroup()
	{
		$this->_assertOptionOrGroupDefinitionEditable();

		$groupId = $this->_input->filterSingle('group_id', XenForo_Input::STRING);

		if ($this->isConfirmedPost())
		{
			$dw = XenForo_DataWriter::create('XenForo_DataWriter_OptionGroup');
			$dw->setExistingData($groupId);
			$dw->delete();

			return $this->responseRedirect(
				XenForo_ControllerResponse_Redirect::SUCCESS,
				XenForo_Link::buildAdminLink('options')
			);
		}
		else // show delete confirmation dialog
		{
			$viewParams = array('group' => $this->_getOptionGroupOrError($groupId));

			return $this->responseView('XenForo_ViewAdmin_Option_DeleteGroup', 'option_group_delete', $viewParams);
		}
	}

	/**
	 * Allows easy editing of the display order of options in the specified group
	 *
	 * @return XenForo_ControllerResponse_Abstract
	 */
	public function actionDisplayOrder()
	{
		$this->_assertOptionOrGroupDefinitionEditable();

		$groupId = $this->_input->filterSingle('group_id', XenForo_Input::STRING);
		$group = $this->_getOptionGroupOrError($groupId);

		$optionModel = $this->_getOptionModel();
		$options = $optionModel->getOptionsInGroup($groupId);

		if ($this->isConfirmedPost())
		{
			// save adjusted display orders
			$displayOrders = $this->_input->filterSingle('option', array(XenForo_Input::UINT, 'array' => true));

			$relations = $optionModel->getOptionRelationsGroupedByOption(array_keys($options));

			XenForo_Db::beginTransaction();

			foreach ($displayOrders AS $optionId => $displayOrder)
			{
				if (isset($relations[$optionId][$groupId]))
				{
					if ($relations[$optionId][$groupId]['display_order'] != $displayOrder)
					{
						$relations[$optionId][$groupId]['display_order'] = $displayOrder;

						$newRelations = array();
						foreach ($relations[$optionId] AS $relation)
						{
							$newRelations[$relation['group_id']] = $relation['display_order'];
						}

						$dw = XenForo_DataWriter::create('XenForo_DataWriter_Option', XenForo_DataWriter::ERROR_EXCEPTION);
						$dw->setExistingData($optionId);
						$dw->setRelations($newRelations);
						$dw->save();
					}
				}
			}

			XenForo_Db::commit();

			return $this->responseRedirect(
				XenForo_ControllerResponse_Redirect::SUCCESS,
				XenForo_Link::buildAdminLink('options/list', $group)
			);
		}
		else
		{
			$viewParams = array(
				'group' => $group,
				'options' => $options,
				'preparedOptions' => $optionModel->prepareOptions($options)
			);

			return $this->responseView('XenForo_ViewAdmin_Option_DisplayOrder', 'option_display_order', $viewParams);
		}
	}

	/**
	 * Gets the controller response for adding or editing an option.
	 *
	 * @param array $option Information about the option
	 * @param array $relations Option group relations ([group id] => display order)
	 *
	 * @return XenForo_ControllerResponse_Abstract
	 */
	protected function _getOptionAddEditResponse(array $option, array $relations = array())
	{
		$optionModel = $this->_getOptionModel();
		$addOnModel = $this->_getAddOnModel();

		$viewParams = array(
			'option' => $option,
			'groups' => $optionModel->prepareOptionGroups($optionModel->getOptionGroupList()),
			'masterTitle' => $optionModel->getOptionMasterTitlePhraseValue($option['option_id']),
			'masterExplain' => $optionModel->getOptionMasterExplainPhraseValue($option['option_id']),
			'relations' => $relations,
			'addOnOptions' => $addOnModel->getAddOnOptionsListIfAvailable(),
			'addOnSelected' => (isset($option['addon_id']) ? $option['addon_id'] : $addOnModel->getDefaultAddOnId()),

			'redirect' => $this->getDynamicRedirect()
		);

		return $this->responseView('XenForo_ViewAdmin_Option_EditOption', 'option_edit', $viewParams);
	}

	/**
	 * Displays a form to add an option.
	 *
	 * @return XenForo_ControllerResponse_Abstract
	 */
	public function actionAddOption()
	{
		$this->_assertOptionOrGroupDefinitionEditable();

		$option = $this->_getOptionModel()->getDefaultOption();

		$groupId = $this->_input->filterSingle('group_id', XenForo_Input::STRING);
		$relations = array($groupId => 1);

		return $this->_getOptionAddEditResponse($option, $relations);
	}

	/**
	 * Displays a form to edit an existing option.
	 *
	 * @return XenForo_ControllerResponse_Abstract
	 */
	public function actionEditOption()
	{
		$this->_assertOptionOrGroupDefinitionEditable();

		$optionId = $this->_input->filterSingle('option_id', XenForo_Input::STRING);

		$option = $this->_getOptionOrError($optionId);
		$relations = $this->_getOptionModel()->getOptionRelationsByOptionId($optionId);

		return $this->_getOptionAddEditResponse($option, $relations);
	}

	/**
	 * Inserts a new option or updates an existing one.
	 *
	 * @return XenForo_ControllerResponse_Abstract
	 */
	public function actionSaveOption()
	{
		$this->_assertPostOnly();
		$this->_assertOptionOrGroupDefinitionEditable();

		$input = $this->_input->filter(array(
			'original_option_id' => XenForo_Input::STRING,
			'relations' => XenForo_Input::ARRAY_SIMPLE
		));

		$dwInput = $this->_input->filter(array(
			'option_id' => XenForo_Input::STRING,
			'default_value' => XenForo_Input::STRING,
			'edit_format' => XenForo_Input::STRING,
			'edit_format_params' => XenForo_Input::STRING,
			'data_type' => XenForo_Input::STRING,
			'sub_options' => XenForo_Input::STRING,
			'can_backup' => XenForo_Input::UINT,
			'validation_class' => XenForo_Input::STRING,
			'validation_method' => XenForo_Input::STRING,
			'addon_id' => XenForo_Input::STRING
		));
		$phrase = $this->_input->filter(array(
			'title' => XenForo_Input::STRING,
			'explain' => XenForo_Input::STRING
		));

		$dw = XenForo_DataWriter::create('XenForo_DataWriter_Option');
		if ($input['original_option_id'])
		{
			$dw->setExistingData($input['original_option_id']);
		}

		$dw->bulkSet($dwInput);
		$dw->setExtraData(XenForo_DataWriter_Option::DATA_TITLE, $phrase['title']);
		$dw->setExtraData(XenForo_DataWriter_Option::DATA_EXPLAIN, $phrase['explain']);

		$relations = array();
		$firstRelationGroupId = null;
		foreach ($input['relations'] AS $groupId => $data)
		{
			if (!empty($data['selected']) && isset($data['display_order']))
			{
				$relations[$groupId] = intval($data['display_order']);

				if ($firstRelationGroupId === null)
				{
					$firstRelationGroupId = $groupId;
				}
			}
		}
		$dw->setRelations($relations);

		$dw->save();

		$group = $this->_getOptionModel()->getOptionGroupById($firstRelationGroupId);

		$redirectType = ($input['original_option_id']
			? XenForo_ControllerResponse_Redirect::RESOURCE_CREATED
			: XenForo_ControllerResponse_Redirect::RESOURCE_UPDATED);

		return $this->responseRedirect(
			$redirectType,
			$this->getDynamicRedirect(XenForo_Link::buildAdminLink('options/list', $group), false)
		);
	}

	/**
	 * Deletes an existing option.
	 *
	 * @return XenForo_ControllerResponse_Abstract
	 */
	public function actionDeleteOption()
	{
		$this->_assertOptionOrGroupDefinitionEditable();

		$optionId = $this->_input->filterSingle('option_id', XenForo_Input::STRING);

		if ($this->isConfirmedPost())
		{
			$dw = XenForo_DataWriter::create('XenForo_DataWriter_Option');
			$dw->setExistingData($optionId);
			$dw->delete();

			return $this->responseRedirect(
				XenForo_ControllerResponse_Redirect::SUCCESS,
				$this->getDynamicRedirect(XenForo_Link::buildAdminLink('options'))
			);
		}
		else // show confirmation dialog
		{
			$option = $this->_getOptionOrError($optionId);

			$viewParams = array(
				'option' => $option,
				'redirect' => $this->getDynamicRedirect()
			);

			return $this->responseView('XenForo_ViewAdmin_Option_DeleteOption', 'option_delete', $viewParams);
		}
	}

	/**
	 * Gets the specified option group or throws an exception.
	 *
	 * @param integer $groupId
	 * @param array $fetchOptions
	 *
	 * @return array
	 */
	protected function _getOptionGroupOrError($groupId, array $fetchOptions = array())
	{
		$info = $this->_getOptionModel()->getOptionGroupById($groupId, $fetchOptions);
		if (!$info)
		{
			throw $this->responseException($this->responseError(new XenForo_Phrase('requested_option_group_not_found'), 404));
		}

		if (!empty($fetchOptions['join']) && $fetchOptions['join'] & XenForo_Model_Option::FETCH_ADDON)
		{
			if ($this->getModelFromCache('XenForo_Model_AddOn')->isAddOnDisabled($info))
			{
				throw $this->responseException($this->responseError(
					new XenForo_Phrase('option_group_belongs_to_disabled_addon', array(
						'addon' => $info['addon_title'],
						'link' => XenForo_Link::buildAdminLink('add-ons', $info)
					))
				));
			}
		}

		return $this->_getOptionModel()->prepareOptionGroup($info);
	}

	/**
	 * Gets the specified option or throws an exception.
	 *
	 * @param integer $optionId
	 *
	 * @return array
	 */
	protected function _getOptionOrError($optionId)
	{
		$info = $this->_getOptionModel()->getOptionById($optionId);
		if (!$info)
		{
			throw $this->responseException($this->responseError(new XenForo_Phrase('requested_option_not_found'), 404));
		}

		return $this->_getOptionModel()->prepareOption($info);
	}

	/**
	 * Lazy load the option model.
	 *
	 * @return XenForo_Model_Option
	 */
	protected function _getOptionModel()
	{
		return $this->getModelFromCache('XenForo_Model_Option');
	}

	/**
	 * Get the add-on model.
	 *
	 * @return XenForo_Model_AddOn
	 */
	protected function _getAddOnModel()
	{
		return $this->getModelFromCache('XenForo_Model_AddOn');
	}

}