<?php

/**
 * Class AppBackendController
 * Base controller for all backend operations.
 *
 * @property Administrator $Administrator The reference to the logged in administrator.
 */
class AppBackendController extends AppController {

	/** @var bool Ensures tha the system regards this a as backend system. */
	public $isBackend = true;

	public $filterDefaults = [
		'is_deleted' => false
	];

	var $crumbs = [];

	# The list of shortcuts
	var $shortcuts = [];

	/** @var string[] The list of used helpers */
	public $helpers = [ 'ModuleRelationships.Relationship' ];

	/**
	 * Only used by backend controllers.
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	public function beforeFilter() {
		$this->modelClass = Inflector::singularize($this->modelClass);
		$this->layout = 'backend';
		parent::beforeFilter();

		# Define the primary color
		define('PRIMARY_COLOR', ClassRegistry::init('Theme')->field('primary_color', true));
		$this->set('primaryColor', PRIMARY_COLOR);

		$this->beforeMenu();

		# Get modules for the menu
		$modulesMenu = [];
		$moduleGroups = ClassRegistry::init('Module')->getGrouped(false);
		foreach ($moduleGroups as $moduleGroup) {

			# Initialize
			$group = [
				'name'    => $moduleGroup['name'],
				'badge'   => 0,
				'modules' => []
			];

			# Populate with modules
			foreach ($moduleGroup['modules'] as $module)
				if (!empty($module['Module']['is_visible'])) {
					$model = ClassRegistry::init($module['Module']['name']);

					# Icon
					$icon = $model->getMenuIcon($module);

					# Name
					$name = $model->getMenuName($module);

					# Link
					$link = [ P => null, C => Inflector::variable($module['Module']['use_table']), A => 'index' ];

					# Badge
					$badge = ClassRegistry::init($module['Module']['name'])->getMenuBadge($module);

					# Group badge
					if (!empty($badge)) {
						if (is_numeric($badge) && is_numeric($group['badge'])) {
							$group['badge'] += $badge;
							if ($group['badge'] > 100)
								$group['badge'] = '...';

						} else if ($group['badge'] !== '...') {
							$group['badge'] = icon('flag');
						}
					}

					# Update the menu item
					$group['modules'][] = compact('icon', 'name', 'link', 'badge');
				}

			# Append to the list
			$modulesMenu[] = $group;
		}

		# Set for the view
		$this->set(compact('modulesMenu'));

		# Get the bookmarks
		$administrator = $this->Auth->user();
		if ($administrator) {
			$bookmarks = ClassRegistry::init('Bookmark')->find('list', [
				'fields'     => [
					'name', 'url' ],
				'conditions' => [
					'administrator_id' => $administrator['id'] ]
			]);
			$this->set('bookmarks', $bookmarks);
		}

		# Set the base for AJAX
		$this->ajaxBase = '/' . $this->request->params['language'] . '/' . $this->request->params['controller'];
		$this->set('ajaxBase', $this->ajaxBase);

		# Is the administrator superadmin
		$this->set('isSuperadmin', $this->Auth->user('group_id') == GROUP_SUPERADMINS);

		# Shortcuts
		$this->addShortcut('shift -', 'location.href = "/clear"');
		if ($this->Auth->user()) {
			$this->addShortcut('shift +', 'var link = document.createElementNS("http://www.w3.org/1999/xhtml", "a"); link.href = $(".admin-tools .log-system").attr("href"); link.target = "_blank"; link.dispatchEvent(new MouseEvent("click", { "view": window, "bubbles": false, "cancelable": true }));');
		}

		$this->setupFront();
	}

	public function beforeMenu() {
	}

	/**
	 * Just before rendering the output.
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	function beforeRender() {

		# Do we need to render?
		if ($this->actionValue !== null) {
			echo $this->actionValue;
			exit(0);
		}

		# Blocks
		$blocks = [];
		if (isset($this->request->data['Layout']['body']) && isset($this->request->data['Block'])) {
			foreach ($this->request->data['Block'] as $i => $data) {
				$values[$data['name']][$data['language']][$i] = $data['content'];
			}

			# Get all dynamic blocks
			$matches = $this->{$this->modelClass}->parseDynamicTags($this->request->data['Layout']['body'], 'block');

			if (!empty($matches)) {
				foreach ($matches['params'] as $params) {
					preg_match_all('` *(?P<name>[^ =]+) *= *"? *(?P<value>[^"]+)? *"? *`i', $params, $attribute);
					$attributes = array_combine($attribute['name'], $attribute['value']);
					if (!empty($attributes['name'])) {
						$attributes = array_merge($attributes, [ 'content' => isset($values[$attributes['name']]) ? $values[$attributes['name']] : [] ]);
						$blocks[$attributes['name']] = $attributes;
					}
				}
			}
		}
		$this->set('blocks', $blocks);

		$renderAction = 'render' . ucfirst($this->request->params['action']);
		if ($renderAction != 'renderFilter' && method_exists($this, $renderAction)) {
			Timer::start("{$this->name}::{$renderAction}()");
			{
				call_user_method_array($renderAction, $this, $this->request->params['pass']);
			}
			Timer::end("{$this->name}::{$renderAction}()");
		}

		# Default fieldOptions
		if (isset($this->viewVars['fieldOptions'])) {
			$fieldOptions = [
				'meta_keywords'    => [
					'rte' => false ],
				'meta_description' => [
					'rte' => false ]
			];
			$this->set('fieldOptions', array_merge($fieldOptions, $this->viewVars['fieldOptions']));
		}

		# afterAction
		$afterAction = 'after' . ucfirst($this->request->params['action']);
		if ($afterAction != 'afterFilter' && is_callable([ $this, $afterAction ])) {
			Timer::start("{$this->name}::{$afterAction}()");
			{
				call_user_func_array([ $this, $afterAction ], $this->request->params['pass']);
			}
			Timer::end("{$this->name}::{$afterAction}()");
		}

		# Update crumbs
		$this->set('crumbs', array_values($this->crumbs));

		# Set the shortcuts
		$this->set('nxShortcuts', $this->shortcuts);

		# Set model
		$this->set('model', $this->{$this->modelClass});
	}

	# Constructor
	function __construct(CakeRequest $request = null, CakeResponse $response = null) {
		if (empty($this->name) || $this->name == 'App') {
			$this->name = substr(get_class($this), 0, -10);
		}

		# Load DebugKit
		if (Configure::read('debug') >= 3) {
			$this->components[] = 'DebugKit.Toolbar';
		}

		parent::__construct($request, $response);
	}

	# Listing
	function index() {
		if ($this->{$this->modelClass}) {

			# Single item redirection
			if ($this->{$this->modelClass}->isSingleItem) {
				$singleItem = $this->{$this->modelClass}->find('first');
				if (empty($singleItem)) {
					$this->redirect([ C => $this->prefix . $this->request->params['controller'], A => 'add' ]);
				} else {
					$this->redirect([ C => $this->prefix . $this->request->params['controller'], A => 'edit', $singleItem[$this->modelClass]['id'] ]);
				}
			}

			# Display as treeview or not
			$treeview = $this->{$this->modelClass}->Behaviors->enabled('Tree')
				&& empty($this->request->params['filter'])
				&& empty($this->request->params['named']['sort'])
				&& empty($this->request->params['named']['direction'])
				&& !$this->{$this->modelClass}->preventSidebar();
			$this->set('treeview', $treeview);

			# Sorting
			if (isset($this->{$this->modelClass}->Behaviors->Tree)) {

				// TODO: Move this to tree scope
				$treeConditions = [];
				if ($this->{$this->modelClass}->schema('is_deleted')) {
					$treeConditions[$this->{$this->modelClass}->alias . '.is_deleted'] = false;
				}

				$tree = $this->{$this->modelClass}->find(
					'threaded',
					[
						'fields'     => [
							'id', 'parent_id', 'lft', 'rght',
							$this->{$this->modelClass}->displayField ],
						'recursive'  => -1,
						'conditions' => $treeConditions,
						'order'      => $this->{$this->modelClass}->alias . '.lft ASC' ]
				);
				$this->set('tree', $tree);
				$this->set('displayField', $this->{$this->modelClass}->displayField);
			}

			# Set category tree
			if ($this->{$this->modelClass}->schema('category_id')) {
				$category_tree = $this->Category->find(
					'threaded',
					[
						'fields'     => [
							'id', 'parent_id', 'lft', 'rght', 'title' ],
						'recursive'  => -1,
						'conditions' => [ 'Category.model' => $this->modelClass, 'Category.is_deleted' => false ]
					]
				);

				if (!empty($category_tree)) {
					$this->set('category_tree', $category_tree);
				}
			}

			# Table customization
			$fields = array_keys(ClassRegistry::init($this->modelClass)->getVisibleFields());
			$fields = array_merge($fields, array_keys($this->{$this->modelClass}->hasOne));
			$this->set('fields', $fields);
			$this->set('model', $this->modelClass);
			if (empty($this->request->data) && $this->modelClass != 'Administrator') {
				$this->request->data['Administrator']['customization'] = $this->Administrator->field('customization', $this->Auth->user('id'));
				$this->set('ordering', isset($this->request->data['Administrator']['customization'][$this->modelClass]) ? $this->request->data['Administrator']['customization'][$this->modelClass] : []);
			}

			# Maintain show all
			$savedSession = $this->Session->read($this->modelClass);
			if (empty($this->request->params['all'])) {

				# If there is all in session, show all.
				if (!empty($savedSession['all'])) {
					$this->request->params['all'] = 'all';
				}

			} else {

				# If there is no all in session, but administrator choosed show all, put it in there.
				if (empty($savedSession['all'])) {
					$savedSession['all'] = 'all';
					$this->Session->write($savedSession);
				}
			}

			# Check if index should be paginated
			$paginated = !$treeview && empty($this->request->params['all']);
			$this->set('paginated', $paginated);

			# Maintain filter data for pagination.
			$filter = $this->Session->read('Filter');
			if (!empty($filter) && empty($this->request->data[$this->modelClass])) {
				$this->request->data = $this->Session->read('Filter');
			}

			# Get the custom restriction
			if (!empty($this->request->named['show'])) {
				$selectedRestriction = @$this->request->named['show'];
				if ($selectedRestriction == '*') {
					$selectedRestriction = null;
				}

			} else {
				$selectedRestriction = $this->Session->read("CustomIndexRestriction.{$this->modelClass}");
			}
			$this->Session->delete("CustomIndexRestriction");
			$this->Session->write("CustomIndexRestriction.{$this->modelClass}", $selectedRestriction);

			# Maintain custom index restrictions
			$restrictions = $this->{$this->modelClass}->customIndexRestrictions();
			$customIndexRestriction = [];
			if (!empty($restrictions)) {
				if (!empty($selectedRestriction) && !empty($restrictions[$selectedRestriction])) {
					$customIndexRestriction = $restrictions[$selectedRestriction];
				} else {
					$selectedRestriction = @key($restrictions);
					$customIndexRestriction = @reset($restrictions);
				}
			}
			$this->set('customIndexRestriction', $selectedRestriction);

			# Set params for search
			$this->{$this->modelClass}->recursive = false;

			# Limit
			if (empty($this->paginate[$this->modelClass]['limit'])) {
				$limit = $paginated ? $this->{$this->modelClass}->paginate : 1000;
				$this->paginate[$this->modelClass]['limit'] = $limit;
			}

			# maxLimit
			if (empty($this->paginate[$this->modelClass]['maxLimit'])) {
				$this->paginate[$this->modelClass]['maxLimit'] = 1000;
			}

			# Conditions
			$this->paginate[$this->modelClass]['conditions'] = array_merge((array) $this->filter(), (array) $customIndexRestriction, (array) $this->{$this->modelClass}->getIndexConditions());

			# Table export
			$this->__handleExport($this->paginate[$this->modelClass]['conditions']);

			# Order by lft if tree
			if ($this->{$this->modelClass}->schema('parent_id') && $this->{$this->modelClass}->schema('lft')) {
				if (!isset($this->paginate[$this->modelClass]['order'])) {
					$this->paginate[$this->modelClass]['order'] = [ $this->modelClass . '.lft' => 'ASC' ];
				}
			}

			# Custom default ordering
			$customDefaultIndexOrder = $this->{$this->modelClass}->getDefaultIndexOrder();
			if (!empty($customDefaultIndexOrder)) {
				$this->paginate[$this->modelClass]['order'] = $customDefaultIndexOrder;
			}

			# Execute the search
			$this->set('records', $this->paginate());

			# Shortcuts
			$add = Router::url([ A => 'add' ]);
			$this->addShortcut('ctrl space', 'location.href = "' . $add . '"');

			# Set crumbs
			$this->crumbs[] = __(Inflector::humanize(Inflector::tableize($this->name)));
		}
	}

	# ~ Handle export- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	private function __handleExport($conditions = []) {

		# Check if export requested
		if (!isset($this->request->query['export']))
			return;
		if (!in_array($this->request->query['export'], [ 'xlsx', 'csv', 'json', 'xml' ]))
			return;

		# Get export type
		$type = $this->request->query['export'];

		# Load export adapter factory
		require_once(VECTORCMS_ROOT . 'Vendor' . DS . 'ExportAdapters' . DS . 'AdapterFactory.php');

		# Initialize factory and export data
		$adapterFactory = new AdapterFactory();
		$adapterFactory
			->initializeAdapter($type, $this->{$this->modelClass})
			->setConditions($conditions)
			->export();
	}

	/**
	 * Set the front configuration and translations.
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	private function setupFront() {

		// Config
		$this->set('jsConfig', [
			'uploadPlaceholder' => Upload::POST_PLACEHOLDER
		]);

		// Translations
		$this->set('jsPolyglot', [

			# Warning if the form has unsaved changes
			'Warning: Your changes have not been saved.' => __('Warning: Your changes have not been saved.'),

			# Upload
			'Choose file'                                => __('Choose file'),
			'No file selected'                           => __('No file selected'),
			'Uploading…'                                 => __('Uploading…'),
			'An error has occurred'                      => __('An error has occurred'),
			'Delete file'                                => __('Delete file'),
			'Undo'                                       => __('Undo'),
			'this file will be deleted'                  => __('this file will be deleted'),
			'click here to upload again'                 => __('click here to upload again'),
			'Click here to upload new file'              => __('Click here to upload new file'),
		]);
	}

	/**
	 * Displays the details about a single record for module.
	 *
	 * @param    id
	 *                Unique identified of record.
	 *                - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	public function view($id = null) {

		if (!is_numeric($id) || !$record = $this->{$this->modelClass}->find('first', [ 'conditions' => [ $this->modelClass . '.id' => $id ], 'full' => true ])) {
			$this->Session->setFlash(__('Invalid id supplied'), 'danger');
			$this->redirect($this->redirect);
		}
		$this->set('record', $record);
		$this->{$this->modelClass}->id = $id;
		$this->{$this->modelClass}->data = $record;

		# Neighbours
		/*!
			$orderField = 'id';
			if(!empty($this->{$this->modelClass}->order) && is_array($this->{$this->modelClass}->order)) {
				$fieldname = lastFromDot(key($this->{$this->modelClass}->order));
				if(isset($record[$this->modelClass][$fieldname])) {
					$orderField = $fieldname;
				}
			}
			$neighbours = $this->{$this->modelClass}->find('neighbors', array(
				'field' => $orderField,
				'value' => $record[$this->modelClass][$orderField],
				'recursive' => -1
			));
			$this->set('prev', $neighbours['prev']);
			$this->set('next', $neighbours['next']);
		*/

		# Get log history
		$logHistory = $this->Log->getLogForItem($this->modelClass, $id);
		$this->set('logHistory', $logHistory);

		# Field options
		if (!empty($this->{$this->modelClass}->viewOptions)) {
			$this->set('fieldOptions', $this->{$this->modelClass}->viewOptions);
		}
		if (!empty($this->{$this->modelClass}->formOptions)) {
			$this->set('formOptions', $this->{$this->modelClass}->formOptions);
		}

		# Append crumb
		$this->crumbs[] = [
			__(Inflector::humanize(Inflector::tableize($this->name))),
			[
				P => $this->request->params[P],
				C => $this->request->params[C],
				A => 'index' ]
		];
		$this->crumbs[] = !empty($record[$this->modelClass][$this->{$this->modelClass}->displayField])
			? $record[$this->modelClass][$this->{$this->modelClass}->displayField]
			: 'ID: ' . $record[$this->modelClass]['id'];
	}

	# Add / Edit
	function form($id = null) {
		$multiple = func_num_args() > 1;
		$this->request->params['multiple'] = $multiple;

		if (!$id && isset($this->request->params['id'])) {
			$id = $this->request->params['id'];
		}

		# Commonly used variables
		if (empty($this->viewVars['pages'])) {
			$this->set('pages', $this->Page->getNodeList());
		}

		$this->set('customIndexRestriction', @$this->request->params['named']['show']);

		if (empty($id)) {

			# Single item redirection
			if ($this->{$this->modelClass}->isSingleItem) {
				$singleItem = $this->{$this->modelClass}->find('first');
				if (!empty($singleItem)) {
					$this->redirect([ C => $this->prefix . $this->request->params['controller'], A => 'edit', $singleItem[$this->modelClass]['id'] ]);
				}
			}
		}

		# Save
		if (!empty($this->request->data)) {
			Timer::start("Saving record to database");

			if (empty($this->request->data[$this->modelClass]['id'])) {

				$singleItemAllowed = $this->{$this->modelClass}->isSingleItem && $this->{$this->modelClass}->find('count') == 0;

				if (!$this->{$this->modelClass}->isAddable() && !$singleItemAllowed) {
					$this->redirect([ A => 'index' ]);
					return;
				}

			}

			# Is it ajax?
			$ajax = $this->request->isAjax();

			# Blocks
			if (!empty($this->request->data['Block'])) {
				$name = null;
				foreach ($this->request->data['Block'] as $i => $block) {
					if (empty($block['id']) && empty($block['content'])) {
						unset($this->request->data['Block'][$i]);
						continue;
					}
					if ($name != $block['name']) {
						$ordering = 0;
						$name = $block['name'];
					}
					$this->request->data['Block'][$i]['ordering'] = $ordering++;

					if (!is_numeric($i)) {
						$this->request->data['Block'][] = $this->request->data['Block'][$i];
						unset($this->request->data['Block'][$i]);
					}
				}

				if (empty($this->request->data['Block'])) {
					unset($this->request->data['Block']);
				}
			}

			# Setup orderings
			foreach ($this->request->data as $model => $data)
				if (is_array($data) && is_array(reset($data))) {
					if (in_array($model, [ 'Seo' ]))
						continue;

					$counter = 1;
					foreach ($data as $i => $field) {
						if (!is_string($this->request->data[$model][$i]) && array_key_exists('manualOrdering', $this->request->data[$model][$i])) {
							$this->request->data[$model][$i]['manualOrdering'] = $counter++;
						}
					}
				}

			# Single
			if ($this->{$this->modelClass}->saveAll($this->request->data)) {
				if ($ajax)
					exit;

				$url = "/{$this->request->params['controller']}/view/{$this->{$this->modelClass}->id}";
				$link = '<a href="' . $url . '">' . __('View details') . '</a>';

				# Save related models
				if (!empty($this->request->data['Related'])) {
					$this->saveAlso($this->request->data['Related']);
				}

				# Callback
				if (method_exists($this->{$this->modelClass}, 'afterSaveAll')) {
					$this->{$this->modelClass}->afterSaveAll($this->request->data);
				}

				$this->afterSave();
				$this->{$this->modelClass}->clearCache();

				# Apply or submit form
				if (in_array('Apply', $this->request->data)) {
					$this->Session->setFlash(__('The record has been successfully saved.'));
					$this->redirect(false);
				} else {
					$this->Session->setFlash(__('The record has been successfully saved.') . ' ' . $link);
					$this->redirect($this->redirect);
				}

				# Multiple
			} else if (isset($this->{$this->modelClass}->itemsSaved)) {
				if ($ajax)
					exit;
				$this->Session->setFlash(__('The records have been successfully saved'));
				$this->redirect($this->redirect);

				# Error
			} else {
				if ($ajax)
					exit('ERROR');
				$this->Session->setFlash(__('The record could not have been saved, please try again'), 'danger');
				if (isset($this->request->data[$this->modelClass][$this->{$this->modelClass}->displayField . '__' . Configure::read('Config.language')])) {
					$this->request->data[$this->modelClass][$this->{$this->modelClass}->displayField] = $this->request->data[$this->modelClass][$this->{$this->modelClass}->displayField . '__' . Configure::read('Config.language')];
				}
			}

			Timer::end("Saving record to database");

			# Edit multiple
		} else if ($multiple) {
			$items = [];
			foreach (func_get_args() as $argument)
				if (is_numeric($argument)) {
					$items[] = $argument;
				}
			if (empty($items) || sizeof($items) == 1) {
				$this->setAction('form', end($items));
				return;
			}
			$records = $this->{$this->modelClass}->find('all', [ 'conditions' => [ $this->modelClass . '.id IN("' . implode('", "', $items) . '")' ] ]);
			foreach ($records as $data) {
				$this->request->data[$this->modelClass]['id'][] = $data[$this->modelClass]['id'];
				foreach ($data[$this->modelClass] as $field => $value)
					if ($field != 'id') {
						$this->request->data[$this->modelClass][$field] = isset($this->request->data[$this->modelClass][$field])
							? ($value === $this->request->data[$this->modelClass][$field] ? $value : false)
							: $value;
					}
			}

			$this->request->data[$this->modelClass]['id'] = implode('/', (array) $this->request->data[$this->modelClass]['id']);

			# Append crumbs
			$this->crumbs[] = [
				__(Inflector::humanize(Inflector::tableize($this->name))),
				[
					P => $this->request->params[P],
					C => $this->request->params[C],
					A => 'index' ]
			];
			$this->crumbs[] = __('Multiple edit');

			# Add
		} else if ($id === null) {

			# Make sure this model is addable
			if (!$this->{$this->modelClass}->isAddable() && !$this->{$this->modelClass}->isSingleItem) {
				$this->Session->setFlash(__('Operation not supported'), 'danger');
				$this->redirect($this->redirect);
			}

			# Fresh data
			$this->request->data = $this->{$this->modelClass}->create();

			# Get all possible parents
			if (isset($this->passedArgs['parent_id'])) {
				$this->request->data[$this->modelClass]['parent_id'] = $this->passedArgs['parent_id'];
			}

			# Get all possible categories
			if (isset($this->passedArgs['category_id'])) {
				$this->request->data[$this->modelClass]['category_id'] = $this->passedArgs['category_id'];
			}

			# Handle filter
			if ($filter = $this->Session->read("Filter.{$this->modelClass}")) {
				foreach ($filter as $field => $value)
					if ($value !== '') {
						$this->request->data[$this->modelClass][$field] = $value;
					}
			}

			# On CMS add - approval_status is Accepted by default
			if ($this->{$this->modelClass}->schema('approval_status')) {
				$this->request->data[$this->modelClass]['approval_status'] = AppModel::STATUS_ACCEPTED;
			}

			# Append crumbs
			$this->crumbs[] = [
				__(Inflector::humanize(Inflector::tableize($this->name))),
				[
					P => $this->request->params[P],
					C => $this->request->params[C],
					A => 'index' ]
			];
			$this->crumbs[] = __('Add new');

			# Edit
		} else if (is_numeric($id) && $this->request->data = $this->{$this->modelClass}->find('first', [ 'conditions' => [ "{$this->modelClass}.id" => $id ], 'full' => true ])) {
			if (isset($this->request->params['id']) && $this->request->params['id'] == $id && !empty($this->request->params['copy'])) {
				$this->request->params['copy'] = true;
				unset($this->request->data[$this->modelClass]['id']);

				# Make sure this model is editable
				if (!$this->{$this->modelClass}->isCopyable($id) && !$this->{$this->modelClass}->isSingleItem) {
					$this->redirect([ A => 'view', $id ]);
				}
			}

			# Make sure this model is editable
			if (!$this->{$this->modelClass}->isEditable($id) && !$this->{$this->modelClass}->isSingleItem) {
				$this->redirect([ A => 'view', $id ]);
			}

			# Append crumbs
			$this->crumbs[] = [
				__(Inflector::humanize(Inflector::tableize($this->name))),
				[
					P => $this->request->params[P],
					C => $this->request->params[C],
					A => 'index' ]
			];
			if (!$this->{$this->modelClass}->isSingleItem) {
				$this->crumbs[] = [
					!empty($this->request->data[$this->modelClass][$this->{$this->modelClass}->displayField])
						? $this->request->data[$this->modelClass][$this->{$this->modelClass}->displayField]
						: 'ID: ' . $this->request->data[$this->modelClass]['id'],
					[
						P => $this->request->params[P],
						C => $this->request->params[C],
						A => 'view',
						!empty($this->request->data[$this->modelClass]['id']) ? $this->request->data[$this->modelClass]['id'] : $this->request->params['id'] ]
				];
			}
			$this->crumbs[] = !empty($this->request->params['copy']) ? __('Create a copy') : __('Edit');

			# Error
		} else {
			$this->Session->setFlash(__('Invalid id supplied'), 'danger');
			$this->redirect($this->redirect);
		}

		# Field options
		if (!empty($this->{$this->modelClass}->formOptions)) {
			$this->set('fieldOptions', $this->{$this->modelClass}->formOptions);
		}
	}

	# Delete
	function delete() {
		$records = func_get_args();
		try {
			if (!empty($records)) {

				# Try to delete each record and keep count of successfull ones
				$success = $failure = 0;
				foreach ($records as $id) {
					$this->{$this->modelClass}->delete($id) || $this->{$this->modelClass}->softDeleted ? $success++ : $failure++;
				}

				# All records have been successfully deleted
				if (empty($failure) && !empty($success)) {
					$this->Session->setFlash(__('Delete was successful'));

					# Some records have been deleted, some returned error
				} else if (!empty($failure) && !empty($success)) {
					$this->Session->setFlash(__('Some records could not have been deleted'), 'warning');

					# No records have been deleted
				} else {
					$this->Session->setFlash(__('Delete failed'), 'danger');
				}
			}
		} catch (Exception $e) {
			$this->Session->setFlash($e->getMessage(), 'danger');
		}
		$this->redirect($this->redirect);
	}

	# ~ Saves additional models for the data - - - - - - - - - - - - - - - - - - -
	public function saveAlso($models) {
		foreach ($models as $model => $data) {
			$model = ClassRegistry::init($model);
			$hasOrdering = $model->schema('ordering');
			$ordering = 1;

			# Check for direct relation
			$key = 'cms_' . Inflector::singularize(Inflector::tableize($this->modelClass)) . '_id';
			$related = $model->schema($key);
			$softRelated = $model->schema('model') && $model->schema('foreign_key');

			# Additional modifications
			foreach ($data as $i => $item)
				if (is_array($item)) {

					# Check for relation
					if ($related) {

						# Set the foreign key
						if (empty($data[$i][$key])) {
							$data[$i][$key] = $this->{$this->modelClass}->id;
						}
					} else if ($softRelated) {
						$data[$i]['model'] = $this->modelClass;
						$data[$i]['foreign_key'] = $this->{$this->modelClass}->id;
					}

					# Set ordering
					if (!$hasOrdering) {
						if (isset($data[$i]['ordering'])) {
							unset($data[$i]['ordering']);
						}
					} else {
						$data[$i]['ordering'] = $ordering++;
					}
				} else {
					unset($data[$i]);
				}

			$ids = Hash::extract($data, '{n}.id');

			# Remove all previous to be sure we have deleted obsolete records
			if ($related) {
				$conditions = [
					"{$model->alias}.{$key}" => $this->{$this->modelClass}->id
				];

				if (!empty($ids)) {
					$conditions["{$model->alias}.id NOT"] = $ids;
				}

				$model->deleteAll($conditions);
			} else if ($softRelated) {
				$conditions = [
					"{$model->alias}.model"       => $this->modelClass,
					"{$model->alias}.foreign_key" => $this->{$this->modelClass}->id
				];

				if (!empty($ids)) {
					$conditions["{$model->alias}.id NOT"] = $ids;
				}

				$model->deleteAll($conditions);
			}

			$setKeys = Hash::extract($model->hasSet, '{s}.field');

			// Handle uploads
			//!!TODO: Move this elsewhere
			if ($related) {

				# Format for save
				$saveItems = [];
				foreach ($data as $i => $item) {
					$saveItem = [
						$model->alias => $item
					];

					# If value is array, that is probably upload
					foreach ($saveItem[$model->alias] as $key => $val) {
						if (isset($model->hasSet[$key])) {
							$saveItem[$key] = $val;
							unset($saveItem[$model->alias][$key]);
						}

						if (!isset($model->hasSet[$key]) && is_array($val)) {
							$val['association'] = $key;
							$val['model'] = $model->alias;
							$saveItem[$key] = $val;
							unset($saveItem[$model->alias][$key]);
						}
					}

					$saveItems[] = $saveItem;
				}

				$data = $saveItems;
			}

			# Save new data
			foreach ($data as $item) {
				$model->saveAll($item);
			}
			$model->clearCache();
		}
	}

	# Filter for listing
	function filter() {
		$conditions = [];
		if ($this->request->params['action'] == 'index' && ((!empty($this->request->data) || $this->request->data = $this->Session->read('Filter')) && isset($this->request->data[$this->modelClass]))) {
			foreach ($this->request->data[$this->modelClass] as $field => $value)
				if ($value !== '') {

					# Exact fields
					$exact = false;
					if (substr($field, -1) == '_') {
						$exact = true;
						$field = substr($field, 0, -1);
					}

					# Related for foreign key field
					if (substr($field, -3) == '_id') {
						$exact = true;
					}

					# Keyword
					if ($field == '_keyword') {
						$conditions[] = $this->{$this->modelClass}->getSearchConditions($value);
						continue;
					}

					# Category and parent
					if ($field == 'category_id' || $field == 'parent_id') {
						$rightModel = $field == 'category_id' ? 'Category' : $this->modelClass;
						$choosenItem = $this->$rightModel->find('first', $value);
						$matchItems = $this->$rightModel->find('all', [
							'fields'     => [ 'id' ],
							'conditions' => [
								"{$rightModel}.lft >=" => $choosenItem[$rightModel]['lft'],
								"{$rightModel}.lft <=" => $choosenItem[$rightModel]['rght'] ]
						]);

						$ids = [];
						foreach ($matchItems as $item) {
							$ids[] = $item[$rightModel]['id'];
						}

						$conditions[$this->modelClass . ".{$field}"] = $ids;
						continue;
					}

					# Boolean
					if ($this->{$this->modelClass}->_schema[$field]['type'] == 'boolean') {
						$conditions[$this->modelClass . '.' . $field] = (bool) $value;
						continue;
					}

					# default
					$conditions[] = [ $this->modelClass . '.' . $field . ' LIKE' => $exact ? $value : "%{$value}%" ];
				}

			$this->Session->write('Filter', $this->request->data);
			$this->request->params['filter'] = !empty($conditions);

			# Insert default filter values.
			$usedFilterKeys = [];
			foreach ($conditions as $cond => $condValue) {
				if (is_array($condValue)) {
					reset($condValue);
					$key = str_replace($this->modelClass . ".", "", key($condValue));
				} else {
					$key = str_replace($this->modelClass . ".", "", $cond);
				}

				# Store all used filter keys in one array
				$usedFilterKeys[] = $key;
			}

			# If default filter value is already in array, use that.
			foreach ($this->filterDefaults as $flKey => $flValue) {
				if (!in_array($flKey, $usedFilterKeys) && $this->{$this->modelClass}->schema($flKey)) {
					$conditions[$this->modelClass . '.' . $flKey] = $flValue;
				}
			}
			return $conditions;

			# Default values
		} else {
			$this->set('filterDefaults', $this->filterDefaults);
			foreach ($this->filterDefaults as $field => $value) {
				if ($this->{$this->modelClass}->schema($field)) {
					$conditions[$this->modelClass . '.' . $field] = false;
				}
			}
		}

		$this->Session->delete('Filter');
		if ($this->request->params['action'] != 'index') {
			$this->redirect([ C => $this->prefix . $this->request->params['controller'], A => 'index' ]);
		}
		return $conditions;
	}

	# Tree Sort
	function sort_tree($id = null, $delta = null, $module = null, $scope = null) {
		if (!empty($module)) {
			$this->modelClass = $module;
			$this->{$this->modelClass} = ClassRegistry::init($module);
		}

		if (!isset($this->{$this->modelClass}->Behaviors->Tree)) {
			die;
		}

		if (empty($id)) {
			$tree = $this->{$this->modelClass}->find(
				'threaded',
				[
					'fields'    => [
						'id', 'parent_id', 'lft', 'rght',
						$this->{$this->modelClass}->displayField ],
					'recursive' => -1 ]
			);
			$this->set('tree', $tree);

		} else {

			# Check if multiple type tree (like Categories)
			if ($this->{$this->modelClass}->schema('model')) {

				# If no scope is provided, try to get it
				if (empty($scope)) {
					$scope = $this->{$this->modelClass}->field('model', [ $this->modelClass . '.id' => $id ]);
				}

				# Reset Tree behavior and instantiate it with scope
				$this->{$this->modelClass}->Behaviors->unload('Tree');
				$this->{$this->modelClass}->Behaviors->attach('Tree', [ 'scope' => [
					$this->modelClass . '.model'      => $scope,
					$this->modelClass . '.is_deleted' => false
				] ]);
				if ($this->{$this->modelClass}->verify()) {
					$this->{$this->modelClass}->repair();
				}
			}
			$this->{$this->modelClass}->moveNode($id, $delta);
			die;
		}

		$this->set('displayField', $this->{$this->modelClass}->displayField);
	}

	function filter_tree($model, $key, $value) {
		$this->request->data[$model][$key] = $value;
		$this->Session->write('Filter', $this->request->data);
		$this->redirect();
	}

	function ajaxSwitch($id = null, $field = null) {
		$this->{$this->modelClass}->id = (int) $id;

		# Check for possible errors
		if (!isset($this->{$this->modelClass}->_schema[$field]) || $this->{$this->modelClass}->_schema[$field]['type'] != 'boolean') {
			die(__('Error!'));
		}

		# Check for polyglot fields
		if (!empty($this->{$this->modelClass}->_schema[$field]['polyglot'])) {
			$field .= '__' . Configure::read('Config.language');
		}

		# Toggle the field value
		$value = $this->{$this->modelClass}->field($field, $id);
		$data = $this->{$this->modelClass}->saveField($field, !$value);

		# Return the response
		$response = [
			'field'  => preg_replace('/__[a-z]+$/i', '', $field),
			'id'     => $id,
			'value'  => !empty($data[$this->modelClass][$field]),
			'single' => in_array($field, $this->{$this->modelClass}->singles)
		];
		return $this->json($response, true);
	}

	# ~ Populates the current module with random data  - - - - - - - - - - - - - -
	function populate($count = 7) {
		$this->{$this->modelClass}->populate($count)
			? $this->Session->setFlash(__('The module has been populated with test data'))
			: $this->Session->setFlash(__('This module does not support populate function'), 'danger');

		$this->redirect([ C => $this->request->params['controller'], A => 'index' ]);
	}

	# ~ Creates an additional action for the secondary menu	 - - - - - - - - - - -
	function addAction($icon, $label, $url, $options = null) {
		$this->viewVars['_actions'][] = [ $icon, $label, $url, $options ];
	}

	# ~ Creates an additional custom action for the secondary menu - - - - - - - -
	function addShortcut($command, $action) {
		$this->shortcuts[] = compact('command', 'action');
	}

	# ~ Define an additional shortcut for the administrator	 - - - - - - - - - - - -
	function addCustomAction($action) {
		$this->viewVars['_actions'][] = $action;
	}

	# ~ Dummy function used for overriding - - - - - - - - - - - - - - - - - - - -
	public function afterSave() {
	}

	# ~ Reordering items on admin front	 - - - - - - - - - - - - - - - - - - - - -
	public function ajax_reorder() {

		# Validate input
		if (!empty($this->request->data['id']) && !empty($this->request->data['ordering'])) {

			# Update the database
			$this->{$this->modelClass}->save([
				'id'       => $this->request->data['id'],
				'ordering' => $this->request->data['ordering']
			], false);
			$this->{$this->modelClass}->clearCache();

			# Get the new ordering
			$list = $this->{$this->modelClass}->find('list', [
				'fields' => [
					'id', 'ordering' ]
			]);

			echo json_encode($list);
		}

		exit;
	}

	/**
	 * Show the not found message to the user
	 */
	public function notFound() {
		$this->backtrack(__('The requested page does not exist.'), 'danger', false);
	}

}
