<?php
App::uses('Module', 'Model');
App::uses('AppHelper', 'View/Helper');

class FormatHelper extends AppHelper {

	var $helpers = [ 'Html', 'Form', 'Text', 'Manicure' ];

	function actions($actions = [], $params = []) {
		Timer::startLoop('FormatHelper::actions()');
		{
			$params = array_merge([ 'paginate' => true ], $params);
			ob_start();
			?>
			<div class="actions">
				<ul class="actions<?= $this->request->params['action'] == 'index' ? ' narrow-actions' : '' ?>">
					<?php foreach ($actions as $action)
						if (!empty($action)) { ?>
							<?php
							# Hardcoded actions
							if (is_string($action)) {
								echo $action;
								continue;
							}

							# Linking actions
							$class = !empty($action[4]) ? "class=\"{$action[4]}\"" : '';
							$icon = !empty($action[0]) ? "<i class=\"fa fa-fw fa-{$action[0]} button\"></i>" : '';
							$caption = "{$action[1]}";
							$options = !empty($action[3])
								? (is_string($action[3]) ? [ 'class' => 'btn btn-default', 'escape' => false ] : array_merge($action[3], [ 'escape' => false ]))
								: [ 'escape' => false, 'class' => 'btn btn-default' ];
							?>
							<?= !empty($action[4]) ? '<li class="' . $action[4] . '">' : '<li>' ?>
							<?= $this->Html->link($icon . $caption, $action[2], $options) ?>
							</li>
						<?php } ?>
				</ul>
			</div>
			<div class="clear"></div>
			<?php

		}
		Timer::endLoop('FormatHelper::actions()');

		return ob_get_clean();
	}

	# ~ Print icon based on the value  - - - - - - - - - - - - - - - - - - - - - - #
	function bool($value) {
		return $value
			? '<i class="' . ON_FA_VALUE . '"></i>'
			: '<i class="' . OFF_FA_VALUE . '"></i>';
	}

	function toggle($controller, $data, $field, $single = false) {
		return $this->Html->link(
			$this->bool($data[$field]),
			[ C => $controller, A => 'ajaxSwitch', $data['id'], $field, $single ],
			[
				'class' => "switch_{$field}_{$data['id']}",
				'rel'   => $field . ' ' . ($single ? 'true' : 'false') ]
		);
	}

	function time($time, $format = null) {

		# Return nothing
		if ($time === 0 || $time === null || $time == '' || preg_match('~^0000-00-00[A-Z ]?(00:00:00)?$~', $time)) {
			return null;
		}

		# Default date format
		if ($format == false) {
			return strftime('%e. %m. %Y.', strtotime($time));

			# Default time format
		} else if ($format === true) {
			return strftime('%e. %m. %Y. %H:%M', strtotime($time));

			# Manual format
		} else {
			return strftime($format, strtotime($time));
		}
	}

	function filesize($size, $sp = false) {
		$sp = $sp === true ? ' ' : $sp;
		switch (true) {
			case $size < 1024:
				return sprintf("%d bytes", $size);
			case round($size / 1024) < 1024:
				return sprintf(__("%d{$sp}KB"), $size / 1024);
			case round($size / 1024 / 1024, 2) < 1024:
				return round($size / 1024 / 1024, 2) . $sp . 'MB';
			case round($size / 1024 / 1024 / 1024, 2) < 1024:
				return sprintf(__("%.2f{$sp}GB"), $size / 1024 / 1024 / 1024);
			default:
				return sprintf(__("%.2f{$sp}TB"), $size / 1024 / 1024 / 1024 / 1024);
		}
	}

	function image($image, $width = null, $height = null, $fancybox = false, $more = [], $onlyUrl = false) {

		# Handle missing images
		if (empty($image) || (is_array($image) && empty($image['filename']))) {
			return $this->image('/theme/no-image.png', $width, $height, false, $more, $onlyUrl);
		}

		# Check if flash
		$flashCheck = is_array($image) && pathinfo($image['filename']);
		if (isset($flashCheck['extension']) && in_array($flashCheck['extension'], [ 'swf', 'flv' ])) {
			return $this->flash($image, $width, $height);
		}

		# Repack pasted image
		$attrs = Set::normalize([ 'alt', 'title', 'class', 'style', 'data-caption' ]);
		$attributes = is_array($image) ? array_intersect_key($image, $attrs) : [];
		if (empty($attributes) && is_array($more))
			$attributes = array_intersect_key($more, $attrs);
		if (!is_array($more))
			$more = $more == 'false' ? [] : [ 'zc' => $more ];
		if (is_array($image)) {
			if (isset($image[0]))
				$image = $image[0];
			$image = $image['file'];
		}
		if (empty($width) || empty($height))
			$more['zc'] = false;

		# If image is missing
		$root = !empty($more['absolute']) ? $more['absolute'] . DS : WWW_ROOT;
		if (empty($image) || !is_readable(urldecode(str_replace("//", "/", $root . $image)))) {
			$image = $root . '/theme/no-image.png';
			$more['zc'] = 'c';
			$fancybox = false;
		}

		$info = array_merge([ 'extension' => '' ], pathinfo($image));
		if (!empty($more['extension']))
			$info['extension'] = $more['extension'];

		# Allow thumbnails for certain filetypes only
		$images = [ 'jpeg', 'jpg', 'png', 'gif', 'svg' ];
		if (!in_array(strtolower($info['extension']), $images)) {
			$type = file_exists(WWW_ROOT . "/img/mimetypes/{$info['extension']}.png") ? $info['extension'] : 'default';
			$image = "img/mimetypes/{$type}.png";
			$fancybox = false;

			# Thumbnail for PDF is also possible
		} else if (strtolower($info['extension']) === 'pdf') {
			$fancybox = false;
		}

		# Title tag
		if (!empty($attributes['title']) && empty($attributes['alt'])) {
			$attributes['alt'] = $attributes['title'];
		}
		unset($attributes['title']);

		$attributes['class'] = empty($attributes['class']) ? 'cms-image' : $attributes['class'] . ' cms-image';
		if (!empty($more['holder'])) {
			$output = $this->Html->image($more['holder'], $attributes);
		} else {
			$output = $this->Html->image($this->Manicure->pad($image, $width, $height, null), $attributes);
		}

		# Fancybox classes and rel
		$class = 'fancybox';
		if (is_array($fancybox) && isset($fancybox['rel'])) {
			$rel = $fancybox['rel'];
			$class .= isset($fancybox['class']) ? ' ' . $fancybox['class'] : '';
		} else {
			$rel = $fancybox !== true ? $fancybox : 'fancybox';
		}

		# No Fancybox #
		if (!$fancybox) {
			return $output;

			# With Fancybox #
		} else {
			return $this->Html->link(
				$output,
				str_replace('//', '/', '/' . $image),
				[ 'class' => $class, 'rel' => $rel ]
			);
		}
	}

	function renderThumbnail($image, $params = []) {
		$params = array_merge([ 'zc' => true ], $params);

		# If no image
		$root = !empty($params['absolute']) ? $params['absolute'] : WWW_ROOT;
		$source = $root . DS . 'theme' . DS . $image;
		if (empty($image) || !is_readable($source)) {
			$source = VECTORCMS_ROOT . 'webroot' . DS . $image;
			if (!is_readable($source)) {
				$image = '/theme/no-image.png';
				$source = WWW_ROOT . $image;
			}
		}

		if (pathinfo($source, PATHINFO_EXTENSION) == 'svg') {
			return [
				'src'    => ltrim($image, '/'),
				'width'  => $params['w'],
				'height' => $params['h'],
			];
		}

		# Zoomcrop
		if (empty($params['w']) || empty($params['h']))
			$this->params['zc'] = '0';
		$x = !empty($params['zc']) ? strtolower($params['zc']) : 'x';
		if (in_array($x, [ 1, 'c' ]))
			$x = 'z';

		# Filters
		$fltr = '.';
		if (!empty($params['fltr']) && is_array($params['fltr'])) {
			$fltr = '.F_' . implode('.F_', $params['fltr']) . '.';
			$fltr = preg_replace('/[^a-z0-9_\|\.-]/i', '', $fltr);
			$fltr = str_replace('|', '-', $fltr);
		}

		# Other params
		$more = '';
		foreach ($params as $param => $value) {
			if (strlen($param) < 5 && !in_array($param, [ 'w', 'h', 'zc', 'src' ]) && ctype_alnum($value)) {
				$more .= "{$param}-{$value}.";
			}
		}

		# Generate thumbnail name
		$info = pathinfo($image);
		if (!empty($params['extension']))
			$info['extension'] = $params['extension'];
		$info['dirname'] = preg_replace('~^/?(upload|img)?/~', '', $info['dirname']);

		# Manicure params moved from filename to parent dir
		$parentDir = $params['w'] . $x . $params['h'] . $fltr . $more;
		if (!empty($parentDir)) {
			$srcUrl = $thumb = "thumbs/{$info['dirname']}/{$parentDir}/{$info['filename']}.{$info['extension']}";
		} else {
			$srcUrl = $thumb = "thumbs/{$info['dirname']}/{$info['filename']}.{$info['extension']}";
		}

		# Manicure or phpThumb generation?
		$generation = Configure::read("Feature.thumb_source");
		if ($generation == null)
			$generation = 'manicure';

		# If not found in cache
		$write = !isset($params['write']) || $params['write'];
		if (!$write || !is_readable(WWW_ROOT . $thumb)) {
			if ($generation == 'manicure' && $write) {

				$srcUrl = 'plugins/phpThumb/phpThumb.php';
				$srcUrl .= '?source=' . $image;
				$srcUrl .= '&thumb=' . $thumb;

				foreach ($params as $param => $value) {
					$srcUrl .= "&" . $param . "=" . $value;
				}

			} else {

				# Import thumbnail
				// TODO: Make this as is should be - with App::uses()
				require_once VECTORCMS_ROOT . 'Vendor/phpThumb/phpthumb.class.php';
				$thumbNail = new phpThumb();

				# Set params #
				$thumbNail->f = $info['extension'];
				$thumbNail->src = $source;
				foreach ($params as $param => $value) {
					$thumbNail->$param = $value;
				}

				# Try to generate and return the path to the image #
				$umask = umask(0);
				@mkdir(WWW_ROOT . dirname($thumb), 0777, true);
				umask($umask);
				if (!$thumbNail->GenerateThumbnail() || ($write && !$thumbNail->RenderToFile(WWW_ROOT . $thumb))) {
					return false;
				}
				$srcUrl = $thumb;
			}

			if (!$write) {

				# Output or return
				if (empty($params['return'])) {
					echo $thumbNail->OutputThumbnail();
					exit(0);

				} else {
					$thumbNail->RenderOutput();
					return $thumbNail->outputImageData;
					die;
				}
			}
		}

		# Get dimensions of image
		$dimension = $this->getImageDimensions($image, $params);

		# Handle optimizations
		if (strpos($srcUrl, PROTOCOL) === false) {

			# Get number of static servers, host and static server prefix.
			$numOfServers = intval(Configure::read("Optimizations.static_servers"));
			$host = str_replace('admin.', '', $_SERVER['HTTP_HOST']);
			$prefix = Configure::read("Optimizations.static_server_prefix");
			$prefix = $prefix == null ? 'static' : $prefix;

			if ($numOfServers != 0) {
				$num = (strlen($thumb) % $numOfServers) + 1;
				$srcUrl = PROTOCOL . "{$prefix}{$num}.{$host}/{$srcUrl}";
			}
		}

		return [
			'src'    => $srcUrl,
			'width'  => $dimension[0],
			'height' => $dimension[1],
		];
	}

	# ~ Get image directly from manicure - - - - - - - - - - - - - - - - - - - - - #
	public function manicure($image, $width = null, $height = null, $options = []) {
		$url = "http://manicure.intellex.rs/?";
		$url .= "domain={$_SERVER['HTTP_HOST']}";
		$url .= "&src=" . $image;
		$url .= "&size=" . (empty($width) ? '' : $width) . 'x' . (empty($height) ? '' : $height);
		# Add all optional params
		foreach ($options as $param => $value) {
			if (in_array($param, [ 'zc', 'match' ])) {
				$url .= "&" . $param . "=" . $value;
				unset($options[$param]);
			}
		}

		$options['width'] = $width;
		$options['height'] = $height;
		return $this->Html->image($url, $options);
	}

	# Get image dimension based on parameters and old image.
	function getImageDimensions($image, $params) {

		# Get actual and requested image size.
		$actualSize = @getimagesize(WWW_ROOT . $image);
		$requestedSize = [ !empty($params['w']) ? $params['w'] : '', !empty($params['h']) ? $params['h'] : '' ];

		# If there was an error calculating the image size
		if (!$actualSize) {
			return $requestedSize;
		}

		# If there is height but not width, scale width based on actual image size.
		if (empty($params['w']) && !empty($params['h'])) {
			$w = round(($actualSize[0] * $params['h']) / $actualSize[1]);
			$h = $params['h'];
			return [ $w, $h ];
		}

		# If there is height but not width, scale width based on actual image size.
		if (empty($params['h']) && !empty($params['w'])) {
			$h = round(($actualSize[1] * $params['w']) / $actualSize[0]);
			$w = $params['w'];
			return [ $w, $h ];
		}

		# If there are both dimensions, check zoomcrop.
		if (!empty($params['w']) && !empty($params['h'])) {
			# If zc is 0, scale one dimension to another.
			if (!empty($params['zc']) && $params['zc'] == '0') {
				# Get ratio, and calculate by larger.
				$widthRatio = $actualSize[0] / $params['w'];
				$heightRatio = $actualSize[1] / $params['h'];
				# Calculate other dimension.
				if ($widthRatio > $heightRatio) {
					$h = ($actualSize[0] * $params['h']) / $actualSize[1];
					return [ $params['w'], $h ];
				} else if ($heightRatio > $widthRatio) {
					$w = ($actualSize[0] * $params['h']) / $actualSize[1];
					return [ round($w), round($params['h']) ];
				} else {
					# If ratios are same.
					return [ round($params['w']), round($params['h']) ];
				}
			} else {

				# If there is no zc, return normal values.
				return [ round($params['w']), round($params['h']) ];
			}
		}
	}

	function flash($flash, $width, $height) {
		if (is_string($flash)) {
			$flash = [
				'path'     => rtrim(dirname($flash), '/') . '/',
				'filename' => basename($flash)
			];
		}
		ob_start();
		?>
		<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="<?= $width ?>" height="<?= $height ?>" wmode="transparent">
			<param name="movie" value="/<?= $flash['path'] . $flash['filename'] ?>" />
			<!--[if !IE]>-->
			<object type="application/x-shockwave-flash" data="/<?= $flash['path'] . $flash['filename'] ?>" width="<?= $width ?>" height="<?= $height ?>" wmode="transparent">
				<!--<![endif]-->
				<p>Alternative content</p>
				<!--[if !IE]>-->
			</object>
			<!--<![endif]-->
		</object>
		<?php
		return ob_get_clean();
	}

	function mail($email) {
		return '<a href="mailto: ' . $email . '">' . $email . '</a>';
	}

	/**
	 * Make sure the URL is an absolute one.
	 *
	 * @param string $url      The raw url.
	 * @param bool   $removeWW Set to true to remove www domain prefix.
	 *
	 * @return    The asboslute version of the URL.
	 */
	function url($url = null, $removeWWW = false) {

		// Extract the data
		preg_match('~^(((?P<protocol>[a-z]+)://)?(?P<domain>([a-z0-9-]+\.)*([a-z0-9-])))?(?P<path>.*)$~ i', $url, $match);

		// Set data
		if (empty($match['protocol']))
			$match['protocol'] = 'http';
		if (empty($match['domain']))
			$match['domain'] = FULL_BASE_URL;

		return "{$match['protocol']}://{$match['domain']}{$match['path']}";
	}

	function pageLink($page, $content = null, $attributes = [], $top = false) {

		# If page id
		if (is_numeric($page)) {
			$pageUrl = ClassRegistry::init('Page')->getPageLink($page);

			# Regular Expression filter
			$reg_exUrl = "/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/";

			# Check if page is url
			if (preg_match($reg_exUrl, $pageUrl)) {
				return $pageUrl;

			} else if (strpos($pageUrl, 'page:') !== false) {
				$pageId = substr($pageUrl, 5);
				return FRONT_BASE_URL . ClassRegistry::init('Page')->getPageLink($pageId);

			} else {
				return FRONT_BASE_URL . $pageUrl;
			}
		}

		# If no page
		if (empty($page['Page']['path'])) {
			return 'javascript: void(0);';
		}

		# Default content
		if ($content === null) {
			$content = $page['Page']['title_short'];
		}

		if (empty($attributes['title'])) {
			$attributes['title'] = $page['Page']['title'];
		}

		# Page wrapper
		if ($page['Page']['type'] === Page::TYPE_WRAPPER && !empty($page['children'])) {
			return $this->pageLink(reset($page['children']), $content, $attributes);

			# External links
		} else if ($page['Page']['type'] == Page::TYPE_LINK && !empty($page['Page']['url']) && strpos($page['Page']['url'], '://')) {

			# Handle target _blank
			$attributes = !empty($page['Page']['url_target']) ? array_merge($attributes, [ 'target' => '_blank' ]) : $attributes;
			return $this->Html->link($content, $page['Page']['url'], $attributes);

			# Category parent
		} else if ($page['Page']['module_id'] !== null && !empty($page['children']) && !$top) {
			$child = reset($page['children']);
			if ($child['Page']['type'] == 'Category') {
				return $this->pageLink($child, $content);
			}

			# Other pages
		} else {
			return $this->Html->link($content, $page['Page']['path'], $attributes);
		}
	}

	function filter($fields = null, $defaults = []) {

		# Return only the button #
		if ($fields === null) {
			$show = __('Show Filter');
			$hide = __('Hide Filter');

			return [
				'filter',
				empty($this->request->params['filter']) ? $show : $hide,
				'javascript: void(0)',
				[
					'class'   => 'btn btn-default',
					'onclick' => "
					$('#FilterForm').slideToggle(
						'fast',
						function() {
							$('.fa-filter').next().text($('#FilterForm').is(':visible') ? '{$hide}' : '{$show}');
							$('#FilterForm').find('input[type=text]:first').focus();
					})" ]
			];
		}

		# Return the complete filter #
		$name = key($this->request->params['models']);
		$model = ClassRegistry::init($name);
		$fields = Set::normalize($fields);
		$strings = [];
		$mapping = [
			'string' => 'text', 'datetime' => 'datetime',
			'time'   => 'time', 'timestamp' => 'datetime',
			'text'   => 'text', 'boolean' => 'select',
			'date'   => 'date', 'float' => 'text',
			null     => null
		];
		$defaults = array_merge([ 'empty' => true, 'default' => false, 'false' => __('No'), 'true' => __('Yes') ], $defaults);

		$filter = $this->Form->create($model->alias, [ 'action' => 'index', 'id' => 'FilterForm', 'style' => empty($this->request->data[$model->alias]) ? 'display: none' : null ]);

		$i = 0;
		foreach ($fields as $field => $params)
			if (!in_array($field, $model->skipFilter)) {
				$params = array_merge($defaults, (array) $params);
				$schema = isset($model->_schema[$field]['type']) && isset($mapping[$model->_schema[$field]['type']]) ? $model->_schema[$field]['type'] : null;

				# Time-related fields
				if (in_array($schema, [ 'date', 'datetime', 'timestamp', 'time' ])) {
					continue;
				}

				# For the set field type
				if (substr($field, -4) == '_set') {
					$setModel = Module::getModelForSetField($field);
					$setModel = ClassRegistry::init($setModel);
					$params['options'] = $setModel->find('list');
				}

				# Options if boolean
				if ($schema == 'boolean') {
					$params['options'] = [
						true  => $params['true'],
						false => $params['false']
					];
				}

				# Set field type
				if (!empty($params['options'])) {
					$params['type'] = 'select';
				} else if (empty($params['type'])) {
					$params['type'] = $mapping[$schema];
				}

				# Exact match for enum fields
				if (isset($model->_schema[$field]['type']) && $model->_schema[$field]['type'] == 'enum' && !empty($model->_schema[$field]['enum'])) {
					$params['options'] = $model->_schema[$field]['enum'];
					$field .= '_';
				}

				# Defaults
				if (isset($this->View->viewVars['filterDefaults'][$field])) {
					$params['default'] = $this->View->viewVars['filterDefaults'][$field];
				}

				# Modulus
				if (++$i % 2) {
					$params['div']['class'] = 'input modulus';
				}

				$params['requried'] = false;
				$params['adjust'] = 'filter';

				$filter .= $this->Form->input($field, $params);
			}

		$filter .= '<div class="clear"></div>';
		$filter .= $this->Form->close('filter');

		return $filter;
	}

	# ~ Truncate string	 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	function truncate($string, $len, $ending = '...') {

		# Count sentances
		if (is_string($len)) {
			$truncated = preg_replace("~^(.{{$len},})\..*$~ U", '$1', substr($string, 0, 2 * $len));
			return $string !== $truncated ? $truncated . $ending : $string;

			# Count chars
		} else {
			return $this->Text->truncate($string, $len, [ 'ending' => '...', 'exact' => true, 'html' => true ]);
		}
	}

	# ~ Output spider protected email link - - - - - - - - - - - - - - - - - - - - #
	public function email($email, $attributes = []) {

		# Simple class definition
		if (is_string($attributes)) {
			$attributes = [ 'class' => $attributes ];
		}

		return $this->Html->link($email, "mailto: {$email}", $attributes);
	}

	# ~ Output described list  - - - - - - - - - - - - - - - - - - - - - - - - - - #
	public function dl($list) {
		$dl = '';
		foreach ($list as $key => $value) {
			$dl .= "<dt>{$key}</dt><dd>{$value}</dd>\n";
		}
		return "<dl>{$dl}</dl>";
	}

	# ~ Handle asstes on the front - - - - - - - - - - - - - - - - - - - - - - -#
	public function getAssets($assets, $type, $inline = false) {
		ksort($assets[$type]);
		# Set extensions and folders
		$extensions = [
			'stylesheets' => 'css',
			'javascripts' => 'js'
		];

		# Read all files
		$ext = $extensions[$type];
		$files = listPublicFiles($ext, $ext, true, true);
		$maxMtime = $files['max_mtime'];
		$files = $files['files'];
		$output = [];

		# Define default additional assets
		$additionalAssets = [
			'javascripts' => [],
			'stylesheets' => []
		];

		# Add additional assets to all assets
		$additionalCounter = 1;
		foreach ($additionalAssets[$type] as $additionalAsset) {
			$asset = [ 'file' => $additionalAsset, 'on_top' => true ];
			$assets[$type]['additional' . $additionalCounter] = $asset;
		}

		# If compression is on
		if (!$inline && Configure::read('Feature.compress_assets')) {
			$suffix = join(array_keys($assets[$type]));
			$filename = 'generated/min-' . $suffix . '.' . $ext;

			$mTime = time();
			if (file_exists(WWW_ROOT . $filename)) {
				$mTime = filemtime(WWW_ROOT . $filename);
			}
			switch ($ext) {
				case 'js':
					$output[] = '<script type="text/javascript" language="javascript" src="/' . $filename . '?' . $mTime . '"></script>';
					$function = 'compressJs';
					break;

				case 'css':
					$output[] = '<link media="all" rel="stylesheet" type="text/css" href="/' . $filename . '?' . $mTime . '">';
					$function = 'compressCss';
					break;
			}

			# If check is disabled
			if (Configure::read('Website.check_asset_modification') && file_exists($filename)) {
				return implode("\n\t", $output);
			}

			# Get compressed file last modification time
			if (file_exists(WWW_ROOT . $filename)) {
				$compressedMtime = filemtime(WWW_ROOT . $filename);
				# If no assets are modified, return output
				if ($compressedMtime >= $maxMtime) {
					return implode("\n\t", $output);
				};
			}

			# Fix imports
			if ($type == 'stylesheets') {
				foreach ($assets['stylesheets'] as $index => $stylesheet) {
					if (empty($stylesheet['data']))
						continue;
					if (preg_match_all('/(@import) (url)\(([^>]*?)\);/', $stylesheet['data'], $matches)) {
						foreach ($matches[0] as $key => $import) {
							$importFile = str_replace([ "'", '"' ], '', $matches[3][$key]);
							$importFileLocation = WWW_ROOT . 'css' . DS . $importFile;
							if (file_exists($importFileLocation)) {
								$assets['stylesheets'][$index]['data'] = str_replace($import, file_get_contents($importFileLocation), $assets['stylesheets'][$index]['data']);
							}
						}
					}
				}
			}

			# Merge and compress all assets
			$merged = '';
			foreach ($assets[$type] as $id => $asset) {
				if (empty($asset['data'])) {
					$asset['data'] = file_get_contents(VECTORCMS_ROOT . 'webroot' . DS . $asset['file']);
				}
				$merged .= $function($asset['data']) . "\r\n";
			}

			# Save file and return tag
			file_put_contents(WWW_ROOT . $filename, $merged);
			return implode("\n\t", $output);

		} else {
			$additionalCounter = 1;
			foreach ($additionalAssets[$type] as $addedAsset) {
				$files['additional' . $additionalCounter++] = $addedAsset;
			}
		}

		# Iterate over assets
		foreach ($assets[$type] as $id => $asset) {

			# Output from the file system
			if (isset($files[$id])) {
				$filename = $files[$id];

				# Create file from the database
			} else {
				throw new CakeException(__("Cache filename not found for asset {$type}:{$id}"));

				# Create filename
				$ext = $extensions[$type];
				$slug = strtolower(Inflector::slug($asset['name']));
				$modified = !empty($stylesheet['under_construction']) ? preg_replace('/[: -]/', '', $asset['modified']) : 'ts' . time();
				$filename = "{$ext}/{$slug}~{$id}.{$ext}";

				# Check for cache
				if (!is_file(WWW_ROOT . $filename)) {
					$files = glob(WWW_ROOT . "{$ext}/{$slug}_*.{$ext}", false);
					foreach ($files as $file) {
						unlink($file);
					}
					file_put_contents(WWW_ROOT . $filename, $asset['data']);
				}
			}

			$inlined = preg_match("~\.inline\.{$ext}\?\d+$~", $filename);

			# Output the asset definition
			if ($inlined == $inline) {
				switch ($ext) {
					case 'js':
						if ($inline) {
							$filename = preg_replace('~\?\d+$~', '', $filename);
							$content = str_replace("\n", "\n\t\t", trim(file_get_contents(WWW_ROOT . $filename)));
							$output[] = "<script type=\"text/javascript\">\n\t\t{$content}\n\t</script>";
						} else {
							$output[] = '<script type="text/javascript" language="javascript" src="' . STATIC_BASE_URL . $filename . '"></script>';
						}
						break;
					case 'css':
						if (isset($asset['on_top'])) {
							array_unshift($output, '<link media="all" rel="stylesheet" type="text/css" href="' . STATIC_BASE_URL . $filename . '" />');
						} else {
							$output[] = '<link media="all" rel="stylesheet" type="text/css" href="' . STATIC_BASE_URL . $filename . '" />';
						}
						break;
				}
			}
		}

		return implode("\n\t", $output);
	}

	# ~ Handle head javascripts - - - - - - - - - - - - - - - - - - - - - - - - - #
	public function headJS() {

		# List of default assets
		$headScripts = [
			//'/js/jquery/jquery-ui.min.js'
			//'/js/jquery/jquery.easing-1.3.pack.js',
			//'/js/jquery/jquery.fancybox-2.1.4.pack.js'
		];

		# If no compression needed
		if (!Configure::read('Feature.compress_assets')) {
			$scripts = [];
			foreach ($headScripts as $script) {
				$scripts[] = '<script type="text/javascript" language="javascript" src="' . $script . '"></script>';
			}
			return implode("\n\t", $scripts);
		}

		$generatedFile = WWW_ROOT . 'generated/top.min.js';

		# If check is disabled
		if (Configure::read('Website.check_asset_modification') && file_exists($generatedFile)) {
			return '<script type="text/javascript" language="javascript" src="/generated/top.min.js"></script>';
		}

		# Pack assets
		$assets = [];

		# Determine max modification time
		$maxMtime = 0;
		foreach ($headScripts as $additionalAsset) {
			$filePath = str_replace('//', '/', VECTORCMS_ROOT . '/webroot/' . $additionalAsset);
			$mtime = filemtime($filePath);
			if ($maxMtime < $mtime) {
				$maxMtime = $mtime;
				break;
			}
		}

		# If compressed file is older than some asset
		if (file_exists($generatedFile) && filemtime($generatedFile) >= $maxMtime) {
			return '<script type="text/javascript" language="javascript" src="/generated/top.min.js"></script>';
		}

		# Pack assets
		$merged = '';
		foreach ($headScripts as $additionalAsset) {
			$filePath = str_replace('//', '/', VECTORCMS_ROOT . '/webroot/' . $additionalAsset);
			$fileContents = file_get_contents($filePath);
			$merged .= compressJs($fileContents) . ";";
		}

		# Save file and return tag
		file_put_contents($generatedFile, $merged);
		return '<script type="text/javascript" language="javascript" src="/generated/top.min.js"></script>';
	}

	# ~ Outputs file for download  - - - - - - - - - - - - - - - - - - - - - - - - #
	public function download($upload, $params = [], $attributes = []) {

		# Set params
		$params = $params + [ 'title' => null, 'filesize' => true, 'extension' => true, 'blank' => true ];
		setDefault($params['title'], preg_replace('~\.[a-z0-9]+$~', '', $upload['filename']));

		# Build options
		$ext = strtolower(lastFromDot($upload['filename']));
		$options = [
			'target' => $params['blank'] ? '_blank' : 'self',
			'class'  => "download download-{$ext}",
		];

		# Build additional file info
		if (!empty($params['extension']))
			$info[] = $ext;
		if (!empty($params['filesize']))
			$info[] = $this->filesize($upload['filesize']);
		if (!empty($info))
			$info = '&nbsp;<span class="download-info">' . implode(', ', $info) . '</span>';

		# echo
		return $this->Html->link($params['title'] . $info, $upload['file'], $attributes + $options);
	}

	/**
	 * Show one log item.
	 *
	 * @param    log
	 *        Array with log data.
	 * @param    view
	 *        Flag, type of log.
	 *
	 * @return
	 *        Html log code.
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	public function showLog($log, $view = 'global') {
		# Set class according to log type.
		if ($view == 'view') {
			$class = 'list';
		} else {
			$class = 'log';
		}
		# Generate html code.
		ob_start();
		?>
		<dl class="<?php echo $class; ?> log-item-<?= $log['Log']['id'] ?>" style="margin-bottom:10px;">
			<dt class="list" style="padding:0px;">
				<?php echo date('j. M Y<b\r />H:i:s', strtotime($log['Log']['created'])); ?>
			</dt>
			<dd class="list clearfix">
				<?php echo $this->formatLogMessage($log, $view); ?>
			</dd>
			<div style="clear:both"></div>
		</dl>
		<?php

		return ob_get_clean();
	}

	/**
	 * Formats messages for log.
	 *
	 * @param    log
	 *        Array with log data.
	 * @param    view
	 *        Flag, type of log (global or in view log).
	 *
	 * @return
	 *        Formatted message.
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	public function formatLogMessage($log, $view = 'global') {
		$message = '';
		$after = '';

		# Default message start
		if (isset($log['Administrator'])) {
			$message = __('Administrator') . '&nbsp;' . $this->Html->link($log['Administrator']['full_name'], [ C => 'Administrators', A => 'view', $log['Administrator']['id'] ]) . '&nbsp;';
		}

		# Check type and generate message
		switch ($log['Log']['type']) {
			case 'insert':
				if ($view == 'view') {
					$message .= __('inserted this item');
				} else {
					$message = $this->getGlobalMessage($log, 'insert');
				}
				break;

			case 'delete':
				if ($view == 'view') {
					$message .= __('deleted this item');
				} else {
					$message = $this->getGlobalMessage($log, 'delete');
				}
				break;

			case 'update':
				if ($view == 'view') {
					$message .= __('updated this item');
					$after = '<br />' . $this->Html->link(__('show details'), 'javascript:void(0)', [ 'onclick' => 'showDetails(' . $log['Log']['id'] . ')' ]);
					$after .= $this->createLogDetails($log, 'view');
				} else {
					$message = $this->getGlobalMessage($log, 'update');
					$after = '<br />' . $this->Html->link(__('show details'), 'javascript:void(0)', [ 'onclick' => 'showDetails(' . $log['Log']['id'] . ')' ]);
					$after .= $this->createLogDetails($log, 'view');
				}
				break;
		}

		# Return message with after code
		return $message . '.' . $after;
	}

	/**
	 * Generate preview for changed fields in log.
	 *
	 * @param    field
	 *        Field to generate preview for.
	 * @param    flag
	 *        'from' or 'to'.
	 *
	 * @return
	 *        Html preview code.
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	public function logField($field, $flag = 'from') {

		# Initialize default variables.
		$fieldString = '';
		$afterField = ' <i class="fa fa-arrow-right" style="padding:0 0.5em; color: #ccc;"></i>';

		# If there is no value, and field type is not boolean, return No data.
		if ($field['type'] !== 'boolean' && ($field[$flag] === '' || $field[$flag] === null)) {
			return '<i class="muted">' . __('empty') . '</i>' . ($flag == 'from' ? $afterField : null);
		}

		# Based on field type generate preview.
		switch ($field['type']) {
			case 'text':
				$fieldString = $field[$flag];

				# Vertical arrow if value is too big.
				if (strlen($fieldString) > 50) {
					$afterField = '<div style="padding-left:2em;"><i class="fa fa-arrow-down"></i></div>';
				}
				break;

			case 'date':

				# Format date.
				$fieldString = $this->time($field[$flag]);
				break;

			case 'upload':

				# Generate thumbnails for pictures, download links for other.
				$pathInfo = pathinfo($field[$flag]);
				if (in_array($pathInfo['extension'], [ 'jpeg', 'jpg', 'png', 'gif' ])) {
					$fieldString = $this->image($field[$flag], 84, 84, 'fancy-' . $field['field']);
				} else {
					$fieldString = '<a href="' . $field[$flag] . ' target="_blank"">' . $pathInfo['basename'] . '</a>';
				}
				break;

			case 'boolean':

				# Generate icons for boolean values.
				$fieldString = $this->bool($field[$flag]);
				break;

			default:
				$fieldString = $field[$flag];

		}

		# Put arrow after from field.
		if ($flag != 'to') {
			$fieldString .= $afterField;
		}

		return trim($fieldString);
	}

	/**
	 * Create details for one log item.
	 *
	 * @param    log
	 *        Array with log data.
	 * @param    view
	 *        Flag, type of log.
	 *
	 * @return
	 *        Html log code.
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	public function createLogDetails($log, $view = 'global') {
		ob_start();
		?>
		<div class="log-history-item" id="history-item-<?= $log['Log']['id'] ?>">
			<?php
			foreach ($log['LogDetails'] as $field) {
				array_shift($field);
				?>
				<dl style="list">
					<dt class="list">
						<?= __(Inflector::humanize($field['field'])) . (!empty($field['language']) ? " [{$field['language']}]" : null) ?>
					</dt>

					<dd class="list clearfix">
						<?= $this->logField($field, 'from'); ?>
						<?= $this->logField($field, 'to'); ?>
					</dd>
					<div style="clear:both"></div>
				</dl>
			<?php } ?>
		</div>
		<?php

		return ob_get_clean();
	}

	/**
	 * Formats a string so it is valid to be used as the HTML attribute.
	 *
	 * @param    data
	 *        Data to be formated.
	 * @param    lenght
	 *        Maximal length of the returned string. If the original content is to big, [...] is
	 *        appended at the end.
	 *
	 * @return
	 *        The prepared string.
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	public function htmlAttribute($data, $length = null) {
		$clean = html(trim(preg_replace('~(&nbsp;|\s)+~', ' ', trim(strip_tags($data)))));

		return $length
			? $this->Format->truncate($clean, $length, ' [...]')
			: $clean;
	}

	/**
	 * Get appropriate message for global log.
	 *
	 * @param    log
	 *        Array with log data.
	 * @param    view
	 *        Flag, type of operation.
	 *
	 * @return
	 *        Message.
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	public function getGlobalMessage($log, $flag) {
		$message = '';
		$after = '';

		# Instantiate model, to get display_field and its value.
		$modelClass = $log['Log']['model'];
		$instance = ClassRegistry::init($modelClass);

		$conditions = [
			$instance->alias . '.id' => $log['Log']['foreign_key']
		];

		$dfValue = $instance->find('first', [
			'all'        => true,
			'conditions' => $conditions,
			'fields'     => [
				$instance->displayField ] ]);

		$dfValue = $dfValue[$instance->alias][$instance->displayField];

		# Generate and return message.
		$message = __('Administrator');
		if (isset($log['Administrator'])) {
			$message .= "&nbsp;" . $this->Html->link($log['Administrator']['full_name'], [ C => 'administrators', A => 'view', $log['Administrator']['id'] ]);
		}

		if ($flag == 'delete') {
			$message .= "&nbsp;" . __("deleted") . "&nbsp;" . __(Inflector::humanize(Inflector::tableize($instance->alias)));
		} else if ($flag == 'insert') {
			$message .= "&nbsp;" . __("inserted new") . "&nbsp;" . __(Inflector::humanize(Inflector::tableize($instance->alias)));
		} else {
			$message .= "&nbsp;" . __("updated") . "&nbsp;" . __(Inflector::humanize(Inflector::tableize($instance->alias)));
		}

		$message .= "&nbsp;" . $this->Html->link($dfValue, [ C => $instance->useTable, A => 'view', $log['Log']['foreign_key'] ]);

		return $message;

	}

	/**
	 * Get path of module page (Needs more work).
	 *
	 * @param    data
	 *        Module item data.
	 *
	 * @return
	 *        Link to page.
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	public function getModulePath($data, $module = null) {
		if (empty($data) || !is_array($data)) {
			return 'javascript:void(0)';
		}

		$moduleData = reset($data);
		if (empty($module)) {
			$module = key($data);
		}

		return ClassRegistry::init($module)->getLink($moduleData);

	}

	# ~ Print the menu from the supplied root  - - - - - - - - - - - - - - - - - - #
	public function printMenu($template, $rootPageId = 2) {
		$menu = [];

		# Add all visible pages to the menu
		$pages = $this->getPageFromSitemap($rootPageId, Stash::read('Sitemap'));
		if ($pages) {
			foreach ($pages['children'] as $slug => $page)
				if (!empty($page['Page']['is_visible'])) {

					$children = [];
					foreach ($page['children'] as $child) {
						if($child['Page']['is_visible']) {
							$children[] = $child;
						}
					}
					$menu[] = $this->_View->element($template, [
						'page'     => $page['Page'],
						'children' => $children,
						'active'   => $page['Page']['selected']
					]);
				}
		}

		return implode("\n", $menu);
	}

	# ~ Return page with provided id from sitemap  - - - - - - - - - - - - - - - - #
	public function getPageFromSitemap($pageId, $sitemap, $field = null) {
		return ClassRegistry::init('Page')->getPageFromSitemap($pageId, $sitemap, $field);
	}

	# ~ Return page link - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
	public function getPageLink($link, $sitemap = null) {
		if (empty($link)) {
			return 'javascript:void(0)';
		}

		if (empty($sitemap) && !empty($this->_View->viewVars['sitemap'])) {
			$sitemap = $this->_View->viewVars['sitemap'];
		}

		# If link is page_id
		if (is_int($link) || is_numeric($link)) {
			return $this->getPageFromSitemap($link, $sitemap, 'path');
		}

		# Check for format page:#
		if (preg_match('~page:(\d+)~', $link, $url)) {
			if (isset($url[1]) && !empty($url[1])) {
				return $this->getPageFromSitemap($url[1], $sitemap, 'path');
			}
		}

		# Append http:// to other links.
		if (!preg_match('~^https?\:\/\/~', $link, $url)) {
			return PROTOCOL . $link;
		}

		return $link;
	}

	# ~ Tries to overwrite with app specific view element  - - - - - - - - - - - - #
	public function override($file) {
		return trim($this->_View->element('Override' . DS . $file));
	}

}
